☰
Spring Boot食堂订餐系统源码解析:从跑通到二次开发
2026/10/28 19:14:56 网站建设 项目流程

简介:这份资源是面向计算机专业学生与Java Web开发学习者的毕业设计文档,围绕基于Spring Boot的校园食堂订餐系统展开,针对疫情下食堂拥挤、学生外卖饮食不健康、商家销售统计困难等现实问题给出完整设计方案。文档系统梳理了系统的总体功能设计,涵盖手机端菜品浏览、订单提交与限时修改撤销、校园一卡通在线支付、菜品销售统计等核心模块,并说明了后台采用Spring Boot轻量框架与B/S架构、前端借助uni-app跨Android、iOS、H5及小程序开发、MySQL存储数据的技术选型与实现过程,同时包含系统测试与调试内容。资源包共1个doc文件,约3.32MB,内容完整、结构清晰,适合作为同类订餐系统开发与论文撰写的参考范本。目前已有9254人学习下载,可帮助读者快速理解需求分析、功能划分与技术落地思路,为毕业设计选题和项目实践提供借鉴。

1. 从一份食堂订餐系统源码说起:它到底能跑出什么

去年帮一个做后勤信息化的朋友看项目,他手里有一份基于 Spring Boot 的校园食堂订餐系统源码,需求很朴素:学生下课路上用手机把菜点好,到窗口扫码取餐,别在高峰期挤成一锅粥。这套代码正好覆盖了这条链路——学生端选菜下单、一卡通在线支付、商家端菜品维护和订单处理、后台按菜品维度做销售统计。技术栈是 Spring Boot 做后端、uni-app 做跨端前台、MySQL 存数据,B/S 架构,前后端分离。适合两类人:一是拿它当毕业设计或课程设计底稿的学生,二是想照着搭一套小型订餐/预约类系统的开发者。下面按「能干什么 → 怎么跑 → 怎么改」的顺序,把这份源码拆开讲。

2. 技术选型与整体架构:为什么是 Spring Boot + uni-app + MySQL

2.1 三层结构怎么落

这套系统走的是典型的 B/S 前后端分离:浏览器/小程序端只负责渲染和交互,所有业务逻辑和数据都压在后端。后端按 Controller → Service → Mapper 三层切,Controller 收请求、做参数校验,Service 承载业务规则(下单、改单、支付回调、统计),Mapper 走 MyBatis 访问 MySQL。这样切的好处是订单状态流转这类容易出错的逻辑集中在 Service 层,改规则只动一处。

前端用 uni-app 是这套代码里比较关键的一个决定。同一套 Vue 语法编译出 H5、小程序、App 三端,学生用微信小程序点餐、用 H5 看订单,不用维护两份代码。代价是 uni-app 的组件和原生小程序有差异,写页面时得注意条件编译。

2.2 技术栈清单与版本约束

层次技术说明
后端框架Spring Boot轻量、内嵌 Tomcat,起步依赖省配置
持久层MyBatisXML 写 SQL,统计类查询好控制
数据库MySQL 8.0订单、菜品、用户、统计四类核心表
前端uni-app(Vue 语法)一套代码编译多端
开发工具IntelliJ IDEA / WebStorm后端与前端分开编码
运行环境JDK 8+、Tomcat 7+、Windows 10原文给出的软件环境

提示:原文写的是 Tomcat 7.0,但 Spring Boot 内嵌容器默认是 Tomcat 9,实际跑起来以 pom 里 spring-boot-starter-web 带的版本为准,别被文档里的旧版本号带偏。

2.3 目录结构长什么样

拿到源码先看目录,能省一半时间。后端大致是:

canteen-server/ ├── src/main/java/com/canteen/ │ ├── controller/ # 登录、菜品、订单、统计、餐厅管理接口 │ ├── service/ # 业务逻辑,订单状态机在这里 │ ├── mapper/ # MyBatis 接口 │ ├── entity/ # 与数据表对应的实体 │ └── common/ # 统一返回、分页、数据库连接配置 ├── src/main/resources/ │ ├── mapper/ # 对应 XML SQL │ └── application.yml # 数据源、端口、支付配置 └── pom.xml

前端是 uni-app 标准结构,pages 下按学生端、商家端分目录,api 目录统一封装请求。先认清这两棵树,后面定位问题基本靠它。

3. 把项目跑起来:数据库、后端、前端三步走

3.1 建库建表与初始数据

第一步永远是数据库。原文的 E-R 设计里核心是用户表、餐厅表、菜品表、订单表、订单明细表,统计功能靠订单明细按菜品聚合。建库语句大致如下,字段名按实体类对齐,别自己改名,否则 Mapper 里的 SQL 全要跟着改:

CREATE DATABASE canteen DEFAULT CHARACTER SET utf8mb4; USE canteen; CREATE TABLE dish ( id BIGINT PRIMARY KEY AUTO_INCREMENT, restaurant_id BIGINT NOT NULL, -- 所属餐厅/商家 name VARCHAR(64) NOT NULL, price DECIMAL(8,2) NOT NULL, stock INT DEFAULT 0, -- 供应量,下单时扣减 material VARCHAR(255), -- 原材料,供采购参考 status TINYINT DEFAULT 1 -- 1上架 0下架 ); CREATE TABLE orders ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, total DECIMAL(8,2) NOT NULL, status TINYINT DEFAULT 0, -- 0待支付 1已支付 2已完成 3已取消 create_time DATETIME DEFAULT CURRENT_TIMESTAMP );

订单明细表存 dish_id、数量、单价快照,统计时按 dish_id 分组求和即可。初始数据至少插一个餐厅、几个菜品、一个测试学生账号,否则前端列表是空的,容易误判成接口挂了。

3.2 后端启动与配置项

application.yml 里要改的就三处:数据源、端口、支付回调地址。数据源按本机 MySQL 填:

server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/canteen?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml

改完在 IDEA 里跑主启动类,控制台出现 Tomcat started on port 8080 就算起来了。用浏览器直接访问一个查询接口(比如 /api/dish/list)验证,能返回 JSON 说明后端和数据库通了。这一步别急着开前端,后端单独验证能省掉「到底是接口错还是页面错」的扯皮。

3.3 前端编译与联调

前端在 WebStorm 里打开,先改 api 目录下的 baseUrl 指向 http://localhost:8080,然后 npm install、npm run dev:h5。H5 跑通后再编译小程序端,注意小程序要求 HTTPS 域名,本地调试要在开发者工具里勾选「不校验合法域名」。登录页用初始学生账号进去,能看到菜品列表、能下单,整条链路就算通了。

注意:uni-app 的 H5 端存在跨域问题,后端要加 CORS 配置或在开发环境用代理,否则浏览器控制台一片红,接口其实没毛病。

4. 核心模块拆解:登录、下单、支付、统计四条链路

4.1 登录与权限:三种角色怎么区分

系统里有食堂管理员、商家、学生/教职工三类角色。登录接口校验账号密码后签发 token,前端存本地,后续请求带在 header 里。后端用拦截器解析 token 拿到角色,再决定能访问哪些接口。核心逻辑大致是:

// 登录:校验通过后签发 token,角色写进载荷 public String login(String username, String password) { User user = userMapper.findByUsername(username); if (user == null || !passwordEncoder.matches(password, user.getPassword())) { throw new BizException("账号或密码错误"); } // role: 0管理员 1商家 2学生 return jwtUtil.sign(user.getId(), user.getRole()); }

参数说明:passwordEncoder 用 BCrypt,别存明文;jwtUtil.sign 里把 userId 和 role 编进去,过期时间设 2 小时左右够用。拦截器里按 role 做接口级白名单,商家只能碰自己餐厅的菜品和订单,这个过滤一定要在 Service 层再校验一次 restaurant_id,光靠前端隐藏菜单是挡不住直接调接口的。

4.2 下单与订单状态流转

下单是整个系统最容易翻车的地方。用户提交订单时要做三件事:校验菜品是否上架、扣减库存、生成订单和明细。库存扣减必须防超卖,常见做法是 SQL 带条件更新:

@Transactional public Long createOrder(Long userId, List<OrderItem> items) { BigDecimal total = BigDecimal.ZERO; for (OrderItem item : items) { // 条件更新:库存足够才扣,返回影响行数 int rows = dishMapper.reduceStock(item.getDishId(), item.getNum()); if (rows == 0) { throw new BizException("菜品库存不足:" + item.getDishId()); } total = total.add(item.getPrice().multiply(new BigDecimal(item.getNum()))); } Order order = new Order(userId, total, 0); // 0 待支付 orderMapper.insert(order); orderItemMapper.batchInsert(order.getId(), items); return order.getId(); }

对应的 Mapper SQL 是UPDATE dish SET stock = stock - #{num} WHERE id = #{id} AND stock >= #{num},靠数据库行锁保证并发下不超卖。@Transactional 保证扣库存和写订单同生共死,任何一步失败整体回滚。订单状态按 0 待支付 → 1 已支付 → 2 已完成 流转,取消走 3,取消时要把库存加回去,这段补偿逻辑别漏。

4.3 一卡通支付与回调

支付这块原文说的是一卡通在线支付,实际落地一般是调学校一卡通平台的支付接口,拿到支付链接或二维码,用户支付成功后平台回调通知。回调接口要做两件事:验签、幂等。验签防止伪造通知,幂等防止重复回调把订单状态改乱:

public String payCallback(String orderNo, String sign, BigDecimal amount) { if (!payUtil.verify(orderNo, amount, sign)) { return "fail"; // 验签失败,平台会重试 } Order order = orderMapper.findByNo(orderNo); if (order.getStatus() != 0) { return "success"; // 已处理过,直接返回成功保证幂等 } orderMapper.updateStatus(orderNo, 1); // 置为已支付 return "success"; }

参数说明:orderNo 是业务订单号,别用自增 id 暴露给外部;amount 要和订单金额比对,防止改价。回调地址必须是公网可达的,本地调试可以用内网穿透工具临时映射,但上线前一定换成正式域名。

4.4 菜品销售统计

统计模块是给商家看的数据面板,按菜品维度聚合订单明细。SQL 大致是:

SELECT d.name, SUM(oi.num) AS sold, SUM(oi.num * oi.price) AS amount FROM order_item oi JOIN orders o ON oi.order_id = o.id JOIN dish d ON oi.dish_id = d.id WHERE o.status IN (1, 2) -- 只统计已支付和已完成 AND o.create_time >= #{start} GROUP BY oi.dish_id ORDER BY sold DESC;

只统计已支付及之后的订单,待支付和已取消的不算销售额,这个口径要和商家对齐,否则数字对不上又要返工。导出功能用 EasyExcel 之类的库把结果写成 xlsx,商家拿去预估第二天采购量。

5. 避坑与排查:跑这套源码最容易踩的五个坑

5.1 中文乱码

现象:菜品名在页面显示成问号。原因:数据库字符集不是 utf8mb4,或 JDBC url 没带 characterEncoding。解决:建库时指定 utf8mb4,url 加characterEncoding=utf8,连接排序规则统一 utf8mb4_general_ci。

5.2 下单后库存对不上

现象:并发下单后库存变负数或订单数超过库存。原因:先查库存再更新,中间有窗口期。解决:改成条件更新stock >= num并判断影响行数,配合事务,别在 Java 里先 select 再 set。

5.3 支付回调重复处理

现象:订单状态被改两次,或重复发货。原因:支付平台会重试回调,接口没做幂等。解决:回调入口先查订单状态,非待支付直接返回 success,状态更新用带原状态条件的 update。

5.4 前端请求 404 或跨域

现象:H5 页面接口全红。原因:baseUrl 没改、后端没开 CORS、或小程序没关域名校验。解决:逐项核对 baseUrl、后端加 CorsFilter、开发者工具勾选不校验合法域名。

5.5 统计数字和订单列表对不上

现象:统计销售额比订单总额少。原因:统计 SQL 过滤了状态,或订单明细单价没存快照、菜品改价后历史订单金额跟着变。解决:订单明细落库时存下单时的单价快照,统计口径和列表口径写进同一份说明。

6. 二次开发怎么改:从单食堂到多校区的一份改造清单

拿到这套代码想往上加东西,我的建议是先别动订单主流程,从外围模块切入。最常见的改造方向有三个:多校区/多食堂隔离、菜品推荐、取餐码核销。

多食堂隔离是改动量最大也最值得做的。现有代码里 restaurant_id 已经贯穿菜品和订单,改造时把它提升为数据权限维度:商家登录后所有查询强制拼 restaurant_id 条件,管理员可以跨食堂看汇总。具体做法是在 Service 层加一个当前登录商家上下文的工具类,Mapper 方法统一多传一个 restaurantId 参数,别在 Controller 里手动拼,容易漏。

菜品推荐可以先做最朴素的版本:按用户历史订单的菜品分类做频次统计,取 TopN 在首页展示。不用上协同过滤,数据量小的时候规则推荐反而更稳。取餐码核销则是在订单表加一个取餐码字段,支付成功后生成短码,窗口端扫码或输码把订单置为已完成,这条链路能直接复用现有的状态机。

验证改造是否成功,我一般用三步:先用 Postman 把改动接口的入参出参跑一遍,再在 H5 端走完整下单流程,最后用两个账号并发下单压一下库存扣减。改完订单相关代码,务必回归「下单 → 支付 → 取消 → 再下单」这条闭环,库存加减是否平衡一眼就能看出来。

血泪经验是:这套代码里订单状态散落在多个 Service 方法里,改状态机之前先把所有 updateStatus 的调用点列出来,画一张状态流转表贴在旁边,不然改完一处漏一处,测试阶段能把你折腾到怀疑人生。从那以后我每次动订单状态相关代码,都强制先把状态流转表过一遍再提交。希望这份拆解能帮你少走点弯路,把这份源码真正跑成自己能用的东西。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询