
简介这是一份面向计算机相关专业毕业设计及微信小程序开发学习者的完整项目资料围绕校园外卖平台实现从学生端购买、商家端管理到管理员后台审核的闭环业务。项目后端采用SSM框架前端包含小程序端与Vue后台管理页面数据库使用MySQL并配套毕业论文与视频演示能直接支撑课题分析、代码讲解与答辩准备。压缩包共946个文件约28.48MB其中java、xml、properties为后端逻辑与配置vue、js、css为后台管理界面wxml、wxss、js等为小程序源码sql为数据库脚本另有png、jpg、svg等设计素材及mp4演示视频结构分类清楚。目前已有295人学习该资源适合需要快速搭建校园外卖类项目、理解SSM前后端交互或撰写毕业设计文档的学生使用。包内还包含bat启动脚本、说明文件等内容便于学习其部署与运行方式。1. 校园外卖平台小程序毕设为什么我建议你先跑数据库再谈改代码毕业设计拿到一套“小程序SSMMySQL”源码时大部分人第一件事是打开微信开发者工具看页面然后被页面唬住直到调接口才被“request:fail”拍回现实。这套校园外卖平台小程序把用户端、商家端和管理员后台都放在一份包里后端是 SSMSpringSpringMVCMyBatis数据落在 MySQL还附带毕业论文和视频演示。它能解决的最直接问题你不需要同时维护三套独立系统按“数据库→后端→小程序”的顺序启动就能演示完整的“用户下单—商家接单—管理员看后台”闭环。适合课程设计、毕业设计二次开发也适合想练手前后端分离的人拿来拆。注意这是一套需要你自己落地的源码包不是一份在线教程。2. 拆解技术选型微信小程序 SSM MySQL 是怎么协作的先说选型。现在新项目都在谈 Spring Boot但这套毕设用的是更传统的 SSM 组合Spring 管 Bean、SpringMVC 管路由和参数绑定、MyBatis 管 SQL。为什么它还能打因为毕业设计要求你能逐层解释请求如何从页面落到数据库SSM 的 XML 和配置比 Spring Boot 自动装配更直观评审老师看到的也是教科书上的写法。另一个原因是SSM 改造成 Spring Boot 的成本很低你拿它当脚手架完全可行。下面从角色、表结构和接口三个角度拆。2.1 三端角色与 SSM 分层分工这套系统里的“用户”不是同一个入口的三种按钮。商家和用户都从微信小程序端注册登录区别在登录后的菜单和接口权限管理员走的是另一套后台管理前端不和小程序共用目录。所以你在源码里会看到三类工程小程序端微信开发者工具打开、SSM 后端IDEA 或 Eclipse 导入、管理后台带 Vue 文件的那部分。这种结构把消费端和管理端隔开是很多毕设项目的常见形态。从后端看SSM 三层各司其职。Controller 层接收小程序发来的 HTTP 请求只做参数接收和路由分发Service 层负责业务规则比如下单时要同时扣库存、生成订单、写购买记录Mapper 层把 SQL 和 Java 方法绑定一张表对应一个 Mapper 接口和 XML。你把一个购买流程从接口入口追到 SQL顺序一定是 Controller→Service→Mapper反过来排查问题也一样。我一般会让读者先理解一个原则小程序端不做核心业务判断所有涉及钱、库存、订单状态的操作必须回到后端。比如用户点“购买”小程序只是把菜品 ID 和数量发给后端真正写数据库的是后端 Service 里的事务方法。这在毕设答辩时是高频问题——评审老师一定会问“订单数据的唯一性在哪里保证”答案就在 MySQL 和事务里。下面用一张表把角色和关键操作对应起来方便你定位源码位置角色登录方式主要操作后端落点用户小程序端注册/登录浏览菜品、购买、查看订单OrderController / FoodController商家小程序端注册/登录菜品增删改、查看订单、订单领取MerchantFoodController / OrderController管理员后台管理系统用户管理、商家管理、菜品管理、订单管理、系统管理Admin 相关 Controller这张表的价值在于你看到一个功能时能根据角色先去对应 Controller 里找接口而不是在小程序端翻页面逻辑。小程序端的页面只是展示真正的数据来源永远是后端。2.2 核心表设计与订单状态字段数据库是这套源码里最该细看的部分。毕设源码如果表结构乱后面所有功能都会跟着乱。这套系统的表可以拆成几个核心组用户与商家账号体系、菜品与分类商品体系、购买记录与订单交易体系、订单领取履约体系。你可以把它理解成简化版的外卖订单模型。我拿“订单”这条线拆给你看。用户端看到的是“购买菜品”对应数据库里的订单主表和订单明细表。订单主表存用户 ID、商家 ID、总金额、状态、下单时间订单明细表存每道菜、数量、单价。为什么要拆两张表因为一个订单可以包含多个菜品如果不拆字段会重复统计商家销量时也麻烦。商家端“订单领取”一般就是给订单主表加一个 status 字段从“待领取”改成“已领取”最后变“已完成”。下面是一套我认为合格的表设计你在源码里可以对照着看不用完全一致-- 用户表 CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, openid VARCHAR(64) UNIQUE, -- 小程序登录后拿到的用户标识 nickname VARCHAR(32), phone VARCHAR(20), avatar VARCHAR(255), create_time DATETIME ); -- 菜品分类表 CREATE TABLE t_category ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(32), merchant_id INT, -- 商家可以维护自己的分类 sort INT DEFAULT 0 ); -- 菜品信息表 CREATE TABLE t_food ( id INT PRIMARY KEY AUTO_INCREMENT, category_id INT, -- 关联分类 merchant_id INT, -- 关联商家 food_name VARCHAR(64), price DECIMAL(10,2), stock INT DEFAULT 999, image VARCHAR(255), status TINYINT DEFAULT 1 -- 1上架 0下架 ); -- 订单主表 CREATE TABLE t_order ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) UNIQUE, user_id INT, merchant_id INT, total_amount DECIMAL(10,2), status TINYINT DEFAULT 0, -- 0待领取 1已领取 2已完成 3已取消 create_time DATETIME ); -- 订单明细表 CREATE TABLE t_order_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT, -- 关联订单主表 food_id INT, food_name VARCHAR(64), -- 冗余菜名防止菜品改名后历史订单变化 price DECIMAL(10,2), quantity INT );注意三个细节。第一openid 一定要建唯一索引这是小程序识别用户的唯一凭证重复会导致登录错乱。第二food_name 在明细表里冗余存储是为了菜品被商家删除后历史订单仍然可读。第三status 用 TINYINT 存数字而不是字符串省空间也方便写范围查询。你在毕业论文的数据表说明里可以直接用这套逻辑去解释“为什么订单和明细要分开”。另外这套表的设计里我没有加物理外键而是靠 Service 层保证一致性。这是毕设项目里很常见的取舍物理外键在删除用户时会带来一堆约束错误而业务层事务已经能覆盖核心链路。如果你的老师要求画 ER 图可以把 Controller 里用到的关联字段都标成逻辑外键。2.3 小程序端请求封装与登录状态管理小程序的 wx.request 是原生请求方法但如果在每个页面里都写一遍 url 和 header后面改接口地址会让你改到怀疑人生。这套项目里一般会有一个 utils/request.js 之类的封装把所有请求统一走一个入口。// utils/request.js 典型封装 const BASE_URL http://localhost:8080/ssm; // 后端接口根路径 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, Authorization: wx.getStorageSync(token) || }, success(res) { // 后端返回统一结构 { code, message, data } if (res.statusCode 200 res.data.code 200) { resolve(res.data.data); } else { wx.showToast({ title: res.data.message || 请求失败, icon: none }); reject(res.data); } }, fail(err) { wx.showToast({ title: 网络异常, icon: none }); reject(err); } }); }); } module.exports { request };这段代码有三处必须改BASE_URL 改成你自己本机或服务器的 IPAuthorization 取自本地缓存的 token适合后端用 Token 或 Session Key 鉴权的项目success 里判断的 code需要和后端 Result 类的 code 保持一致。如果后端返回的是 { success: true } 这类结构就把 if 条件替换掉。登录状态为什么不用 openid 直接存前端因为 openid 属于敏感身份信息一旦被爬出整张用户表都可以被伪造。更安全的口径是后端用 openid 查到用户后生成一个带过期时间的 token 返回前端只保存 token。这个思路在答辩时也是一个加分回答老师问“用户身份如何识别”你可以把这条链路完整讲一遍。3. 从零跑通数据库初始化、SSM 后台启动和小程序导入3.1 环境准备清单在跑任何源码之前先把环境固定住否则每一个报错都可能变成“玄学”。这套项目我用到的环境如下配置完全一致时成功率最高组件推荐版本用途JDK1.8SSM 后端编译运行Maven3.6依赖管理MySQL5.7 或 8.0业务数据库Tomcat8.5后端部署容器微信开发者工具最新稳定版小程序端调试Navicat 或 MySQL Workbench任意导入数据库、查看表如果你在包里看到 1-install.bat、2-run.bat、3-build.bat 和一堆 .bak 前端配置文件说明原作者习惯用命令行串联构建流程。我的习惯是1-install.bat 负责 Maven 依赖安装和数据库初始化2-run.bat 负责启动后端服务3-build.bat 负责把管理后台前端打成静态包。因为不同机器的路径不一样这些 bat 内容可能和你本机冲突建议先打开看一遍不要直接双击盲跑。版本不是越新越好。我见过有人用 JDK 17 跑 SSM 老项目javax.servlet 的依赖直接编不过也见过 MySQL 8.0 没问题但 MySQL 5.7 反而少踩坑的情况。如果没有特别要求优先复现作者环境。怎么确认作者环境看 pom.xml 里 java.version 和数据库驱动的 groupId驱动是 mysql-connector-java 5.x 就偏向老 MySQL。这种判断方法比问作者更快。3.2 第一步初始化数据库数据库文件一般在包的 sql 目录或根目录文件名类似于 campus_food.sql。用 Navicat 导入的步骤是打开 MySQL 连接新建数据库 campus_food字符集选择 utf8mb4排序规则选 utf8mb4_general_ci然后右键数据库选“运行 SQL 文件”选中 campus_food.sql。如果走命令行用下面这段mysql -uroot -p${你的密码} --default-character-setutf8mb4 create database campus_food default character set utf8mb4; use campus_food; source /path/to/campus_food.sql;逻辑说明--default-character-setutf8mb4 保证中文和 emoji 不写入乱码分步执行而不是 mysql xxx.sql便于看到每条语句的报错位置。source 路径里不要带中文目录否则控制台可能解析失败。导入后重点检查 t_user、t_order 是否有初始数据没有的话管理员账号需要手工插一条。初始管理员一般放在 t_admin 或 t_user 里用 role 字段区分你打开表看一眼就能定位。3.3 第二步启动 SSM 后台用 IDEA 打开后端工程先让 Maven 重新导入依赖等右下角索引结束。然后打开 src/main/resources 下的数据库配置文件一般是 jdbc.properties 或 application.properties。jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/campus_food?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8mb4 jdbc.usernameroot jdbc.password123456参数说明MySQL 8.0 驱动必须写 com.mysql.cj.jdbc.DriverMySQL 5.7 用 com.mysql.jdbc.Driver 也能跑但建议统一用 cj 版。url 里的 serverTimezone 缺失会直接报时区错误useSSLfalse 是避免本地 SSL 握手带来延迟。密码改成你自己的。然后在 IDEA 里配置 TomcatDeployment 里添加 war explodedApplication context 填 /ssm注意不要填 /否则后面 BASE_URL 会多一层路径。启动后看到类似 “Spring MVC 启动完成” 的日志就算成功。包里的 2-run.bat 脚本通常就是把 Tomcat 启动命令封装了一下前提是你已经把项目编译成 war 包且 Tomcat 路径正确。如果你对命令行不熟直接在 IDEA 里用集成 Tomcat 跑更稳。后台起没起来最简单的验证方式是浏览器访问 http://127.0.0.1:8080/ssm/login能返回 JSON 说明接口层是通的。另一个常见误用是跳过 IDE 直接把 war 包扔进 Tomcat 的 webapps。这种做法不是不行但你在测试小程序时会遇到两个问题一是 context path 变成 war 包名和 BASE_URL 不一致二是没有 IDE 控制台日志接口报错只能去 Tomcat logs 里翻效率低很多。所以我建议毕设阶段就在 IDEA 里跑等最后打包演示再考虑独立部署。3.4 第三步导入微信小程序前端后端起来之后打开微信开发者工具选择“导入项目”目录指向小程序端文件夹。这里有两个关键配置。第一个是 AppID。如果你没有自己的小程序 AppID可以点“测试号”但测试号模式下部分能力受限真机预览也可能不顺畅。如果有自己的 AppID直接填上登录态相关代码不需要改动。第二个是本地调试域名。小程序官方要求 request 必须是 HTTPS 且配置合法域名但本地开发是 HTTP。解决办法在微信开发者工具右上角“详情→本地设置”里勾选“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”。不勾这一步你写的所有请求都会被拦掉。登录功能跑通的标准是用户打开小程序能拿到 token后台 t_user 表多一条用户记录。如果卡在这里回到 request.js 看 BASE_URL 是不是 localhost端口是否与后端 Tomcat 端口一致。还有一个容易漏的地方小程序端项目的 project.config.json 里如果有 appid 字段导入后微信开发者工具可能按缓存来识别建议先在“详情”里确认当前使用的 AppID 与项目配置一致。导入后还要检查 app.json 里的页面路径。如果源码里有多个 tabBar 页面但路径写错编译会直接失败。此外检查权限提示小程序端往往会用 wx.getSetting 检查授权如果测试号下没有相应权限页面可能一直停在空白。遇到这种先在“模拟器”面板清缓存、清授权别急着改代码。4. 把代码读薄登录、下单、商家接单三条主线的实现套路4.1 小程序端登录从 wx.login 到自定义 token小程序登录不像传统网页写账号密码而是通过微信的 code 换 openid。后端拿到 openid 后去 t_user 表查一下用户是否存在不存在则自动注册存在就直接发 token。这是校园外卖平台里最标准的登录流程。// 后端 LoginController 片段 PostMapping(/login) ResponseBody public Result login(RequestBody LoginDTO dto) { // 1. 小程序端通过 wx.login 获取 code 调用微信接口换 openid String openid wxService.code2Session(dto.getCode()); if (openid null) { return Result.error(登录凭证失效); } // 2. 查询用户不存在则创建商家同理 User user userMapper.selectByOpenid(openid); if (user null) { user new User(); user.setOpenid(openid); user.setNickname(微信用户); userMapper.insert(user); } // 3. 生成 token 返回小程序端 String token SessionHelper.createToken(user.getId(), user.getRole()); return Result.ok(token); }逻辑说明code 是一次性的后端绝不能把它存库而是拿它去微信服务器换 openidtoken 在毕设项目里常见做法是放一个带过期时间的 TokenMap不用引入 Redis。这里注意别把 openid 原样返回前端否则接口被刷时用户身份规则就会暴露。小程序端配合的代码很简短wx.login({ success(res) { if (res.code) { request(/login, POST, { code: res.code }) .then(token { wx.setStorageSync(token, token); // 登录成功跳转首页 }); } } });注意 wx.login 拿 code 后要立即用掉放置时间过长会过期后端调用微信接口换 openid 也可能失败。token 存到 Storage 后request.js 每次请求都会带 Authorization后端通过解析 token 判断当前用户。这个机制决定了你可以在小程序端做菜单级权限显示用户端看一眼 token 里的 role 字段决定渲染哪组按钮。4.2 用户下单与库存校验用户点“购买”的核心动作是检查菜品是否上架、库存是否充足、计算总金额、生成订单主表和明细表。一个常见错误是只更新库存不写明细导致结算单金额与菜品实际价格对不上。后端应该在一个事务里完成Transactional(rollbackFor Exception.class) public String createOrder(OrderDTO dto) { // 1. 查菜品锁定行数据 Food food foodMapper.selectByIdForUpdate(dto.getFoodId()); if (food null || food.getStock() dto.getQuantity()) { throw new ServiceException(库存不足); } // 2. 扣库存 foodMapper.decreaseStock(food.getId(), dto.getQuantity()); // 3. 生成主订单与明细 String orderNo FO System.currentTimeMillis(); orderMapper.insert(orderNo, dto.getUserId(), food.getMerchantId(), totalPrice); orderItemMapper.insert(orderNo, food.getId(), food.getFoodName(), food.getPrice(), dto.getQuantity()); return orderNo; }参数说明Transactional(rollbackFor Exception.class) 保证中途抛异常时“扣库存”和“生成订单”同时回滚这是下单功能不容商量的一点selectByIdForUpdate 是悲观锁锁住菜品行防止两个人同时买最后一个鸡腿时出现超卖。对毕设演示来说数据量不大这套写法足够应付。如果你想让代码更现代可以把锁粒度改成“先查后扣”并且用 UPDATE 影响行数判断是否超卖但架构上会复杂一点。用户端“查看订单”时后端只要按 user_id 查 t_order 倒序返回即可。每个订单带一个 status小程序端用 wx:if 显示“待领取 / 已领取 / 已完成”状态变化来自商家端的操作。这个 status 字段是整个订单模块的核心状态机你可以给它写一个简单的枚举public enum OrderStatus { PENDING(0, 待领取), TAKEN(1, 已领取), COMPLETED(2, 已完成), CANCELED(3, 已取消); }状态机的好处是你在改订单列表显示时不会把魔法数字 0/1 撒得满代码都是。答辩时提到这个枚举类也能表明你考虑过状态流转的健壮性。4.3 商家端菜品管理与订单领取商家在小程序登录后看到的是另一组菜单。商家维护菜品、查看某个用户买了什么、把订单置为“领取”。查看某个用户购买记录这个功能在后端实际是一条多表连接查询订单表 join 用户表加上 merchant_id 条件。SELECT o.order_no, u.nickname, o.total_amount, o.status, o.create_time FROM t_order o LEFT JOIN t_user u ON o.user_id u.id WHERE o.merchant_id #{merchantId} ORDER BY o.create_time DESC;这段 SQL 是商家端订单列表的核心。LEFT JOIN 保证即使 t_user 里数据被删订单仍然出现在列表里只是用户字段为空。查询条件里的 merchantId 不能从前端直接拿必须从 token 里解析出来否则商家 A 可以查到商家 B 的订单。这是一个非常常见的越权漏洞很多毕设源码都在这点翻车你拿到代码后可以留意一下。商家“订单领取”更简单就是把 status 从 0 改成 1。事务开销很小但要注意加一个 WHERE status 0 条件避免重复点击产生状态错乱。后台管理员的功能点摘要里写得很清楚个人中心、用户管理、商家管理、菜品分类、菜品信息、购买菜品、订单信息、订单领取、系统管理。对照管理后台的 Vue 工程你会发现每个菜单都对应一组 REST 接口。管理员端本质上是这系统的“数据字典”入口重点在分页列表和带条件的搜索筛选。分页查询的写法一般是这样SELECT * FROM t_food WHERE food_name LIKE CONCAT(%, #{keyword}, %) AND status #{status} ORDER BY id DESC LIMIT #{offset}, #{pageSize}LIMIT 的两个参数offset 是当前页码减一乘以 pageSizepageSize 是每页条数。管理后台的搜索框传 keyword分类选择传 categoryId状态开关传 status后端把这些条件拼进 SQL 就行。你如果想把搜索做得好一点可以在 Service 层统一包装一个 PageResult 对象包含总条数和当前页数据前端表格组件也方便渲染分页条。5. 避坑与排查我复现这套毕设时踩过的五个坑5.1 数据库连接报 CommunicationsException现象启动后台时控制台提示 “Communications link failure”有时还有 “The last packet successfully received from the server was X milliseconds ago”。原因最常见是数据库 URL 没带 serverTimezone导致连接建立阶段驱动无法完成时区转换第二个常见原因是 MySQL 端口不是默认 3306或者 root 密码与配置文件不一致。解决把 URL 改成带 serverTimezoneAsia/Shanghai 和 useSSLfalse 的写法再用 Navicat 测试同样参数能否连接。如果依然失败检查 MySQL 服务是否真的在运行Windows 下在服务列表看 MySQL80 或 MySQL 的状态。注意 MySQL 8.0 默认驱动类名带 cj写错会报 ClassNotFoundException。最后用下面的命令确认端口在监听netstat -ano | findstr :3306如果没输出说明 MySQL 没起来如果输出但不是 3306说明配置的端口是自定义的去 my.ini 里看实际端口。排查顺序上先查密码再查 URL最后查服务状态避免浪费时间。5.2 小程序页面上有数据但请求全部失败现象手机预览时列表为空Console 报 “request:fail”开发者工具里同样报 “url not in domain list”。原因前者是把 BASE_URL 写成了 localhost 或 127.0.0.1真机上的 localhost 指向手机自己后者是没勾选本地调试的域名校验跳过。解决后端启动时监听 0.0.0.0小程序 BASE_URL 改成电脑局域网 IP比如 http://192.168.1.101:8080/ssm。查看电脑 IPipconfig找到 IPv4 地址替换即可。同一局域网下手机访问这个 IP请求就能通。同时检查手机和电脑是否在同一个 WiFi校园网如果开启了 AP 隔离即使同网段也互相不通这时候可以去路由器或交换机后台关掉隔离或者老老实实用开发者工具模拟器演示。工具内调试的话在“详情→本地设置”勾选“不校验合法域名”。这个坑最磨人因为界面正常、代码没报错但数据就是不出来别急先看 Console 的报错关键词。5.3 导入 SQL 后中文乱码或建表失败现象部分表建不出来或 Navicat 里看到的是“???”插入中文菜名直接报 Incorrect string value。原因SQL 文件本身是 utf8mb4 编码但导入会话用了默认 latin1 字符集或者表字段建成了 utf8不支持生僻字和 emoji。解决导入前设置 --default-character-setutf8mb4建库时统一用 utf8mb4。已建好的表可以这样修正ALTER TABLE t_food CONVERT TO CHARACTER SET utf8mb4; ALTER TABLE t_order CONVERT TO CHARACTER SET utf8mb4;以后每次手工执行 SQL养成先执行 SET NAMES utf8mb4; 的习惯。另外导入工具里如果选择的是 utf8 而不是 utf8mb4界面可能正常但存储 4 字节 emoji 时会失败奶茶店菜名如果带 emoji就很需要这个坑被提前堵住。检查表字符集用 show create table t_food; 一眼就能看到 DEFAULT CHARSET 的值。5.4 Tomcat 端口被占用小程序接口 404现象Tomcat 启动立刻报 “Port 8080 was already in use”或者后台页面能打开但小程序接口一直 404。原因本机其他 Java 进程或之前残留的 Tomcat 实例占用了端口另一个原因是后端发布路径和 BASE_URL 不一致。很多毕设把 Controller 映射成 /ssm/goods/list但 Tomcat 的 Application context 是 /于是小程序请求 http://ip:8080/ssm/goods/list 会落到 Tomcat 根路径自然 404。解决先查占用进程并清理netstat -ano | findstr :8080 taskkill /PID 进程号 /F然后统一三处Tomcat connector 端口、后端 context path、小程序 BASE_URL。我在复现这套项目时最讨厌的其实是“后台能看但接口 404”后来发现是 context path 配成了 ROOT而代码里的路由前缀没跟着改。你只需要保证小程序端请求的路径和后端 RequestMapping 的完整路径一字不差即可。排查时先访问接口文档里一个最简单的接口看返回是 404 还是 500404 是路径问题500 是代码问题方向完全不同。5.5 商家修改菜品后用户端不显示最新数据现象商家上架新菜品用户端下拉刷新后还是看不到有的用户能看到有的不能。原因小程序页面 onLoad 只请求一次列表切页不会重新拉取另外 wx.request 在小程序端默认可能命中缓存列表接口时序相同就会被复用。解决业务层面给列表页加 onPullDownRefresh在刷新回调里重新 request技术层面给请求加 no-cache 头部或在 url 后拼一个时间戳参数const url /food/list?timestamp Date.now();这是最简单的做法也可以把 timestamp 加到 request.js 的统一处理中这样所有列表接口自动带新参数不用每个页面单独改。演示前一定先测试“商家改价→用户端刷新→价格变化”这条路这是现场最容易翻车的场景。如果你发现改完还是旧数据打开 Network 面板看请求 url 尾部有没有 timestamp没有就是缓存没绕过去。6. 进阶把演示从“能跑”变成“能答辩”我给这套项目准备的演示路径代码跑通只是开始答辩演示的顺序和话语比代码本身更能加印象分。我的做法是准备一套三层演示脚本。先用管理员身份登录后台展示用户管理和商家管理。这时可以强调“权限是分角色的管理员无法在小程序端下单”把系统架构一句话讲清楚。接着切到商家端小程序演示新增菜品、修改价格、下架菜品。这里要提前准备两张不同价格的菜品图片现场改价后立刻刷新用户端界面让评委看到数据是实时同步的。最后是用户端下单链路选菜、加入购买、生成订单、切到商家端看到“待领取”、再点“领取”。这个闭环覆盖了订单表、明细表、状态字段和事务回滚四个知识点。每做一步就指一下对应的数据库表。我会刻意把 MySQL 的终端日志放在旁边查到插入语句时停下来说明“这一步执行了两条 insert分别写入主表和明细表”。我还准备一个“异常演示”故意把一个菜品库存改成 0然后用户端下单界面弹出“库存不足”后端日志里没有任何异常堆栈。这段会让老师觉得你真正理解了下单逻辑里的事务和校验。避免现场联网翻车的方式是提前录一遍完整操作把录屏文件放在源码说明书里万一现场网络连不上还能播放已有视频。从那以后我每次拿到一套毕设源码都会先按“数据库 → 后端 → 小程序”的顺序强制跑一遍再改代码而且一定会把 BASE_URL、数据库密码和 context path 统一改到一处之后才开始看业务。希望这份拆解能帮你在复现时少走点弯路早日把项目变成自己的东西祝你顺利。本文还有配套的精品资源点击获取