1. 项目概述:基于Python-Flask与Vue的全栈食谱商城系统
这套"家庭食谱菜谱食材网上商城系统"是一个典型的全栈应用开发项目,采用Python Flask作为后端框架,Vue.js构建前端界面,同时支持Web端和小程序端的访问。我在实际开发这类系统时发现,食谱类电商平台与传统电商最大的区别在于需要处理大量非标准化的商品数据——每道菜谱涉及的食材、用量、烹饪步骤都是高度个性化的数据单元。
系统核心功能模块包括:
- 多终端用户体系(微信小程序账号与Web账号打通)
- 智能菜谱推荐引擎(基于用户浏览记录和收藏行为)
- 食材供应链管理系统(对接第三方供应商API)
- 社交化功能模块(菜谱收藏、评分评论、厨友关注)
- 订单与支付系统(特别处理生鲜食材的特殊配送需求)
提示:在开发初期就要考虑小程序审核规范,特别是涉及食品销售的类目需要《食品经营许可证》备案,这也是为什么很多食谱小程序会提示"由于违规支付功能暂时无法使用"。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术架构设计与选型依据
2.1 后端技术栈:Python Flask的工程化实践
选择Flask而非Django主要考虑到:
- 微服务架构更适合逐步迭代的业务场景(初期专注食谱功能,后期扩展社交模块)
- 与Vue的配合更灵活(RESTful API设计不受模板引擎限制)
- 轻量级ORM(SQLAlchemy)对复杂菜谱关系建模更友好
关键后端依赖包:
python复制# requirements.txt核心组件
Flask==2.3.2
Flask-SQLAlchemy==3.0.3 # 处理菜谱-食材多对多关系
Flask-JWT-Extended==4.4.4 # 小程序token验证
Flask-Caching==2.0.2 # 菜谱详情页缓存
Pillow==9.5.0 # 用户上传菜品图片处理
2.2 前端技术栈:Vue3 + Vant的混合开发方案
采用Vue3的组合式API开发主要解决:
- 小程序与Web端的组件复用问题(通过条件编译)
- 复杂菜谱展示页的状态管理(使用Pinia替代Vuex)
- 响应式布局适配(使用vw/vh单位配合媒体查询)
典型页面结构示例:
javascript复制// 菜谱详情页组件结构
<template>
<div class="recipe-container">
<van-image-preview v-model="showPreview" :images="stepImages" />
<header-component :title="recipeData.title" />
<author-info :data="recipeData.author" />
<ingredient-list :items="recipeData.ingredients" />
<cooking-steps @image-click="handleStepClick" />
<related-recipes :id="recipeData.id" />
<footer-menu :price="recipeData.price" />
</div>
</template>
3. 核心业务逻辑实现细节
3.1 菜谱-食材关系数据库设计
采用多对多关系模型解决核心业务问题:
python复制# models.py 核心定义
recipe_ingredient = db.Table('recipe_ingredient',
db.Column('recipe_id', db.Integer, db.ForeignKey('recipe.id')),
db.Column('ingredient_id', db.Integer, db.ForeignKey('ingredient.id')),
db.Column('amount', db.String(20)), # 用量字段如"适量""200g"
db.Column('notes', db.String(50)) # 备注如"切丝""去骨"
)
class Recipe(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
ingredients = db.relationship('Ingredient', secondary=recipe_ingredient,
back_populates='recipes')
class Ingredient(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), unique=True)
recipes = db.relationship('Recipe', secondary=recipe_ingredient,
back_populates='ingredients')
3.2 小程序端特殊处理方案
3.2.1 音频视频播放兼容性
针对热词反映的"wav m4a文件安卓小程序播放正常,苹果小程序没有声音"问题:
javascript复制// 统一处理音频播放
function playAudio(url) {
if (wx.getSystemInfoSync().platform === 'ios') {
// iOS需要特定格式
const innerAudioContext = wx.createInnerAudioContext()
innerAudioContext.src = url.replace(/\.m4a$/, '.mp3')
innerAudioContext.play()
} else {
// 安卓直接播放
wx.downloadFile({
url: url,
success(res) {
wx.playVoice({ filePath: res.tempFilePath })
}
})
}
}
3.2.2 Webview与H5通信
实现小程序内嵌H5页面与原生小程序的通信:
javascript复制// H5页面
window.addEventListener('message', function(e) {
if (e.data.type === 'recipeData') {
updateRecipe(e.data.payload)
}
})
// 小程序webview组件
<web-view
src="https://yourdomain.com/h5page"
bindmessage="handleH5Message"
/>
4. 开发中的典型问题与解决方案
4.1 Flask与Vue的跨域会话管理
使用JWT替代传统的Cookie-Session方案:
python复制# auth.py 认证模块
from flask_jwt_extended import create_access_token
@app.route('/login', methods=['POST'])
def login():
user = User.query.filter_by(username=request.json['username']).first()
if user and user.check_password(request.json['password']):
# 添加小程序标识到token
additional_claims = {"is_miniprogram": request.headers.get('X-MiniProgram')}
access_token = create_access_token(
identity=user.id,
additional_claims=additional_claims
)
return jsonify(access_token=access_token)
return jsonify({"msg": "Bad credentials"}), 401
前端axios拦截器配置:
javascript复制// http.js 请求拦截
instance.interceptors.request.use(config => {
if (store.getters.token) {
config.headers['Authorization'] = `Bearer ${store.getters.token}`
}
// 小程序环境标识
if (wx?.getSystemInfoSync) {
config.headers['X-MiniProgram'] = 'true'
}
return config
})
4.2 菜谱图片的智能处理流程
针对用户上传的菜品图片:
- 使用Pillow进行自动裁剪(保持1:1或4:3比例)
- 生成不同尺寸缩略图(原图、中图、小图)
- 通过CLIP模型自动打标签(识别菜品类型、主要食材)
- 存储到OSS并记录CDN地址
python复制# utils/image_processor.py
def process_recipe_image(file_stream):
try:
img = Image.open(file_stream)
# 自动裁剪最大正方形区域
width, height = img.size
crop_size = min(width, height)
left = (width - crop_size)/2
top = (height - crop_size)/2
right = (width + crop_size)/2
bottom = (height + crop_size)/2
img = img.crop((left, top, right, bottom))
# 生成三种尺寸
sizes = {
'original': (1080, 1080),
'medium': (600, 600),
'small': (300, 300)
}
processed = {}
for name, size in sizes.items():
temp_img = img.resize(size, Image.LANCZOS)
buffer = BytesIO()
temp_img.save(buffer, format='JPEG', quality=85)
processed[name] = upload_to_oss(buffer.getvalue())
return processed
except Exception as e:
current_app.logger.error(f"Image process failed: {str(e)}")
raise
5. 性能优化与部署实践
5.1 数据库查询优化方案
针对菜谱列表页的N+1查询问题:
python复制# 错误做法(产生N+1查询)
recipes = Recipe.query.limit(20).all()
for r in recipes:
print(r.ingredients) # 每次循环都查询数据库
# 正确做法(使用joinedload)
from sqlalchemy.orm import joinedload
recipes = Recipe.query.options(
joinedload(Recipe.ingredients)
).limit(20).all()
5.2 小程序端渲染性能优化
- 使用虚拟列表加载长菜谱步骤:
html复制<van-list
v-model:loading="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<div v-for="(item, index) in list" :key="index">
{{ item.content }}
</div>
</van-list>
- 图片懒加载与渐进式加载:
javascript复制// vue.config.js
chainWebpack: config => {
config.module
.rule('images')
.test(/\.(png|jpe?g|gif|webp)(\?.*)?$/)
.use('url-loader')
.loader('url-loader')
.tap(options => ({
...options,
limit: 4096,
esModule: false,
name: 'img/[name].[hash:8].[ext]'
}))
}
6. 项目扩展与进阶方向
6.1 智能推荐系统实现
基于用户行为的协同过滤算法:
python复制# recommender.py
from surprise import Dataset, KNNBasic
def train_collaborative_filtering():
# 加载用户-菜谱评分数据
data = Dataset.load_from_df(ratings_df[['user_id', 'recipe_id', 'rating']],
reader=Reader(rating_scale=(1, 5)))
# 使用KNN基础算法
algo = KNNBasic(k=40, min_k=5,
sim_options={'user_based': False})
trainset = data.build_full_trainset()
algo.fit(trainset)
# 保存模型
dump.dump('recipe_recommender.model', algo=algo)
return algo
6.2 微信支付集成注意事项
处理小程序支付的特殊流程:
- 需要微信商户平台账号(个体户也可申请)
- 后端生成预支付订单时必须传递小程序的openid
- 支付结果通知需要处理重复通知问题
- 必须处理"支付功能暂时无法使用"的降级方案
典型支付流程代码:
python复制# payment.py
@app.route('/create_wxpay_order', methods=['POST'])
@jwt_required()
def create_wxpay_order():
user_id = get_jwt_identity()
recipe_id = request.json.get('recipe_id')
# 获取用户openid(从小程序登录时获取)
user = User.query.get(user_id)
if not user or not user.wx_openid:
abort(400, description="Missing wechat openid")
# 创建支付订单
order = create_order(user_id, recipe_id)
# 调用微信支付统一下单接口
wxpay_params = {
'appid': app.config['WX_APPID'],
'mch_id': app.config['WX_MCHID'],
'nonce_str': generate_nonce_str(),
'body': f'菜谱购买-{order.recipe_title}',
'out_trade_no': order.order_no,
'total_fee': int(order.amount * 100),
'spbill_create_ip': request.remote_addr,
'notify_url': app.config['WX_NOTIFY_URL'],
'trade_type': 'JSAPI',
'openid': user.wx_openid
}
# 添加签名并调用接口
wxpay_params['sign'] = generate_sign(wxpay_params)
result = wxpay_unifiedorder(wxpay_params)
# 返回小程序支付所需参数
return jsonify({
'timeStamp': str(int(time.time())),
'nonceStr': result['nonce_str'],
'package': f"prepay_id={result['prepay_id']}",
'signType': 'MD5',
'paySign': generate_payment_sign(result)
})
在开发这类全栈项目时,最深的体会是必须建立完整的异常处理机制——从用户上传的非法图片格式,到第三方API调用失败,再到小程序端的各种兼容性问题,每个环节都需要设计降级方案。特别是在处理食品类电商业务时,要提前了解平台审核规则,避免因资质问题导致功能受限。
