1. 项目背景与核心需求
"美好食荐"美食推荐系统是一个典型的移动互联网应用案例,它需要解决的核心问题是:如何通过微信小程序这一高频入口,为用户提供个性化的美食推荐服务。这个项目选择了两种主流PHP框架(ThinkPHP和Laravel)作为后端技术栈,体现了现代Web开发中"轻前端重后端"的架构特点。
从技术选型来看,微信小程序作为前端载体具有天然优势:无需安装、即用即走、用户基数大。而后端选择ThinkPHP和Laravel双框架实现,可能是出于以下考虑:
- ThinkPHP更适合快速开发(国内文档丰富、社区支持好)
- Laravel在复杂业务逻辑处理上更有优势(Eloquent ORM、队列系统等)
- 项目可能作为教学案例,需要展示不同框架的实现差异
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统架构设计解析
2.1 整体技术架构
一个完整的美食推荐系统通常包含以下核心模块:
code复制微信小程序端
│
▼
API网关层(Restful接口)
│
▼
业务逻辑层(ThinkPHP/Laravel)
│
▼
数据存储层(MySQL + Redis)
│
▼
第三方服务(地图API、支付接口等)
2.2 双框架实现方案对比
在实际开发中,我们采用了"业务逻辑隔离"的方式实现双框架并存:
- ThinkPHP实现方案:
- 使用TP5.1的API开发模式
- 路由配置:route/route.php中定义小程序接口
- JWT鉴权:通过中间件实现
- 数据库操作:Query Builder + Model
- Laravel实现方案:
- 采用Laravel 8.x Sanctum做API认证
- 资源路由:Route::apiResource()
- 数据交互:Eloquent + Resource Collection
- 队列系统:处理推荐算法计算任务
关键提示:两个框架共用同一个数据库时,需要注意数据表命名规范的一致性。我们建议采用
prefix_表名的方式,如food_recommend_restaurants
3. 核心功能实现细节
3.1 用户系统设计
微信小程序用户体系与传统Web有所不同,我们采用openid作为唯一标识:
php复制// ThinkPHP获取用户标识示例
public function login()
{
$code = input('post.code');
$wxapi = "https://api.weixin.qq.com/sns/jscode2session?appid={$appid}&secret={$secret}&js_code={$code}&grant_type=authorization_code";
$res = json_decode(file_get_contents($wxapi), true);
$openid = $res['openid'];
// 查询或创建用户
$user = UserModel::where('openid', $openid)->findOrEmpty();
if ($user->isEmpty()) {
$user = UserModel::create([
'openid' => $openid,
'register_time' => time()
]);
}
return json(['token' => Jwt::create($user->id)]);
}
3.2 推荐算法实现
美食推荐的核心在于算法设计,我们实现了基于协同过滤的混合推荐:
- 冷启动策略:
- 基于地理位置推荐(3km内评分最高的20家店)
- 热门榜单(近7天访问量Top 10)
- 用户行为分析:
mysql复制CREATE TABLE user_behavior (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
shop_id INT NOT NULL,
behavior_type TINYINT COMMENT '1浏览 2收藏 3下单',
weight FLOAT DEFAULT 1.0,
created_at TIMESTAMP
);
- 推荐计算逻辑(Laravel实现):
php复制// 在app/Services/RecommendService.php
public function recommendForUser($userId)
{
// 获取用户最近行为
$behaviors = UserBehavior::where('user_id', $userId)
->orderBy('created_at', 'desc')
->limit(100)
->get();
// 计算兴趣向量(简化版)
$interestVector = [];
foreach ($behaviors as $behavior) {
$shop = Shop::find($behavior->shop_id);
foreach ($shop->tags as $tag) {
$interestVector[$tag] = ($interestVector[$tag] ?? 0) + $behavior->weight;
}
}
// 基于向量相似度检索
return Shop::query()
->with(['tags'])
->whereIn('id', $this->findSimilarShops($interestVector))
->orderBy('rating', 'desc')
->limit(10)
->get();
}
4. 微信小程序端关键实现
4.1 页面布局要点
微信小程序的页面设计需要特别注意适配问题:
- 导航栏适配:
javascript复制// app.js
wx.getSystemInfo({
success: (res) => {
this.globalData.statusBarHeight = res.statusBarHeight
this.globalData.navBarHeight = (res.platform === 'android' ? 48 : 44) + res.statusBarHeight
}
})
- 列表性能优化:
- 使用recycle-view组件处理长列表
- 图片懒加载:
<image lazy-load> - 分页加载阈值:onReachBottom时加载下一页
4.2 地图功能集成
美食推荐离不开地图展示,我们采用微信原生地图组件:
wxml复制<map
id="foodMap"
longitude="{{longitude}}"
latitude="{{latitude}}"
markers="{{markers}}"
bindmarkertap="onMarkerTap"
style="width: 100%; height: 300px;">
</map>
对应的地图数据处理:
javascript复制// 转换店铺数据为markers
processShopsToMarkers(shops) {
return shops.map(shop => ({
id: shop.id,
latitude: shop.latitude,
longitude: shop.longitude,
iconPath: '/images/marker.png',
width: 30,
height: 30,
callout: {
content: `${shop.name}\n评分:${shop.rating}`,
color: '#333',
borderRadius: 4,
padding: 5,
display: 'ALWAYS'
}
}))
}
5. 部署与运维实践
5.1 服务器环境配置
针对双框架项目,我们建议的部署方案:
- Nginx配置要点:
nginx复制server {
listen 80;
server_name api.food-recommend.com;
# ThinkPHP入口
location /tp/ {
root /var/www/food-recommend/public;
index index.php index.html;
try_files $uri $uri/ /tp/index.php$is_args$args;
}
# Laravel入口
location /laravel/ {
root /var/www/food-recommend-laravel/public;
index index.php index.html;
try_files $uri $uri/ /laravel/index.php$is_args$args;
}
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
5.2 性能优化方案
在高并发场景下,我们实施了以下优化措施:
- 缓存策略:
- 使用Redis缓存热门推荐结果
- 小程序端启用本地缓存(wx.setStorageSync)
- 接口响应添加ETag标识
- 数据库优化:
mysql复制ALTER TABLE shops ADD INDEX idx_geo (latitude, longitude);
ALTER TABLE user_behavior ADD INDEX idx_user_shop (user_id, shop_id);
- 异步处理:
使用Laravel队列处理计算密集型任务:
php复制// 生成推荐任务
GenerateRecommendation::dispatch($userId)->onQueue('recommendation');
6. 开发中的典型问题与解决方案
6.1 微信登录会话维护
常见问题:session_key过期导致用户状态丢失
解决方案:
php复制// 双框架共享的会话处理类
class WechatSession
{
private $redis;
public function __construct() {
$this->redis = new Redis();
$this->redis->connect('127.0.0.1', 6379);
}
public function storeSession($openid, $sessionKey) {
$this->redis->setex("wsess:{$openid}", 3600 * 24 * 7, $sessionKey);
}
public function getSession($openid) {
return $this->redis->get("wsess:{$openid}");
}
}
6.2 跨框架数据一致性
当两个框架操作同一张表时,我们采用以下策略:
- 事务处理:
php复制// Laravel中的事务示例
DB::transaction(function () use ($userId, $shopId) {
// 操作user_behavior表
Behavior::create([...]);
// 通过HTTP调用ThinkPHP接口
Http::post('http://api/tp/update-recommend', [...]);
});
- 最终一致性:
- 使用消息队列(RabbitMQ)处理跨框架更新
- 设置版本号字段检测冲突
7. 安全防护措施
7.1 接口安全
- 参数校验(Laravel示例):
php复制$validator = Validator::make($request->all(), [
'shop_id' => 'required|integer|exists:shops,id',
'rating' => 'required|integer|between:1,5',
'comment' => 'nullable|string|max:500'
]);
if ($validator->fails()) {
return response()->json([
'code' => 400,
'errors' => $validator->errors()
], 400);
}
- 防刷机制:
- 使用Laravel的速率限制:
php复制Route::middleware('throttle:60,1')->group(function () {
Route::post('/comment', 'CommentController@store');
});
7.2 数据安全
- 敏感数据加密:
php复制// ThinkPHP中加密手机号
public function encryptPhone($phone)
{
$key = config('app.encrypt_key');
return openssl_encrypt($phone, 'AES-128-ECB', $key);
}
- SQL防护:
- ThinkPHP:使用参数绑定
php复制Db::name('user')->where('id', ':id')->bind(['id'=>$id])->select();
- Laravel:直接使用Eloquent或查询构造器(自动防护)
8. 项目扩展方向
基于现有架构,可以考虑以下扩展:
- 多端适配:
- 使用Taro或Uni-app重构小程序端,实现多平台发布
- 开发管理后台(Vue + ElementUI)
- 推荐算法升级:
- 引入TensorFlow Serving部署深度学习模型
- 实时用户画像系统
- 运营功能增强:
- 优惠券系统
- 拼团/秒杀活动模块
- 用户成长体系
在开发过程中,我们发现Laravel更适合构建复杂的后台业务逻辑,而ThinkPHP在快速实现基础接口方面更高效。微信小程序与PHP后端的配合需要注意会话管理和接口安全,采用JWT+Redis的方案在实践中表现良好。
