基于Spring Boot与uni-app的校园食堂订餐系统设计与实现
发布时间:2026/10/28 5:40:21 锦皓数字建站

简介这份文档面向计算机专业毕业设计学生与Java Web初学者围绕校园食堂订餐系统的设计与实现展开针对疫情下食堂拥挤、学生外卖饮食不健康、商家销售统计困难等现实问题给出了一套可参考的完整课题方案。资源包共1个doc文件约3.32MB内容涵盖系统总体功能设计、Spring Boot后台与uni-app前台的架构选型、MySQL数据存储方案以及手机端点餐、订单提交与限时修改撤销、一卡通在线支付、菜品销售统计等核心模块的实现思路并附系统测试与调试过程记录。目前已有9254人学习下载说明该选题在毕业设计场景中具有较高的参考价值。读者可从中获取需求分析、技术选型、功能模块划分与论文写作结构的完整范本适合需要快速搭建订餐类项目或撰写同类毕设论文的同学对照借鉴。1. 校园食堂订餐系统从排队拥挤到线上预订这套 Spring Boot 方案要解决什么每到中午十一点半食堂窗口前的队伍能拐两个弯学生排二十分钟只为打一份饭商家忙到串单漏单管理员手里还攥着一沓手写统计表——这是很多高校食堂的真实写照。校园食堂订餐系统要做的不是把菜单搬到网页上那么简单而是把「选菜、下单、支付、出餐、统计」这条链路整体搬到线上让学生错峰取餐、商家按单备料、管理员有数据可看。这套基于 Spring Boot 与 uni-app 的方案适合正在做毕业设计或课程设计的同学直接参考结构也适合需要交付一套可运行订餐系统的开发者按模块落地。下面从需求、架构、数据、实现到验收一层层拆开讲。2. 需求分析与技术选型三个角色、六类功能怎么定下来2.1 角色划分与功能边界这套系统的角色只有三个学生/教职工下称用户、商家、食堂管理员。角色少不代表功能简单关键在于把每个角色的动作边界划清楚否则后期权限判断会写成一团乱麻。用户侧的核心动作是「看菜—下单—支付—查单」。看菜要能按食堂、按窗口、按菜品分类筛选下单要能选数量、填备注、选取餐时间段支付走校园一卡通查单要能看到订单状态流转并且在规定时间窗口内还能改单或撤单。商家侧的核心动作是「维护菜品—接单—改状态—看统计」。菜品维护包括上架下架、库存数量、单价、原材料说明接单后要把订单从「待接单」推到「制作中」「待取餐」「已完成」统计要能按菜品、按时间段导出销售数据。管理员侧则是「管商家—管食堂—看全局」新增或停用商家账号、维护食堂与窗口信息、处理异常订单。把这三条线画出来之后功能清单自然就出来了餐厅管理、菜品管理、订单管理、菜品统计、我的订单、用户订餐正好对应需求分析里的六个模块。这里有个血泪经验——功能边界一定要在需求阶段写死我见过太多项目做到一半发现「商家要不要能改价」「用户撤单后库存怎么回滚」这类问题没定义返工成本极高。2.2 技术选型为什么是 Spring Boot uni-app MySQL后台选 Spring Boot理由很实在毕设和课设的时间窗口通常只有两三个月Spring Boot 的自动配置和内嵌 Tomcat 能把环境搭建时间压到最低一个main方法就能跑起来不用在 XML 配置上耗掉一周。配合 MyBatis 或 MyBatis-Plus 做数据访问分页、条件查询这些高频操作都有现成封装。前端选 uni-app 而不是纯 Vue 页面是因为需求里明确要求「手机端随时随地选菜」。uni-app 一套代码能编译到 H5、小程序和 App答辩时用手机扫码演示比开电脑浏览器有说服力得多。后台管理页面则用 Vue Element UI 那套常规组合表格、表单、弹窗都是现成组件。数据库用 MySQL 8.0订单、菜品、用户这些结构化数据用关系型库最稳妥事务支持也成熟。开发工具按原文用 IntelliJ IDEA 和 WebStorm硬件 4 核 1.6G 主频、4G 内存起步就够跑开发和本地测试。层次技术选型选它的理由后端框架Spring Boot自动配置、内嵌容器、上手快数据访问MyBatis / MyBatis-PlusSQL 可控分页封装成熟前端用户端uni-app一套代码多端编译手机演示方便前端管理端Vue Element UI表格表单组件齐全开发效率高数据库MySQL 8.0事务可靠结构化数据适配好架构B/S免安装浏览器/手机直接访问提示技术选型不要贪多。毕设阶段把 Spring Boot、MyBatis、MySQL 这三样吃透比堆一堆中间件更能保证系统跑通、答辩讲清。2.3 性能与非功能需求需求分析里提到的及时性、准确性、灵活性、可靠性落到实现上就是几条硬指标订单状态变更后用户端刷新要能立刻看到这要求接口响应控制在秒级订单金额、库存扣减必须准确不能出现超卖查询要支持多条件组合不能只给一个全量列表不同角色访问不同数据登录态和权限校验不能省。这些指标不需要复杂的架构去撑单机部署加合理的索引和事务就能满足校园场景的并发量。真正容易翻车的是「及时性」——如果订单状态靠用户手动刷新体验会很差通常的做法是前端在订单详情页做轮询或者用 WebSocket 推送状态变更毕设阶段轮询就够用实现简单也好讲。3. 总体架构与数据库设计三层结构如何撑起六个模块3.1 三层架构与模块职责划分系统整体走经典的 B/S 三层结构表现层uni-app 用户端 Vue 管理端、业务逻辑层Spring Boot 的 Controller-Service-Mapper、数据层MySQL。表现层只负责展示和收集输入业务规则全部压在 Service 层数据访问收敛到 Mapper 层。这样分层的好处是订单状态流转、库存扣减这些逻辑只写一遍用户端和商家端调用同一个 Service 方法不会出现两边规则不一致的情况。六个功能模块的职责这样分餐厅管理归管理员负责商家账号和食堂窗口的增删查菜品管理归商家负责菜品 CRUD 和上下架订单管理归商家负责接单和状态推进菜品统计归商家负责销售数据聚合与导出我的订单归用户负责查单、撤单、删单用户订餐是用户侧主流程串起选菜、下单、支付。模块之间通过订单表关联菜品表被订单明细引用用户表被订单和权限引用。3.2 核心数据表设计数据库设计遵循一条原则先画 E-R 图定实体关系再落表。核心实体有用户、商家、食堂窗口、菜品、订单、订单明细。用户和订单是一对多商家和菜品是一对多订单和订单明细是一对多菜品和订单明细通过明细表关联。下面给出几张关键表的结构字段名和类型按实际开发习惯来。-- 菜品表商家维护订单明细引用 CREATE TABLE dish ( id BIGINT PRIMARY KEY AUTO_INCREMENT, merchant_id BIGINT NOT NULL COMMENT 所属商家, canteen_id BIGINT NOT NULL COMMENT 所属食堂窗口, name VARCHAR(64) NOT NULL COMMENT 菜品名称, category VARCHAR(32) COMMENT 分类主食/荤菜/素菜/汤, price DECIMAL(10,2) NOT NULL COMMENT 单价, stock INT NOT NULL DEFAULT 0 COMMENT 可售库存, material VARCHAR(255) COMMENT 原材料说明, status TINYINT DEFAULT 1 COMMENT 1上架 0下架, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 订单表状态机是核心 CREATE TABLE orders ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE COMMENT 订单号, user_id BIGINT NOT NULL, merchant_id BIGINT NOT NULL, total_amount DECIMAL(10,2) NOT NULL, pickup_slot VARCHAR(32) COMMENT 取餐时间段, status TINYINT DEFAULT 0 COMMENT 0待支付 1待接单 2制作中 3待取餐 4已完成 5已取消, pay_time DATETIME COMMENT 支付时间, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 订单明细表记录下单时的菜品快照 CREATE TABLE order_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT NOT NULL, dish_id BIGINT NOT NULL, dish_name VARCHAR(64) NOT NULL COMMENT 下单时名称快照, price DECIMAL(10,2) NOT NULL COMMENT 下单时单价快照, quantity INT NOT NULL );订单明细里存dish_name和price快照是个容易被忽略但很关键的设计。菜品价格后期可能调整如果订单只存dish_id历史订单金额会跟着变对账就乱了。存快照等于给订单留了「后悔药」统计和退款都以快照为准。3.3 订单状态机设计订单状态是整个系统的主线状态流转必须定义清楚否则商家和用户看到的状态会对不上。这套方案的状态流转是用户下单生成「待支付」支付成功变「待接单」商家接单变「制作中」出餐变「待取餐」用户取餐后变「已完成」用户在「待支付」和「待接单」阶段可以撤单撤单后库存回滚。// 订单状态流转校验放在 Service 层统一拦截非法跳转 public void changeStatus(Long orderId, int targetStatus) { Orders order orderMapper.selectById(orderId); int current order.getStatus(); // 只允许相邻状态推进防止商家跳步或用户乱改 boolean valid (current 0 targetStatus 1) || (current 1 targetStatus 2) || (current 2 targetStatus 3) || (current 3 targetStatus 4) || ((current 0 || current 1) targetStatus 5); if (!valid) { throw new BizException(当前状态不允许该操作); } order.setStatus(targetStatus); orderMapper.updateById(order); }这段校验的意义在于把状态规则收口到一个方法里用户撤单、商家接单、系统自动完成都走它避免每个接口各写一套判断导致规则漂移。参数targetStatus对应上面注释里的状态码非法跳转直接抛业务异常前端拿到统一错误提示。4. 核心模块实现登录、订餐、支付与统计怎么落地4.1 公共模块统一返回、分页与登录鉴权动手写业务之前先把公共模块搭好后面每个模块都能省事。统一返回格式用一个Result类包住code、msg、data前端只认这一种结构错误处理也统一。分页用 MyBatis-Plus 的Page对象传页码和每页条数即可。登录鉴权用 JWT用户登录后签发 token前端每次请求带在 header 里后端拦截器校验并解析出用户身份和角色。// 统一返回结构所有接口都返回它 public class ResultT { private int code; // 200成功其他为业务错误码 private String msg; private T data; public static T ResultT ok(T data) { ResultT r new Result(); r.code 200; r.msg success; r.data data; return r; } public static T ResultT fail(String msg) { ResultT r new Result(); r.code 500; r.msg msg; return r; } }登录接口拿到账号密码后先查用户表比对密码实际项目里密码要加盐哈希存储毕设也别存明文校验通过生成 JWT把userId和role写进载荷。拦截器里解析 token 后放进ThreadLocalService 层随时能取当前用户不用每个方法都传参。角色判断放在拦截器或注解上商家接口只允许rolemerchant访问管理员接口只允许roleadmin越权请求直接返回 403。4.2 用户订餐主流程选菜、下单与库存扣减用户订餐是系统最核心的链路顺序是浏览菜品列表 → 加入购物车或直接下单→ 确认订单 → 支付 → 生成待接单订单。这里最关键的是库存扣减的时机和方式。常见误用是「下单就扣库存」结果用户不支付库存被占死正确做法是支付成功回调里扣库存并且用数据库行锁或乐观锁防止超卖。// 支付成功后扣减库存用乐观锁版本号防并发超卖 Transactional public void deductStock(Long dishId, int quantity) { int affected dishMapper.deductStock(dishId, quantity); if (affected 0) { // 影响行数为0说明库存不足或并发冲突整单回滚 throw new BizException(菜品库存不足请重新选择); } }对应的 Mapper SQL 写成UPDATE dish SET stock stock - #{qty} WHERE id #{id} AND stock #{qty}把库存判断和扣减放在一条语句里靠数据库的原子性保证不超卖。Transactional保证订单状态更新和库存扣减要么都成功要么都回滚。参数quantity来自订单明细扣减前要校验明细里的菜品都属于同一商家避免跨商家订单混在一起。下单接口还要处理取餐时间段。用户在确认页选一个时间段比如 11:30-12:00系统按时间段统计已下单量超过窗口承载量就提示换时段。这个逻辑不复杂但能体现「错峰就餐」的设计意图答辩时是个加分点。4.3 一卡通支付与订单状态推进支付模块在毕设里通常不接真实支付渠道而是模拟一卡通扣款用户确认支付后调用一个模拟支付接口校验一卡通余额是否充足充足则扣款并回调订单服务把状态从「待支付」推到「待接单」。真实项目里这一步会对接学校一卡通中心的接口毕设阶段用模拟实现完全够用关键是把「支付回调驱动状态变更」这个模式讲清楚。// 模拟一卡通支付校验余额 - 扣款 - 回调订单状态 public Result pay(Long orderId, String cardNo) { Orders order orderMapper.selectById(orderId); if (order.getStatus() ! 0) { return Result.fail(订单状态异常无法支付); } BigDecimal balance cardService.getBalance(cardNo); if (balance.compareTo(order.getTotalAmount()) 0) { return Result.fail(一卡通余额不足); } cardService.deduct(cardNo, order.getTotalAmount()); orderService.changeStatus(orderId, 1); // 推到待接单 return Result.ok(支付成功); }支付成功后商家端订单列表就能看到新订单商家点「接单」推进到「制作中」出餐后点「出餐」推进到「待取餐」用户取餐后系统或商家点「完成」。每一步都走 3.3 里的状态校验方法保证流转合法。用户撤单只在「待支付」和「待接单」阶段开放撤单时把库存加回去形成闭环。4.4 菜品统计按菜品与时间段聚合销售数据统计模块是商家最关心的功能也是答辩时容易被追问的点。需求是「查询菜品统计列表并导出」实现上就是按菜品分组聚合订单明细的销量和金额支持按日期范围过滤。-- 按菜品统计销量与销售额支持时间段过滤 SELECT oi.dish_id, oi.dish_name, SUM(oi.quantity) AS total_qty, SUM(oi.price * oi.quantity) AS total_amount FROM order_item oi JOIN orders o ON oi.order_id o.id WHERE o.status 4 -- 只统计已完成订单 AND o.create_time BETWEEN #{start} AND #{end} GROUP BY oi.dish_id, oi.dish_name ORDER BY total_qty DESC;这条 SQL 只统计「已完成」订单避免把撤单和未支付的数据算进去统计口径要和商家对账口径一致。导出功能用 EasyExcel 或 POI 把查询结果写成 Excel字段就是菜品名、销量、销售额商家拿去就能估算第二天备料量。参数start和end由前端日期选择器传入后端做格式校验防止 SQL 注入。5. 避坑与常见问题这几处最容易翻车5.1 库存超卖并发下扣成负数现象是压测或多人同时下单时某菜品库存变成负数订单却生成成功。原因在于「先查库存再扣减」分了两步两个请求同时查到库存为 1都判断充足然后各自扣减。解决办法是把判断和扣减合并成一条带条件的 UPDATE靠数据库行锁保证原子性影响行数为 0 就抛异常回滚整单如 4.2 所示。5.2 订单状态乱跳商家和用户看到的不一致现象是用户撤单后商家还能接单或者订单没支付就显示「制作中」。根因是状态变更散落在多个接口里各写各的判断。解决办法是收口到一个changeStatus方法所有状态推进都走它非法跳转统一抛异常前端根据返回码提示。这条规则写进 Service 层比在每个 Controller 里补判断可靠得多。5.3 金额对不上菜品改价后历史订单跟着变现象是商家调整菜品价格后用户查历史订单发现金额变了对账对不上。原因是订单只存了dish_id展示时实时关联菜品表取价格。解决办法是下单时把菜品名称和单价快照写进订单明细表统计和展示都以快照为准菜品表改价不影响历史订单。5.4 登录态失效token 过期后接口报错不友好现象是用户停留久了再操作接口直接抛 500 或空白页。原因是拦截器解析 token 失败后没有统一处理。解决办法是拦截器捕获 token 异常返回明确的 401 状态码和「登录已过期」提示前端拦截 401 后跳转登录页而不是让用户对着报错发呆。5.5 统计口径不一致商家看到的销量和实际不符现象是统计报表里的销量比商家自己记的多或少。原因通常是统计时没过滤订单状态把待支付、已取消的订单也算进去了。解决办法是统计 SQL 固定加status 4已完成条件并在页面上标注统计口径避免商家误解。6. 验收与答辩怎么证明这套系统真的能用验收这件事我一般分三步走功能走查、异常路径测试、数据核对。功能走查按角色来用户端把「选菜—下单—支付—查单—撤单」完整走一遍商家端把「上架—接单—出餐—统计导出」走一遍管理员端把「新增商家—停用商家」走一遍每一步截图留档。异常路径重点测三处库存不足时下单是否被拦、未支付订单能否被商家接单、越权访问商家接口是否返回 403。数据核对则是下单前后对比菜品库存和订单金额确认扣减和快照都对。答辩时老师最爱问的无非几个库存怎么防超卖、订单状态怎么保证不乱、统计口径是什么、权限怎么控制。提前把这几处的代码和 SQL 准备好能当场翻出来讲比背 PPT 管用。下面这张表可以当自测清单用。验收项操作预期结果下单扣库存库存 1 时两人同时下单仅一人成功库存不为负状态流转未支付订单点接单提示状态异常拒绝操作撤单回滚待接单订单撤单状态变已取消库存加回权限隔离用户 token 调商家接口返回 403统计口径含撤单订单的日期范围统计只统计已完成订单金额快照改菜品价后查历史订单历史金额不变答辩演示有个小技巧提前准备好两个账号用户和商家和几条测试数据现场用手机扫码演示用户下单再切到电脑演示商家接单和统计导出整个链路一气呵成比逐个页面点过去有说服力。演示前一定要把数据重置到干净状态别让上一位同学留下的脏数据把流程卡住。从那以后我每次带项目验收前都强制自己按角色把主流程和异常路径各走一遍宁可多花半小时也不在答辩现场翻车。希望这套结构和清单能帮到你照着改改就能变成你自己的方案。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。