基于SpringBoot+微信小程序的校园点餐系统设计与实现指南

发布时间:2026/9/7 19:45:54
基于SpringBoot+微信小程序的校园点餐系统设计与实现指南 1. 项目概述1.1 为什么校园点餐系统是毕设里的“黄金选题”每年到了毕业季计算机相关专业的同学都会在选题上纠结很久。我这些年看过不下三百个毕设项目总结出一个规律真正好做、好讲、好过审的题目往往不是那些听起来特别高深的而是那些“业务清晰、技术栈主流、场景感强”的项目。基于springboot小程序的校园点餐系统恰好就属于这一类。这个系统要解决的问题很直观大学校园食堂每到饭点就是排队长龙学生下课时间高度集中传统窗口打饭模式效率低、体验差。校园点餐系统通过小程序下单、后台出单、叫号取餐的方式把“排队等餐”变成“预约取餐”本质上是对校园餐饮场景中“高峰期流量汇聚”问题的一次数字化拆解。从毕设评分角度看这个题目有几个天然优势业务场景真实且清晰评审老师一看就懂不需要花大量时间解释背景技术栈覆盖前端小程序、后端接口、数据库设计、接口联调知识面广但不深奥功能扩展空间大不管你是想做简单版还是升级版都能控制好工作量演示效果好手机小程序操作直观比纯网页演示更有视觉冲击力。1.2 这个项目适合谁、能学到什么我知道看这篇文章的读者大概有几种一种是正在为毕设选题发愁的大四学生一种是已经选了类似题目但不知道从哪下手的“拖延症晚期”还有一种是想把这个项目吃透作为求职简历里的一块敲门砖。不管你属于哪种这篇博文都会给你一份“从零到答辩”的完整路线图。在开始写代码之前先把目标拆解清楚。一个合格的校园点餐系统至少要解决三个核心问题用户怎么登录、怎么识别身份学生/商家/管理员用户怎么浏览菜品、下单、支付、取餐商家怎么管理菜品、处理订单、查看统计。把这三个问题落实到代码层面就会拆成小程序端页面、后端接口、数据库表结构、权限认证机制等若干模块。整个项目做下来你对Spring Boot的后端开发流程、小程序原生开发的基础写法、数据库设计的规范化思路都会有一个质的提升。即使你之前只学过SSM框架或者根本没写过完整项目按照这篇博文的节奏一步步走也是能拿下来的。2. 技术方案选型与核心思路拆解2.1 为什么选SpringBoot而不是SSH或SSM很多同学在选后端框架时犹豫不决既然学过SSM为什么不直接用SSM我的建议是如果是现在做毕设优先选SpringBoot。SpringBoot本质上是Spring全家桶的“快速启动器”它通过自动配置把原来SSM时代繁琐的XML配置和注解配置大大简化。你写一个SpringBoot项目一个RestController就能对外提供接口spring-boot-starter-data-jpa或者MyBatis-Plus依赖一引入数据库操作也不需要手写一堆模板代码。更重要的是SpringBoot内置了Tomcat打包成Jar直接就能跑部署成本比传统SSM低很多。在毕设答辩时如果你报出“基于SpringBoot 微信小程序 MySQL”评审老师的第一反应通常是比较认可——你会用的不是过时的技术而是目前中小型企业实际在用的主流组合。这在就业导向的评价维度上是有加分的。SpringBoot的启动类只需要一个main方法配合application.yml做配置管理接口开发完直接用Postman测试。它对新手最大的友好之处在于“约定大于配置”项目结构有一套默认的规范三层架构Controller、Service、Mapper在工程里一目了然评审老师看代码时也好找重点。2.2 小程序端原生还是UniApp这是很多同学会纠结的第二个选择。小程序端开发有两种主流方式微信小程序原生开发使用WXML、WXSS、JS、JSON四个文件构成页面语法接近HTMLCSSJS微信开发者工具直接调试上手门槛不高UniApp跨端开发用Vue语法写一套代码同时编译到微信小程序、支付宝小程序、H5、App等多个平台。我的推荐是如果只做毕设选原生开发就够了。原因很简单原生开发的资料多遇到问题能直接搜到解决方案微信开发者工具就是官方工具预览调试都非常直接不需要额外引入一套编译链出错概率更小。等你以后想商业化运营或扩展到多平台再迁移到UniApp也不迟。但就毕设而言原生开发已经能覆盖全部需求。2.3 数据库设计思路与表结构规划系统的核心数据都落在MySQL里表结构设计是否合理直接决定了后续开发效率。我在设计校园点餐系统时规划了这几张核心表表名用途关键字段user用户表id, openid, nickname, avatar, role, phone, create_timecategory菜品分类表id, name, sort, statusdish菜品表id, category_id, name, image, price, description, sales, statuscart购物车表id, user_id, dish_id, quantity, add_timeorders订单主表id, order_no, user_id, total_amount, status, pay_time, remark, create_timeorder_detail订单明细表id, order_id, dish_id, dish_name, dish_image, price, quantitycomment评论表id, order_id, user_id, content, rating, create_time这个设计的核心思路是订单主表与订单明细表分离。一个订单对应多条菜品记录如果全部塞在同一张表里查询订单列表时会非常混乱。拆成两张表后查主表显示订单概览点进去再查明细表逻辑清晰也符合第三范式的基本要求。用户表里我特意设计了role字段用一个整数区分管理员、商家、普通用户。如果你做的是简化版可以不区分商家统一由管理后台维护菜品这样能节省不少工作量。2.4 系统架构与请求流转过程整个系统是标准的前后端分离架构请求流转过程是用户在微信小程序里点击“点餐”按钮小程序通过wx.request向后端发起HTTP请求POST或GETSpringBoot的Controller接收请求通过Service层调用Mapper层操作MySQL数据库处理完成后返回统一格式的JSON数据给小程序端小程序解析JSON更新页面数据。这里有一个关键点小程序与后端之间的接口通信需要保证数据格式的统一。我在项目中定义了一个统一的返回体类包含code状态码、msg提示信息、data实际数据三个字段所有接口都返回这个结构。小程序端封装好请求方法后统一先判断code是否为200再处理数据这样错误处理逻辑就能集中管理不会分散在每个页面里。3. 后端核心接口设计与功能实现3.1 项目工程结构说明先看整个后端项目的结构规划我给你一份可以直接照着建的目录树src/main/java/com/example/campusorder/ ├── CampusOrderApplication.java // 启动类 ├── controller/ // 控制层接收请求、返回结果 │ ├── UserController.java │ ├── CategoryController.java │ ├── DishController.java │ ├── CartController.java │ ├── OrderController.java │ └── CommentController.java ├── service/ // 业务层处理核心逻辑 │ ├── UserService.java │ ├── CategoryService.java │ ├── DishService.java │ ├── CartService.java │ ├── OrderService.java │ └── CommentService.java ├── mapper/ // 持久层操作数据库 │ ├── UserMapper.java │ ├── CategoryMapper.java │ ├── DishMapper.java │ ├── CartMapper.java │ ├── OrderMapper.java │ └── CommentMapper.java ├── entity/ // 实体类与数据库表字段对应 │ ├── User.java │ ├── Category.java │ ├── Dish.java │ ├── Cart.java │ ├── Order.java │ └── OrderDetail.java ├── common/ // 公共工具类 │ ├── Result.java // 统一返回体 │ ├── JwtUtil.java // JWT工具类 │ └── GlobalExceptionHandler.java // 全局异常处理 └── config/ // 配置类 └── WebConfig.java // 拦截器配置3.2 统一返回体与全局异常处理后端接口的质量很大程度体现在返回数据是否规范。我在common/Result.java里定义了这样的结构public class ResultT { private Integer code; private String msg; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMsg(操作成功); result.setData(data); return result; } public static T ResultT error(String msg) { ResultT result new Result(); result.setCode(500); result.setMsg(msg); return result; } }这里要说明一个经验异常处理千万别写try-catch满天飞。更好的做法是使用SpringBoot的RestControllerAdvice做全局异常处理业务代码里只抛出自定义异常统一在异常处理器里捕获并转成Result格式返回。我见过太多毕设代码里Controller裹着大段try-catch业务逻辑被异常处理切割得七零八落阅读体验极差。3.3 用户登录与JWT鉴权微信小程序的登录机制是前端通过wx.login()拿到临时登录凭证code后端再用这个code向微信接口换取用户身份信息。但这里有一个毕设避坑点调用微信接口需要AppID和AppSecret如果你用的是测试号或者个人开发者资质不够可能出现接口调用失败。所以我建议在毕设阶段采用“模拟登录真实登录”双模式前端如果拿到有效code后端调微信接口换取openid完成真实登录如果接口调用失败比如没有正式注册的小程序则用前端传入的模拟openid直接创建或查找用户。登录成功后的核心机制是JWTJSON Web Token。JWT的作用相当于一张“门禁卡”用户登录成功后后端签发一个带有用户信息的签名Token给小程序端小程序端后续每次请求都把这个Token放在请求头里后端通过拦截器统一验证。JwtUtil的核心代码大致是这样public class JwtUtil { private static final String SECRET your-secret-key; private static final long EXPIRE_TIME 7 * 24 * 60 * 60 * 1000; // 7天 public static String createToken(Integer userId, String role) { Date now new Date(); Date expireDate new Date(now.getTime() EXPIRE_TIME); return Jwts.builder() .claim(userId, userId) .claim(role, role) .setIssuedAt(now) .setExpiration(expireDate) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } public static Claims parseToken(String token) { return Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(token) .getBody(); } }有了JWT之后还需要一个拦截器来统一校验用户是否登录。我建了一个LoginInterceptor实现HandlerInterceptor接口在preHandle方法里从请求头取出Token校验通过后把userId存入请求上下文供Controller直接使用。这样可以避免每个接口都重复做登录判断。有个细节要注意小程序端发起请求时Header里的Key名不要用下划线有些服务器对带下划线的Header做了特殊处理可能导致取不到值。推荐全部使用中划线比如Authorization或者token取值时保持大小写一致即可。3.4 菜品浏览与购物车模块菜品列表接口是最简单的CRUD操作但有一个细节值得优化列表接口需要返回菜品对应的分类名、销量等信息如果直接查dish表分类名在category表里需要做表关联。在Mapper层使用MyBatis-Plus的Select注解时可以用SQL联表查询一次取回所有字段避免在Service层循环查数据库造成“N1问题”。购物车模块的逻辑相对固定加入购物车时先查当前用户是否已有同款菜品记录有则数量加一没有则新增一条记录查询购物车时需要联表查出菜品名称、价格、图片保证前端能直接渲染删除和清空操作按接口语义分类实现即可。这里有个隐藏的坑**购物车表里的菜品价格应该存冗余字段还是实时关联**我的建议是购物车阶段不用存价格直接关联dish表取实时价格等到生成订单、写入订单明细表时才把当时的菜品名称、价格、图片“快照”存进order_detail表。这样做的原因是商家以后可能会调整菜品价格如果订单明细始终关联菜品表历史订单的总金额就会跟着变化导致财务数据对不上。3.5 下单流程与订单状态机下单是整个系统里业务逻辑最复杂的部分。一个完整的下单流程是前端把购物车里的菜品列表和备注信息提交到后端后端校验用户登录状态、菜品是否存在、库存是否充足后端计算订单总金额以数据库实时价格为准不能信任前端传过来的金额写入订单主表和订单明细表更新菜品销量清空用户购物车返回订单号给前端提示“下单成功”。为什么说不能相信前端传的金额因为小程序跑在用户手机里代码可以被人为修改或抓包篡改。一个恶意用户把订单金额从100元改成1分钱如果后端直接按前端金额入库系统就存在严重安全隐患。正确做法是后端根据菜品ID从数据库重新查价计算总金额前端传的totalAmount一律忽略。订单状态我用整数表示方便后续扩展状态码含义说明0待支付下单成功但未支付1已支付用户完成支付等待商家接单2制作中商家已接单正在制作3待取餐菜品制作完成等待用户取餐4已完成用户确认收货5已取消超时未支付或用户主动取消6已退款支付后退款完成每次状态变更时都要判断前置状态是否合法比如“已支付”的订单不能直接跳到“已完成”必须经过“制作中”和“待取餐”。这种状态机设计不只是为了严谨更重要是让评审老师看到你有工程化思维。3.6 支付模块设计与风险规避支付是校园点餐系统里最敏感、也最容易翻车的模块。这里必须提醒各位一句微信支付正式接口需要企业资质才能开通个人主体的开发者基本无法申请。如果你做的只是毕设演示没必要也不应该强接真实支付功能。我的实践方案是模拟支付用户点击“支付”按钮后后端直接把订单状态从“待支付”改为“已支付”并记录支付时间和支付方式。演示时在后台造一条“模拟支付”的记录效果上完全够用。你可以在答辩时大方告诉评审老师“支付模块采用模拟方案若替换为微信支付V3接口只需在支付服务中替换实现类业务逻辑不变。”这样反而能体现你对系统扩展性的设计考虑。如果你确实想在项目里展示微信支付V3对接的代码结构可以在项目中预留一个PayService接口写一个MockPayServiceImpl作为默认实现再写一个WechatPayServiceImpl作为参考实现。这样既不会因为资质问题卡住项目进展又能展示你的技术视野。有个细节不得不提支付回调的验签逻辑和订单状态更新操作必须保证幂等也就是同一笔支付结果回调多次最终效果和只回调一次完全一致。很多同学第一次写支付回调时没做状态判断就直接把订单改为已支付结果回调两次出现异常数据。正确的做法是先查订单当前状态只有“待支付”才允许改为“已支付”。4. 小程序端核心功能实现4.1 小程序页面结构与路由规划小程序端的页面规划我建议控制在十个页面以内既能覆盖核心功能又不至于工作量失控pages/index/index首页显示轮播图、分类导航、热销菜品推荐pages/category/category菜品分类页左侧分类列表右侧菜品列表pages/dish/detail菜品详情页展示图片、价格、描述加入购物车入口pages/cart/cart购物车页面可修改数量、清空、去结算pages/order/confirm确认订单页选择地址/备注、查看金额明细、提交订单pages/order/list订单列表页按状态Tab切换pages/order/detail订单详情页展示订单状态流转、菜品明细pages/user/user个人中心页显示用户信息、订单入口、设置pages/comment/comment消费评价页pages/login/login登录授权页。小程序冷启动时最先加载app.json里配置的pages数组第一项作为首页。TabBar的选择我放在了“首页、分类、购物车、我的”四个页面对应项目的四个核心入口。4.2 请求封装与登录态管理小程序不推荐在每一个页面里直接写wx.request否则当后端域名切换、统一加鉴权Header、错误提示逻辑调整时你会改到怀疑人生。正确做法是封装一个request.js工具模块const BASE_URL http://localhost:8080/api; function request(url, method, data) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL url, method: method || GET, data: data || {}, header: { Content-Type: application/json, token: wx.getStorageSync(token) || }, success: (res) { if (res.data.code 200) { resolve(res.data.data); } else if (res.data.code 401) { wx.removeStorageSync(token); wx.navigateTo({ url: /pages/login/login }); reject(res.data); } else { wx.showToast({ title: res.data.msg, icon: none }); reject(res.data); } }, fail: (err) { wx.showToast({ title: 网络请求失败, icon: none }); reject(err); } }); }); } module.exports { request, BASE_URL };这里有两个关键经验。第一是登录态存入wx.setStorageSync后每次请求从存储中取出Token放进Header如果Token过期后端返回401统一跳转登录页而不是在每个页面里重复判断。第二是在开发阶段后端跑在本地时小程序必须在开发者工具里勾选“不校验合法域名”选项否则浏览器环境模拟时会拦截本地请求这是新手最容易踩的坑之一。4.3 首页数据渲染与会话保持首页是用户第一眼看到的东西体验好不好直接影响整个项目的演示效果。我的首页结构分三块顶部轮播图、分类快捷入口、热销菜品列表。轮播图的图片路径建议存图片URL而不是本地路径方便后期更换运营图片。在“我的”页面需要展示当前登录用户的信息。这里有一个小技巧用户每次打开小程序时先读取本地缓存的userInfo立即渲染界面同时在onShow生命周期后台静默调用/user/info接口更新用户信息和Token实现“先展示、后刷新”的效果。这个方案在弱网环境下体验远好于白屏等待。会话保持的核心逻辑是小程序端在app.js的onLaunch里调用wx.login拿到code后传后端获取Token并缓存。用户访问需要鉴权的页面时请求头里自动带上Token即可。5. 商家端与后台管理功能设计5.1 角色权限控制很多同学的毕设项目在权限控制上只做到了“前端按钮级隐藏”也就是登录成管理员后多显示几个按钮但这在答辩时容易被追问“如果用户绕过前端直接调后端接口怎么办”正确的做法是后端接口做角色校验。我在SpringBoot的拦截器里校验完Token后从Token中取出role字段然后判断当前请求的接口是否需要特定角色才能访问。例如管理员的菜品新增、修改、删除接口都要求role为管理员或商家。具体实现上可以自定义一个RequireRole注解标注在Controller方法上Target({ElementType.METHOD, ElementType.TYPE}) Retention(RetentionPolicy.RUNTIME) public interface RequireRole { String value(); // 例如 admin 或 merchant }然后在拦截器的preHandle里读取方法上的注解校验当前用户角色是否匹配。这套设计比你写一堆if判断要优雅得多也更容易在答辩环节展示你对“安全设计”的思考。5.2 商家处理订单的完整流程商家角色的核心操作是查看待接单的订单、接单、设置菜品制作完成。商家端一般用H5后台或者Web管理页面如果时间和精力有限直接在SpringBoot的同端口下做一套简单的Thymeleaf页面也可以接口仍然复用后端的RESTful API。订单列表接口需要支持按状态查询以商家视角返回所有用户提交的订单待接单的排最前面处理中的依次排列已完成的归档到后面。每次商家点击“接单”时后端更新订单状态为“制作中”同时可以给用户推送一条订阅消息这里需要小程序后台申请模板消息权限不是所有主体都有制作时先留好开关。这里有一个容易忽略的细节订单列表的分页查询。如果订单量大了不分页前端一次性渲染几百条数据会明显卡顿。我在Mapper层使用MyBatis-Plus的分页插件PaginationInnerInterceptor前端请求时传pageNum和pageSize参数后端返回分页数据结构。这个改动量不大但属于明显的加分项。5.3 数据统计功能给评审老师一个亮点如果你想在毕设里脱颖而出我强烈建议加上一个“数据统计看板”模块。统计维度不需要太复杂主要输出三个数据今日订单数、今日营收按支付时间统计排除已取消菜品销量排行榜TOP10根据order_detail表按菜品聚合统计近七日营收趋势按天分组统计订单总金额。这些统计SQL都不难核心是掌握MySQL的聚合函数和日期函数。比如近七日营收可以这样写SELECT DATE(pay_time) as day, SUM(total_amount) as revenue FROM orders WHERE status 1 AND pay_time DATE_SUB(CURDATE(), INTERVAL 6 DAY) GROUP BY DATE(pay_time) ORDER BY day;在做这个功能时我会用Hutool工具库来简化日期处理和集合操作省去手写一堆日期判断的麻烦。至于图表展示前端可以用ECharts后端只需要把统计结果封装成chartData结构返回即可。6. 数据库设计与接口文档规范6.1 建表语句与关键索引设计数据库表结构是整个项目的“地基”地基不稳后面的业务代码写得再漂亮也容易出问题。我把核心建表SQL写出来节选各位可以直接在Navicat或命令行执行CREATE TABLE user ( id int(11) NOT NULL AUTO_INCREMENT, openid varchar(64) DEFAULT NULL COMMENT 微信openid, nickname varchar(50) DEFAULT NULL COMMENT 昵称, avatar varchar(255) DEFAULT NULL COMMENT 头像URL, role tinyint(4) DEFAULT 0 COMMENT 0-普通用户 1-商家 2-管理员, phone varchar(20) DEFAULT NULL COMMENT 手机号, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_openid (openid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE dish ( id int(11) NOT NULL AUTO_INCREMENT, category_id int(11) NOT NULL COMMENT 分类ID, name varchar(100) NOT NULL, image varchar(255) DEFAULT NULL, price decimal(10,2) NOT NULL COMMENT 单价, description varchar(500) DEFAULT NULL, sales int(11) DEFAULT 0 COMMENT 销量, status tinyint(4) DEFAULT 1 COMMENT 1-上架 0-下架, PRIMARY KEY (id), KEY idx_category_id (category_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE orders ( id int(11) NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 订单号, user_id int(11) NOT NULL, total_amount decimal(10,2) NOT NULL, status tinyint(4) DEFAULT 0 COMMENT 订单状态, pay_time datetime DEFAULT NULL, remark varchar(255) DEFAULT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_id (user_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;设计数据库时有几个细节需要强调我用了utf8mb4而不是utf8因为utf8在MySQL里最多存3字节用户昵称里如果有生僻字或特殊表情emoji会直接报错或乱码价格字段一定用decimal而不是float或double浮点数在金额计算时会有精度丢失问题这个在项目里是硬伤级别的坑订单号用UNIQUE KEY保证唯一生成逻辑建议是“时间戳 用户ID 随机数”绝对不要只用时间戳并发情况下一定会重复。6.2 接口文档的编写思路接口文档的意义不只是给答辩老师看更是为了你自己在联调时能快速定位问题。我不推荐用Word写接口文档修改起来太痛苦。有两个方案供选择使用Postman的Collection功能把所有接口按模块建好分享成一个链接使用SwaggerSpring Boot直接集成springfox或springdoc代码里用注解标注接口描述启动项目后访问/swagger-ui.html就能看到所有接口的在线文档。如果你的项目时间紧张二选一即可。我自己更推荐Swagger因为注解写在代码里不会因为文档更新不及时而对不上这在答辩时也是一个不错的加分项。7. 部署上线与演示环境准备7.1 本地开发运行的完整流程把项目跑起来的流程其实很固定写下来省得大家去网上东拼西凑。这里以Windows开发环境为例安装JDK 8或11SpringBoot 2.x版本不需要更高JDK安装Maven 3.6配置settings.xml里的阿里云镜像否则下载依赖能等到怀疑人生安装MySQL 5.7或8.0在Navicat里新建数据库campus_order导入sql脚本用IDEA打开后端项目等待Maven自动下载依赖修改application.yml里的数据库用户名密码为你本地的实际值启动CampusOrderApplication.java的main方法控制台出现“Started CampusOrderApplication”即成功打开微信开发者工具导入小程序前端目录修改request.js里的BASE_URL为http://localhost:8080/api在开发者工具详情设置里勾选“不校验合法域名、web-view业务域名、TLS版本以及HTTPS证书”编译运行首次体验完整点餐流程。7.2 上线部署的准备如果毕设要求线上演示建议在演示前把项目部署到云服务器上。部署方案是后端打成Jar包用nohup java -jar campus-order.jar --spring.profiles.activeprod 命令在Linux上后台运行。因为微信小程序请求接口要求HTTPS域名如果没有备案域名和SSL证书建议买一台有公网IP的云服务器用Nginx做HTTPS反向代理转发到后端端口。但这里必须提醒个人开发者的微信小程序受限很多尤其是支付类接口如果没有企业资质临时上线小程序需要走体验版流程。我的建议是——如果你不是计算机相关专业里那种“必须上线可用”的要求演示时直接用微信开发者工具的模拟器录屏把操作过程录下来答辩现场播放视频再配合接口调试演示效果完全够用了。真实的部署和上线是加分项但千万不要因为它耽误了核心功能开发和论文撰写。毕设的核心是把系统做完整、把论文写扎实部署只是锦上添花。8. 核心代码实现演示与关键踩坑8.1 购物车与下单的Service实现购物车与下单的核心Service代码我给你一份可以在项目中直接参考的骨架。这一段是很多同学面试或答辩时的高频考察点Service public class OrderServiceImpl implements OrderService { Autowired private OrderMapper orderMapper; Autowired private OrderDetailMapper orderDetailMapper; Autowired private CartMapper cartMapper; Autowired private DishMapper dishMapper; Override Transactional(rollbackFor Exception.class) public OrderVO createOrder(Integer userId, ListInteger cartIds, String remark) { // 1. 查询购物车信息联表带出dish信息 ListCartVO cartList cartMapper.selectCartDetailByIds(cartIds, userId); if (CollectionUtils.isEmpty(cartList)) { throw new BizException(购物车为空无法下单); } // 2. 后端重新计算总金额 BigDecimal totalAmount BigDecimal.ZERO; for (CartVO cart : cartList) { totalAmount totalAmount.add(cart.getPrice().multiply(new BigDecimal(cart.getQuantity()))); } // 3. 生成订单号并插入订单主表 String orderNo generateOrderNo(userId); Order order new Order(); order.setOrderNo(orderNo); order.setUserId(userId); order.setTotalAmount(totalAmount); order.setStatus(0); order.setRemark(remark); orderMapper.insert(order); // 4. 插入订单明细表 for (CartVO cart : cartList) { OrderDetail detail new OrderDetail(); detail.setOrderId(order.getId()); detail.setDishId(cart.getDishId()); detail.setDishName(cart.getDishName()); detail.setDishImage(cart.getDishImage()); detail.setPrice(cart.getPrice()); detail.setQuantity(cart.getQuantity()); orderDetailMapper.insert(detail); // 5. 更新菜品销量 dishMapper.increaseSales(cart.getDishId(), cart.getQuantity()); } // 6. 清空购物车 cartMapper.deleteByIds(cartIds, userId); // 7. 返回VO OrderVO vo new OrderVO(); vo.setOrderNo(orderNo); vo.setTotalAmount(totalAmount); return vo; } }这份代码里有三个关键设计点值得在答辩时主动提到Transactional(rollbackFor Exception.class)声明事务任何一步出错全部回滚不会出现“订单主表写了、明细表没写”这种脏数据总金额在Service层重新计算不信任前端数据操作购物车用deleteByIds并传入userId条件防止用户删掉别人的购物车记录。8.2 小程序端点餐流程的实现小程序端页面间的传参方式主要有两种页面跳转URL带参和全局缓存。我在点餐流程中用的是URL带参用户点击菜品卡片时跳转到详情页wx.navigateTo({ url: /pages/dish/detail?id dishId });然后在详情页的onLoad(options)中接收dishId再通过request向后端请求菜品详细信息onLoad(options) { this.setData({ dishId: options.id }); this.loadDishDetail(options.id); }, loadDishDetail(id) { request(/dish/detail?id id, GET).then(data { this.setData({ dish: data }); }); }加入购物车的操作本质就是向后端接口传{ dishId, quantity }后端返回成功后前端通过wx.showToast提示成功并更新底部购物车角标数量。购物车结算跳转到确认订单页时需要带上购物车条目ID列表。这里我遇到过一个很隐蔽的坑wx.navigateTo的URL有长度限制大约为1024字符。如果购物车里有二十几样菜把所有ID拼成URL参数很容易超长导致页面跳转后参数丢失或直接失败。正确做法是加入购物车后把购物车条目ID列表存入全局变量getApp().globalData.cartIds确认订单页在onShow里读取全局数据而不是通过URL传参。这个小坑我当初排查了整整半天写出来希望你们别踩。8.3 图片上传与存储的简化方案菜品图片上传是一个看起来简单、做起来容易卡壳的功能。如果直接在小程序端调用wx.uploadFile把图片传回SpringBoot后端后端的接口需要通过MultipartFile接收文件、决定存储路径、生成访问URL整套流程涉及到不少配置。我推荐的简化方案是云存储或图床而不是本地存储。把图片传到云存储各大云厂商对象存储都有学生优惠返回图片URL直接把URL存数据库。这样有几个好处后端不需要写文件读写代码节省大量时间图片访问不依赖后端服务是否运行演示时不至于因为服务重启导致图片失效小程序访问图片URL不受本地路径资源限制。如果坚持用本地存储有一个必须避开的坑SpringBoot默认的静态资源映射不会把你上传到/upload目录的文件直接暴露出来需要额外在WebMvcConfigurer里注册静态资源映射Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceHandler(file: System.getProperty(user.dir) /upload/); } }不做这一步你会遇到一个经典场景图片上传显示成功但前端img标签访问图片URL时永远404。8.4 评论模块的实现有评价体系的点餐系统会更完整也让公益性的“用户反馈”在评审老师眼里更加闭环。评论模块的逻辑相对简单用户对已完成订单发表评价内容包括星级评分和文字内容后端记录评论并更新对应菜品的评分如果有评分字段或者只展示评论内容。评论前需要校验订单确实属于当前用户并且状态是“已完成”防止用户随便给别人的订单刷评价。这里的前置校验和你做订单状态机时用到的判断逻辑一脉相承代码写起来不复杂但对系统的严谨性提升很明显。9. 常见问题与排查技巧实录9.1 前后端联调阶段的高频Bug第一类问题404或405。接口404多半是路由写错了或者项目没有正确启动405则是Controller方法注解和前端请求方式不匹配比如后端是GetMapping前端发的是POST请求这时必须检查Controller层的注解声明和前端request方法的method参数是否一致。第二类问题跨域报错。虽然小程序请求不算浏览器跨域但如果你用H5页面测试后端接口时浏览器控制台会因为跨域报错。解决方案是在后端加跨域配置要么写一个CorsFilter要么在Controller上加CrossOrigin注解。不过这两个方案有一个区别——CrossOrigin只能解决单个Controller的跨域而Filter对所有接口生效。建议使用全局Filter方案Configuration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.setAllowCredentials(true); config.addAllowedHeader(*); config.addAllowedMethod(*); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }第三类问题数据库连接失败。最常见的是Access denied for user排查顺序是数据库是否启动、用户名密码是否正确、数据库名是否存在。我见过很多同学因为application.yml里的database-name写错查了半天代码却没发现问题根源。还有一个容易被忽略的坑MySQL 8.0的驱动类名是com.mysql.cj.jdbc.Driver如果你的SpringBoot版本比较老默认驱动还是com.mysql.jdbc.Driver会直接报ClassNotFoundException。9.2 JWT登录态失效问题遇到过最多的情况是小程序端登录成功后过了一段时间大概几秒钟或几分钟再次请求接口就提示“未登录”。排查方向有两个Token过期时间设置太短。JWT默认有效期建议设置为2小时到7天之间如果设置了10分钟开发和演示过程中频繁过期会非常烦人拦截器取Token的方式与前端不一致。前端请求头里写的是token后端却用request.getHeader(Authorization)取值自然永远取不到。9.3 小程序真机预览与开发者工具的差异有些功能在微信开发者工具里完全正常但一真机预览就崩了。最常见的原因是开发者工具默认开启了“不校验合法域名”真机上这个选项无效请求的URL必须是HTTPS并且域名已经在小程序后台配置为白名单本地开发环境的后端地址是localhost或局域网IP手机和电脑不在同一网络时无法访问。解决方法是把后端部署到服务器或者用内网穿透工具把本地端口映射到一个公网地址。9.4 订单金额浮点运算的精度问题这个坑我必须单拎出来讲。假如你在Java里直接写0.1 0.2得到的结果不是0.3而是0.30000000000000004。如果用float或double存金额累计到一定程度会出现无法解释的误差。处理方式有两条路数据库层面使用DECIMAL(10,2)类型Java层面使用BigDecimal进行金额运算并且用BigDecimal.valueOf(double)或构造字符串的方式赋值绝不能直接用new BigDecimal(0.1)。创建一个金额计算工具类统一处理加减乘除四舍五入规则用RoundingMode.HALF_UP这是中国人日常使用的“四舍五入”语义。9.5 管理端删除菜品被外键报错删除一个菜品时如果订单明细表里还存在该菜品的引用如果用数据库外键约束删除会直接报错。很多同学的方案是把order_detail表的外键删掉但更好的方案是菜品表设计一个status字段删除时执行逻辑删除把status置为0而不是物理删除DELETE语句。这样既不会报错还能保留历史订单的数据完整性。菜品在售状态显示“已下架”在管理端也只是多一个状态标签的问题。10. 一条龙定制服务里论文与答辩素材怎么准备10.1 论文结构搭建毕业设计论文的评审通常看重的是“你做没做过、做得好不好”而不是你的文采。论文结构我建议按照下面这个骨架来组织第一章 引言背景、意义、国内外研究现状、论文结构第二章 相关技术介绍SpringBoot、小程序、MySQL、框架原理第三章 系统分析可行性分析、需求分析、用例图、数据流图第四章 系统设计总体架构、功能模块设计、数据库设计、接口设计第五章 系统实现每个功能模块的实现截图、核心代码片段、关键逻辑说明第六章 系统测试功能测试用例表、性能测试简述第七章 总结与展望总结项目成果后期改进方向。技术介绍章节一定要结合实际不要抄教材。比如介绍SpringBoot时要说“本项目使用SpringBoot的自动配置机制简化了项目搭建通过starter依赖统一管理第三方库版本”这就比单纯的“SpringBoot是Spring的升级版”有信息量得多。10.2 答辩常问问题清单我根据自己的答辩经验和带过的学生遇到的问题整理了一份高频问题清单为什么选择SpringBoot而不是传统的SSMJWT的工作原理是什么和Session相比有什么优缺点如果用户下单前修改了菜品价格你的系统怎么防止恶意篡改订单状态是怎么流转的每个状态对应的业务操作是什么小程序端有没有遇到过真机预览和模拟器不一致的问题怎么解决的如果并发量大你的系统瓶颈会在哪里有什么优化思路数据库索引在哪些字段上建了为什么这样建这些问题都不算难但如果你没深入想过被追问时容易紧张。我的建议是每做完一个模块在代码里顺手写一段注释标注“这里为什么这么设计”答辩前过一遍注释问题基本都能答上。10.3 演示环境的最后检查清单答辩前一周一定要按照下面的清单逐项检查你的演示环境后端服务能否一键启动不依赖手动改配置数据库脚本能否从零恢复没有多余测试数据小程序端页面能否在开发者工具里直接编译运行核心功能闭环是否完整注册登录→浏览菜品→加入购物车→下单→支付→商家接单→完成订单→评价空数据和正常数据的展示效果分别是否正常网络断开时有没有统一的错误提示答辩用的电脑和备用录屏视频都准备好了吗我见过太多翻车现场答辩现场WiFi突然断了小程序请求失败页面白屏老师盯着屏幕等了三分钟最后只能尴尬地切PPT。所以强烈建议提前录一份完整操作视频时长控制在五到八分钟作为现场演示的兜底方案。这个习惯不仅适用于答辩做作品集、找工作展示项目时同样适用。写在最后的一点个人经验我做了这么多年毕设指导和项目开发最大的感受是毕设真正考察的不是你用了多牛逼的技术而是你有没有完整地解决一个实际问题的能力。校园点餐系统这个项目技术栈主流、需求清晰、扩展性强是一个进可攻退可守的稳妥选择——做基础版能过审做升级版能拿高分往分布式、消息队列、数据分析方向扩展也都有空间。如果你是从零开始的小白不要被这篇博文的篇幅吓到。把功能拆成一个个小块先从最核心的“登录菜品列表下单”跑通闭环再逐步加购物车、订单管理、评论、后台统计按优先级迭代即可。项目做到一半卡住了就先放一放换个模块做等整体框架成型后再回头解决卡点思路往往会清晰很多。最后分享一个习惯每次写完一个接口用Postman把请求参数和响应结果截图存下来最后统一整理到“项目开发过程记录”文档里。这些素材写论文时能直接用答辩时也可以展示完整开发轨迹比你口头说“做了很多”有说服力得多。希望这篇博文能帮你在毕设路上少走一些弯路做出一份自己满意、老师认可的作品。

相关新闻