SpringBoot+Vue外卖订餐系统毕业设计:从架构到答辩全流程解析

发布时间:2026/10/12 16:17:18
SpringBoot+Vue外卖订餐系统毕业设计:从架构到答辩全流程解析
每到毕业季我都能看到一堆人在选题上卡死。管理系统太水、纯算法太难、App开发没安卓环境最后兜兜转转还是回到了Web开发。说实话SpringBootVue这类前后端分离的外卖订餐系统几乎算得上是计算机毕业设计里的万金油选题。我前前后后带过、评过不少届毕设SpringBootVue组合的订餐平台年年都有但真正能把它做扎实的其实没几个。今天这篇就把外卖O2O系统从选题、架构、数据库、后端接口到前端联调的全过程拆开聊一聊顺便把我踩过的坑和答辩经验一并交代清楚。无论是准备做六六嗨吃校内外卖还是食速达这类题目核心思路都是通用的照着这条链路走你至少能少走两周弯路。1. 为什么外卖订餐是毕设里的常青树选题价值与痛点拆解1.1 这个选题到底好在哪先别急着写代码把选题逻辑想明白你后面写论文的时候会发现轻松很多。外卖订餐系统在毕设里一直是常青树原因很简单它几乎覆盖了一个完整Web项目该有的全部知识点但每一个知识点单独拿出来难度又没有超过本科毕设的合理范围。你算算看系统要有用户注册登录权限管理、商家和菜品管理后台CRUD、购物车状态暂存、订单生成事务、库存、金额计算、订单流转状态机、甚至还能延伸到支付模拟、配送调度、数据统计。这一套下来前后端分离、数据库设计、接口规范、异常处理、部署上线全都有了。论文里研究内容和技术难点这两章素材天然就是现成的不用硬编。而且外卖是日常场景答辩的时候评委不需要背景知识铺垫演示一分钟就能看懂系统是干什么的。这一点其实很重要我见过不少同学把游戏商城、二手交易平台做得花里胡哨结果评委第一反应是这个东西怎么保证交易安全、你凭什么解决信任问题直接给自己挖坑。外卖订餐没有这种解释成本用户下单、商家接单、骑手配送三方角色一摆业务逻辑天然清晰。1.2 六六嗨吃校内外卖食速达本质上是同一套东西这几年毕设题目越来越喜欢加壳什么六六嗨吃、校内外卖智慧订餐平台、食速达本地O2O系统名字看着五花八门但你把名字扒掉核心业务模块都是一样的用户端点餐 商家端管理 平台管理端。我建议拿到这种题目时先做一个抽象把标题里的品牌名放到系统名称字段里把校内外卖本地O2O理解为业务场景核心流程永远是选菜品 - 加购物车 - 提交订单 - 商家接单 - 完成配送 - 评价。你甚至可以一套代码改个Logo和背景色就能在三个题目之间切换。这不是偷懒而是软件工程里的业务建模论文里写出来还是加分项。真正需要动脑子的是搞清楚你的系统是单商家模式还是多商家平台模式。单商家模式就是一个食堂/一个餐厅的订餐网页表结构简单很多多商家平台模式则意味着你要在订单里维护商家维度、购物车里区分不同商家的商品、订单分配要按商家拆分。毕设选题里我看到大多数写的是平台型也就是美团/饿了么这种多商家O2O那从一开始你就得把商家这个维度设计进去不然后面改起来是伤筋动骨的。1.3 从选题到答辩的时间规划如果你现在才开始动手别再纠结先学SpringBoot还是先学Vue这种问题了。直接以项目驱动边做边学。我一般建议一个月左右的工作量第1周做需求分析和数据库设计第2周完成后端主体接口第3周搭Vue前端并联调第4周留白做论文、测试和答辩PPT。算上中间摸鱼的时间一个半月是舒服的节奏。最怕的是上来就敲代码做到一半发现订单表设计缺了状态字段、购物车没有用户维度、金额精度用了double导致对不上账然后推倒重来。所以下面几章我按先后顺序把核心环节捋一遍耐心看完比你自己瞎折腾十天有效。2. 技术选型不是堆砌框架SpringBootVue前后端分离的取舍逻辑2.1 后端为什么是SpringBoot而不是SSH、SSM或者Python我知道很多学校课程还在教SSMSpring SpringMVC MyBatis但毕设真不建议再用XML配置折腾自己。SpringBoot的本质是把Spring家族繁琐的配置自动化它内嵌了Tomcat一个mvn spring-boot:run就能起服务。对毕设来说它最大的价值不是技术上的新而是让你把精力放在业务代码而不是配置文件里。我见过有人项目里摆着七八个XML配置文件bean配置、事务配置、视图解析器配置铺了一桌代码量没多少配置文件倒是几百行。用SpringBoot之后application.yml一页纸搞定数据源、端口、上传大小限制这些常规配置。而且SpringBoot的starter机制让集成三方库变得极其无脑spring-boot-starter-web、mybatis-plus-boot-starter、shiro-spring-boot-starter引进去就能用。到时候你的论文里还能名正言顺地写上一句采用当前主流微服务基础框架SpringBoot降低了开发复杂度评委看到的是你关注工程的演进这属于课题意义层面的加分。2.2 前端为什么选Vue以及环境准备里最容易被忽略的细节Vue能成为毕设前端的事实标准核心是组件化 响应式数据绑定 渐进式引入。你不一定需要精通JavaScript会基本的ES6语法就能上手组件化让你把点餐页、购物车、订单列表拆成独立组件每个页面管好自己的事这一点在答辩演示项目结构清晰时特别好讲。环境准备这块我建议直接上Vue CLI或者Vite创建项目 Element Plus做UI组件库 Pinia或Vuex管理登录态和购物车。Node版本不要装太老用16或18以上的LTS版本不然新项目脚手架跑不起来。前端代码我习惯用Visual Studio Code配Vetur/Volar插件标签着色和自动补全能省很多事。有个小坑提前说如果你用Element Plus它是按需自动按需引入的但图标库要单独安装并全局注册很多人做着做着发现菜单展开的箭头图标不显示就是这个原因。另外一个就是axios的引入Vue CLI项目里直接npm install axios别用vue-axios那个再包一层的东西多一层封装多一层麻烦。2.3 O2O的业务含义和C/S模式在新项目里怎么体现标题里的O2OOnline To Offline看起来很高端翻译过来就是线上支付/下单、线下体验/配送。放到你项目里体现在两个地方一是数据链路要打通用户在线选品 - 订单信息流转到商家 - 线下备餐配送 - 用户确认收货二是权限体系要按端拆分用户端、商家端、管理端看到的是同一套数据的不同视角。很多毕设把前后端分离做得非常彻底但分离不等于完全割裂。我用一张表描述三个端的请求边界这个表可以直接写进论文端访问路径核心功能权限要求用户端/api/user/**注册登录、浏览商家和菜品、购物车、下单、历史订单登录用户商家端/api/seller/**菜品管理、订单接单/完成、营业设置、销量统计商家角色管理端/api/admin/**用户管理、商家审核、分类管理、平台数据概览管理员角色这样在代码里就是Controller层的目录划分加上对应路径的前缀鉴权用一个拦截器统一放行白名单、拦截其余请求。比Spring Security那套全配置的方式好解释也好调试。2.4 关于Redis、MQ这些高级组件的克制使用每年都有学生问我要不要上Redis缓存要不要引入RabbitMQ做订单超时我的建议非常明确如果这是第一次完整做项目克制住别引。原因很简单毕设的核心是展示你解决问题的能力而不是用了多少框架。你拿Redis做缓存能讲清楚缓存穿透、缓存击穿、缓存一致性吗讲不清楚答辩里就是给自己立靶子。订单超时自动取消用Spring自带的Scheduled就能实现何必再引入一套消息中间件增加部署复杂度。等基础功能全部跑通还有时间有余力再加Redis做验证码存储、ECharts做销售数据统计、支付宝沙箱做支付模拟这些属于锦上添花的增量项。主次不能颠倒。3. 数据库设计与订单状态机外卖系统最难的不是CRUD是状态流转3.1 核心表结构设计一张图画清楚业务边界数据库设计是外卖系统的地基。我见过最惨的翻车现场是有人把订单和订单明细合并成一张表一个订单有五个菜品就插五条完全相同的订单记录最后统计订单数的时候全是错的。正确的做法是订单主表 订单明细表一对多关系分开存储。我把最核心的几张表列一下每张表的字段设计都有讲究用户表userid、用户名、密码BCrypt加密存储、昵称、手机号、角色1用户/2商家/3管理员、创建时间。性别头像这些可以做进去但别过度设计字段够用就行。商家表businessid、商家名、Logo、地址、联系电话、起送价、配送费、营业状态1营业/0打烊、评分。注意一个用户如果是商家角色可以用user_id外键关联也可以独立注册表前者更统一推荐前者。菜品分类表和菜品表category / product分类表有商家维度business_id不能少菜品表包含名称、图片、原价、现价、库存、月售量、状态上架/下架。购物车表cartid、用户id、商家id、菜品id、菜品数量。为什么要冗余商家id因为购物车里可能出现多个商家的商品下单时必须按商家分组拆单提前存好省去联表查询。订单表ordersid、订单号、用户id、商家id、订单金额、实付金额、配送地址、联系人、联系方式、订单状态用数字或字符串枚举、下单时间、支付时间、完成时间、备注。订单金额和商品单价快照一定要冗余在订单里不然商家以后改了菜价你历史订单上的金额就跟着变了这在逻辑上是灾难。订单明细表order_detailid、订单id、菜品id、菜品名称快照、菜品图片、单价快照、数量、小计。快照字段防止菜品信息被删改这是电商订单设计的共识。配送地址表address用户常用地址包含联系人、电话、具体地址、默认标识。也可以把地址直接冗余到订单里看你的业务边界。这几张表建好整个系统的数据流就闭环了。表数量别贪多十张以内把核心业务讲清楚比堆二十张没人用的表要好得多。3.2 订单状态机的设计是评委最喜欢追问的点订单状态是外卖系统最核心的业务逻辑。我给你一套完整的流转定义直接拿去用就行待支付0- 用户支付模拟成功后 -已支付/待接单1- 商家接单 -制作中2- 商家标记出餐 -配送中3- 用户确认或定时自动确认 -已完成4待支付0- 超时未支付 -已取消5任意环节商家或用户取消需约定规则 -已取消5设计状态机时要注意两点一是状态变更必须单向可控比如已完成状态的订单绝不能回到待支付二是状态字段在代码里要用常量或枚举类集中定义不要散落在业务代码里写死数字。我建议在Java里定义一个OrderStatus常量类或者在数据库中直接存可读性更好的字符串如PENDING_PAYMENT、PAID这样排查问题的时候一眼就能看懂。3.3 事务、外键和索引基础但决定成败外卖系统里有两处必须用事务一是下单时生成订单 扣减库存 清空购物车这三个操作要在一个事务里任何一个失败都要整体回滚二是商家接单时更新订单状态 更新商家销量要保证一致性。SpringBoot里用Transactional注解就能搞定但注意事务失效的几个场景同类内部方法相互调用、方法不是public、异常被try-catch吞掉这三点我后面踩坑章节里还会细说。索引方面不需要太多花活但有两个索引建议建上订单表的user_id用户查看我的订单高频和订单表的business_id商家端查看订单列表高频。这样数据量上来之后联查不会明显变慢。外键我建议在逻辑层面维护物理外键可以不加这是现在企业开发的主流做法体现在论文里也能展示你关注生产环境的实践习惯。4. 后端核心接口实现实录从登录鉴权到下单减库存的完整链路4.1 JWT鉴权以及为什么我不用Spring Security外卖系统是一个多角色系统用户、商家、管理员三种身份对应三种权限。这里我不推荐直接上Spring SecuritySpring Security OAuth2那一套配置繁琐不说大部分毕设用不到那么细的授权模型。我的方案是JWT 拦截器 自定义注解简单可靠、解释起来也清楚。流程是登录接口校验用户名密码通过后使用jjwt工具生成一个带过期时间的tokentoken里存放用户id、角色等载荷信息前端拿到token后存在localStorage里每次请求在axios拦截器里自动加到Authorization头后端写一个HandlerInterceptor在preHandle里解析token并判断角色把用户信息放进ThreadLocal或RequestScope供后续业务代码使用。核心代码长这样你们可以直接参照public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token null || token.isEmpty()) { throw new BusinessException(401, 未登录); } // 解析token并获取用户id和角色 Claims claims JwtUtil.parseToken(token.replace(Bearer , )); request.setAttribute(userId, claims.get(userId)); request.setAttribute(role, claims.get(role)); return true; } }拦截器只负责放行白名单比如登录注册、商家图片访问其余接口统一校验。你把这套逻辑在答辩时用横切关注点讲出来评委一听就知道你理解了AOP思想。4.2 菜单查询与购物车缓存没必要但排序和分组要做用户端首页要把商家列表和每个商家的菜品一次性返回。看到很多学生一上来就搞什么左右联查然后前端自己拼实际上更好的做法是后端提供一个聚合接口GET /api/user/business/list返回商家基础信息和商家下的菜品分组列表前端拿到数据直接渲染减少一次HTTP往返。这里用一个VO类承载商家信息和List结构别把Entity直接裸奔返回既不好扩展也不安全。购物车我选择用数据库存储而不是Redis理由很现实毕设环境没有独立的Redis服务数据库对买家人人可查还永远不会因为断电丢数据。购物车接口就三个POST /api/user/cart加购、PUT /api/user/cart修改数量、DELETE /api/user/cart删除外加一个GET /api/user/cart查我的购物车。加购时要注意的细节是如果同一用户同一菜品已经存在于购物车则做数量累加而不是新增一条记录不然列表会越来越乱。4.3 下单接口事务、金额计算和减库存的高并发问题下单接口是整个后端最核心的入口。我给一个标准流程缺一不可从请求里拿到用户id、商家id、地址信息和订单备注查询购物车中属于该商家的全部菜品逐条计算小计累加出订单总额校验起送价是否达到、校验菜品库存是否充足扣减库存这一步用乐观锁后面详细说插入订单主表生成订单号、批量插入订单明细删除该商家维度下的购物车记录整个过程抛异常则整体回滚。这个接口的伪代码如下Transactional(rollbackFor Exception.class) public Long createOrder(CreateOrderReq req) { // 1. 获取购物车列表按商家分组 ListCart cartList cartMapper.selectByUserAndBusiness(req.getUserId(), req.getBusinessId()); if (cartList.isEmpty()) { throw new BusinessException(购物车为空); } // 2. 计算金额 BigDecimal totalAmount BigDecimal.ZERO; for (Cart cart : cartList) { Product product productMapper.selectById(cart.getProductId()); totalAmount totalAmount.add(product.getPrice().multiply(new BigDecimal(cart.getQuantity()))); } // 3. 检查起送价 Business business businessMapper.selectById(req.getBusinessId()); if (totalAmount.compareTo(business.getMinPrice()) 0) { throw new BusinessException(未达到起送价); } // 4. 生成订单主记录 Order order new Order(); order.setOrderNo(generateOrderNo()); order.setUserId(req.getUserId()); order.setBusinessId(req.getBusinessId()); order.setAmount(totalAmount); order.setStatus(OrderStatus.PENDING_PAYMENT); orderMapper.insert(order); // 5. 插入明细并扣库存 for (Cart cart : cartList) { int rows productMapper.deductStock(cart.getProductId(), cart.getQuantity()); if (rows 0) { throw new BusinessException(菜品库存不足: cart.getProductName()); } OrderDetail detail new OrderDetail(); // ... 组装明细并插入 } // 6. 清空购物车 cartMapper.deleteByUserAndBusiness(req.getUserId(), req.getBusinessId()); return order.getId(); }注意第5步的deductStock不能是先查库存再减去库存而要用一条更新SQLUPDATE product SET stock stock - #{num} WHERE id #{id} AND stock #{num}受影响行数为0就表示库存不足。这就是乐观锁思想等量级下能防超卖。金额用BigDecimal绝不用double。4.4 订单超时自动取消Scheduled的简单用法外卖系统里用户下单后很可能一直不付款这时候需要定时任务把超过15分钟的待支付订单自动取消并恢复库存。SpringBoot自带的Scheduled足够解决Component public class OrderTimeoutTask { Scheduled(fixedDelay 60000) public void cancelTimeoutOrders() { // 查询创建时间超过15分钟且状态为待支付的订单列表 ListOrder orders orderMapper.selectTimeoutOrders(15); for (Order order : orders) { // 更新订单状态为已取消 orderMapper.updateStatus(order.getId(), OrderStatus.CANCELLED); // 恢复库存遍历订单明细把数量加回去 restoreStock(order.getId()); } } }这里有个细节fixedDelay表示上一个任务跑完后再隔60秒触发下一次适合这种低频扫表任务。在单机环境下这么写没问题但你要是用Scheduled(cron ...)写成精确到秒的表达式反而容易在任务执行过久时出现重叠执行。超时时间别写死在代码里放进application.yml配置项答辩时可以展示你考虑到了可配置化设计。5. Vue前端搭建要点页面组织、axios封装与购物车状态同步5.1 项目初始化和路由规划前端我用Vue CLI初始化项目命令很简单vue create food-front选上Router、Vuex/Pinia、ESLint这几个插件一路回车就行。然后把项目里的views目录按业务模块划分Home商家列表、Shop点餐详情页、Cart购物车、OrderList我的订单、Login登录、Register注册、SellerDashboard商家后台、AdminDashboard管理后台。路由这块最核心的是路由守卫判断localStorage里有没有token没有就跳登录页以及根据角色字段判断能否访问商家后台/管理后台。代码写起来也就十几行但这是前端权限控制的体现很多毕设连这一步都省了碰到评委问不登录直接输URL能访问商家后台吗就只能尬住。router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (to.meta.requiresAuth !token) { next(/login); return; } if (to.meta.role JSON.parse(localStorage.getItem(userInfo)).role ! to.meta.role) { next(/); return; } next(); });5.2 axios封装与拦截器统一处理token和错误码不要在每个页面里各自axios.get(...)那样改个baseURL或者统一处理401都要全局替换。正确做法是把axios实例和拦截器抽到一个request.js文件里const service axios.create({ baseURL: /api, // 开发环境走Vite代理 timeout: 10000 }); service.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers.Authorization Bearer token; } return config; }); service.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) { // token过期清除登录态并跳转登录页 localStorage.clear(); router.push(/login); } ElMessage.error(error.message); return Promise.reject(error); } );开发环境的baseURL我推荐写/api然后配代理而不是写死http://localhost:8080。这样后端如果换端口前端一行不用改也能顺带把开发阶段的跨域问题解决掉第6章细说。5.3 购物车状态管理与组件通信购物车在页面里是全局存在的点餐页加购后底部的购物车栏要实时变切换页面后购物车数据不能丢。所以购物车状态不能放在单个组件里必须放到全局状态管理器。我用PiniaVue3或者VuexVue2都行思路一致state里存一个cartItems数组actions里封装addToCart、updateCart、removeCart、clearCart组件里只调用action不直接改状态。一个我在代码评审里反复看到的错误是把购物车数据也从后端拉到前端缓存然后本地增删改后不再同步后端。这样刷新页面就出现诡异状态。我的建议是操作走后端接口后端返回全量购物车数据后覆盖本地state。简单粗暴但逻辑永远正确不用处理各种本地与服务器不一致的边界。5.4 页面级功能拆解点餐页是最能体现前端水平的页面。我推荐左侧商家菜品分类栏 右侧菜品列表 底部悬浮购物车栏的布局。菜品列表里的加入购物车按钮是个计数器组件点击加号、减号会触发后端更新接口同时更新底部栏的总价和数量。这里有个性能细节连点加减号时不要每次都立刻发请求可以用防抖debounce延后200ms等用户停止操作后再把最终数量提交体验更流畅也能减少后端压力。订单列表页则要学会用ElTabs切换不同状态全部/待支付/待接单/配送中/已完成每个Tab对应一个状态码传给后端查询。这里要注意后端接口设计成GET /api/user/order?statusxxxstatus不传就返回全部。前端的支付按钮我用的是模拟支付弹窗——直接在前端弹出确认框点击确认后调用后端POST /api/user/order/{id}/pay把状态从待支付改成已支付。如果你想提升亮点可以接上支付宝沙箱SDK但成本较高性价比一般。6. 踩坑实录跨域、并发超卖、订单超时取消这些坎是怎么过的6.1 跨域开发环境的终极解法是代理不是CORS配置前后端分离第一道坎就是跨域。浏览器安全策略默认禁止前端从一个端口访问另一个端口的接口比如前端在http://localhost:5173后端在http://localhost:8080直接axios.get(http://localhost:8080/api/...)十有八九被拦。网上最流行的解决方式是后端加CORS配置Configuration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedMethod(*); config.addAllowedHeader(*); return new CorsFilter(new UrlBasedCorsConfigurationSource()); } }这样能解决但我更推荐在开发环境用Vite/Webpack的proxy代理。在vite.config.js里加server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }前端请求还是写/api开头Vite服务器把请求转发到后端浏览器看到的是同源请求自然没有了跨域。这个方案的好处是你只管前端代码后端不用写任何CORS配置上线部署时再用Nginx反代也可以用同样的代理思路。生产环境我建议前后端打成一个构建产物部署在Nginx下用location /api/ { proxy_pass http://127.0.0.1:8080; }解决。这属于部署层面的工程能力能在论文系统部署一章露一手。6.2 并发下单超卖一条SQL解决别在代码里先查再扣我第一次做订餐系统时在减库存这里踩过大坑。当时的写法是先select stock from product where id 1Java代码里判断if (stock 0) { update product set stock stock - 1 }。单用户测试没问题但用JMeter模拟20个并发请求一起下单时库存直接变成负数了。原因很简单多个请求同时读到旧库存都判定库存大于0然后都执行了自减。修复方案就是第4章提到的那条原子更新SQLUPDATE product SET stock stock - #{num} WHERE id #{id} AND stock #{num}这条SQL在数据库层面上保证了判断和更新是一个原子操作无论多少并发来抢最终扣减后的库存不会低于0。你要在答辩里讲这个点就属于解决了并发场景下的数据一致性问题是实打实的加分项。如果库存扣减后需要更新月售量我建议也在这个事务里完成保持一致。6.3 订单超时取消任务的双重坑本地时间与服务端时间不一致定时任务的第一个坑我是吃过亏的。之前写SQL判断超时订单用的是WHERE create_time NOW() - INTERVAL 15 MINUTE这在MySQL里是数据库服务器的当前时间。但如果你的电脑改了系统时间或者生产服务器时区没配置create_time存进去的时间可能和NOW()对不上导致该取消的订单没取消。解决方案有两个方向一是下单时在Java代码用LocalDateTime.now()生成时间并存入数据库同时MySQL连接串里加serverTimezoneAsia/Shanghai参数二是把超时判断也放在Java代码里查出来订单后用createTime.plusMinutes(15).isBefore(LocalDateTime.now())过滤。第二种更稳妥因为时间计算完全在应用层不受数据库时区影响。第二个坑是定时任务在每次应用重启后会立刻扫描一遍全表如果上一轮任务没执行完就重启可能重复取消已取消的订单幂等性问题。我的做法是在取消前先update ... set status 5 where id #{id} and status 0受影响行数为0就跳过天然保证幂等。6.4 前后端联调时最容易互撕的数据类型坑联调阶段前后端经常因为数据格式问题你推我、我推你。我总结了三个最高频的一是Long类型精度丢失。数据库订单号如果用雪花ID或MyBatisPlus的ID_WORKER到前端JavaScript里可能变成1.234567891E19因为JS的Number精度只有2的53次方。解决办法是后端序列化时把Long转成字符串在字段上加JsonFormat(shape JsonFormat.Shape.STRING)或者在全局配置里把Long统一转String。二是时间格式不一致。Java的LocalDateTime默认序列化成数组格式[2026,1,1,12,0,0]前端根本没法直接显示。必须在application.yml里配spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai这样前端拿到的就是2026-01-01 12:00:00这种可读字符串。三是BigDecimal精度问题。前端传金额的时候如果用了parseFloat再转JSON可能带来浮点误差。前端不要对金额做计算展示就好后端接收用BigDecimal。如果你在Vue里用号把字符串转数字要小心1.1 2.2输出3.3000000000000003的尴尬。7. 答辩亮点设计与演示脚本让评委眼前一亮的细节7.1 演示前要做的三件事答辩翻车往往不是因为系统不行而是演示准备不到位。我建议提前做三件事第一准备一份干净的演示数据——商家信息、菜品图片、用户账号、商家账号提前录入好不要现场注册、现场上传图片网络不稳的时候直接卡在注册验证码上。第二把项目跑在本地而不是演示电脑的云端确保数据库服务已启动、端口没被占用关键页面提前打开缓存好。第三准备一份备用方案如果Vue开发服务器起不来直接展示打包后的静态页面对接后端接口如果后端也崩了提前录一段五分钟的演示视频兜底。演示路径也要设计好别从首页开始无脑乱点。我建议的万能顺序是用户登录 - 浏览商家 - 点餐加购 - 提交订单 - 模拟支付 - 切换商家账号登录 - 接单并制作 - 标记配送 - 切回用户端确认收货 - 展示管理端数据统计。这条路径把三个端、完整订单流转全走了一遍评委看下来心里就有这系统闭环了的印象。7.2 三个轻但高级的加分功能在你时间有余力的情况下我推荐三个性价比最高的扩展功能一是ECharts销售统计图。在商家后台加一个菜品销量Top10的柱状图和一个近七日的订单趋势折线图。后端只要提供一个聚合查询接口用GROUP BY按日期或菜品汇总前端接ECharts一画整个系统的数据可视化和辅助决策就出来了论文里还能多出一小节系统测试与结果分析。二是图片上传与预览。菜品图片用本地上传存储后端做一个/api/upload接口把文件写到服务器指定目录然后返回访问URL。注意要配置静态资源映射spring.web.resources.static-locations指向本地目录并且限制上传文件大小不然大图直接让接口超时。三是验证码。登录页加一个简单的算数验证码或者字母验证码用后端生成图片返回前端。如果你不想上Redis就把验证码结果存在Session里效果差不多。这个功能成本低但能在系统安全性答辩环节起到很好的作用。7.3 论文和项目怎么对齐以及评委常问的问题怎么答论文不是项目的说明书而是提出一个问题 - 设计一个系统 - 解决这个问题的论证过程。你可以把论文的章节设置为绪论背景意义、国内外现状、研究内容 - 相关技术介绍SpringBoot、Vue、MySQL、前后端分离思想 - 需求分析可行性分析、功能需求、角色用例 - 系统设计总体架构、功能模块设计、数据库设计、接口设计 - 系统实现按模块贴核心代码截图加运行截图 - 系统测试功能测试用例表、性能测试、测试结论 - 总结与展望。评委最常问的几个问题你提前准备好答案就行了为什么选SpringBoot——因为它自动化配置、内置容器、简化部署适合快速构建微服务是目前Java后端的主流。订单超时怎么实现的——Scheduled定时扫表配合状态更新时的条件判断保证幂等。并发情况下怎么防止库存超卖——乐观锁更新时带stock num条件判断受影响行数。怎么保证系统安全性——JWT鉴权、密码BCrypt加密、后端统一参数校验、拦截器处理白名单。你系统里最难的点是什么——订单状态机的设计和事务的边界控制并具体讲某个坑。每个问题你都能接得住而不是背PPT评委的印象分会明显不一样。归根结底这个选题不是要求你发明什么新东西而是考察你有没有能力把一个真实业务用主流技术完整落地。能做到这一点你已经赢过一大半人了。最后分享一个我自己的习惯项目做完后我会顺手写一份README把启动步骤、测试账号、模块结构、技术栈版本全记下来。这一方面方便自己答辩前快速恢复环境另一方面导师或下一个接手的人看了也省力。做毕设的过程就像跑一场小型的完整软件生命周期你认真跑完一遍后面找工作面试时聊起项目来那才是真的有东西可以讲。