1. 项目背景与核心需求
作为一个长期混迹在厨房和代码之间的开发者,我发现身边不少朋友都有这样的困扰:收藏了无数菜谱却总是找不到,尝试新菜时手忙脚乱忘记记录关键步骤,或是想分享自己的烹饪心得却没有合适的平台。这正是我决定用Flask搭建这个美食笔记平台的初衷——用技术解决真实存在的厨房痛点。
这个平台需要满足三个核心场景:
- 灵感收集:能够快速保存来自各处的菜谱(网页链接、图片、文字等)
- 烹饪记录:支持分步骤记录实操过程,包括材料用量、火候控制等细节
- 社区分享:可以发布自己的改良配方,获取其他厨友的反馈
技术选型上,Python+Flask的组合特别适合这类中小型Web应用。Flask的轻量级特性让我们可以快速搭建原型,而Python丰富的生态圈(特别是数据处理和自然语言处理库)为后续扩展食谱分析、智能推荐等功能留足了空间。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统架构设计
2.1 技术栈选型
前端层:
- 基础框架:Jinja2模板引擎 + Bootstrap 5
- 交互增强:Vanilla JavaScript(避免过度依赖框架)
- 富文本编辑:TinyMCE(支持菜谱步骤的图文混排)
后端层:
- 核心框架:Flask 2.0+
- 数据库:PostgreSQL(考虑到菜谱数据的半结构化特性)
- 文件存储:本地文件系统(开发环境)/ AWS S3(生产环境)
- 搜索引擎:Whoosh(实现菜谱全文检索)
辅助工具:
- 部署:Gunicorn + Nginx
- 监控:Prometheus客户端(自定义指标采集)
- 测试:Pytest + Selenium
提示:虽然SQLite在开发初期更方便,但考虑到菜谱图片等二进制数据的存储效率,建议直接从PostgreSQL起步。
2.2 数据模型设计
核心实体关系如下:
python复制class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True)
recipes = db.relationship('Recipe', backref='author', lazy='dynamic')
class Recipe(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(120))
ingredients = db.Column(db.JSON) # 存储结构化配料数据
steps = db.Column(db.Text) # 包含HTML格式的步骤说明
created_at = db.Column(db.DateTime)
collections = db.relationship('Collection', secondary=recipe_collection)
class Collection(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80))
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
特别说明几个设计决策:
- 使用JSON字段存储配料表,便于后续实现"根据现有食材推荐菜谱"功能
- 步骤内容保留HTML格式,支持图文混排的烹饪说明
- 收藏夹(Collection)采用多对多关系,允许用户自由组织菜谱
3. 核心功能实现细节
3.1 菜谱编辑器的技术实现
传统表单难以满足菜谱编辑的复杂需求,我们采用组合方案:
python复制# 前端使用TinyMCE初始化编辑器
tinymce.init({
selector: '#recipe-steps',
plugins: 'image paste link',
paste_data_images: True // 允许直接粘贴图片
});
# 后端处理图片上传
@app.route('/upload-image', methods=['POST'])
def upload_image():
file = request.files.get('file')
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(filepath)
return jsonify({'location': url_for('static', filename='uploads/'+filename)})
return jsonify({'error': 'Invalid file'}), 400
实际开发中遇到的坑:
- 图片需要压缩处理:使用Pillow库添加自动压缩
- XSS防护:在展示HTML内容时仍需转义非可信标签
- 移动端适配:需要额外CSS确保编辑区域可操作性
3.2 智能表单解析
为降低用户输入门槛,我们实现了一个配料表解析器:
python复制import re
def parse_ingredients(text):
# 匹配"数量 单位 名称"模式,如"2 汤匙 酱油"
pattern = r'(\d+\.?\d*)\s*([^\d\s]+)\s*(.+)'
ingredients = []
for line in text.split('\n'):
if match := re.match(pattern, line.strip()):
amount, unit, name = match.groups()
ingredients.append({
'name': name.strip(),
'amount': float(amount),
'unit': unit.strip()
})
return ingredients
这个简单的正则表达式可以处理80%的中文配料表达,剩余特殊情况通过用户手动调整解决。实测比完全自由格式的输入体验提升明显。
4. 部署与性能优化
4.1 生产环境配置
推荐使用以下Docker Compose配置:
yaml复制version: '3'
services:
web:
build: .
ports:
- "5000:5000"
environment:
- DATABASE_URL=postgresql://dbuser:dbpass@db:5432/recipeapp
depends_on:
- db
db:
image: postgres:13
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_USER=dbuser
- POSTGRES_PASSWORD=dbpass
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
volumes:
postgres_data:
关键优化点:
- 使用Gunicorn多worker模式(CPU核心数*2+1)
- Nginx配置静态文件缓存
- PostgreSQL连接池设置
4.2 监控指标实现
通过Prometheus客户端暴露关键指标:
python复制from prometheus_client import Counter, Gauge
RECIPE_CREATED = Counter('recipe_created', 'Total recipes created')
ACTIVE_USERS = Gauge('active_users', 'Currently logged in users')
@app.route('/new-recipe', methods=['POST'])
def new_recipe():
RECIPE_CREATED.inc()
# ...创建逻辑...
建议监控的重点指标:
- 菜谱创建成功率
- 图片上传耗时P99值
- 数据库查询延迟
- 并发用户数
5. 实际开发中的经验总结
5.1 图片处理的最佳实践
在开发过程中,我们发现用户上传的图片存在几个典型问题:
- 手机直拍的照片体积过大(经常超过5MB)
- 方向信息(EXIF Orientation)导致显示异常
- 不同设备的色彩空间不一致
最终采用的解决方案:
python复制from PIL import Image, ImageOps
import io
def process_upload(image_file, max_size=1024):
img = Image.open(image_file)
# 处理方向问题
img = ImageOps.exif_transpose(img)
# 等比例缩放
img.thumbnail((max_size, max_size))
# 转换为标准RGB模式
if img.mode != 'RGB':
img = img.convert('RGB')
# 优化存储
output = io.BytesIO()
img.save(output, format='JPEG', quality=85, optimize=True)
return output.getvalue()
5.2 全文搜索的实现技巧
虽然Whoosh已经足够轻量,但在菜谱搜索场景下仍需特殊处理:
- 对中文支持需要添加jieba分词:
python复制from whoosh.analysis import Tokenizer, Token
import jieba
class ChineseTokenizer(Tokenizer):
def __call__(self, value, **kwargs):
for word in jieba.cut(value):
yield Token(text=word)
analyzer = ChineseTokenizer()
-
对配料字段建立独立索引,支持"不含花生"这样的否定查询
-
缓存热门查询结果,减轻搜索压力
6. 扩展功能思路
这个基础平台可以沿多个方向扩展:
烹饪辅助功能:
- 根据现有食材自动推荐菜谱(需构建食材-菜谱图谱)
- 烹饪计时器集成(WebSocket实现实时提醒)
- 单位换算小工具(克↔毫升等)
社交功能增强:
- 菜谱复刻打卡系统
- 厨友问答社区
- 视频教程嵌入支持
技术深化方向:
- 使用OpenCV分析菜品完成度
- 基于NLP的菜谱结构化解析
- 个性化推荐算法
在实现这些扩展时,Flask的蓝图(Blueprint)功能可以很好地保持代码组织清晰。比如单独创建recommendation.py蓝图处理所有推荐相关路由。
这个项目最让我惊喜的是看到非技术用户如何与系统互动——很多家庭厨师会创造性地使用标签功能来组织他们的私房菜谱,这提醒我们在设计系统时要保留足够的灵活性。技术永远应该服务于人的真实需求,而不是反过来限制可能性
