1. 项目背景与核心价值
餐厅点餐系统是餐饮行业数字化转型的基础设施,也是Python全栈开发的经典练手项目。这个看似简单的系统背后,实际上融合了数据库设计、业务逻辑处理、用户交互界面三大核心模块的开发经验。
我去年为一家连锁快餐店部署这套系统时,发现市面上很多教学项目存在几个通病:数据库表设计不合理导致高峰期锁表现象严重、前后端交互缺乏事务处理机制、打印模块对特殊字符处理不完善。这些问题在实际运营中会造成订单丢失、库存不同步等严重事故。
本系统采用Python+Django+MySQL技术栈,包含以下特色设计:
- 采用读写分离的数据库架构,订单表与菜单表物理分离
- 引入Redis作为缓存层缓解数据库压力
- 实现前后端双重校验机制
- 特别优化了打印模块的字符编码处理
2. 系统架构设计
2.1 技术选型分析
后端选择Django框架而非Flask的考虑:
- 自带ORM简化数据库操作
- Admin后台可快速生成管理界面
- 完善的中间件支持
- 内置用户认证系统
数据库选型对比:
| 特性 | MySQL | SQLite | PostgreSQL | |---------------|-------------|------------|------------| | 并发性能 | ★★★★☆ | ★★☆☆☆ | ★★★★★ | | 事务支持 | 完整 | 有限 | 完整 | | 部署复杂度 | 中等 | 简单 | 较高 | | 适合场景 | 生产环境 | 开发测试 | 复杂业务 |最终选择MySQL 8.0的原因:
- 支持JSON字段存储菜品定制化需求
- 良好的事务隔离级别控制
- 社区资源丰富
2.2 核心数据模型
ER图关键实体关系:
- 顾客(Customer) 1:n 订单(Order)
- 订单(Order) m:n 菜品(Dish) 通过中间表OrderDetail关联
- 餐桌(Table) 1:n 订单(Order)
特别注意的字段设计:
class Order(models.Model): STATUS_CHOICES = [ ('created', '已创建'), ('paid', '已支付'), ('completed', '已完成'), ('cancelled', '已取消') ] table = models.ForeignKey(Table, on_delete=models.PROTECT) status = models.CharField(max_length=20, choices=STATUS_CHOICES) created_at = models.DateTimeField(auto_now_add=True) # 使用Decimal而非Float保证金额精度 total_amount = models.DecimalField(max_digits=10, decimal_places=2)3. 核心功能实现细节
3.1 订单状态机设计
采用状态模式实现订单生命周期管理:
class OrderState(ABC): @abstractmethod def pay(self, order): pass @abstractmethod def cancel(self, order): pass class CreatedState(OrderState): def pay(self, order): order.status = 'paid' order.save() # 触发厨房打印 PrinterService.print_kitchen_ticket(order) def cancel(self, order): if order.created_at < timezone.now() - timedelta(minutes=30): raise InvalidStateError("超时订单不可取消") order.status = 'cancelled' order.save()3.2 并发控制方案
解决超卖问题的三种方案对比实现:
- 乐观锁实现:
def add_to_order(order_id, dish_id): with transaction.atomic(): order = Order.objects.select_for_update().get(pk=order_id) dish = Dish.objects.get(pk=dish_id) if dish.stock < 1: raise OutOfStockError() # 使用F()避免竞态条件 Dish.objects.filter( pk=dish.id, stock__gte=1 ).update(stock=F('stock') - 1) OrderDetail.objects.create(order=order, dish=dish)- Redis分布式锁:
def add_to_order_with_lock(order_id, dish_id): lock_key = f"dish_{dish_id}_lock" with redis.lock(lock_key, timeout=10): dish = Dish.objects.get(pk=dish_id) if dish.stock < 1: raise OutOfStockError() dish.stock -= 1 dish.save() OrderDetail.objects.create( order_id=order_id, dish_id=dish_id )3.3 打印模块特殊处理
热敏打印机常见问题解决方案:
- 中文乱码:强制使用GB18030编码
- 条形码打印:使用python-barcode库生成Code128
- 自动切纸:发送ESC/POS指令\x1B\x69
def print_receipt(order): printer = Usb(0x0416, 0x5011) # 设置中文字符集 printer.set( codepage='gb18030', align='center' ) # 打印订单头 printer.text("=== 点餐清单 ===\n") # 打印菜品列表 for item in order.items.all(): printer.text(f"{item.dish.name} x{item.quantity}\n") # 打印条形码 barcode = Code128(str(order.id)) barcode.write(printer) # 执行切纸 printer.cut()4. 部署与性能优化
4.1 数据库配置要点
my.cnf关键参数配置:
[mysqld] # 连接池设置 max_connections = 200 wait_timeout = 300 # InnoDB优化 innodb_buffer_pool_size = 1G innodb_flush_log_at_trx_commit = 2 innodb_file_per_table = ON # 查询缓存 query_cache_type = 0 # 禁用QC,用Redis替代4.2 缓存策略设计
采用多级缓存架构:
- 本地缓存:使用django.core.cache做请求级缓存
- Redis缓存:
- 菜单数据:设置5分钟过期
- 库存计数:使用Redis原子操作
- 缓存击穿防护:
def get_dish(dish_id): # 缓存标记模式 cache_key = f"dish_{dish_id}" data = cache.get(cache_key) if data is None: # 获取锁防止缓存击穿 lock_key = f"lock_{cache_key}" if cache.add(lock_key, 1, timeout=10): try: data = Dish.objects.get(pk=dish_id) cache.set(cache_key, data, 300) finally: cache.delete(lock_key) else: time.sleep(0.1) return get_dish(dish_id) return data5. 踩坑实录与解决方案
5.1 订单流水号重复问题
现象:高并发下出现订单号重复 根因:使用时间戳+随机数生成方式存在冲突可能 解决方案:改用Snowflake算法
class Snowflake: def __init__(self, worker_id): self.worker_id = worker_id self.sequence = 0 self.last_timestamp = -1 def next_id(self): timestamp = int(time.time() * 1000) if timestamp < self.last_timestamp: raise ClockMoveBackError() if timestamp == self.last_timestamp: self.sequence = (self.sequence + 1) & 0xFFF if self.sequence == 0: timestamp = self.wait_next_millis() else: self.sequence = 0 self.last_timestamp = timestamp return ((timestamp - 1288834974657) << 22) | (self.worker_id << 12) | self.sequence5.2 支付超时处理
典型支付流程问题:
- 用户支付后网络中断
- 支付平台回调丢失
- 订单状态不一致
解决方案:
def check_payment_status(): # 查询处于"已创建"状态超过15分钟的订单 expired_orders = Order.objects.filter( status='created', created_at__lt=timezone.now() - timedelta(minutes=15) ) for order in expired_orders: # 调用支付平台查询接口 payment_status = PaymentService.query(order.payment_id) if payment_status == 'paid': order.status = 'paid' order.save() elif payment_status == 'failed': order.status = 'cancelled' order.save() # 每天凌晨执行对账 reconcile_payments()这套系统在实际运营中每天处理3000+订单,高峰期QPS达到50,通过合理的架构设计和持续优化,系统运行稳定可靠。特别建议在开发阶段就考虑好监控方案,我们使用Prometheus+Grafana监控关键指标:
- 订单创建成功率
- 平均支付处理时间
- 数据库查询延迟
- 打印任务队列长度