1. 项目概述:甜品店管理系统的核心价值
去年帮朋友改造一家连锁奶茶店的运营系统时,我深刻体会到传统手工记账的痛点:每天打烊后要花2小时核对订单,新品上市时手写菜单要重印三次,会员积分总是对不上账。这正是我们开发这套甜品店管理系统的初衷——用SpringBoot技术栈为中小型餐饮门店打造轻量级数字化解决方案。
这套系统主要解决三个核心问题:一是多终端订单统一管理(堂食扫码点餐+外卖平台接单+收银台现金订单),二是动态库存预警(鲜果、奶油等原料的保质期监控),三是经营数据分析(爆品识别、时段销量统计)。特别适合日均订单量在50-300单的独立甜品店、社区奶茶店和小型蛋糕工坊。
2. 技术架构设计解析
2.1 为什么选择SpringBoot
在技术选型阶段,我们对比了传统的SSM框架和SpringBoot。某连锁品牌的技术总监分享过他们的教训:用SSM搭建的旧系统,启动一个模块就需要配置7个XML文件,而SpringBoot的约定优于配置特性,让我们的开发效率提升了40%。特别是自动装配机制,使得整合MyBatis和Redis等组件时,只需添加starter依赖就能开箱即用。
系统采用经典的三层架构:
- 表现层:Thymeleaf模板引擎+响应式布局
- 业务层:Spring MVC+自定义注解实现权限控制
- 数据层:MyBatis-Plus配合多数据源切换
2.2 数据库设计要点
甜品行业的业务特殊性决定了数据库设计的几个关键点:
- 商品表的组合属性字段(如奶茶要记录糖度、冰度、加料等20多种组合)
- 原料批次管理(动物奶油开封后保质期只有72小时)
- 时段促销策略(下午茶时段第二杯半价)
我们采用MySQL 8.0的JSON类型字段存储商品定制选项,配合触发器实现库存实时扣减。这里有个踩坑经验:最初使用字符串拼接存储定制选项,导致促销活动时WHERE条件无法命中索引,后来改用JSON_EXTRACT函数才解决性能问题。
3. 核心功能实现细节
3.1 智能订单处理模块
订单状态机设计是核心难点,我们采用Spring StateMachine实现以下流程:
// 订单状态定义 public enum OrderStates { INITIAL, PAID, MAKING, DELIVERING, COMPLETED, CANCELLED } // 状态转换配置 @Configuration @EnableStateMachine public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> { @Override public void configure(Transitions<OrderStates, OrderEvents> transitions) { transitions .withExternal() .source(OrderStates.INITIAL) .target(OrderStates.PAID) .event(OrderEvents.PAY) .and() .withExternal() .source(OrderStates.PAID) .target(OrderStates.MAKING) .event(OrderEvents.START_MAKE); } }特别注意:甜品订单存在"反悔期"——顾客支付后5分钟内可免费取消。我们通过Redis的过期键通知实现:
@Bean RedisMessageListenerContainer container(RedisConnectionFactory factory) { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(factory); container.addMessageListener(new OrderCancelListener(), new PatternTopic("__keyevent@0__:expired")); return container; }3.2 库存预警系统
原料管理采用双维度监控:
- 数量预警:当库存低于安全库存时触发补货提醒
- 时效预警:对开封原料启动倒计时提醒
核心SQL示例:
-- 临期原料查询 SELECT material_name, DATEDIFF(expire_date, NOW()) AS remain_days FROM material_stock WHERE store_id = ? AND status = 'OPENED' AND DATEDIFF(expire_date, NOW()) < 3;我们在实际部署中发现,频繁的全表扫描会导致高峰期系统卡顿。最终解决方案是给expire_date和status字段添加联合索引,并在业务低峰期执行批量检查。
4. 典型问题排查实录
4.1 订单超卖问题
在促销活动期间,出现同一份杨枝甘露被卖出5次的严重BUG。排查过程:
- 先检查@Transactional注解是否生效
- 确认MySQL隔离级别为REPEATABLE_READ
- 最终发现是缓存不一致导致:
// 错误写法:先删缓存再更新数据库 public void deductStock(Long itemId, int num) { redisTemplate.delete("stock:" + itemId); // 步骤1 itemMapper.reduceStock(itemId, num); // 步骤2 } // 正确做法:先更新再删缓存(或使用CAS方案) public void deductStock(Long itemId, int num) { int affected = itemMapper.reduceStockWithVersion(itemId, num, version); if(affected > 0) { redisTemplate.delete("stock:" + itemId); } }4.2 打印小票乱码
热敏打印机出现中文乱码的解决方案:
- 确认打印机字符集支持GB18030
- 在SpringBoot中强制指定响应编码:
spring: thymeleaf: encoding: GB18030 servlet: content-type: text/plain;charset=GB18030- 使用Esc/Pos指令集时,注意发送初始化序列:
byte[] init = new byte[]{0x1B, 0x40}; // ESC @ printerOutputStream.write(init);5. 部署优化实践
5.1 多环境配置技巧
我们采用Profile区分开发、测试和生产环境:
# application-dev.properties spring.datasource.url=jdbc:mysql://localhost:3306/dessert_dev # application-prod.properties spring.datasource.url=jdbc:mysql://prod-db:3306/dessert_prod启动时通过VM参数指定环境:
java -jar dessert-system.jar --spring.profiles.active=prod5.2 健康检查端点
为满足容器化部署需求,暴露关键健康指标:
@RestController @RequestMapping("/actuator") public class HealthCheckController { @GetMapping("/db") public ResponseEntity<?> checkDB() { try { jdbcTemplate.queryForObject("SELECT 1", Integer.class); return ResponseEntity.ok().build(); } catch (Exception e) { return ResponseEntity.status(503).build(); } } }在Kubernetes中配置存活探针:
livenessProbe: httpGet: path: /actuator/db port: 8080 initialDelaySeconds: 60 periodSeconds: 15这套系统在某网红奶茶店上线后,其订单处理时间从平均3分钟缩短到40秒,原料损耗率下降了28%。特别提醒:甜品行业有很强的季节性波动,建议在系统设计时预留300%的流量余量,比如夏季水果茶销量可能是冬季的5倍以上。