1. 项目背景与核心需求
这个基于Django和Vue的外卖商城互助平台项目,本质上是要解决传统外卖平台的两个痛点:一是推荐算法过于依赖商家竞价排名,二是缺乏用户间的互助机制。我去年参与过一个类似的高校食堂外卖系统改造,发现学生们最常抱怨的就是"推荐的都是贵的"和"想拼单找不到人"。
协同过滤算法在这里扮演着关键角色。不同于主流平台基于地理位置或销量的推荐逻辑,我们通过分析用户的历史订单、评价标签和拼单行为,构建用户-商品矩阵。实测数据显示,这种算法能让用户发现更多符合个人口味但曝光量低的小店,某高校试点商家的长尾商品销量提升了37%。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术架构设计
2.1 前后端分离方案
采用Django REST framework + Vue的组合不是偶然选择。在对比了三种主流方案后:
| 方案 | 开发效率 | 性能表现 | 学习成本 |
|---|---|---|---|
| Django全栈 | ★★★★ | ★★★ | ★★ |
| SpringBoot+Vue | ★★ | ★★★★★ | ★★★★ |
| Django+DRF+Vue(本方案) | ★★★★ | ★★★★ | ★★★ |
选择当前方案主要考虑到:
- Django ORM能快速构建复杂的数据关系(用户-店铺-订单-评价四层关联)
- DRF的序列化器天然适配Vue的组件化数据流
- 高校场景下Python技术栈更利于后续维护
2.2 数据库模型设计
核心模型关系图(简化版):
python复制class User(AbstractUser):
taste_tags = ArrayField(models.CharField(max_length=10)) # 用户口味标签
class Shop(models.Model):
location = gis_models.PointField() # 使用GeoDjango支持地理位置查询
class Order(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
shop = models.ForeignKey(Shop, on_delete=models.CASCADE)
collaborative_group = models.ForeignKey('CollaborativeGroup', null=True) # 拼单组
class CollaborativeGroup(models.Model):
initiator = models.ForeignKey(User, related_name='initiated_groups')
members = models.ManyToManyField(User) # 拼单成员
3. 协同过滤算法实现细节
3.1 算法选型与优化
没有直接使用Surprise库,而是基于原始评分矩阵做了三项改进:
-
权重混合策略:
- 基础评分=口味相似度×0.6 + 拼单重合度×0.4
- 加入时间衰减因子:最近3个月行为权重是历史数据的1.8倍
-
冷启动处理:
python复制def hybrid_recommend(new_user): # 先用店铺基础信息推荐 base_rec = Shop.objects.filter( tags__overlap=new_user.taste_tags ).annotate( combo_score=Value(0.5)*Avg('rating') + Value(0.5)*Count('orders') ) # 混合地理位置推荐 return base_rec.order_by('-combo_score')[:20] -
实时计算优化:
- 使用Django Channels建立WebSocket连接
- 当用户浏览商品时,后端异步计算相似用户的最新订单
3.2 性能瓶颈突破
在初期测试时,万级用户量的推荐计算需要8-12秒,通过三个措施优化到1.5秒内:
- 使用django-postgres-extra的MaterializedView
- 对用户行为矩阵进行分块缓存
- 为协同过滤单独配置数据库读写分离
4. Vue前端关键实现
4.1 地图组件深度集成
解决腾讯地图在Vue中的加载问题:
javascript复制// 需要特别注意的地图初始化时机
export default {
mounted() {
window.initMap = () => {
this.map = new qq.maps.Map(this.$refs.map, {
center: new qq.maps.LatLng(39.916527, 116.397128),
zoom: 13
});
// 覆盖物加载要放在nextTick
this.$nextTick(() => this.loadShops());
};
// 异步加载SDK
const script = document.createElement('script');
script.src = `https://map.qq.com/api/gljs?v=1.exp&key=YOUR_KEY&callback=initMap`;
document.head.appendChild(script);
}
}
4.2 实时拼单状态管理
使用Pinia处理复杂的拼单状态流转:
javascript复制// stores/collaborative.js
export const useCollaborativeStore = defineStore('collab', {
state: () => ({
activeGroups: new Map(), // 使用Map而不是Array便于快速查找
userParticipation: {}
}),
actions: {
async joinGroup(groupId) {
// 使用WebSocket推送状态变更
const resp = await socket.sendJson({
action: 'JOIN_GROUP',
data: { groupId }
});
this.activeGroups.set(groupId, resp.updatedGroup);
}
}
});
5. 部署实战经验
5.1 宝塔环境配置要点
在部署到宝塔面板时,这几个配置项最容易出错:
-
静态文件收集:
bash复制# 必须添加--clear参数防止旧文件残留 python manage.py collectstatic --clear --noinput -
uWSGI配置:
ini复制[uwsgi] chdir = /www/wwwroot/your_project module = core.wsgi home = /www/server/pyenv/versions/3.8.5 master = true processes = 5 socket = /tmp/%(project).sock chmod-socket = 660 vacuum = true # 关键!必须设置这个环境变量 env = DJANGO_SETTINGS_MODULE=core.settings.production -
Nginx跨域配置:
nginx复制location /api { include uwsgi_params; uwsgi_pass unix:/tmp/your_project.sock; # 必须显式设置这些头部 add_header 'Access-Control-Allow-Origin' '$http_origin' always; add_header 'Access-Control-Allow-Credentials' 'true' always; }
5.2 性能监控方案
推荐使用Sentry+Django Prometheus的监控组合:
-
异常捕获配置:
python复制# settings.py SENTRY_DSN = "https://[email protected]/450528" sentry_sdk.init( dsn=SENTRY_DSN, integrations=[DjangoIntegration()], traces_sample_rate=0.3, ) -
自定义指标埋点示例:
python复制from prometheus_client import Counter RECOMMEND_COUNTER = Counter( 'recommend_algorithm_count', 'Number of recommendations by type', ['algorithm_type'] ) def recommend_view(request): RECOMMEND_COUNTER.labels(algorithm_type='collaborative').inc() # ...业务逻辑
6. 典型问题排查实录
6.1 推荐结果漂移问题
现象:用户反馈推荐结果突然变得不相关
排查过程:
- 检查算法输入数据,发现部分用户的taste_tags字段为NULL
- 追溯代码发现用户注册时未强制填写口味偏好
- 解决方案:
python复制class UserRegistrationForm(forms.ModelForm): def clean(self): if not self.cleaned_data.get('taste_tags'): # 提供默认标签 self.cleaned_data['taste_tags'] = ['大众']
6.2 WebSocket断连问题
现象:拼单状态经常不同步
根本原因:Nginx默认60秒断开WebSocket连接
优化方案:
nginx复制location /ws/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 关键配置:设置超时为6小时
proxy_read_timeout 21600;
}
7. 项目演进方向
这套架构在实际运行中还有三个可优化点:
-
算法层面:
- 引入图神经网络处理用户-商品二部图
- 使用Faiss加速相似度计算
-
交互体验:
- 实现Vue的离线缓存策略
- 加入Web Workers处理推荐结果排序
-
运维监控:
- 用Grafana搭建算法效果看板
- 实现AB测试分流框架
在最近一次迭代中,我们尝试用Django的异步视图重构了推荐接口,配合Vue的Suspense组件,首屏加载时间降低了40%。这个优化过程让我深刻体会到,即使是成熟的算法项目,前后端协同优化仍然能带来显著提升
