1. 项目概述与背景
中央厨房作为现代餐饮行业的重要基础设施,正在经历从传统手工操作向数字化管理的转型。我在实际调研中发现,许多中央厨房仍在使用Excel表格甚至纸质单据管理食材采购、生产计划和订单配送,这种模式存在数据孤岛、信息滞后、追溯困难等痛点。基于SpringBoot的中央厨房系统正是为解决这些问题而设计。
这个系统本质上是一个B2B餐饮供应链管理平台,连接了食材供应商、中央厨房和下游餐饮门店三个关键环节。与传统餐饮管理系统不同,我们的设计重点在于实现全链条的数据贯通和业务协同。举个例子,当某家餐厅下单10份宫保鸡丁预制菜时,系统能自动计算原料库存消耗,触发补货预警,并生成配送路线——这种端到端的自动化正是现代中央厨房最需要的。
2. 系统架构设计
2.1 技术选型解析
选择SpringBoot作为后端框架主要基于三个实际考量:
- 快速启动特性:中央厨房业务具有明显的季节性波动(如节假日订单激增),SpringBoot的内置Tomcat和自动配置能快速应对流量变化。我在测试环境中实测,单个服务实例在4核8G配置下可稳定支撑800+ TPS。
- 生态整合能力:需要同时集成权限控制(Spring Security)、缓存(Redis)、消息队列(RabbitMQ)等组件,SpringBoot的starter机制让这些集成变得简单。比如安全模块只需引入spring-boot-starter-security依赖即可获得完整的认证授权能力。
- 微服务友好:考虑到未来可能扩展冷链物流跟踪等子系统,SpringBoot与SpringCloud的无缝对接为系统演进预留了空间。
前端选用Vue3+Element Plus的组合,主要看中其响应式编程模型对复杂业务表单的处理优势。例如在订单创建页面,当用户切换配送方式时,系统需要实时计算运费、更新预计送达时间,Vue的computed属性可以优雅地实现这种联动逻辑。
2.2 数据库设计要点
采用MySQL 8.0作为主数据库,设计时特别注意了以下几点:
订单系统的核心表结构:
sql复制CREATE TABLE `order_master` (
`order_id` bigint NOT NULL AUTO_INCREMENT,
`order_type` tinyint COMMENT '1-原料采购 2-预制菜销售 3-预制菜采购',
`order_no` varchar(32) UNIQUE,
`total_amount` decimal(10,2),
`payment_status` tinyint DEFAULT 0,
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`order_id`),
INDEX `idx_order_no` (`order_no`),
INDEX `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
关键设计决策:
- 采用订单主表+扩展表的分表模式,既保持核心字段统一,又满足不同业务场景的特殊字段需求
- 所有金额字段使用DECIMAL(10,2)类型,避免浮点数计算精度问题
- 为高频查询字段(如订单号、创建时间)建立复合索引,实测查询性能提升5-8倍
3. 核心功能实现
3.1 多角色权限控制
基于RBAC模型实现四层权限体系:
- 角色定义:ADMIN(系统管理员)、KITCHEN(中央厨房)、SUPPLIER(食材供应商)、STORE(餐饮门店)
- 权限粒度:菜单权限→页面权限→操作权限→数据权限
- 技术实现:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.antMatchers("/api/kitchen/**").hasAnyRole("KITCHEN", "ADMIN")
.antMatchers("/api/supplier/**").hasRole("SUPPLIER")
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
避坑经验:
- JWT token的有效期不宜过长(建议2小时),refresh token机制更安全
- 权限校验要前后端双重验证,防止直接调用API绕过前端控制
- 对敏感操作(如删除)需要增加二次确认和操作日志记录
3.2 订单业务流程
以预制菜销售订单为例,完整流程包括:
- 门店下单:选择商品→填写数量→选择配送时间
- 中央厨房接单:订单审核→库存预留→生产排期
- 配送准备:生成拣货单→包装→物流分配
- 交付确认:门店签收→评价反馈
关键技术点:
- 分布式事务处理:使用Seata保证"扣减库存"与"创建订单"的数据一致性
- 状态机设计:采用状态模式实现订单状态流转,避免if-else嵌套
java复制public enum OrderStatus {
INIT(0), PAID(1), PRODUCING(2),
DELIVERING(3), COMPLETED(4), CANCELLED(-1);
// 状态流转规则
private static final Map<OrderStatus, Set<OrderStatus>> STATUS_FLOW = Map.of(
INIT, Set.of(PAID, CANCELLED),
PAID, Set.of(PRODUCING, CANCELLED),
PRODUCING, Set.of(DELIVERING),
DELIVERING, Set.of(COMPLETED)
);
public boolean canTransferTo(OrderStatus next) {
return STATUS_FLOW.get(this).contains(next);
}
}
4. 关键问题解决方案
4.1 高并发订单处理
通过压力测试发现,当并发用户超过500时,系统出现订单号重复问题。解决方案:
- 雪花算法生成唯一订单号:
timestamp(41bit) + workerId(10bit) + sequence(12bit) - Redis分布式锁控制订单创建:
java复制public String createOrder(OrderDTO orderDTO) {
String lockKey = "order_lock:" + orderDTO.getUserId();
String orderNo = null;
try {
// 获取分布式锁,超时时间3秒
boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 3, TimeUnit.SECONDS);
if (locked) {
orderNo = orderService.create(orderDTO);
} else {
throw new BusinessException("操作太频繁,请稍后重试");
}
} finally {
redisTemplate.delete(lockKey);
}
return orderNo;
}
4.2 食品安全追溯
采用批次号串联实现全链路追溯:
- 原料入库时生成
batch_no(格式:供应商ID+日期+序号) - 生产预制菜时记录使用的原料批次
- 销售订单关联生产批次
追溯SQL示例:
sql复制-- 查找问题批次的原料来源
SELECT m.* FROM material_batch m
JOIN production_material pm ON m.batch_no = pm.material_batch
WHERE pm.production_batch = '问题批次号';
-- 查找该批次销售去向
SELECT o.order_no, s.store_name FROM order_master o
JOIN order_pre_sell ops ON o.order_id = ops.order_id
JOIN store_info s ON ops.store_id = s.store_id
WHERE ops.production_batch = '问题批次号';
5. 性能优化实践
5.1 ECharts大数据量展示
针对年度销售报表可能包含上万条数据的情况,采用以下优化方案:
- 后端数据聚合:按周/月汇总原始数据
java复制public List<SalesDataVO> getSalesTrend(LocalDate start, LocalDate end) {
String sql = """
SELECT
DATE_FORMAT(create_time, '%Y-%u') AS week,
SUM(total_amount) AS amount
FROM order_master
WHERE create_time BETWEEN ? AND ?
GROUP BY week
ORDER BY week""";
return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(SalesDataVO.class),
start, end);
}
- 前端懒加载:初始只加载最近3个月数据,滚动时动态加载历史数据
- WebWorker处理数据转换:将耗时计算移出主线程
5.2 缓存策略设计
采用多级缓存架构:
- 本地缓存(Caffeine):缓存静态数据如菜品分类,TTL=1小时
- Redis缓存:
- 热点数据:订单状态等高频访问数据,TTL=5分钟
- 分布式锁:控制并发操作
- MySQL查询缓存:
- 对统计报表使用物化视图
- 对历史订单数据进行分表(按季度)
6. 开发经验总结
在实际开发过程中,有几个值得特别注意的技术点:
-
事务边界控制:中央厨房业务涉及多个子系统协作,需要合理划分事务边界。我们的经验是将一个完整的业务流程拆分为多个本地事务,通过消息队列实现最终一致性。例如订单创建流程:
- 事务1:创建订单主记录(状态=待支付)
- 事务2:支付成功后更新订单状态(状态=已支付)
- 事务3:库存系统扣减(通过RabbitMQ消息触发)
-
接口兼容性管理:随着业务迭代,接口版本管理尤为重要。我们采用URL路径版本控制:
code复制/api/v1/orders // 旧版
/api/v2/orders // 新版
同时为所有接口添加Swagger文档注解,确保前后端开发人员对接口语义理解一致。
- 日志监控体系:完善的日志系统能快速定位线上问题。我们搭建的日志体系包括:
- 业务日志:记录关键操作(如订单状态变更)
- 请求日志:记录所有API调用(Filter实现)
- 错误日志:集中收集异常信息(ELK集群)
- 审计日志:记录数据变更(通过MyBatis拦截器实现)
这个项目让我深刻体会到,一个好的系统不仅要有完善的功能设计,更需要考虑实际业务场景中的各种边界情况。比如在配送管理模块,我们最初没有考虑"部分配送"的情况(某菜品缺货时先配送其他菜品),导致后期不得不重构代码。这些经验教训都值得在后续项目中引以为戒。