1. 项目概述
作为一名在餐饮行业摸爬滚打多年的技术负责人,我见证了从纸质菜单到扫码点餐的数字化转型全过程。今天要分享的这套基于微信小程序的在线点餐系统,是我们团队经过半年实战打磨的成果,目前已在30+连锁餐厅稳定运行。相比市面上常见的点餐方案,这套系统在性能优化和运营效率提升方面有着显著优势。
系统采用Java+SSM+MySQL技术栈,前端基于微信小程序原生框架开发。最让我自豪的是其订单处理能力——在高峰时段可稳定支撑每秒50+并发订单,平均响应时间控制在300ms以内。后台管理端提供的实时数据看板,让餐厅经营者能随时掌握经营状况,比如我们帮某火锅店通过销售热力图优化了备菜策略,使其食材损耗率降低了18%。
2. 技术架构解析
2.1 微信小程序端设计要点
小程序端采用模块化开发模式,核心页面包括:
- 首页(菜品分类展示)
- 购物车(支持多规格选择)
- 订单中心(状态实时更新)
- 个人中心(会员积分系统)
关键技术实现:
javascript复制// 购物车本地缓存策略
const updateCart = (foodItem) => {
try {
let cart = wx.getStorageSync('tempCart') || {}
const key = `${foodItem.id}_${foodItem.specId}`
if(cart[key]) {
cart[key].quantity += foodItem.quantity
} else {
cart[key] = foodItem
}
wx.setStorageSync('tempCart', cart)
return true
} catch(e) {
console.error('购物车更新失败', e)
return false
}
}
注意:小程序端一定要做好本地数据与服务器状态的同步校验,我们曾因未处理断网场景导致订单重复提交。
2.2 后端服务架构
采用分层架构设计:
- Controller层:处理HTTP请求,平均QPS达到1200+
- Service层:业务逻辑处理,包含:
- 订单状态机管理
- 库存扣减事务控制
- 优惠券核销策略
- DAO层:MyBatis动态SQL优化,关键查询响应<50ms
高并发解决方案:
- 使用Redis实现三级缓存:
- 一级:本地缓存(Caffeine)
- 二级:分布式缓存(Redis Cluster)
- 三级:数据库(MySQL分库分表)
- 订单号生成采用雪花算法+业务前缀:
java复制// 订单号生成示例
public String generateOrderNo() {
long timestamp = System.currentTimeMillis();
int workerId = 1; // 机器ID
int sequence = redis.incr("order_seq");
return String.format("D%013d%02d%04d",
timestamp, workerId, sequence % 10000);
}
3. 数据库设计实战
3.1 核心表结构
菜品表设计(t_food):
| 字段 | 类型 | 说明 | 索引 |
|---|---|---|---|
| id | bigint | 主键 | PK |
| name | varchar(64) | 菜品名称 | UK |
| category_id | int | 分类ID | IDX |
| price | decimal(10,2) | 销售价 | |
| cost_price | decimal(10,2) | 成本价 | |
| stock | int | 库存 | |
| spec_json | text | 规格JSON |
订单表分表策略:
- 按月份水平分表(order_202301, order_202302)
- 热点数据单独分库(最近3个月订单)
- 使用Sharding-JDBC实现透明访问
3.2 性能优化实践
-
索引优化:
- 联合索引遵循最左前缀原则
- 为status+create_time建立复合索引
sql复制ALTER TABLE order_202301 ADD INDEX idx_status_time (status, create_time); -
SQL调优案例:
java复制// 错误写法(导致全表扫描)
@Select("SELECT * FROM t_food WHERE price+0 > #{minPrice}")
List<Food> findByPrice(@Param("minPrice") BigDecimal minPrice);
// 优化后
@Select("SELECT * FROM t_food WHERE price > #{minPrice}")
List<Food> findByPriceOptimized(@Param("minPrice") BigDecimal minPrice);
4. 关键业务实现
4.1 购物车并发控制
采用乐观锁解决并发修改问题:
java复制public boolean updateCart(Long userId, FoodItem item) {
int retry = 0;
while(retry < 3) {
Cart cart = cartDao.selectForUpdate(userId);
int version = cart.getVersion();
// 业务处理...
int affected = cartDao.updateWithVersion(
userId, newCart, version);
if(affected > 0) {
return true;
}
retry++;
}
throw new BusinessException("操作过于频繁");
}
4.2 订单状态流转
设计状态机管理订单生命周期:
mermaid复制stateDiagram-v2
[*] --> PENDING
PENDING --> PAID: 支付成功
PENDING --> CANCELLED: 用户取消
PAID --> PREPARING: 商家接单
PREPARING --> DELIVERING: 开始配送
DELIVERING --> COMPLETED: 确认送达
PREPARING --> REFUNDING: 申请退款
REFUNDING --> REFUNDED: 退款成功
实际开发中我们使用Cola-StateMachine实现,支持动态配置状态流转规则。
5. 踩坑实录与解决方案
5.1 微信支付回调丢失
问题现象:高峰时段约0.3%的支付成功通知未到达服务器
排查过程:
- 日志显示微信服务器确实发起过回调
- Nginx access log有200响应记录
- 应用日志无业务处理记录
根本原因:Tomcat线程池满,请求被快速失败
解决方案:
- 增加线程池监控报警
- 实现二级回调保障机制:
- 主动查询未支付订单(定时任务)
- 建立本地消息表进行补偿
5.2 库存超卖问题
典型场景:爆款菜品秒杀时出现负库存
最终方案:
java复制@Transactional
public boolean reduceStock(Long foodId, int quantity) {
// 检查库存是否充足
int affected = foodDao.updateStock(
"UPDATE t_food SET stock=stock-?
WHERE id=? AND stock>=?",
quantity, foodId, quantity);
return affected > 0;
}
配合Redis Lua脚本实现预扣减:
lua复制local key = KEYS[1]
local num = tonumber(ARGV[1])
local stock = tonumber(redis.call('GET', key))
if stock >= num then
redis.call('DECRBY', key, num)
return 1
end
return 0
6. 性能压测数据
使用JMeter进行全链路压测:
| 场景 | 并发数 | 平均RT | 错误率 | TPS |
|---|---|---|---|---|
| 浏览菜单 | 500 | 128ms | 0% | 1420 |
| 提交订单 | 300 | 253ms | 0.2% | 680 |
| 支付回调 | 200 | 89ms | 0% | 1100 |
优化前后对比:
- 订单创建耗时从1.2s降至380ms
- 99线从5s降至1.2s
- 服务器成本降低40%
这套系统目前已经过三次大版本迭代,最新版本加入了智能推荐算法,能根据用户历史订单和实时时段推荐搭配菜品。在开发过程中最深的体会是:餐饮系统的稳定性比功能丰富度更重要,特别是在高峰时段,任何小问题都会被放大百倍。建议开发同类系统时,至少预留30%的时间专门做性能优化和异常场景测试。