1. 项目概述:基于Python与AI技术的美食分享平台
作为一个长期混迹在美食圈的技术人,我一直在思考如何用技术手段解决美食爱好者们的痛点。经过多次迭代开发,终于完成了一个结合Django和Flask框架的美食分享平台,并集成了AI推荐和图像识别功能。这个系统不仅能展示餐厅信息、管理美食活动,还能通过智能算法为用户提供个性化推荐。
对于开发者而言,这个项目最吸引人的地方在于:
- 采用了Python生态中成熟的Django+Flask双框架架构
- 实现了基于用户行为的协同过滤推荐算法
- 整合了预训练图像识别模型处理美食图片
- 设计了高可用的微服务化部署方案
提示:在开发类似平台时,建议先从核心功能入手,再逐步添加AI模块。过早引入复杂算法会增加开发难度和维护成本。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术架构设计与选型考量
2.1 后端框架组合策略
选择Django作为主框架主要基于以下考虑:
- 内置的ORM系统能快速构建餐厅、活动、用户等复杂数据关系
- Admin后台可立即用于内容管理,节省开发时间
- 完善的用户认证系统(django.contrib.auth)开箱即用
而Flask则负责处理需要更高灵活性的功能模块:
- 活动报名API接口
- 推荐算法微服务
- 图像处理服务
这种组合方式的优势在于:
python复制# 示例:Django模型与Flask API的协同工作
# Django models.py
class Event(models.Model):
title = models.CharField(max_length=100)
capacity = models.IntegerField()
# Flask app.py
@app.route('/api/events/<int:event_id>/register', methods=['POST'])
def register(event_id):
event = DjangoEvent.objects.get(pk=event_id)
if event.registrations.count() < event.capacity:
# 处理报名逻辑
return jsonify({"status": "success"})
return jsonify({"status": "full"}), 400
2.2 数据库选型与优化
经过对比测试,我们最终选择了PostgreSQL作为主数据库,主要因为:
- 对JSON字段的良好支持,便于存储动态的餐厅属性
- 强大的全文搜索功能,适合美食名称和描述的检索
- 优于MySQL的地理位置查询性能(用于附近餐厅搜索)
数据库优化措施包括:
- 为高频查询字段添加索引:
sql复制CREATE INDEX idx_restaurant_location ON restaurant USING GIST(location);
CREATE INDEX idx_event_start_time ON event(start_time);
- 使用Redis缓存:
- 热门餐厅列表(TTL 30分钟)
- 用户推荐结果(TTL 1小时)
- 活动剩余名额计数(TTL 5分钟)
3. 核心功能模块实现细节
3.1 用户系统设计与扩展
虽然Django提供了基础的用户认证,但我们进行了多项增强:
- 第三方登录集成:
python复制# settings.py
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = 'your-key'
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = 'your-secret'
# 自定义用户模型
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
food_preferences = JSONField(default=list) # 存储饮食偏好
avatar = models.ImageField(upload_to='avatars/')
- 手机验证码登录流程:
mermaid复制sequenceDiagram
用户->>+服务器: 提交手机号
服务器-->>-短信平台: 发送验证码(6位数字)
用户->>+服务器: 提交验证码
服务器->>Redis: 验证码校验
alt 验证成功
服务器-->>用户: 返回JWT令牌
else 验证失败
服务器-->>用户: 返回错误信息
end
3.2 活动报名系统的并发控制
对于热门餐厅活动,需要特别注意并发报名问题。我们实现了两种解决方案:
- 数据库层面:
python复制# 使用select_for_update锁定记录
with transaction.atomic():
event = Event.objects.select_for_update().get(pk=event_id)
if event.registrations.count() < event.capacity:
Registration.objects.create(user=user, event=event)
- Redis分布式锁:
python复制def register_with_lock(user_id, event_id):
lock_key = f"event_lock:{event_id}"
with redis.lock(lock_key, timeout=10):
# 报名逻辑
return _do_register(user_id, event_id)
4. AI模块深度集成
4.1 混合推荐算法实现
我们结合了两种推荐算法提升准确度:
- 基于用户的协同过滤(UserCF):
python复制from sklearn.metrics.pairwise import cosine_similarity
def calculate_user_similarity(user_ratings):
# user_ratings: 用户-餐厅评分矩阵
similarity = cosine_similarity(user_ratings)
return similarity
def recommend_by_cf(user_id, similarity_matrix, k=5):
similar_users = similarity_matrix[user_id].argsort()[-k-1:-1][::-1]
# 聚合相似用户的喜好
return weighted_recommendations
- 随机森林特征工程:
python复制from sklearn.ensemble import RandomForestClassifier
def train_rf_model(user_features, restaurant_features, interactions):
# 构造特征向量
X = []
y = []
# ...特征工程...
model = RandomForestClassifier(n_estimators=100)
model.fit(X, y)
return model
最终推荐结果通过加权融合:
code复制最终得分 = 0.6*UserCF得分 + 0.4*RF预测概率
4.2 图像识别实践技巧
使用预训练的ResNet50模型进行美食识别时,我们总结了几点经验:
- 迁移学习调整:
python复制from tensorflow.keras.applications import ResNet50
base_model = ResNet50(weights='imagenet', include_top=False)
x = base_model.output
x = GlobalAveragePooling2D()(x)
x = Dense(1024, activation='relu')(x)
predictions = Dense(num_classes, activation='softmax')(x)
- 数据增强策略:
python复制train_datagen = ImageDataGenerator(
rotation_range=20,
width_shift_range=0.2,
height_shift_range=0.2,
horizontal_flip=True,
zoom_range=0.2
)
- 在实际部署中发现,对亚洲美食的识别准确率较低,解决方案是:
- 收集本地美食数据集进行微调
- 加入菜品成分作为辅助特征
- 实现多模型投票机制
5. 性能优化与部署实战
5.1 微服务化架构
将系统拆分为多个独立服务:
code复制 +-----------------+
| Nginx (80) |
+--------+--------+
|
+------------------+------------------+
| | |
+--------+--------+ +-------+-------+ +--------+--------+
| Django (8000) | | Flask (5000) | | Redis (6379) |
| - 主Web应用 | | - 推荐服务 | | - 缓存 |
| - 管理后台 | | - 图像服务 | | - 会话存储 |
+-----------------+ +---------------+ +-----------------+
| |
+---------+--------+
|
+-------+-------+
| PostgreSQL |
| - 主数据库 |
+---------------+
5.2 关键配置示例
- Nginx配置片段:
nginx复制upstream django {
server 127.0.0.1:8000;
}
upstream flask {
server 127.0.0.1:5000;
}
server {
location /api/recommend {
proxy_pass http://flask;
}
location / {
proxy_pass http://django;
}
}
- Gunicorn启动命令:
bash复制# Django
gunicorn --workers 4 --threads 2 --bind 0.0.0.0:8000 core.wsgi:application
# Flask
gunicorn --workers 2 --bind 0.0.0.0:5000 app:app
- Celery配置:
python复制# celery_config.py
broker_url = 'redis://localhost:6379/0'
result_backend = 'redis://localhost:6379/1'
6. 踩坑经验与解决方案
6.1 跨服务会话共享问题
初期Django和Flask使用不同的session机制,导致用户需要重复登录。最终解决方案:
- 使用Redis作为统一session存储:
python复制# Django settings.py
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "default"
# Flask配置
app.secret_key = 'same-as-django'
app.config['SESSION_TYPE'] = 'redis'
- JWT令牌统一签发:
python复制# 共享的JWT工具模块
def generate_jwt(user):
payload = {
'user_id': user.id,
'exp': datetime.utcnow() + timedelta(days=1)
}
return jwt.encode(payload, 'shared-secret', algorithm='HS256')
6.2 推荐系统冷启动
新用户没有历史数据时,推荐质量很差。我们实现了以下策略:
- 基于内容的推荐:
python复制def content_based_recommend(new_user_preferences):
# 使用餐厅标签匹配用户偏好
restaurants = Restaurant.objects.all()
scores = []
for r in restaurants:
score = calculate_similarity(new_user_preferences, r.tags)
scores.append((r.id, score))
return sorted(scores, key=lambda x: x[1], reverse=True)[:10]
- 热门榜单兜底:
sql复制SELECT r.*, COUNT(f.id) as fav_count
FROM restaurant r LEFT JOIN favorite f ON r.id = f.restaurant_id
GROUP BY r.id
ORDER BY fav_count DESC
LIMIT 10;
- 引导式问卷:
- 首次登录时让用户选择喜欢的菜系
- 对特定食材进行偏好评分
- 收集饮食禁忌信息
7. 扩展功能与创新实践
7.1 实时弹幕系统实现
采用WebSocket实现活动页面的实时互动:
python复制# Flask-SocketIO示例
from flask_socketio import SocketIO, emit
socketio = SocketIO(app, cors_allowed_origins="*")
@socketio.on('new_comment')
def handle_comment(data):
# 保存到数据库
comment = save_comment(data)
# 广播给所有客户端
emit('new_comment',
{'text': comment.text, 'user': comment.user.name},
broadcast=True)
前端配合Vue实现:
javascript复制const socket = io.connect('http://api.yourdomain.com')
socket.on('new_comment', (data) => {
this.comments.push(data)
})
7.2 智能预警系统设计
针对餐厅库存和活动报名情况设置预警:
- 模型定义:
python复制class AlertRule(models.Model):
target = models.CharField(max_length=50) # 'inventory' or 'event'
condition = models.CharField(max_length=20) # 'lt', 'gt', etc
threshold = models.FloatField()
message_template = models.TextField()
is_active = models.BooleanField(default=True)
- 检查任务:
python复制# Celery定时任务
@app.task
def check_alerts():
for rule in AlertRule.objects.filter(is_active=True):
if rule.target == 'inventory':
items = InventoryItem.objects.filter(quantity__lt=rule.threshold)
for item in items:
send_alert(rule.message_template.format(
item=item.name,
quantity=item.quantity
))
- 前端展示优化:
- 使用不同颜色区分紧急程度
- 支持一键查看关联数据
- 提供临时屏蔽选项
8. 测试策略与质量保障
8.1 自动化测试体系
- Django单元测试:
python复制class EventTestCase(TestCase):
def setUp(self):
self.user = User.objects.create(username='test')
self.event = Event.objects.create(title='Test', capacity=10)
def test_registration(self):
self.client.force_login(self.user)
response = self.client.post(
f'/events/{self.event.id}/register/')
self.assertEqual(response.status_code, 200)
self.assertEqual(self.event.registrations.count(), 1)
- API接口测试:
python复制# 使用pytest测试Flask接口
def test_recommend_api(client):
response = client.post('/api/recommend',
json={'user_id': 1, 'count': 5})
assert response.status_code == 200
assert len(response.json['results']) == 5
- 负载测试:
bash复制# 使用locust模拟并发用户
locust -f load_test.py --host=http://localhost:8000
8.2 A/B测试实施
对比不同推荐算法的效果:
- 实验设计:
- 将用户随机分为A组和B组
- A组使用纯协同过滤算法
- B组使用混合推荐算法
- 关键指标:点击率、转化率、停留时长
- 数据分析:
python复制# 使用pandas分析结果
df = pd.read_csv('ab_test_results.csv')
a_group = df[df['group'] == 'A']
b_group = df[df['group'] == 'B']
print(f"A组转化率: {a_group['conversion'].mean():.2%}")
print(f"B组转化率: {b_group['conversion'].mean():.2%}")
- 结果应用:
- 统计显著性检验(p < 0.05)
- 逐步灰度发布优胜算法
- 持续监控核心指标
在实际开发过程中,最大的体会是要平衡技术先进性和工程实用性。比如在推荐算法上,并不是模型越复杂效果越好,而是要结合业务场景选择最适合的方案。另外,微服务架构虽然灵活,但也带来了运维复杂度的提升,需要根据团队规模谨慎选择架构粒度。
