ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于SpringBoot的智能食堂管理系统:从订单状态机到WebSocket实时推送

基于SpringBoot的智能食堂管理系统:从订单状态机到WebSocket实时推送 1. 项目整体定位与技术选型1.1 为什么是“智能食堂管理系统”而不是普通订餐网站先聊个有意思的现象很多同学做毕设时一看到“食堂订餐”就容易掉进“电商网站”的思维定式——加购物车、下单、支付、等着收货。这套逻辑搬到校园食堂场景里其实撑不住因为食堂和外卖电商的核心痛点完全不一样。外卖电商的核心痛点是“骑手配送效率”而校园食堂的核心痛点其实是“高峰时段的拥堵、备餐的确定性、以及食堂运营方的数据盲区”。饭点就那么一两个小时几千号人同时涌进来窗口排队十分钟不只是学生难受食堂管理方更难受——他们完全不知道哪个窗口会爆单、哪个窗口没人去、什么菜应该多做一百份、什么菜做出来只能倒掉。所以这个项目把名字定为“智能食堂管理系统”而不是“在线订餐网站”。“智能”这两个字不是营销话术而是整个系统的设计主线线上预定、备餐联动、数据驱动运营决策。Java SpringBoot作为后端技术栈Web端作为访问入口组合起来就是一个能同时解决学生端体验和食堂端管理效率的完整闭环。1.2 技术栈选型的深层逻辑标题里已经明确写了Java和SpringBoot这里就不再纠结语言层面的事情但选型背后的理由值得展开说。很多同学做毕设都习惯“哪个火选哪个”但实际上是“哪个你答辩时说得出所以然哪个才该选”。SpringBoot这套东西核心价值在于“约定优于配置”。做毕设最怕的不是功能多而是环境配置和项目搭建消耗掉大半时间。SpringBoot内嵌Tomcat不用单独部署容器写个启动类直接跑起来这对毕设来说能省下巨量时间。而且SpringBoot生态极其成熟Spring MVC做Web层、Spring Data JPA或MyBatis操作数据库、Spring Security做权限控制全部是一套生态的东西写起来顺手答辩评委问起来你也答得清楚。校园食堂这个场景有几个技术需求是需要认真对待的高并发场景饭点峰值流量是一波一波的虽然不是双十一级别的量级但几百上千人同时抢着订餐对后端接口的响应速度和数据库的压力是有要求的。业务状态流转订单从“已下单”到“备餐中”到“已完成”每个状态的转换触发什么逻辑这是毕设展示“业务建模能力”的最佳位置。数据可视化需求既然叫“智能”前端要展示菜品销量分析、窗口热度趋势后端就要有数据统计接口。基于这几个需求选型就很自然了SpringBoot 2.x做基础框架MyBatis-Plus操作MySQL数据库Redis做缓存和Session共享Vue Element UI做管理后台页面微信小程序或移动H5做订餐端。这套组合在Java Web方向非常主流网上资料丰富遇到问题能找到解决方案对毕设来说本身就是一种隐性保障。1.3 这个系统解决的真实痛点在动手写代码之前我建议你先想清楚一件事这个系统到底给谁用解决了他们什么具体问题我给出的答案是三端角色对应三套核心痛点学生端痛点是不想排队。在线查看今日菜品、实时查看窗口排队人数、提前下单预约取餐时间、到点直接去窗口取走餐品。这个流程能大幅压缩排队时间不用再在窗口前纠结吃什么。食堂窗口端痛点是备餐没有预判。传统模式下厨师只能靠经验预估今天做什么菜、做多少份做多了浪费做少了不够卖。通过系统学生在饭点之前就完成了下单窗口根据订单量动态调整备餐计划什么菜多炒、什么菜少炒数据说了算。系统管理端痛点是运营数据缺失。哪个窗口销量最高、哪个菜品点击率远超实际销量、哪个时间段订单量最集中、用户的消费偏好是什么这些数据在传统食堂模式下几乎不可能采集。有了系统这些数据实时生成食堂经营者可以在数据驱动下优化菜品结构。明确了这三层痛点整个系统的功能边界就划清楚了——不是做一个花哨的订餐平台而是做一个能落地的、解决问题闭环的智能管理系统。这也是答辩时最重要的“项目价值”论述基础。2. 系统功能模块与核心设计思路2.1 功能模块全景拆解一个合格的毕设项目功能模块不能太少显得空也不能堆得太多做不完。我推荐的模块划分是这样的用户模块用户注册、登录学生用户、食堂窗口管理员、系统超级管理员个人信息维护、收货信息管理实际上就是取餐人、联系电话、备注口味偏好密码修改、头像上传菜品与档口模块档口管理食堂下辖多个档口/窗口每个窗口绑定自己的菜品列表菜品分类管理素菜、荤菜、汤品、主食、饮料等菜品信息维护图片、价格、描述、每日可售数量菜品上下架管理售完自动标记、或者窗口主动下架订餐交易模块菜品浏览与筛选按档口、按分类、按价格区间、按销量排序购物车管理加购、删减、批量结算订单生成与状态流转待支付、已支付、备餐中、待取餐、已完成、已取消模拟支付功能毕设不需要真的接支付宝微信支付做一个余额充值 模拟扣款即可但设计上要考虑未来对接真实支付接口的扩展性排队与取餐模块预约取餐时间段例如11:00-11:15、11:15-11:30这样的时间槽排队号生成用户在某个时间段内下单自动分配一个取餐号叫号状态推送Web端轮询或者WebSocket实时推送备餐状态变化数据统计模块菜品销量排行榜按日、按周、按月档口营收统计应对管理端的数据看板需求用户消费行为分析高频菜品、复购率、客单价订单量峰谷时段分析用于指导食堂备餐时间安排系统管理模块用户管理冻结/解冻、角色分配档口与菜品审核订单异常处理超时未取餐自动标记、退款申请处理数据字典维护这套模块划分的合理性在于每个模块都对应一个真实业务场景业务边界清晰表结构设计的时候也不会纠结“这个字段到底放哪张表”的问题。2.2 为什么选择单体架构而不是微服务这个决定我必须重点讲因为这是毕设答辩时评审老师几乎必问的一个问题你为什么不把系统拆成微服务。答案很简单场景不需要复杂度不允许。微服务架构要处理服务注册发现、配置中心、分布式事务、链路追踪、网关路由、容器编排光把这些基础设施写好代码量就已经远超毕设本身了。而校园食堂这个场景单机部署的SpringBoot应用加一个MySQL完全能够支撑业务需求。但“不用微服务”不等于“完全没有架构意识”。单体项目里依然要做好模块化设计代码按功能分包业务逻辑层Service、控制层Controller、数据访问层Mapper严格分离。这样即使以后业务规模扩大也能按模块平滑拆分到独立服务。我在答辩材料里给这一块留了一个专门的说明页讲清楚“基于当前业务规模和团队规模单体架构是最优选择同时为未来演进保留了模块化基础”。这个思路很加分很容易让老师看出你是真的思考过架构问题而不是只会用现成脚手架。2.3 数据库表设计要点数据库设计是毕设项目中直接展示功底的环节。我建议表结构至少包含以下内容核心业务表设计参考表名核心字段关键备注sys_userid, username, password, role, status用户表role区分学生/窗口/管理员canteenid, name, location, open_time食堂信息表stallid, canteen_id, name, manager_id档口表关联到具体食堂dishid, stall_id, category, name, price, image, stock菜品表stock表示每日可售数量cartid, user_id, dish_id, quantity购物车表ordersid, order_no, user_id, total_amount, status, pick_up_time订单主表order_itemid, order_id, dish_id, dish_name, price, quantity订单明细表冗余菜品快照queuingid, user_id, stall_id, queue_no, status排队叫号表payment_recordid, order_id, amount, channel, transaction_id支付流水表consumption_statid, dish_id, order_count, revenue, stat_date每日经营统计表这里有两个细节必须强调订单明细表一定要存菜品快照。菜品价格和名称可能随时调整如果订单明细只存一个菜品ID第二天菜品改名了历史订单显示就会对不上。保存dish_name和price快照允许冗余就是为了保证历史订单的不可变性。关于数据统计表我建议设计一张日汇总表。很多同学做到统计功能时直接用SQL对订单表做聚合查询。短期看没问题但当订单量积累到几十万条之后每次打开看板都做全表聚合MySQL会非常吃力。设计一张每日跑批的统计表记录每个菜品当天的订单量和营收报表页面只查这张汇总表。毕设的数据量可能不大但这个设计思路要写出来体现你考虑过性能和扩展性。2.4 订单状态机的设计细节订单状态变化是整个系统最核心的业务逻辑它本质上是一个有限状态机。我建议把状态定义为一个枚举类在代码里明确状态流转的合法性。状态流转图文字描述版待支付用户提交订单但未支付。超过15分钟未支付则自动关闭释放菜品库存。已支付支付成功后进入此状态同时扣减菜品库存向食堂窗口端推送新的备餐任务。备餐中窗口管理员看到新订单后点击“开始制作”状态从已支付变为备餐中。待取餐窗口完成制作点击“出餐”状态变为待取餐系统生成取餐码通知用户凭码取餐。已完成用户在取餐时点击“确认取餐”或者窗口端点击“确认交付”订单完结。已取消用户主动取消仅限待支付和已支付状态或后台管理员异常处理。这套状态机看着简单但有几个坑点必须处理干净超时关闭的定时任务待支付超时关闭可以用Spring的Scheduled做定时扫描每分钟跑一次把创建时间超过15分钟且状态为待支付的订单改为已取消同时回滚库存。这里要注意回滚库存的SQL必须是原子操作用UPDATE dish SET stock stock 1 WHERE id ? AND stock daily_limit这样的条件更新防止并发下单时数据不一致。并发扣除库存的处理学生A和学生B同时抢最后一个菜品两个请求同时扣减stock如果不做控制很可能两个订单都显示成功但菜品实际只剩一个。最简单的处理方式是在SQL层做条件更新UPDATE dish SET stock stock - 1 WHERE id ? AND stock 0返回受影响行数为0则说明库存不足直接提示用户。这个方案虽然简单但确实是处理秒杀场景最基本的手段写进论文里也拿得出手。3. 核心功能模块实现与代码拆解3.1 登录鉴权模块JWT Spring Security校园食堂系统的角色权限差异明显学生、窗口管理员、超级管理员各有一套菜单权限和操作权限。这里我用Spring Security JWT做了一套无状态认证方案。先解释为什么不用传统的Session方案Web端前后端分离前端Vue部署在一台服务器后端SpringBoot部署在另一台服务器。如果用Session就得开启Spring Session支持通过Redis做Session共享。而JWT天然适合前后端分离后端只负责签发和验签不保存会话状态水平扩展的时候完全无状态。核心实现步骤第一步集成JWT工具类。生成Token时把用户ID、角色、过期时间等关键信息写进claims用HMAC256算法签名加密密钥配置在application.yml里public String generateToken(Long userId, String role) { Date nowDate new Date(); Date expireDate new Date(nowDate.getTime() 24 * 60 * 60 * 1000L); // 24小时过期 return Jwts.builder() .setHeaderParam(typ, JWT) .setSubject(userId.toString()) .claim(role, role) .setIssuedAt(nowDate) .setExpiration(expireDate) .signWith(SignatureAlgorithm.HS256, secretKey) .compact(); }第二步配置Spring Security的过滤链。写一个JWT认证过滤器继承OncePerRequestFilter在每个请求进来时从请求头的Authorization字段里取出Token验签通过后把用户ID和角色放到SecurityContext里protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) { String token request.getHeader(Authorization); if (StringUtils.hasText(token)) { try { Claims claims Jwts.parserBuilder() .setSigningKey(secretKey) .build() .parseClaimsJws(token.replace(Bearer , )) .getBody(); Long userId Long.valueOf(claims.getSubject()); String role (String) claims.get(role); UsernamePasswordAuthenticationToken authentication new UsernamePasswordAuthenticationToken(userId, null, AuthorityUtils.commaSeparatedStringToAuthorityList(role)); SecurityContextHolder.getContext().setAuthentication(authentication); } catch (Exception e) { // Token无效或过期不设置Authentication后续会被Security拒绝 } } chain.doFilter(request, response); }第三步角色权限控制。在Controller层直接用PreAuthorize(hasRole(ADMIN))这样的注解控制接口权限学生用户调不了管理接口管理用户也碰不了学生的下单流程。这一套做下来权限模型清晰答辩时可以说“基于RBAC模型实现了角色的最小权限控制”。需要注意的一个细节密码存储不要用明文。Spring Security的BCryptPasswordEncoder是业界推荐方案注册用户时加密存储登录校验时用bcrypt的matches方法对比。答辩时如果老师问你“密码怎么存的”你答“BCrypt加盐哈希不可反解出明文”这是一个很加分的点。3.2 订餐核心流程购物车到下单到支付的完整链路这个流程是整个系统的高潮部分代码量最大、业务逻辑最密集。我按一个“学生用户完成一次订餐”的全过程拆解每个环节该做什么、注意什么、为什么这么做一次讲清楚。环节一加购菜品用户点击菜品详情页的“加入购物车”前端发POST请求到/cart/add参数是菜品ID和数量。后端的逻辑分两步第一步查菜品状态确认菜在售、库存大于0如果当日限量已经售完直接返回“今日已售罄”。第二步写购物车表。这里要注意幂等性同一个用户反复点击“加购”不应该产生多条相同的购物车记录。我的实现是先查一遍用户购物车里是否已有该菜品有则累加数量没有就新增一行。这虽然多了一次数据库查询但能保证购物车表的数据干净。环节二订单生成用户从购物车点击“去结算”后端做的事比较多我按顺序列一下查询购物车里的所有菜品校验“是否有菜品已下架”和“是否有菜品库存变动”。按档口维度把购物车拆分成多个订单也就是说用户一次性买了一家麻辣烫窗口和三兄弟盖浇饭窗口的东西系统会生成两个订单分别推送给对应的档口。这是食堂场景的特殊之处——外卖电商是一单对应一个配送方食堂是一单对应多个出餐窗口。计算总金额插入orders表和order_item表订单初始状态设为“待支付”。生成订单号注意订单号不能用数据库自增ID直接给用户看。我用的是时间戳加随机数的策略yyyyMMddHHmmss加四位数随机码保证用户层面看不到订单之间的关联关系。生成订单的时候事务边界一定要划分清楚。Transactional加在service层的方法上购物车检查、订单头插入、订单明细插入、购物车清空必须在一个事务里完成任何一个环节报错都要整体回滚。这是我见过很多同学最容易漏掉的地方——订单创建了购物车没清空或者订单明细丢了数据一致性直接崩掉。环节三模拟支付支付这块毕设不接真实渠道但设计逻辑要自洽。我的方案是用户账户里有余额提前做充值操作模拟银行卡充值下单时从余额里扣款生成一条支付流水记录。扣款操作要特别小心重复支付的问题。前端极有可能因为跳转异常导致用户重复点击支付按钮后端必须保证“一个订单只能支付成功一次”。我的做法是在扣款SQL上再加一个状态条件Update(UPDATE orders SET status paid, payment_time now() WHERE id #{orderId} AND status pending) int markOrderPaid(Long orderId);如果返回值为0说明这单已经不是待支付状态直接往上层抛异常。加上用户余额扣款也要校验余额充足否则提示“余额不足请先充值”。三个操作扣余额、插流水、改订单状态再包一个事务逻辑闭环就完整了。环节四通知档口备餐订单支付成功之后系统要立刻让对应档口知道“来了新活儿”。这里我用的是Redis发布订阅支付完成接口里发送一条消息到频道stall:${stallId}档口管理端的WebSocket监听这个频道收到消息后前端弹窗提醒。这块用Redis做发布订阅比直接用WebSocket点对点推送要轻量也顺便给档口号和WebSocket连接做一个解耦后续就算前端技术栈换掉后端的消息通知逻辑完全不用改动。3.3 排队叫号与取餐状态推送排队叫号是食堂场景里一个比较有特色的功能模块做得好会让整个系统的“智能”属性大幅提升。我的实现思路是每个档口每天维护一个自增的队列计数器用户支付成功后系统为该订单生成一个取餐号号段格式是C01-015C01表示档口ID015表示第15个订单。取餐号按时间顺序递增用户端实时显示“前面还有几单”窗口端显示器显示“当前呼叫到C01-012”。前端有两种方案可以拿到状态变化方案A前端轮询。每3秒调一次查询接口拿当前队列进度。简单稳定但3秒一次请求在几百人同时使用时对后端压力还是有点大。方案BWebSocket推送。后端在状态流转的关键节点出餐完成、呼叫取餐主动推送消息给对应排队用户的页面。实时性强体验好但代码复杂度明显提升。毕设阶段我推荐方案B因为WebSocket是高频考点。SpringBoot里用原生TextWebSocketHandler实现一个WebSocket处理器加上WebSocketConfigurer配置类注册WebSocket端点Configuration public class WebSocketConfig implements WebSocketConfigurer { Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(queueNotifyHandler(), /ws/queueNotify) .setAllowedOrigins(*); } }用户登录时前端把JWT Token作为WebSocket连接的参数传过来WebSocket握手阶段用拦截器校验Token把用户ID映射到WebSocket Session。后面需要给指定用户推送消息时根据用户ID找到对应Session直接发送JSON消息前端收到后触发取餐弹窗。这里有一个经验之谈WebSocket连接断开之后要记得清理Session映射。用户关掉页面不需要通知但如果不清理Map的条目长此以往会内存泄漏导致服务器内存越堆越高。我一般会在afterConnectionClosed回调里做remove操作这也是一个能在答辩时被追问到的“细节加分项”。3.4 管理端数据看板与统计报表系统的“智能”程度最终要从管理端数据看板上体现出来。如果只是把订单查出来罗列在表格里那不叫智能叫存证系统。真正的智能是要把数据变成决策参考。我做了一套四块核心指标的数据看板档口热度排行榜按近7天订单量和营业额排出各档口的热度用柱状图展示。这个指标直接指导食堂管理方考虑窗口位置调整和资源分配。菜品销售排行TOP10按销量排名同时展示“点击率/下单率”的比值。如果一个菜被浏览了很多次但下单很少大概率是价格、图片或描述出了问题属于潜在优化对象。经营时段分布图把一天的订单量按小时聚合画出走势图。正常情况下能看到上午10点到12点、下午5点到7点的两个峰峰值数据能指导排班和备餐计划。库存预警与售罄分析统计当天各菜品的售罄时间提前售罄说明备餐量不够经常剩菜说明量备多了。这个数据对后厨采购计划而言价值非常高。这些统计接口的实现主要靠给orders表和order_item表写聚合SQL。我的建议是SQL聚合逻辑放在Mapper层用注解或XML写清楚不要用简单的循环遍历去累加统计性能和代码可读性天差地别。举个例子查“近7天档口营业额”SELECT s.id AS stall_id, s.name AS stall_name, SUM(oi.price * oi.quantity) AS total_revenue FROM orders o LEFT JOIN order_item oi ON o.id oi.order_id LEFT JOIN stall s ON oi.stall_id s.id WHERE o.status IN (paid, ready, completed) AND o.payment_time DATE_SUB(CURDATE(), INTERVAL 7 DAY) GROUP BY s.id, s.name ORDER BY total_revenue DESC;再配合前端的ECharts组件把接口返回的JSON直接绑到series.data上图表就出来了。整个看板的开发效率很高后端接口一回车前端就有图可看。4. 系统部署环境配置与实操记录4.1 本地开发环境搭建很多同学在这一步就卡住了卡的原因不是不懂Java而是环境版本的排列组合出了问题。我给出一份实测可用的环境组合这个组合是我用来跑通整个项目的方案组件推荐版本说明JDK8 或 11SpringBoot 2.7.x 最稳定Maven3.6.3 及以上统一管理项目依赖MySQL5.7 或 8.0字符集必须配置为 utf8mb4Redis5.0 及以上用于缓存与发布订阅Node.js14 及以上前端Vue项目的构建环境IDEIntelliJ IDEA社区版做毕设完全够用一个容易被忽略的坑是MySQL的字符集配置。如果数据库字符集不是utf8mb4在存储用户昵称、菜品描述的emoji表情时直接报错。我建议建库SQL里就显式指定CREATE DATABASE smart_canteen DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;后端项目结构我推荐保持maven的经典结构com.canteen ├── configuration # 全局配置类Redis、WebSocket、跨域等 ├── controller # 控制层 ├── service # 业务逻辑层 ├── mapper # MyBatis接口层 ├── entity # 数据库实体类 ├── common # 通用工具类JWT、Result封装、异常处理 └── CanteenApplication.java # SpringBoot启动类包结构的整齐程度直接关系到答辩印象分。很多同学把实体类、DTO、VO、工具类全堆在一个包下代码查起来让人头大。分好包之后阅读代码的人第一眼就能感知到项目规范。4.2 后端核心配置文件精讲application.yml是这个系统运转的“总控室”这里把最核心的几项配置和参数逐一说明server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/smart_canteen?useUnicodetruecharacterEncodingutf8mb4useSSLfalseserverTimezoneGMT%2B8 username: root password: 你的数据库密码 driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 database: 0 timeout: 3000ms servlet: multipart: max-file-size: 10MB max-request-size: 10MB mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 jwt: secret: 你自己的高强度随机字符串 expire-hours: 24几个容易踩坑的关键点MySQL连接串里的serverTimezoneGMT%2B8必须加上否则会报时区错误。其中%2B是加号的URL编码直接用在某些环境下会被解析成空格导致连接失败。MyBatis-Plus的逻辑删除配置。实体类里加上TableLogic注解的字段删除数据的时候自动变成UPDATE逻辑删除标记而不是物理DELETE。这个方案保留了数据完整性也方便数据回查是当前企业级Java项目的常用做法。文件上传大小限制。菜品图片上传是刚需默认的1MB限制太小菜品图片动辄2到3MB。这里配置了10MB上限基本覆盖所有场景。4.3 前后端联调与部署实操前端代码我用的Vue 2.x加Element UI后台管理系统和管理端页面混在一个项目里通过路由区分。和SpringBoot后端联调时最大的坑是跨域问题。开发环境下前端跑在localhost:8081后端跑在localhost:8080两个端口不同必然触发跨域。我建议在后端做一个全局CORS配置类统一处理所有跨域请求Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }这里有个细节.allowedOriginPatterns(*)和allowCredentials(true)必须搭配使用。如果只是allowedOrigins(*)浏览器会拒绝带Cookie的跨域请求因为这样配置存在安全风险浏览器层就会直接拦截。别问我为什么知道这个问题我调试了大半个下午才定位到这一行。打包部署环节后端直接执行mvn clean package -DskipTests在target目录下生成一个可执行的Jar包通过nohup java -jar canteen-0.0.1-SNAPSHOT.jar canteen.log 21 方式在服务器后台启动。前端构建执行npm run build生成dist目录扔到Nginx的html目录下然后配置Nginx做反向代理server { listen 80; server_name your-domain-or-ip; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }Nginx配置里的try_files $uri $uri/ /index.html这行非常关键。Vue是单页应用前端路由是history模式刷新/dashboard页面时Nginx如果按物理路径找文件会返回404加了这行配置就会把所有不存在的路径都指向index.html由前端路由接管页面才能正常刷新。生产环境上线时记得在Nginx里配置gzip on;开启压缩静态JS和CSS体积能压缩掉一半以上页面加载速度提升非常明显。5. 常见问题排查与避坑指南5.1 开发期高发问题速查表把我在实测过程中遇到的典型问题整理成一张速查表这些问题在毕设答辩项目演示环节最容易翻车提前排查能省很多麻烦。问题现象根本原因解决方案启动报Failed to configure a DataSource数据库连接参数错误或MySQL没启动检查application.yml的数据源配置先手动用客户端工具连接一次跨域请求失败前端端口与后端端口不一致或CORS配置缺失在后端加全局CORS配置类登录接口正常但前端拿不到用户信息JWT未正确返回或前端没存Token检查登录接口返回体确认Token放在响应里前端登录后存入localStorage下单报库存超卖扣库存SQL没有加条件守卫扣库存SQL加AND stock 0条件判断中文乱码MySQL连接参数未配置characterEncodingutf8连接串加上characterEncodingutf8并确保数据库字符集为utf8WebSocket连不上握手拦截器里JWT校验失败或跨域配置缺OPTIONS放行在拦截器里放行OPTIONS预检请求校验失败的Session直接关闭图片上传404静态资源映射没配置或上传目录不存在通过WebMvcConfigurer映射/**/upload/**到本地目录5.2 Redis 缓存下线的“三连坑”这个要单独拎出来讲。我的项目里Redis承担了三份职责Session共享的替代JWT其实不需要Redis、菜品列表的缓存、WebSocket消息的发布订阅。Redis一旦挂掉系统不会直接崩溃但会出现三类令人摸不着头脑的故障菜品列表接口变慢因为缓存失效每次都要查数据库。WebSocket的消息收不到因为发布订阅通道断掉后订阅方不会收到补发消息。登录状态不受影响得益于JWT造成一种“系统好像没问题”的错觉。排查Redis是否正常最简单的命令是redis-cli ping返回PONG就是活着。我还建议在配置里加一个简单的健康检查逻辑启动时测试一下Redis连接连不上就打印一条醒目的警告日志。毕设演示时如果Redis没启动而系统照常跑这种感觉是最折磨人的——你根本不知道问题出在哪一层。5.3 答辩演示时最应该预演的三个场景答辩现场演示翻车是最高频的意外事件。我说的不是代码写不出来那种翻车而是环境问题导致的“不配合”。三个场景必须提前预演场景一从零启动后端项目。关闭所有服务清空MySQL和Redis中项目相关数据重新启动SpringBoot确认能正常初始化。很多同学平时开发都不关服务导致真实演示时启动报错。我特别建议在演示前做一次“冷启动测试”列表数据为空也好至少系统跑起来了你能顺手演示菜品初始化模块反而是一个自然的过渡环节。场景二演示“售罄”逻辑。这是最能体现系统“智能”属性的功能点。把某个菜品的可售数量直接改为1然后用两个不同账号同时下这个菜的订单第一个订单成功第二个订单弹窗提示“已售罄试试其他美食吧”。这个场景演示完再把库存调大操作一个菜品从“售罄”到“可售”的完整过程评委能看到前后端的联动系统实时性一目了然。场景三演示WebSocket实时叫号。打开档口管理页面和学生订餐页面两个窗口学生端支付完成后档口端立刻弹出新订单提醒状态从“待支付”变成“已支付”点击“出餐”学生端的取餐状态实时变成“待取餐”。整个链路用不了两分钟但把系统的核心亮点全部展现了——订单实时流转、状态机驱动、WebSocket推送。6. 从毕设到项目这套系统还能怎么扩展项目做到当前阶段功能闭环已经完整可以顺利收官。但如果你时间富裕、想在答辩时再多展示一些亮点或者在项目描述里增加一些有深度的内容有三个扩展方向非常合适方向一接入推荐算法做“今日推荐”栏目。现在菜品列表只是按分类和销量排列说不上“智能”推荐。你可以基于用户的历史订单数据计算“常点的档口”“常吃的菜品类目”再结合当前时段的销量热度给每个用户生成一张个性化的推荐列表。落地方式也很轻量不需要深度学习相关的东西在service层做一个简单的多因子加权算法即可但代码量不大故事价值很大。方向二定时任务自动生成经营日报。当前的数据看板是手动刷新查出来的扩展方案是把经营统计做成每日凌晨自动跑批汇总前一天的订单数据到consumption_stat表生成一份格式化的经营日报表推送给管理员的微信或邮件。这个功能非常适合体现SpringBoot的定时任务调度能力。方向三针对“备餐等待时间”做预测。系统记录了每个档口每个订单从“已支付”到“备餐完成”的耗时数据。基于这份数据计算每个档口当前的平均备餐时间在学生下单选取餐时间时展示“预计等待15分钟”。这个功能看似不起眼但它把一个静态的订餐系统升级成了有时间感知能力的动态系统。这三个扩展方向都基于现有数据和框架不需要引入额外技术栈但叙事上能把项目从“一个简单的管理系统”升维到“一个数据驱动的智能决策平台”。我在实际操作中最大的体会是毕设项目的价值不在于你用了多酷炫的技术而在于你能否把一个真实的业务痛点讲清楚再用合理的技术方案完整解决它。“食堂线上订餐”这个场景胜在人人有感、场景真实、痛点明确技术选型和业务需求高度匹配这套逻辑本身就足够支撑高质量的项目呈现。落到代码上核心链路清晰、状态机严谨、数据闭环完整只要老老实实做扎实答辩时你就有底气说“这是我独立设计并实现的一个完整系统”。
返回列表