1. 项目概述:为什么选择Django+Flask构建餐厅点餐系统?
在餐饮行业数字化转型浪潮中,我最近为一家连锁餐厅完成了点餐系统的重构。原系统采用纯PHP开发,存在响应慢、高峰期崩溃、扩展困难等问题。经过技术选型评估,最终确定采用Django+Flask双框架方案,6个月后系统上线,订单处理效率提升300%,客户投诉率下降65%。
这个技术组合的独特优势在于:Django提供了"开箱即用"的全套解决方案(自带Admin后台、ORM、认证系统等),而Flask的轻量化特性则完美适配需要快速迭代的微服务模块。比如我们用Django处理核心的订单流水线,用Flask构建实时通知服务,当订单状态变化时,服务人员的手持设备能立即收到推送。
2. 技术架构设计解析
2.1 整体架构设计
系统采用典型的前后端分离架构:
code复制[Vue.js前端] ←HTTP→ [Nginx] ←REST API→
[Django主服务]
[Flask微服务]
↑ ↑
↓ ↓
[MySQL] [Redis]
前端使用Vue 3组合式API开发,通过Axios与后端通信。特别优化了移动端交互流程,测试发现将点餐步骤从5步缩减到3步后,用户放弃率降低了40%。
2.2 数据库设计要点
MySQL表结构设计遵循几个原则:
- 高频查询字段建立复合索引(如(status, create_time))
- 使用Decimal(10,2)存储金额避免浮点误差
- 菜品表采用"主子表"设计,主表存基本信息,子表存规格价格
python复制# Django Model示例
class Dish(models.Model):
name = models.CharField(max_length=100)
description = models.TextField()
image = models.ImageField(upload_to='dishes/')
is_active = models.BooleanField(default=True)
class DishVariant(models.Model):
dish = models.ForeignKey(Dish, on_delete=models.CASCADE)
size = models.CharField(max_length=50)
price = models.DecimalField(max_digits=10, decimal_places=2)
inventory = models.IntegerField(default=0)
踩坑提醒:Django的ImageField需要配合Pillow库使用,且务必配置MEDIA_ROOT和MEDIA_URL。我们曾因Nginx配置错误导致图片403,后来通过添加location规则解决。
2.3 缓存策略实现
使用Redis实现三级缓存:
- 全菜品缓存:定时任务每小时更新
- 热销榜缓存:每10分钟更新Top20
- 用户会话缓存:存储购物车等临时数据
python复制# Flask中实现Redis缓存的装饰器
def redis_cache(key_prefix, timeout=300):
def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
cache_key = f"{key_prefix}:{str(kwargs)}"
data = redis_client.get(cache_key)
if data:
return json.loads(data)
result = f(*args, **kwargs)
redis_client.setex(cache_key, timeout, json.dumps(result))
return result
return wrapper
return decorator
3. 核心功能模块实现
3.1 订单状态机设计
订单生命周期管理是系统的核心难点。我们采用状态模式实现:
python复制class OrderState(enum.Enum):
PENDING = "待支付"
PAID = "已支付"
PREPARING = "制作中"
READY = "待取餐"
COMPLETED = "已完成"
CANCELLED = "已取消"
STATE_TRANSITIONS = {
OrderState.PENDING: [OrderState.PAID, OrderState.CANCELLED],
OrderState.PAID: [OrderState.PREPARING, OrderState.CANCELLED],
# ...其他状态转换规则
}
def change_order_state(order, new_state):
if new_state not in STATE_TRANSITIONS[order.state]:
raise InvalidStateTransitionError()
order.state = new_state
order.save()
# 触发相关事件
if new_state == OrderState.PAID:
dispatch_payment_notification(order)
3.2 支付集成实践
接入支付宝和微信支付时,特别注意:
- 使用官方SDK而非第三方封装包
- 支付结果采用异步通知+主动查询双验证
- 记录所有支付交互日志
python复制# Django支付回调处理示例
@csrf_exempt
def alipay_callback(request):
try:
data = request.POST.dict()
# 验证签名
if not alipay.verify(data):
raise SuspiciousOperation("Invalid signature")
order = Order.objects.get(pk=data['out_trade_no'])
if order.amount != Decimal(data['total_amount']):
logger.warning(f"Amount mismatch for order {order.id}")
return HttpResponseBadRequest()
if data['trade_status'] == 'TRADE_SUCCESS':
order.mark_as_paid()
return HttpResponse("success")
except Exception as e:
logger.error(f"Alipay callback error: {str(e)}")
return HttpResponseBadRequest()
3.3 实时通知服务
用Flask+SocketIO实现的服务端推送:
python复制from flask_socketio import SocketIO, emit
socketio = SocketIO(app, cors_allowed_origins="*")
@socketio.on('connect')
def handle_connect():
staff_id = request.args.get('staff_id')
if staff_id:
join_room(f'staff_{staff_id}')
def send_order_update(order):
data = {
'order_id': order.id,
'new_state': order.state.value,
'update_time': order.update_time.isoformat()
}
socketio.emit('order_update', data, room=f'staff_{order.store.id}')
前端连接代码:
javascript复制const socket = io('https://api.example.com', {
query: { staff_id: userStore.staffId }
});
socket.on('order_update', (data) => {
showNotification(`订单${data.order_id}状态变更为${data.new_state}`)
});
4. 性能优化实战记录
4.1 数据库查询优化
通过Django Debug Toolbar发现N+1查询问题后,我们进行了以下改进:
- 使用select_related/prefetch_related:
python复制# 优化前(产生N+1查询)
orders = Order.objects.filter(store=request.store)
for order in orders:
print(order.customer.name) # 每次循环都查询customer表
# 优化后
orders = Order.objects.filter(store=request.store).select_related('customer')
- 对分页列表添加计数缓存:
python复制from django.core.cache import cache
def get_paginated_orders(request):
cache_key = f"order_count:{request.store.id}"
count = cache.get(cache_key)
if count is None:
count = Order.objects.filter(store=request.store).count()
cache.set(cache_key, count, 300)
paginator = Paginator(Order.objects.all(), 20)
page = paginator.get_page(request.GET.get('page'))
return page
4.2 异步任务处理
对耗时操作使用Celery+Redis:
python复制@app.task(bind=True)
def process_order(self, order_id):
try:
order = Order.objects.get(pk=order_id)
# 生成厨房打印任务
generate_kitchen_ticket(order)
# 发送客户短信
send_order_confirmation_sms(order)
except Exception as exc:
self.retry(exc=exc, countdown=60)
配置Celery beat定时任务:
python复制app.conf.beat_schedule = {
'check-expired-orders': {
'task': 'orders.tasks.check_expired_orders',
'schedule': crontab(minute='*/5'), # 每5分钟执行
},
}
5. 安全防护方案
5.1 常见攻击防护
- CSRF防护:Django默认启用CSRF中间件,对Flask服务手动添加:
python复制@app.before_request
def check_csrf():
if request.method in ["POST", "PUT", "DELETE"]:
csrf_token = request.headers.get('X-CSRF-Token')
if not csrf_token or csrf_token != session.get('csrf_token'):
abort(403)
- XSS防护:
- 前端使用vue-sanitize过滤输入
- 后端模板系统自动转义(Django的|safe需要显式声明)
- SQL注入防护:
- 坚持使用ORM或参数化查询
- 禁止拼接SQL语句
5.2 敏感数据保护
- 密码存储:
python复制from django.contrib.auth.hashers import make_password
user = User.objects.create(
username=form.cleaned_data['username'],
password=make_password(form.cleaned_data['password'])
)
- 支付信息加密:
python复制from cryptography.fernet import Fernet
cipher_suite = Fernet(settings.ENCRYPTION_KEY)
def encrypt_payment_data(data):
return cipher_suite.encrypt(data.encode())
def decrypt_payment_data(encrypted_data):
return cipher_suite.decrypt(encrypted_data).decode()
6. 部署实战经验
6.1 容器化部署
使用Docker Compose编排服务:
yaml复制version: '3.8'
services:
web:
build: ./django_app
command: gunicorn --bind 0.0.0.0:8000 core.wsgi
volumes:
- static_data:/app/static
depends_on:
- redis
- db
flask:
build: ./flask_service
command: python app.py
environment:
- REDIS_URL=redis://redis:6379/1
redis:
image: redis:6-alpine
ports:
- "6379:6379"
db:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
volumes:
static_data:
mysql_data:
6.2 性能调优
Gunicorn配置建议:
python复制# gunicorn.conf.py
workers = min(4, (os.cpu_count() * 2) + 1)
worker_class = 'gevent'
keepalive = 60
timeout = 300
Nginx关键配置:
nginx复制location /static/ {
alias /app/static/;
expires 30d;
}
location / {
proxy_pass http://web:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_connect_timeout 300s;
proxy_read_timeout 300s;
}
7. 项目演进方向
当前系统已在15家门店稳定运行半年,接下来的改进计划包括:
- 智能推荐系统:基于用户历史订单和相似用户行为,使用协同过滤算法实现菜品推荐
python复制from surprise import Dataset, KNNBasic
def train_recommend_model():
data = Dataset.load_from_df(ratings_df, reader)
trainset = data.build_full_trainset()
sim_options = {'name': 'cosine', 'user_based': False}
algo = KNNBasic(sim_options=sim_options)
algo.fit(trainset)
return algo
- 后厨自动化:对接厨房显示系统(KDS),优化出餐顺序算法,考虑:
- 菜品制作时长
- 订单优先级(堂食/外卖)
- 食材准备情况
- 数据大屏:使用Apache Superset构建实时经营看板,监控:
- 每小时销售额
- 菜品销售排行
- 平均候餐时间
- 客户满意度趋势