news 2026/10/6 17:37:39

Spring Boot外卖点餐系统毕业设计实战指南:从技术选型到答辩

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot外卖点餐系统毕业设计实战指南:从技术选型到答辩

简介:面向Java毕业设计打造的一套Spring Boot外卖点餐系统完整交付包,内含系统源码、毕业论文与PPT答辩演示文档,覆盖从选题开题、数据库设计、前后端开发到最终答辩的全链路需求。系统划分为用户后台、商家端、管理员端、用户前台和骑手端五大功能模块,论文按绪论、技术介绍、需求分析、系统设计、数据库ER图与数据表、系统实现、系统测试逐章展开,结构完整且便于直接复用。资源包共638个文件,以Java源码、Vue组件、HTML页面和JavaScript脚本为主,辅以jpg/gif/png图片素材、SQL数据库脚本以及一键启动脚本,压缩后大小约25.24MB,解压后可按目录结构快速定位代码与文档。已有134人学习下载,适合需要完成Java课程设计或毕业设计的同学,既能参照源码理解Spring Boot结合Vue的实际写法,也能借助论文和PPT快速搭建答辩方案,进行二次开发。

1. Spring Boot 外卖点餐系统:一套能演示、能答辩、能交差的完整方案

如果你正在为毕业设计或课程设计找题目,"Spring Boot 外卖点餐系统"大概率已经躺在你的候选清单里了。这个题目看起来普通,但它覆盖了 Java 后端开发的完整链路:用户登录、商品展示、购物车、下单、订单状态流转、管理员后台,每一块都是面试和答辩时能拿出来讲的技术点。更实在的是,这个系统自带"源码+论文+PPT 答辩"三件套,意味着你不需要从零开始写文档,而是要把运行效果和论文里的技术描述对上号,让评委觉得这套系统真的是你做的、你真正理解它。

我见过太多人栽在这类项目上,不是因为代码跑不起来,而是因为"只会跑、不会讲"。系统能启动、页面能点,但被问到底层怎么实现的,答不上来。这篇笔记就按我做这类毕业设计的思路,把技术选型、表结构设计、核心模块实现、前端联调、避坑清单、论文与答辩准备全部拆开,每一步都给你能抄的代码和参数,也把那些只有踩过坑才知道的细节写清楚。无论你手里拿到的是别人的源码还是自己从零写,这篇文章都能帮你把项目吃透,让它变成你答辩时的加分项,而不是扣分项。

2. 提前定技术栈与表结构:把订单状态机和金额计算先想清楚

2.1 为什么选 Spring Boot 2.7 + MyBatis Plus,而不是最新 3.x

外卖点餐系统是一个典型的 CRUD 密集型业务,核心价值在于业务逻辑的完整性,而不是框架版本追新。很多同学一上来就建 Spring Boot 3.x 项目,结果发现依赖一堆不兼容,浪费两三天时间。做毕业设计,稳定性永远是第一位的。

我一般建议选 Spring Boot 2.7.x,搭配 JDK 1.8 或 11、MyBatis Plus 3.5.x、MySQL 5.7 或 8.0。理由有三条:第一,2.7 版本的资料量巨大,遇到任何问题都能搜到现成答案,这在赶工阶段就是救命稻草;第二,MyBatis Plus 在 2.7 下的兼容性早已被验证,不需要处理 Spring Boot 3 需要的 jakarta 命名空间迁移;第三,答辩时评委大概率也就熟悉到这个版本,你讲起来不会露怯。

Spring Boot 2.7 版本太高的坑,之后避坑章节单独说,这里先把正确的 pom 依赖结构给出来:

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.18</version> <relativePath/> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.3.2</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.7</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>

这段依赖的核心在于 mybatis-plus-boot-starter 的版本选择。3.5.7 这个版本对 Spring Boot 2.7 的支持非常稳定,分页插件、条件构造器都用得顺手。数据库驱动用 8.0.33 对应 MySQL 8.x,如果你的本机是 MySQL 5.7,换成 5.1.49 即可。需要注意,Spring Boot 2.7.18 是 2.x 的最后一个版本,比它更低的小版本可能存在已知安全漏洞,论文里写"基于 Spring Boot 2.7.18 构建"也比写一个模糊的"Spring Boot 2"更有说服力。

2.2 六张核心表:从用户表到订单明细表的设计要点

外卖点餐系统的表结构并不复杂,实际开发中六个表就能撑起全部功能:用户表、菜品分类表、菜品表、购物车表、订单表、订单明细表。每张表都要预留论文里好写的设计思路,评委问起来能说得清。

用户表(user)是系统的基础,设计时要有 id、username、password、phone、address、create_time 这些字段。密码的存储不要用明文,用 BCrypt 加密,Spring Security 自带 BCryptPasswordEncoder 可以直接用。有些同学图省事用 MD5,这在答辩时容易被追问"MD5 存在彩虹表攻击风险",能避开就避开。

菜品分类表(category)和菜品表(dish)是典型的一对多关系。category 表只需要 id、name、sort 字段;dish 表除了 id、category_id、name、price 之外,建议加 image 字段存图片路径、status 字段控制是否在售、description 字段做菜品描述。price 字段必须用 DECIMAL(10,2),不能想当然用 DOUBLE,否则后面做金额计算时会受到浮点精度影响,这属于老生常谈但总有人踩的雷。

购物车表(shopping_cart)其实是可以单独建的,字段包括 id、user_id、dish_id、number、create_time。也可以不建表直接在前端用 localStorage 存购物车数据,后端只处理订单。但从论文工作量角度考虑,建表会让系统更完整,答辩时也多一个可以讲的点。注意购物车表和订单表一样,都需要冗余一份 dish_name 和 dish_price 快照,因为菜品的价格会变,下单时是什么价就得记录什么价。

订单表(orders)是整个系统的核心,字段设计直接决定订单流程的代码复杂度。至少要包含 id、user_id、order_number、amount、status、pay_method、address、remark、create_time。order_number 建议用时间戳加随机数生成,保证全局唯一,不要用数据库自增 ID 当订单号,这在答辩时属于会被质疑的设计漏洞。

订单明细表(order_detail)记录订单里的每一项菜品,包含 id、order_id、dish_id、dish_name、dish_image、dish_price、number,其中 order_id 和 dish_id 要建联合索引。为什么需要明细表?因为订单表只存总金额,如果用户想查看"我点的鱼香肉丝当时是多少钱",就得靠明细表的数据。这也是订单表不做菜品冗余、明细表做菜品名称冗余的原因:名称会过期,但历史订单里的名称不能变。

2.3 订单状态机:四种状态与五个迁移条件

外卖订单的状态流转是这个项目里最有技术含量的部分,也几乎是答辩必问的一题。常见做法是四种状态:待支付(0)、待接单(1)、配送中(2)、已完成(3),外加一个已取消(-1)。如果系统里要做退款,可以再加退款状态,但毕设做到前五个就够了。

状态迁移条件是必须硬编码校验的,不能只靠前端按钮控制。前端只是"看起来能点",后端才是真正把关的地方。比如"用户只能取消待支付订单,商家接单后不能取消"这个规则,如果只在前端 disable 按钮,用 Postman 直接调接口就能绕过。所以后端订单控制层里要有这样的校验逻辑:

@Override public boolean cancelOrder(Long orderId, Long userId) { Orders order = getBaseMapper().selectById(orderId); // 参数说明:order.getStatus() 是数据库中的状态值,0-待支付 1-待接单 2-配送中 3-已完成 if (!order.getUserId().equals(userId)) { throw new BusinessException("不能操作他人订单"); } // 只有待支付状态允许用户取消 if (order.getStatus() != 0) { throw new BusinessException("当前订单状态不可取消"); } order.setStatus(-1); return updateById(order); }

这段代码的逻辑核心就一句话:状态机不允许跳变。用户取消订单只允许发生在待支付状态下,订单状态从 0 到 -1 是合法迁移,从 1 或 2 到 -1 在当前业务规则里就是非法的。这比前端判断"按钮能不能点"可靠得多。注意这里我用了 BusinessException 自定义异常,配合全局异常处理器返回统一 JSON,前端拿到 message 直接弹出提示即可。

3. 后端核心模块落地:登录鉴权、菜品缓存与订单提交

3.1 JWT 登录与拦截器:RSA 别用,直接上 HS256

外卖点餐系统的用户端和管理端可以共用同一套登录接口,只是返回的角色不同。常见做法是用户提交 username 和 password,后端校验通过后生成一个 JWT 令牌返回前端,前端后续请求在 Header 里带Authorization: Bearer <token>,后端通过拦截器解析 token 拿到当前用户 ID。

这里我建议用 HS256 对称加密,不要用 RSA。原因是毕设系统只有单一后端服务,不存在需要多个服务验证 token 的场景,RSA 的私钥签发、公钥验签在架构上没有任何收益,反而增加了配置复杂度。用一个 256 位的密钥字符串就够了:

@Configuration public class JwtConfig { // HS256 要求密钥长度至少 256 位(32 字节),随便写个短密钥启动时会报错 @Value("${jwt.secret}") private String secret; // 过期时间,单位毫秒;毕设项目建议设置为 24 小时,答辩演示时不容易中断 @Value("${jwt.expiration}") private Long expiration; public String generateToken(Long userId, String role, String username) { return Jwts.builder() .setSubject(username) .claim("userId", userId) .claim("role", role) .setExpiration(new Date(System.currentTimeMillis() + expiration)) .signWith(SignatureAlgorithm.HS256, secret.getBytes(StandardCharsets.UTF_8)) .compact(); } public Claims parseToken(String token) { return Jwts.parser() .setSigningKey(secret.getBytes(StandardCharsets.UTF_8)) .parseClaimsJws(token) .getBody(); } }

参数说明里最值得关注的是jwt.secret的配置,这个值不能太短。HS256 算法要求密钥至少 32 字节,不然 jjwt 库在运行时直接抛出 WeakKeyException。我见过有人复制网上的配置,secret 写的是短单词,启动时一切正常,一旦调用登录接口就报错,查半天查不到原因。建议在 application.yml 里配一个 40 位以上的随机字符串。

拦截器的写法用的是 Spring MVC 的 HandlerInterceptor,在 preHandle 方法里从 Header 取 token 并解析。注意这里有一个隐藏规则:登录接口本身、菜品列表接口这类公开接口要放行,不能全部拦截,否则前端第一次进系统什么都拿不到。路径匹配的放行规则要写清楚,比如/api/user/login、/api/dish/list放行,/api/order/ 下的所有接口都拦截,保证只有登录用户能下单。

3.2 菜品分页查询:MyBatis Plus 分页插件与 Redis 缓存

菜品列表是用户打开系统看到的第一个页面,接口设计的好坏直接影响体验。毕设项目通常不需要 Redis,但如果论文里的"创新点"或者"技术亮点"写了 Redis 缓存,可以在这里落地。菜品数据有两个特点:变更频率低、读取频率极高,这正是缓存的最佳场景。

先看 MyBatis Plus 分页插件的配置,这个配置在 Spring Boot 2.7 + MyBatis Plus 3.5.7 下是需要手动加到配置类里的,不加分页查询会得到一个诡异的结果:

@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 参数说明:DbType.MYSQL 指定数据库类型,分页方言不同会导致 SQL 语法错误 PaginationInnerInterceptor pagination = new PaginationInnerInterceptor(DbType.MYSQL); // 单页最大数量限制,防止有人恶意传入 pageSize=100000 打垮数据库 pagination.setMaxLimit(100L); interceptor.addInnerInterceptor(pagination); return interceptor; } }

setMaxLimit(100L)这行很多人忽略,但它是真实业务里必须有的底线防护。外卖系统的菜品列表不该允许一次拉取 1 万条数据,设置最大 100 条每页,既合理又安全。

分页查询的 Service 代码用 LambdaQueryWrapper 条件构造器,按分类过滤、按关键词模糊搜索、按销量排序,这些都能体现 MyBatis Plus 的功底。这里的关键是排序字段要不要索引,销量字段在 dish 表里可以用一个 sale_count 字段记录,查询时orderByDesc(Orders::getSaleCount)即可。注意 orderByDesc 里要写实体类的 Lambda 表达式,不是数据库字段名的字符串,这是 MyBatis Plus 3.x 推荐的方式,能避免字段名拼写错误在运行时才暴露。

3.3 提交订单:事务、库存扣减与超卖处理

下单是整个系统里唯一涉及写多张表的操作,也是评委眼中最能体现实力的地方。用户点击"提交订单"后,后端要同时做三件事:扣减菜品库存、生成订单主表记录、生成订单明细记录。这三件事必须在一个事务里完成,要么全部成功,要么全部回滚。

Spring 的@Transactional注解提供了声明式事务,最常见的坑是事务不生效。原因一般是两个:一是方法被同类内部调用,Spring AOP 代理失效;二是方法不是 public 的。代码这样写才对:

@Service public class OrderServiceImpl extends ServiceImpl<OrderMapper, Orders> implements OrderService { // 注意:事务必须从 Controller 层之外的方法进入,同类内部调用 this.submitOrder() 会导致事务失效 @Transactional(rollbackFor = Exception.class) @Override public OrderSubmitVO submitOrder(OrderSubmitDTO dto, Long userId) { // 1. 校验购物车 List<ShoppingCart> cartList = cartService.list( new LambdaQueryWrapper<ShoppingCart>().eq(ShoppingCart::getUserId, userId)); if (CollectionUtils.isEmpty(cartList)) { throw new BusinessException("购物车为空"); } // 2. 计算总金额,必须用 BigDecimal,禁止使用 double 累加 BigDecimal amount = BigDecimal.ZERO; for (ShoppingCart cart : cartList) { Dish dish = dishService.getById(cart.getDishId()); amount = amount.add(dish.getPrice().multiply(BigDecimal.valueOf(cart.getNumber()))); } // 3. 生成订单号 String orderNo = System.currentTimeMillis() + String.format("%04d", new Random().nextInt(10000)); // 4. 保存订单(status 为 0 即待支付) Orders order = new Orders(); order.setOrderNumber(orderNo); order.setUserId(userId); order.setAmount(amount); order.setStatus(0); save(order); // 5. 保存订单明细 for (ShoppingCart cart : cartList) { OrderDetail detail = new OrderDetail(); detail.setOrderId(order.getId()); detail.setDishId(cart.getDishId()); detail.setDishName(cart.getDishName()); detail.setDishImage(cart.getDishImage()); detail.setDishPrice(cart.getDishPrice()); detail.setNumber(cart.getNumber()); detailService.save(detail); } // 6. 清空购物车 cartService.remove(new LambdaQueryWrapper<ShoppingCart>().eq(ShoppingCart::getUserId, userId)); return new OrderSubmitVO(order.getId(), orderNo, amount); } }

参数说明:rollbackFor = Exception.class是关键,默认情况下 Spring 声明式事务只回滚 RuntimeException,如果业务代码里抛的是 checked exception(比如 IOException),事务不会回滚。在毕设项目里统一用 Runtime 类型的 BusinessException 能让事务生效。

超卖问题是下单场景的高频追问点。上面的代码在保存订单前没有做库存预扣,如果两个用户同时下单同一道菜会出现库存被扣成负数的情况。解决方式有乐观锁和悲观锁两种。乐观锁就是在 dish 表加一个 version 字段,UPDATE 语句带上WHERE id = ? AND version = ?,更新后 version 加一。毕设阶段用简单的"先查库存,再 UPDATE 扣减,影响行数为 0 则失败"就够了:

boolean success = dishService.update( new LambdaUpdateWrapper<Dish>() .setSql("stock = stock - " + number) .eq(Dish::getId, dishId) .ge(Dish::getStock, number)); // 关键:库存必须大于等于购买数量 if (!success) { throw new BusinessException("菜品库存不足"); }

ge(Dish::getStock, number)这个条件把库存校验和扣减合并成一条原子 SQL,数据库层面就保证了不会超卖。这段代码的巧妙之处在于不需要事务锁,也不需要额外查询一次库存,论文答辩时间瓶颈时可以把这个作为"如何解决并发超卖"的答案。

4. 前端与联调:Vue3 + Element Plus 调通接口的完整链路

4.1 前端项目结构与代理配置

外卖点餐系统的前端有两种路线:服务端渲染用 Thymeleaf,前后端分离用 Vue。毕设阶段更推荐前后端分离,因为论文里可以多写一章"前后端分离架构设计",PPT 里也能放系统架构图。Vue 这边我用 Vue3 + Element Plus + Axios + Vite 这套组合,是当前最主流也最不容易出问题的技术栈。

前端项目的核心结构是 views 目录下的页面组件:Login.vue、Register.vue、DishList.vue、Cart.vue、OrderList.vue、AdminDashboard.vue。路由用 vue-router,状态管理如果项目不大可以不引入 Pinia,直接在每个页面内维护数据就行。这里有一个常见误区:不要为了凑技术栈强行引入 Pinia 或 Vuex,毕设答辩时评委更看重你能否解释清楚"为什么用"。

前后端联调最关键的配置是 Vite 的代理。开发环境下前端跑在 5173 端口,后端跑在 8080 端口,浏览器的同源策略会拦截跨域请求。解决方式是在 vite.config.js 里配 proxy 代理:

export default defineConfig({ plugins: [vue()], server: { port: 5173, proxy: { // 所有以 /api 开头的请求都转发到后端 8080 端口 '/api': { target: 'http://localhost:8080', changeOrigin: true // 注意:这里不重写路径,后端接口本身就是 /api/xxx 风格 } } } })

changeOrigin: true的作用是让后端收到的请求头里的 Host 变成 localhost:8080,很多后端框架(包括 Spring Security)会校验 Host 头,不设置会报 403。代理配置好后,前端 Axios 请求可以直接写相对路径/api/dish/list,不需要写完整 URL,这样部署到服务器后也不需要改前端代码。

Axios 的封装是前端项目里必写的工具类,核心逻辑是请求拦截器里加 token、响应拦截器里统一处理错误码:

// src/utils/request.js import axios from 'axios' import { ElMessage } from 'element-plus' const request = axios.create({ baseURL: '/api', timeout: 10000 // 10秒超时,后端接口如果超过10秒需要排查慢查询 }) // 请求拦截器:统一附加 JWT token request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) // 响应拦截器:统一处理业务错误 request.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { ElMessage.error(res.msg || '请求失败') return Promise.reject(new Error(res.msg)) } return res.data }, error => { if (error.response && error.response.status === 401) { // 登录过期,跳回登录页 localStorage.removeItem('token') window.location.href = '/login' } ElMessage.error('网络异常,请稍后重试') return Promise.reject(error) } )

这里统一错误码处理的思路值得说清楚:前后端约定code = 200表示业务成功,其他 code 都是业务失败。这样前端每个接口的 then 回调里不需要重复写错误判断,拦截器统一弹消息提示。401 状态码统一处理也让登录过期这个场景不用在每个页面单独写逻辑。

4.2 购物车与结算页的联动逻辑

购物车页面是用户端交互最多的地方,加菜、减菜、全选、删除、结算,每一步都涉及前端状态和后端数据的同步。一个设计取舍是:购物车数据完全放在前端 localStorage 还是同步到后端数据库?我建议毕设直接操作后端表,这样系统更完整,也方便管理员后台查看用户购物车数据(虽然实际没人看)。

购物车页面的核心逻辑是 localStorage 里存一个 cartId 列表,每次加购调用后端接口写入数据库。结算按钮点击后调用下单接口,成功后前端清空购物车数据并跳转到订单列表页。这里交互上有一个必踩的坑:用户在购物车页改了数量,点结算时后端拿到的却是旧数据。原因在于前端没有把修改后的数量传给后端,下单接口用的是数据库里的购物车记录。

解决办法是购物车数量变更时立即调后端更新接口,不要等到结算时才提交。更新接口接收 dishId 和 number,后端查出购物车记录后更新 number 字段。这样结算时后端直接查库即可,不会出现前端与后端数据不一致。

下单成功后跳转到的支付页面,毕设里通常是模拟支付。点击"模拟支付"按钮时调后端接口把订单状态从 0 改成 1,不要真的接入支付宝或微信支付。如果要让系统看起来更完整,可以加一个支付记录表进行流水留痕,但核心还是演示用。

5. 六个高频避坑记录:都让毕业设计翻过车

5.1 Spring Boot 版本太高,xml 配置文件加载不出来

现象:按照网上的教程建 Spring Boot 3.2 项目,引入 MyBatis Plus 后启动直接报错,说找不到SqlSessionFactory或mybatis-plus-boot-starter依赖冲突。还有人搭了 3.x 项目后,原来 Spring Boot 2.x 的配置文件里的spring.redis.host写法全部失效。

原因:Spring Boot 3.x 把 javax 包迁移到了 jakarta 包,MyBatis Plus 3.5.7 及以下版本不兼容 Spring Boot 3。同时 Spring Boot 3 的配置属性命名规则发生了变化,很多配置项被重构。

解决:直接把 Spring Boot 版本锁到 2.7.18,不要追新。如果你的机器上已经装了 Spring Boot 3 的项目模板,去 pom.xml 里把 parent 版本改成 2.7.18,再把依赖中的 javax.servlet 改成 jakarta.servlet(或者直接删掉相关依赖),基本就能恢复。

5.2 MySQL 连接超时与时区问题

现象:项目启动时数据库连接正常,但运行几个小时后第一次查询报The last packet successfully received from the server was 57,000,000 milliseconds ago,或者插入时间字段时发现时间差了 8 小时。

原因:MySQL 默认 wait_timeout 是 8 小时,连接空闲超过这个时间会被服务端断开,而 HikariCP 连接池不会自动感知,继续拿旧连接去查询就会报错。时区问题则是 JDBC URL 里少了 serverTimezone 参数。

解决:在 application.yml 里给数据源 URL 加上serverTimezone=Asia/Shanghai和useSSL=false,同时配置连接池空闲超时小于 MySQL 的 wait_timeout:

spring: datasource: url: jdbc:mysql://localhost:3306/ordering?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true username: root password: 123456 hikari: max-lifetime: 1800000 idle-timeout: 600000

max-lifetime设为 30 分钟,小于 MySQL 的 8 小时,可以保证连接池中的连接不会被数据库提前断开。这个问题在答辩现场最容易出丑:白天测得好好的,下午评委一打开系统就白屏。

5.3 BigDecimal 与金额精度

现象:购物车总金额计算结果是 59.999999999,或者订单金额明明在下单时是 21.1,数据库里查出来变成 21.099999。

原因:下单时用 double 类型做累加。二进制浮点数在表示 0.1 这种十进制小数时存在精度损失,拟合误差会随累加次数放大。

解决:数据库金额字段统一用 DECIMAL(10,2),Java 实体类统一用 BigDecimal,所有金额计算必须用 BigDecimal 的 add 和 multiply 方法。注意 BigDecimal 的构造参数,不要传 double,要传字符串:

// 错误:会保留浮点数的二进制噪声 new BigDecimal(0.1d) // 正确:字符串构造是精确的 new BigDecimal("0.1")

这些细节在论文里写一句"金额计算采用 BigDecimal 保证财务数据精度",就能和那些用 double 的选手拉开差距。

5.4 MyBatis Plus 分页插件失效

现象:调用分页查询接口返回的总记录数和列表都对,但每页数量不对,或者干脆没有 WHERE 条件,直接查到全表。更隐蔽的是,在 Service 层直接用list(Page, Wrapper)方法时 Page 对象里的 records 只有当前页,但 total 字段永远是 0。

原因:MyBatis Plus 3.4.0 之后分页插件需要显式配置 MybatisPlusInterceptor,不配置就不会进行 SQL 拦截改写,分页实际上没有执行。Page 对象的 total 不被赋值是因为没有配置拦截器。

解决:在配置类里加 MybatisPlusConfig(前面代码已给出)。还有一类隐蔽问题是分页插件的顺序,如果项目里加了其他拦截器,MybatisPlusInterceptor 必须是最后一个,否则分页 SQL 会被错误改写。

5.5 IDEA 2026 配置 Spring Boot 启动参数的坑

现象:在 IDEA 2026 中右键运行 Spring Boot 主类,控制台没有任何输出,或者报错说端口被占用。查看进程发现上次运行的后端进程没有被终止,还在占用 8080 端口。

原因:IDEA 2026 改变了运行配置的管理方式,旧项目的 Run Configuration 可能在迁移后丢失了环境变量和 JVM 参数。端口占用则是开发中反复重启产生的僵尸进程,尤其是前后端联调时 Ctrl+C 只杀掉了前端进程。

解决:在 IDEA 的 Run/Debug Configurations 里手动添加 Spring Boot 运行配置,指定 Main Class 为启动类。如果端口被占用,在终端里执行lsof -i:8080(macOS/Linux)或netstat -ano | findstr 8080(Windows)找到占用进程后杀掉。还有一种稳妥的方式是在 application.yml 里设置server.port: 8081换个端口,绕开被占用的 8080。

5.6 前后端联调时跨域配置

现象:前端页面打开后,所有请求都在浏览器控制台报 CORS error,提示Access-Control-Allow-Origin不存在。

原因:前后端分离开发时,前端在 5173 端口、后端在 8080 端口,浏览器同源策略拦截了跨域请求。有些同学在前端配了 Vite 代理但还是报错,因为 Axios 请求里写死了http://localhost:8080全路径,代理没生效。

解决:Axios 的 baseURL 写成相对路径/api,让 Vite 代理接管。如果需要后端配合加跨域配置,在 Spring Boot 里写一个 CorsFilter 注册 Bean:

@Bean public CorsFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); // 开发环境允许所有来源;上线后要改成具体的域名 config.addAllowedOriginPattern("*"); config.addAllowedMethod("*"); config.addAllowedHeader("*"); // 允许前端请求携带 Authorization 头 config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); }

setAllowCredentials(true)和addAllowedOriginPattern("*")搭配使用时,注意不能使用addAllowedOrigin("*"),因为 allowCredentials 为 true 时浏览器要求 origin 不能被设为星号。这是 Spring 跨域配置里最常见的 403 报错根源。

6. 论文与 PPT 答辩:把实现过程转成得分点

论文写作的核心逻辑是"先讲问题,再讲方案,最后讲验证",而很多人的论文恰恰反过来了——一上来就贴类图、贴代码,评委看完不知道你要解决什么问题。我建议论文结构按照七章走:绪论(背景与意义)、相关技术介绍、需求分析、系统设计、系统实现、系统测试、总结与展望。

需求分析章节可以用用例图加文字描述的方式,把用户端和管理员端的功能边界画清楚。系统设计章节重点画两张图:系统架构图和数据库 E-R 图。架构图用分层结构——表现层(Vue3)、业务层(Spring Boot 服务)、数据层(MySQL + MyBatis Plus),这张图答辩时直接放 PPT 里讲三分钟没有问题。数据库 E-R 图要标清楚六个实体之间的关联关系,尤其是订单和订单明细的 1:N 关系。

系统实现章节不是代码粘贴板。常见错误是整段粘贴 Controller 和 Service 代码,然后写一句"核心代码如下"。正确做法是对每个模块先用文字描述业务流程,再贴关键代码片段。比如订单模块,先讲"用户提交订单后,系统校验库存,计算金额,生成订单及明细记录,清空购物车",再贴submitOrder方法的核心代码,最后说清楚事务如何保证一致性。这样写出来的论文,复现的时候哪怕代码有删减也不会影响理解。

PPT 答辩页数控制在 12 到 15 页:封面、目录、研究背景、技术选型、需求分析、系统架构、功能展示(2 到 3 页截图)、数据库设计、核心模块讲解、系统测试、总结与展望。核心模块讲解那页放订单状态机和事务处理的逻辑图,评委大部分提问都会集中在这一页。

答辩现场被问频率最高的问题我已经在前面各章节里埋伏了答案:为什么用 Spring Boot 2.7 而不是 3.x、MyBatis Plus 分页插件怎么配置的、订单超卖怎么解决的、金额计算为什么不直接用 double、JWT 密钥为什么不能太短。这些问题你如果能不假思索地回答出来,答辩就稳了。最怕的是那种"我照着 B 站视频敲的,原理不太清楚"的状态,评委一听就知道系统不是你写的。

最后说一个我自己的教训:拿到这类项目,先花半小时把订单状态流转画在纸上,再去看代码和论文,比直接启动应用看页面有效十倍。吃透状态机的五个迁移条件,整个系统的代码脉络就清晰了一半。希望这篇笔记能帮你把外卖点餐系统从"跑得起来"变成"讲得清楚",祝答辩顺利。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/6 17:36:40

Allegro板框设计原理与制造约束全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/6 17:32:32

双向可控硅调光电路详解:从阻容移相到电机驱动

我自己第一次动手做双向可控硅调光电路&#xff0c;是被家里那台风扇逼的。三档调速要么太猛要么太弱&#xff0c;半夜想让它无声慢转&#xff0c;翻遍手头的模块发现全是给直流电机准备的&#xff0c;接上去只会冒烟。后来老老实实回到双向可控硅调光电路的老路上&#xff0c;…

作者头像 李华
网站建设 2026/10/6 17:32:26

Agent-Reach:为大模型Agent构建工具触达与调用质量治理框架

1. Agent-Reach 到底在解决什么问题“Agent-Reach”是我在业余时间折腾的一个小框架&#xff0c;主意来自过去一年做大模型 Agent 业务时的真实痛点&#xff1a;大部分线上事故根本不是模型能力不够&#xff0c;而是智能体在需要调用某个工具时&#xff0c;要么不知道有这个工具…

作者头像 李华
网站建设 2026/10/6 17:31:52

OpenShell:统一Bash、Zsh与PowerShell的终端工作流整合工具

1. OpenShell 是什么&#xff1a;一个把终端工作流重新收拢的小工具 OpenShell 这个名字&#xff0c;听起来平平无奇&#xff0c;但它是我折腾了几年终端之后&#xff0c;真正愿意拿出来分享的第一个项目。简单说&#xff0c;这是一个开源的终端工作流整合工具&#xff0c;负责…

作者头像 李华
网站建设 2026/10/6 17:30:29

商城产品详情页HTML开发实战:从静态骨架到高性能交互

简介&#xff1a;这是一套面向前端初学者与电商页面练习者的商城产品详情页静态模板&#xff0c;围绕HTML5、CSS3与JavaScript三大核心技术展开&#xff0c;可用于课程作业、个人练手或二次开发。压缩包共103个文件&#xff0c;以55张jpg、36张png和8张gif图片资源为主&#xf…

作者头像 李华
网站建设 2026/10/6 17:30:04

搜索旋转排序数组:二分查找边界详解与三语言实现

搜索旋转排序数组这道题&#xff0c;几乎每个刷力扣的人都绕不过去。我第一次做的时候&#xff0c;第一反应是&#xff1a;都旋转过了&#xff0c;还怎么二分&#xff1f;后来才发现&#xff0c;旋转恰恰是这道题的题眼——它把数组切成了两段&#xff0c;每段内部依然有序。如…

作者头像 李华