1. 项目概述:厨房预制菜半成品配菜平台
这个基于Vue和Python的厨房预制菜平台,本质上是一个连接食材预处理与家庭烹饪的智能枢纽。我在开发过程中发现,现代都市人对"半小时搞定三菜一汤"的需求强烈,但传统生鲜电商提供的未加工食材仍需要用户完成择菜、切配等耗时环节。我们的平台核心价值在于:通过标准化的半成品净菜(如已切丝的土豆、腌制好的鸡丁、搭配好的宫保鸡丁组合包),将家庭烹饪时间缩短60%以上。
技术栈选择上,前端采用Vue3+Element Plus实现响应式界面,后端最初用Flask快速搭建原型,后期因需要复杂的关系型数据管理切换到了Django REST framework。开发环境使用PyCharm专业版+WebStorm双IDE协同,数据库选用PostgreSQL处理高并发的订单数据。这个技术组合在开发效率与系统性能之间取得了很好的平衡——Flask的轻量级特性适合初期快速验证业务模型,而Django的ORM和Admin后台在用户量增长后显著降低了开发维护成本。
关键决策点:为什么不用纯前端方案?实测发现菜品SKU管理、智能推荐算法、订单履约等后端逻辑复杂度远超预期,需要完整的MVC框架支持。
2. 核心功能模块设计
2.1 智能配菜系统
这是平台的差异化核心功能,其技术实现包含三个关键层:
- 菜品知识图谱:用Django的models.py构建了包含178个食材节点、326种搭配关系的图数据库,使用django-neomodel集成Neo4j。例如系统知道"土豆"与"青椒"的搭配权重是0.87,而与"牛肉"的搭配权重高达0.92。
- 推荐算法层:基于用户历史订单数据,用Python的scikit-learn实现协同过滤推荐。一个典型用例:当用户购买"鱼香肉丝组合包"时,系统会推荐"米饭套餐"或"解腻的冬瓜汤"。
- 实时库存校验:通过Django Channels实现WebSocket长连接,确保用户看到的推荐菜品的所有配料都有库存。
python复制# Django视图中的推荐逻辑示例
class RecommendView(APIView):
def get(self, request):
user_prefs = UserPreference.objects.get(user=request.user)
# 获取基于用户口味的TOP10推荐
recommendations = Recipe.objects.annotate(
match_score=ExpressionWrapper(
F('spicy_level') * user_prefs.spicy_weight +
F('cooking_time') * user_prefs.time_weight,
output_field=FloatField()
)).order_by('-match_score')[:10]
# 过滤库存不足的菜品
valid_recipes = [r for r in recommendations
if r.ingredients.all().exists()]
return Response(RecipeSerializer(valid_recipes, many=True).data)
2.2 订单履约流程
采用状态机模式管理订单生命周期,关键状态包括:
待支付→已支付:集成支付宝/微信支付SDK已接单:触发厨房分拣任务分拣中:使用Python的Celery异步任务更新库存待配送:调用第三方物流API生成运单已完成:启动用户满意度调查
开发中遇到的典型问题:Flask的上下文管理在Celery异步任务中会导致数据库连接泄漏。最终解决方案是显式地在任务开始时创建新连接,并在结束时强制关闭:
python复制@app.task(bind=True)
def process_order(self, order_id):
try:
with app.app_context():
order = Order.query.get(order_id)
# 库存扣减逻辑...
db.session.commit()
finally:
db.session.remove() # 关键!防止连接堆积
3. 关键技术实现细节
3.1 Vue与Django的跨域协作
前后端分离架构下,需要解决三个核心问题:
- 认证方案:采用JWT+Refresh Token双令牌机制。前端在axios拦截器中自动处理token刷新:
javascript复制// axios请求拦截器
service.interceptors.request.use(config => {
config.headers['Authorization'] = `Bearer ${getToken()}`
return config
})
// 响应拦截器处理401错误
service.interceptors.response.use(
response => response,
async error => {
if (error.response.status === 401) {
await refreshToken()
return service(error.config) // 重试原请求
}
return Promise.reject(error)
}
)
-
API文档同步:使用drf-yasg自动生成Swagger文档,并通过vuepress-plugin-api同步到前端文档站,保证前后端开发进度一致。
-
Mock数据方案:开发阶段配置Vue的proxyTable将特定路由转发到Mock服务器:
javascript复制devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000', // mock服务器
pathRewrite: { '^/api': '' }
}
}
}
3.2 菜品图片处理流水线
用户上传的菜品图片需要经过:
- 自动裁剪:Python的Pillow库检测主体区域
- 色彩校正:OpenCV进行直方图均衡化
- 格式转换:转为WebP格式节省CDN流量
- 水印添加:使用ImageMagick批量处理
典型问题:Django的FileField在开发环境与生产环境的路径处理不一致。解决方案是统一使用相对路径+云存储URL:
python复制class Recipe(models.Model):
image = models.ImageField(
upload_to='recipes/%Y/%m',
storage=CloudStorage(), # 自定义存储后端
blank=True
)
@property
def image_url(self):
return self.image.url if self.image else DEFAULT_IMAGE
4. 性能优化实战记录
4.1 数据库查询优化
通过Django Debug Toolbar发现N+1查询问题严重。例如获取菜品列表时,每个菜品的关联食材都会产生独立查询。优化方案:
- 使用select_related/prefetch_related:
python复制# 优化前(产生N+1查询)
recipes = Recipe.objects.all()
for r in recipes:
print(r.ingredients.all()) # 每次循环都查询数据库
# 优化后
recipes = Recipe.objects.prefetch_related('ingredients').all()
- 添加数据库索引:
python复制class Order(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, db_index=True)
created_at = models.DateTimeField(auto_now_add=True, db_index=True)
- 引入缓存层:对热门菜品使用Redis缓存查询结果,通过Django的cache_page装饰器实现:
python复制@method_decorator(cache_page(60*15), name='dispatch')
class PopularRecipesView(ListAPIView):
queryset = Recipe.objects.filter(is_popular=True)
serializer_class = RecipeSerializer
4.2 前端性能提升
- 组件懒加载:将非首屏组件改为异步加载
javascript复制const RecipeDetail = () => import('@/views/RecipeDetail.vue')
- 图片懒加载:使用Intersection Observer API
html复制<img v-lazy="recipe.imageUrl" alt="菜品图片">
- Webpack分包策略:按路由拆分chunk
javascript复制// vue.config.js
configureWebpack: {
optimization: {
splitChunks: {
chunks: 'all',
maxSize: 244 * 1024 // 控制单个chunk大小
}
}
}
5. 部署架构与监控
5.1 生产环境部署方案
采用Docker Compose编排服务:
yaml复制version: '3'
services:
web:
build: .
command: gunicorn --bind :8000 --workers 4 core.wsgi
ports:
- "8000:8000"
depends_on:
- redis
- db
redis:
image: redis:alpine
db:
image: postgres:13
environment:
POSTGRES_PASSWORD: example
关键配置项:
- Gunicorn worker数 = CPU核心数 * 2 + 1
- PostgreSQL连接池大小 = (最大连接数 - 系统预留) / worker数
- Redis设置最大内存限制防止OOM
5.2 监控告警系统
- 前端监控:使用Sentry捕获Vue错误
javascript复制Vue.config.errorHandler = (err, vm, info) => {
Sentry.captureException(err, {
extra: { component: info, props: vm.$options.propsData }
})
}
- 后端指标:Prometheus+Grafana监控:
- 接口响应时间P99 < 500ms
- 数据库连接池使用率 < 80%
- Celery任务队列积压预警
- 业务级监控:
- 订单取消率突增告警
- 库存周转率异常检测
- 用户投诉关键词聚类分析
6. 典型问题排查实录
6.1 支付回调丢失问题
现象:约3%的支付成功回调未能正确更新订单状态。排查过程:
- 检查Nginx日志发现部分POST请求body被截断
- 原因是默认的client_max_body_size配置过小
- 解决方案:
nginx复制http {
client_max_body_size 10M;
proxy_read_timeout 300s;
}
6.2 内存泄漏定位
现象:服务器内存使用量每天增长约2%。使用工具链定位:
- 通过
mprof生成Python内存使用曲线 - 使用
objgraph找出异常增长的类实例 - 最终定位到是Celery任务中未正确关闭Matplotlib对象
python复制# 错误写法
def generate_report():
plt.plot(data) # 未显式关闭figure
return savefig()
# 正确写法
def generate_report():
fig = plt.figure()
try:
ax = fig.add_subplot(111)
ax.plot(data)
return savefig()
finally:
plt.close(fig) # 确保释放内存
7. 项目演进方向
从技术债清理角度看,下一步重点包括:
- 将JWT令牌改为无状态方式,减轻Redis负担
- 用Django的
QuerySet.iterator()优化大数据量导出 - 实现基于WebSocket的实时厨房看板
业务功能扩展计划:
- 智能菜谱生成:基于用户冰箱现有食材推荐菜谱
- 视频菜谱指导:集成FFmpeg处理教学视频
- 供应链预测:用Prophet时间序列预测食材采购量
在PyCharm中开发时,强烈建议配置Django支持:
- 标记templates目录为模板文件夹
- 启用Django模板语言自动补全
- 配置Database工具直接操作PostgreSQL
