1. 项目概述:基于协同过滤的外卖点餐系统实战
去年接手了一个餐饮连锁品牌的数字化升级项目,他们最大的痛点在于:用户打开小程序面对上千种菜品时无从下手,而商家也不清楚该优先推广哪些产品。我们基于ThinkPHP-Laravel+UniApp技术栈,开发了这套带协同过滤推荐的外卖系统。上线三个月后,客户的核心指标发生了显著变化:订单转化率提升18.7%,用户复购周期缩短40%,最让我意外的是,商家端的热门菜品预测准确率达到了82%。
这个系统的核心价值在于双向赋能——既帮助用户快速发现合口味的菜品(C端推荐),又辅助商家优化菜品结构和营销策略(B端分析)。下面我将从技术选型、算法实现到性能优化,完整还原这个项目的实战经验。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术架构设计与选型思考
2.1 为什么选择Laravel+UniApp组合
在技术预研阶段,我们对比了三种方案:
- 纯原生开发(性能最佳但成本高)
- Taro跨端方案(生态较新)
- UniApp+Vue组合(开发效率与性能平衡)
最终选择UniApp的原因很实际:
- 团队有现成的Vue技术储备
- 微信小程序审核周期长,UniApp"一次开发多端发布"的特性完美解决了安卓/iOS/微信的同步需求
- 通过条件编译可以处理各平台差异,例如微信的登录API与H5不同:
javascript复制// 统一登录方法示例
function unifiedLogin() {
// #ifdef MP-WEIXIN
wx.login({ success: res => {...} })
// #endif
// #ifdef H5
window.location.href = oauthUrl
// #endif
}
后端选择Laravel而非ThinkPHP的决策点:
- Eloquent ORM对复杂关联查询更友好
- 队列系统直接支持Redis延迟队列(用于订单超时处理)
- Telescope调试工具大幅降低排查问题的成本
2.2 系统分层架构详解

整体采用前后端分离架构,关键设计包括:
- 接入层:Nginx处理静态资源,OpenResty做流量控制
- 业务层:Laravel按功能拆分为用户服务、订单服务、推荐服务
- 数据层:
- MySQL主从分离(1主2从)
- Redis集群:缓存热点数据+会话存储
- Elasticsearch:菜品搜索服务
特别说明数据库分表策略:
- 用户行为表按用户ID哈希分表(user_actions_0~15)
- 订单表按月分表(orders_202301)
- 采用Laravel的模型作用域简化分表查询:
php复制// 在Order模型中定义分表逻辑
public function scopeShardTable($query, $userId) {
$tableId = $userId % 16;
return $query->from("user_actions_$tableId");
}
3. 协同过滤算法深度实现
3.1 基于项目的推荐算法优化
原始论文中的余弦相似度公式在餐饮场景遇到两个问题:
- 数据稀疏性:用户平均只点评过3.2个菜品
- 热门菜品偏见:销量高的菜品会主导推荐结果
我们的改进方案:
- 引入权重衰减因子:最近3个月的行为权重=1,3-6个月权重=0.7,6个月以上=0.3
- 惩罚热门商品:在相似度计算中加入流行度惩罚项
改进后的相似度公式:
code复制sim'(i,j) = sim(i,j) / (log(1+N(i)) * log(1+N(j)))
其中N(i)表示商品i被购买的次数。PHP实现代码:
php复制public function calculateSimilarity($itemA, $itemB) {
$commonUsers = $this->getCommonUsers($itemA, $itemB);
$numerator = $denominatorA = $denominatorB = 0;
foreach ($commonUsers as $user) {
$weight = $this->getTimeWeight($user->last_action_time);
$ratingA = $user->rating_a * $weight;
$ratingB = $user->rating_b * $weight;
$numerator += ($ratingA - $itemA->avg_rating) * ($ratingB - $itemB->avg_rating);
$denominatorA += pow(($ratingA - $itemA->avg_rating), 2);
$denominatorB += pow(($ratingB - $itemB->avg_rating), 2);
}
$popularityPenalty = log(1 + $itemA->sales_count) * log(1 + $itemB->sales_count);
return $numerator / (sqrt($denominatorA) * sqrt($denominatorB)) / $popularityPenalty;
}
3.2 商家端聚类分析实战
商家后台的竞品分析模块采用改进的K-means算法:
-
特征工程:
- 菜品结构相似度
- 价格带分布
- 用户画像重合度
- 地理位置系数
-
轮廓系数法确定K值:
通过肘部法则我们发现,当K=5时轮廓系数达到0.62的峰值 -
PHP实现核心逻辑:
php复制public function clusterBusinesses() {
$businesses = Business::with('salesData')->get();
$features = $this->extractFeatures($businesses);
$kmeans = new KMeans(5);
$clusters = $kmeans->cluster($features);
// 缓存聚类结果
Redis::set('business_clusters', json_encode($clusters), 'EX', 3600);
return $clusters;
}
关键优化:在计算距离矩阵时,将欧式距离改为余弦相似度,更适合高维稀疏数据
4. 性能优化关键策略
4.1 推荐结果缓存设计
采用三级缓存策略:
- 用户级缓存:Redis存储每个用户的最新推荐结果(有效期2小时)
- 商品级缓存:相似度矩阵按商品ID哈希分片存储
- 本地缓存:热门商家的推荐结果缓存在Worker内存中
缓存更新策略对比:
| 策略 | 触发条件 | 优点 | 缺点 |
|---|---|---|---|
| 定时刷新 | 每30分钟跑定时任务 | 实现简单 | 实时性差 |
| 事件驱动 | 用户新评价后触发 | 实时性强 | 可能引发雪崩 |
| 混合模式 | 低频事件+定时补偿 | 平衡性较好 | 实现复杂 |
我们最终选择混合模式,核心代码如下:
php复制// 事件监听器
class UpdateRecommendations implements ShouldQueue
{
public function handle(RatingCreated $event) {
$userId = $event->rating->user_id;
$itemId = $event->rating->item_id;
// 立即更新受影响用户的缓存
$this->updateUserCache($userId);
// 异步更新商品相似度
dispatch(new UpdateItemSimilarity($itemId));
}
}
4.2 高并发场景应对方案
在午高峰期间,推荐服务的QPS达到1200+,我们通过以下措施保障稳定性:
- Nginx限流:对/recommend接口实施令牌桶限流
code复制limit_req_zone $binary_remote_addr zone=rec:10m rate=300r/m; - 降级策略:
- 一级降级:返回缓存的热门推荐
- 二级降级:返回基于内容的简单推荐
- 异步计算:使用Laravel Horizon管理推荐任务队列
5. 踩坑实录与解决方案
5.1 冷启动问题
新商家入驻时没有足够的行为数据,导致推荐质量差。我们采用的解决方案:
- 内容填充:人工打标签(如"川菜""快餐")
- 迁移学习:借用同商圈类似商家的数据
- 混合推荐:初期以内容推荐为主,逐渐增加协同过滤权重
5.2 数据稀疏性问题
典型现象:某个小众菜品只有资深用户评价过,导致推荐偏差。解决方法:
- 数据增强:合并相似菜品的评价数据
- 默认评分:对评价不足的商品赋予商圈平均分
- 跨店推荐:在用户常购品类中扩展推荐范围
6. 效果验证与商业价值
通过A/B测试对比新旧版本:
| 指标 | 旧版本 | 新版本 | 提升 |
|---|---|---|---|
| 下单转化率 | 12.3% | 14.6% | +18.7% |
| 客单价 | ¥45.2 | ¥51.8 | +14.6% |
| 30日留存 | 19.2% | 23.5% | +22.3% |
商家端的数据看板也带来了运营变革:
- 某火锅店根据推荐数据调整了套餐组合,月销售额增长37%
- 奶茶店发现某小众配料被频繁推荐,将其加入主打产品线
- 快餐连锁优化了不同时段的产品展示顺序,午高峰产能利用率提升21%
这个项目给我的最大启示是:好的技术方案必须创造可量化的商业价值。在代码之外,我们花了大量时间与商家沟通运营需求,最终让算法真正成为了生意增长的助推器。如果你正在开发类似系统,我的建议是:前期先做MVP快速验证核心假设,数据跑通后再扩展功能边界。
