尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

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

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 groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.2/version /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.7/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/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); // 单页最大数量限制防止有人恶意传入 pageSize100000 打垮数据库 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 ServiceImplOrderMapper, Orders implements OrderService { // 注意事务必须从 Controller 层之外的方法进入同类内部调用 this.submitOrder() 会导致事务失效 Transactional(rollbackFor Exception.class) Override public OrderSubmitVO submitOrder(OrderSubmitDTO dto, Long userId) { // 1. 校验购物车 ListShoppingCart cartList cartService.list( new LambdaQueryWrapperShoppingCart().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 LambdaQueryWrapperShoppingCart().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 LambdaUpdateWrapperDish() .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 加上serverTimezoneAsia/Shanghai和useSSLfalse同时配置连接池空闲超时小于 MySQL 的 wait_timeoutspring: datasource: url: jdbc:mysql://localhost:3306/ordering?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: 123456 hikari: max-lifetime: 1800000 idle-timeout: 600000max-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 参数。端口占用则是开发中反复重启产生的僵尸进程尤其是前后端联调时 CtrlC 只杀掉了前端进程。解决在 IDEA 的 Run/Debug Configurations 里手动添加 Spring Boot 运行配置指定 Main Class 为启动类。如果端口被占用在终端里执行lsof -i:8080macOS/Linux或netstat -ano | findstr 8080Windows找到占用进程后杀掉。还有一种稳妥的方式是在 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 注册 BeanBean 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 站视频敲的原理不太清楚的状态评委一听就知道系统不是你写的。最后说一个我自己的教训拿到这类项目先花半小时把订单状态流转画在纸上再去看代码和论文比直接启动应用看页面有效十倍。吃透状态机的五个迁移条件整个系统的代码脉络就清晰了一半。希望这篇笔记能帮你把外卖点餐系统从跑得起来变成讲得清楚祝答辩顺利。本文还有配套的精品资源点击获取
返回列表