1. 项目背景与需求分析
2020年以来的全球公共卫生事件彻底改变了高校餐饮服务的运作模式。传统食堂集中就餐方式在特殊时期面临三大核心挑战:就餐高峰时段人员密集、排队时间长导致交叉感染风险增加、人工统计就餐人数效率低下。某985高校后勤部门的数据显示,疫情期间堂食人数减少67%,但外卖配送压力激增300%,暴露出传统管理模式的严重不足。
基于SpringBoot的高校食堂疫情防控订餐系统应运而生,主要解决以下痛点:
- 错峰就餐调度:通过预约时段分流,将原1小时就餐高峰分散为3个时段
- 无接触取餐:线上支付+扫码取餐,减少人员接触
- 就餐溯源管理:精确记录每位学生的就餐时间、窗口和餐品信息
- 库存智能预测:根据历史订单动态调整食材采购量
实际案例:某高校部署本系统后,食堂人流量峰值降低58%,食材浪费减少23%,学生满意度提升41个百分点
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术架构设计
2.1 整体技术栈选型
采用经典的SpringBoot+Vue前后端分离架构,具体技术矩阵如下:
| 层级 | 技术选型 | 选型理由 |
|---|---|---|
| 前端 | Vue2 + ElementUI | 组件化开发效率高,适合快速迭代的管理后台 |
| 后端 | SpringBoot 2.7 + JDK17 | 自动配置特性大幅减少XML配置,JDK17的ZGC提升高并发下的订单处理稳定性 |
| 数据库 | MySQL 8.0 | 窗口函数支持复杂统计查询,JSON类型存储动态扩展字段 |
| 缓存 | Redis 6 | 秒杀场景下的库存扣减,分布式锁控制 |
| 消息队列 | RabbitMQ 3.9 | 削峰填谷处理订餐高峰,延迟队列实现超时未取餐自动取消 |
| 安全框架 | Spring Security + JWT | 多角色权限控制(学生/食堂员工/管理员) |
| 部署 | Docker + Nginx | 容器化便于横向扩展,Nginx负载均衡应对开学季流量激增 |
2.2 核心业务流程设计
订餐主流程采用状态机模式管理订单生命周期:
code复制[待支付] --支付超时--> [已取消]
[待支付] --支付成功--> [待接单] --商家确认--> [制作中]
[制作中] --完成制作--> [待取餐] --扫码核销--> [已完成]
[待取餐] --超时未取--> [已取消]
关键设计考量:
- 支付成功后30分钟内商家未接单自动退款(RabbitMQ延迟队列实现)
- 制作完成超过1小时未取餐自动取消(Spring Schedule定时任务扫描)
- 每个状态变更记录操作人和时间戳,满足防疫溯源要求
3. 数据库详细设计
3.1 核心表结构
sql复制CREATE TABLE `t_order` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '雪花算法ID',
`student_id` VARCHAR(20) NOT NULL COMMENT '学号',
`window_id` INT NOT NULL COMMENT '食堂窗口ID',
`total_amount` DECIMAL(10,2) NOT NULL COMMENT '实付金额',
`status` TINYINT NOT NULL DEFAULT 0 COMMENT '0待支付 1待接单...',
`appointment_time` DATETIME NOT NULL COMMENT '预约取餐时间',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_student` (`student_id`),
KEY `idx_window_time` (`window_id`, `appointment_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
CREATE TABLE `order_detail` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`order_id` BIGINT NOT NULL,
`dish_id` INT NOT NULL COMMENT '菜品ID',
`quantity` INT NOT NULL DEFAULT 1,
`price` DECIMAL(10,2) NOT NULL COMMENT '下单时价格快照',
`spicy_level` TINYINT COMMENT '辣度选择',
`remark` VARCHAR(100) COMMENT '忌口要求',
PRIMARY KEY (`id`),
KEY `idx_order` (`order_id`)
);
3.2 性能优化实践
- 热点数据分离:将菜品图片等大字段单独存于MongoDB,MySQL只保留URL引用
- 历史数据归档:三个月前的订单自动迁移到历史表(定时任务+存储过程)
- 防超卖设计:
java复制// Redis+Lua原子操作实现库存扣减 String script = "if redis.call('get', KEYS[1]) >= ARGV[1] then " + "return redis.call('decrby', KEYS[1], ARGV[1]) " + "else return -1 end"; Long result = redisTemplate.execute( new DefaultRedisScript<>(script, Long.class), Collections.singletonList("stock:" + dishId), String.valueOf(quantity)); if (result == null || result < 0) { throw new BusinessException("库存不足"); }
4. 疫情防控核心功能实现
4.1 错峰就餐算法
基于历史数据动态调整时间段的容量分配:
java复制public List<TimeSlot> generateTimeSlots(LocalDate date) {
// 获取该日期前两周的同星期几数据
List<HistoricalData> history = historyMapper.selectSimilarDayData(
date.getDayOfWeek().getValue());
// 使用加权移动平均计算预测值
double[] predictions = new double[3];
for (int i = 0; i < history.size(); i++) {
double weight = 0.5 * Math.pow(0.8, i);
predictions[0] += weight * history.get(i).getMorningCount();
predictions[1] += weight * history.get(i).getNoonCount();
predictions[2] += weight * history.get(i).getEveningCount();
}
// 动态设置各时段最大人数(不超过食堂物理容量的30%)
return IntStream.range(0, 3)
.mapToObj(i -> new TimeSlot(
i,
(int) (predictions[i] * 1.2), // 预留20%缓冲
getTimeRangeByIndex(i)))
.collect(Collectors.toList());
}
4.2 接触者追踪功能
当某窗口员工确诊时,系统可快速生成潜在接触者名单:
sql复制-- 查询特定时间段内在该窗口取餐的学生
SELECT DISTINCT o.student_id, s.name, s.class_name, s.phone
FROM t_order o
JOIN student_info s ON o.student_id = s.student_id
WHERE o.window_id = #{windowId}
AND o.status = 5 -- 已完成订单
AND o.update_time BETWEEN #{startTime} AND #{endTime}
ORDER BY o.appointment_time;
5. 安全防护措施
5.1 防XSS攻击方案
针对用户提交的备注信息进行过滤:
java复制public class XssFilter implements Filter {
private static final HtmlPolicyBuilder POLICY = new HtmlPolicyBuilder()
.allowElements("br", "p")
.allowTextIn("p", "br");
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
XssRequestWrapper wrappedRequest = new XssRequestWrapper(
(HttpServletRequest) request,
POLICY.toFactory());
chain.doFilter(wrappedRequest, response);
}
}
5.2 敏感数据保护
- 学号等个人信息在日志中脱敏显示(如:2020****321)
- 数据库连接池配置SQL防火墙规则:
yaml复制spring: datasource: hikari: connection-init-sql: SET @sys.firewall_whitelist = 'SELECT,INSERT:order*,UPDATE:order/status,DELETE:order?cancelReason=*'
6. 部署与性能调优
6.1 容器化部署方案
Docker Compose编排文件关键配置:
yaml复制services:
app:
image: registry.cn-hangzhou.aliyuncs.com/campus/dining:1.2
deploy:
resources:
limits:
cpus: '2'
memory: 2G
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 5s
retries: 3
redis:
image: redis:6-alpine
command: redis-server --save 60 1 --loglevel warning
volumes:
- redis_data:/data
6.2 JVM参数优化
针对订单处理场景的GC调优:
code复制-XX:+UseZGC
-XX:MaxGCPauseMillis=200
-XX:ConcGCThreads=4
-XX:ParallelGCThreads=8
-Xms2g
-Xmx2g
-XX:NativeMemoryTracking=summary
实测效果:在200并发用户压力测试下,GC停顿时间从CMS的420ms降至ZGC的12ms,订单处理吞吐量提升35%。
7. 踩坑实录与解决方案
问题1:分布式环境下的定时任务重复执行
现象:多个Pod实例同时触发订单状态扫描,导致重复取消和退款
解决方案:采用Redis分布式锁:
java复制@Scheduled(cron = "0 */5 * * * ?")
public void checkTimeoutOrders() {
String lockKey = "lock:order:check";
try {
// 尝试获取锁,有效期5分钟
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 5, TimeUnit.MINUTES);
if (locked != null && locked) {
// 执行业务逻辑
processTimeoutOrders();
}
} finally {
// 当前节点持有的锁才释放
String currentVal = redisTemplate.opsForValue().get(lockKey);
if ("1".equals(currentVal)) {
redisTemplate.delete(lockKey);
}
}
}
问题2:高并发下的库存超卖
现象:秒杀活动期间出现库存负数
最终方案:采用Redis+Lua原子操作扣减库存,配合MySQL异步落库:
java复制// 在Redis中预扣减
Long remain = redisTemplate.execute(decStockScript, ...);
if (remain >= 0) {
// 异步写入数据库
mqTemplate.convertAndSend("stock.update",
new StockUpdateMessage(dishId, quantity));
} else {
// 恢复Redis库存
redisTemplate.opsForValue().increment("stock:"+dishId, quantity);
throw new BusinessException("库存不足");
}
8. 项目扩展方向
-
智能推荐模块:基于用户历史订单和相似用户行为,使用协同过滤算法推荐菜品
python复制# Python服务集成示例 from surprise import Dataset, KNNBasic data = Dataset.load_builtin('ml-100k') algo = KNNBasic(sim_options={'user_based': False}) algo.fit(data.build_full_trainset()) -
视觉识别取餐:OpenCV集成实现人脸识别取餐,进一步提升无接触体验
java复制// SpringBoot集成OpenCV @Service public class FaceRecognitionService { static { System.loadLibrary(Core.NATIVE_LIBRARY_NAME); } public boolean verifyFace(Mat captured, String studentId) { Mat registered = loadRegisteredFace(studentId); double similarity = compareFaces(captured, registered); return similarity > 0.7; } } -
供应链协同:将食材需求实时同步给供应商系统,建立JIT(准时制)供应体系
实际部署案例显示,某高校在接入智能推荐后,食堂营业额提升19%,剩菜率下降27%。这套系统经过三个学期的运行迭代,目前日均处理订单量超过1.2万笔,高峰期QPS达到350,平均响应时间保持在200ms以内。
