1. 餐厅点餐系统项目概述
这个基于SpringBoot+Vue的餐厅点餐系统是我去年为本地一家连锁餐饮集团开发的数字化解决方案。当时他们正面临高峰期服务员手写点单效率低下、后厨订单错乱、财务对账困难等一系列痛点。我们团队用三个月时间完成了这套系统的开发和部署,上线后客户订单处理效率提升了60%,人力成本降低了30%。
系统采用前后端分离架构,后端基于SpringBoot 2.7提供RESTful API,前端使用Vue 3组合式API开发管理后台和顾客点餐界面。数据库选用MySQL 8.0,通过Redis缓存热点数据提升并发性能。特别设计了双端交互模式:服务员使用的Pad端和顾客自助点餐的微信小程序,两者数据实时同步。
2. 技术架构设计解析
2.1 后端技术栈选型
选择SpringBoot作为后端框架主要基于三个考量:
- 快速启动:餐饮行业需求变化快,SpringBoot的约定优于配置特性让我们两周就搭好了基础框架
- 生态丰富:整合MyBatis-Plus、Spring Security等组件非常顺畅
- 易于部署:内嵌Tomcat支持一键打包成可执行JAR,客户服务器环境配置简单
数据库设计时特别注意了几个要点:
- 订单表采用水平分表,按月份存储历史数据
- 菜品表添加fulltext索引支持模糊搜索
- 使用Decimal(10,2)精确存储金额避免浮点误差
// 典型的分页查询实现示例 @GetMapping("/dishes") public R<Page<Dish>> listDishes( @RequestParam(required = false) String name, @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size) { LambdaQueryWrapper<Dish> wrapper = new LambdaQueryWrapper<>(); if (StringUtils.isNotBlank(name)) { wrapper.like(Dish::getName, name); } return R.success(dishService.page(new Page<>(page, size), wrapper)); }2.2 前端技术方案
Vue 3的组合式API特别适合点餐系统这种交互复杂的场景:
- 使用Pinia管理全局状态(如购物车、用户信息)
- Element Plus组件库快速构建管理后台
- Vite构建工具实现秒级热更新
微信小程序端关键技术点:
- 自定义扫码点餐组件
- WebSocket实时接收订单状态变更
- 本地缓存已点菜品防止网络中断
重要提示:Vue和SpringBoot的跨域问题需要通过@CrossOrigin注解配合前端代理解决,生产环境建议使用Nginx统一处理
3. 核心功能实现细节
3.1 实时订单处理流程
订单创建流程:
- 前端生成唯一订单号(时间戳+随机数)
- 提交时包含桌号、菜品列表、备注等信息
- 后端验证库存后写入数据库
状态机设计:
stateDiagram [*] --> 待支付 待支付 --> 已支付: 支付成功 已支付 --> 制作中: 厨房接单 制作中 --> 已上菜: 菜品制作完成 已上菜 --> 已完成: 顾客确认 任何状态 --> 已取消: 超时/手动取消- 消息推送机制:
- 使用Spring WebSocket广播订单状态变更
- 前端通过STOMP协议订阅特定topic
- 加入Redis发布订阅提升并发能力
3.2 购物车设计要点
// Vue3的购物车Store实现 export const useCartStore = defineStore('cart', () => { const items = ref(new Map()) const addItem = (dish) => { if (items.value.has(dish.id)) { items.value.get(dish.id).quantity++ } else { items.value.set(dish.id, { ...dish, quantity: 1 }) } } const total = computed(() => { return [...items.value.values()].reduce( (sum, item) => sum + (item.price * item.quantity), 0) }) return { items, addItem, total } })实际开发中遇到的典型问题:
- 菜品规格选择(如辣度、备注)需要特殊处理
- 并发修改购物车可能产生冲突
- 本地存储需要考虑数据一致性
4. 性能优化实践
4.1 数据库优化
索引策略:
- 订单表:复合索引 (status, create_time)
- 菜品表:唯一索引 (category_id, sort)
- 用户表:哈希索引 (phone)
查询优化:
-- 慢查询优化前 SELECT * FROM orders WHERE status = 'PAID' AND create_time > '2023-01-01'; -- 优化后使用覆盖索引 SELECT id, table_num, total_amount FROM orders WHERE status = 'PAID' AND create_time > '2023-01-01' ORDER BY create_time DESC LIMIT 100;4.2 缓存设计
采用多级缓存架构:
- 本地缓存:Caffeine缓存菜品分类等低频变更数据
- Redis缓存:
- 热点菜品信息(60秒过期)
- 今日销售统计(每日零点清除)
- 分布式锁(防止重复下单)
// SpringCache注解使用示例 @Cacheable(value = "menu", key = "#categoryId") public List<Dish> getDishesByCategory(Long categoryId) { return dishMapper.selectList( new LambdaQueryWrapper<Dish>() .eq(Dish::getCategoryId, categoryId) .orderByAsc(Dish::getSort)); }5. 安全防护措施
5.1 认证授权方案
JWT令牌实现细节:
- 采用HS512算法签名
- 包含用户ID、角色、门店ID等claims
- 刷新令牌机制:access_token 30分钟过期,refresh_token 7天
权限控制示例:
@PreAuthorize("hasRole('WAITER') or hasRole('ADMIN')") @PostMapping("/orders/{id}/cancel") public R cancelOrder(@PathVariable Long id) { return orderService.cancelOrder(id); }5.2 敏感数据保护
支付信息加密:
- 使用AES加密银行卡等敏感信息
- 密钥通过HSM硬件模块管理
日志脱敏:
@Bean public PatternLayoutEncoder encoder() { PatternLayoutEncoder encoder = new PatternLayoutEncoder(); encoder.setPattern("%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n"); encoder.setReplacer(new MaskingPatternLayoutReplacer() .addPattern("(\"phone\":\")(\\d{3})\\d{4}(\\d{4})", "$1$2****$3")); return encoder; }6. 部署与监控
6.1 容器化部署
Docker Compose编排方案:
version: '3' services: backend: image: restaurant-backend:1.2.0 ports: - "8080:8080" environment: - SPRING_PROFILES_ACTIVE=prod depends_on: - redis - mysql mysql: image: mysql:8.0 volumes: - db_data:/var/lib/mysql environment: - MYSQL_ROOT_PASSWORD=restaurant123 redis: image: redis:6-alpine ports: - "6379:6379"6.2 监控指标
Prometheus监控的关键指标:
- 订单创建QPS
- 平均响应时间
- 活跃WebSocket连接数
- 缓存命中率
Grafana仪表盘配置示例:
avg(rate(order_create_total[1m])) by (instance) // 每分钟订单数 histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[1m])) by (le)) // 95分位响应时间7. 典型问题排查
7.1 微信支付回调失败
常见原因及解决方案:
- 网络问题:检查服务器出站443端口是否开放
- 签名验证失败:确认商户密钥配置一致
- 重复通知:实现幂等处理逻辑
7.2 库存扣减异常
解决方案比较:
| 方案 | 优点 | 缺点 |
|---|---|---|
| 乐观锁 | 并发度高 | 需要重试机制 |
| 悲观锁 | 强一致性 | 性能影响大 |
| Redis原子操作 | 性能好 | 需要持久化保障 |
最终采用的分布式锁实现:
public boolean reduceStock(Long dishId, int quantity) { String lockKey = "lock:dish:" + dishId; try { // 尝试获取锁,等待3秒,持有10秒 boolean locked = redisTemplate.opsForValue() .setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS); if (!locked) return false; Dish dish = dishMapper.selectById(dishId); if (dish.getStock() < quantity) { return false; } dishMapper.updateStock(dishId, quantity); return true; } finally { redisTemplate.delete(lockKey); } }8. 项目演进方向
- 智能推荐:基于历史订单数据实现菜品推荐
- 语音点餐:集成ASR技术支持语音输入
- 供应链联动:库存自动预警和采购建议
我在实际部署中发现,餐饮系统的稳定性比功能丰富更重要。曾经因为一个分库配置错误导致高峰期订单丢失,现在我们在关键业务流程都添加了补偿机制和人工复核环节。建议开发类似系统时,至少预留20%时间专门做异常场景测试。