
简介这是一套基于SSM框架与微信小程序的校园订餐系统完整源码适合毕业设计、课程设计以及Java后端与小程序开发入门者使用。系统围绕管理员、用户、商家三类角色展开覆盖餐厅信息管理、美食分类与信息展示、购物车、订单管理、用户充值及收藏等核心业务前台小程序与后台管理端兼备。压缩包大小24.77MB共包含1414个文件其中136个Java文件承担后端业务逻辑148个Vue文件用于后台管理界面202个JS脚本与98个WXML/WXSS文件构成小程序交互页面另有SQL数据库脚本和启动配置文件方便直接部署运行。目前已有108人学习下载。通过学习此资源可深入理解SSM整合流程、RESTful接口设计以及微信小程序调用后端接口的完整链路同时获得一个可直接运行和二次开发的商业场景案例对完成相关课题或积累项目经验均有较大帮助。1. 校园订餐小程序为什么我建议你用 SSM 而不是 Node/Spring Boot拿到一个「基于SSM的校园订餐小程序.zip」你第一反应可能是“这都2025年了SSM还有必要学吗”但我的答案是如果你是奔着毕业设计、课程设计或者接手校园项目去的这个压缩包反而值得花三天跑通。它背后是 Spring SpringMVC MyBatis 作后台、微信小程序作前端一套典型的三层架构单库单机就能部署能解决校园食堂订餐的完整闭环学生在微信里点餐、后台管理菜品和订单、商家接单出餐。适合正在做毕设的学生、刚转 Java 的初级工程师以及需要二次开发校园点单系统的外包人员。这个项目最大的价值是“全链路可见”从数据库表、DAO 层 SQL、Service 事务到小程序端 wx.request、页面渲染每一条请求路径你都能完整追踪。对比 Spring Boot 的自动配置SSM 需要你手动拆包理解 XML 和注解之间的关系——这反而让你在排查问题时不至于面对一个黑匣子。2. 拆开 .zip 看门道项目结构、技术栈与里程杯式的三层架构2.1 从压缩包名字看骨架SSM 与小程序的双方分工解压后你会看到两个平行的路径一个看起来是 Java Web 工程另一个是微信小程序原生目录。Java 工程内部有pom.xml、src/main/java、src/main/resources小程序目录里有app.js、app.json、pages/、utils/。有的压缩包还会把数据库脚本单独放在/sql或/doc下第一次建议先把目录结构tree看一眼再动手。常见的做法是后台只负责出 JSON 接口小程序端不跟 JSP 或 Thymeleaf 混在一起。也就是说后台 SpringMVC 的Controller里控制层返回JsonResult而不是ModelAndView。如果你的压缩包里出现webapp/WEB-INF/views和 JSP那说明这个项目可能是“小程序 后台管理页面”合体的此时后台需要模块化处理——一部分是给小程序提供 REST 接口另一部分给管理员用网页端。处理时要注意接口路径前缀不要冲突比如小程序接口统一/api/user/**管理端用/admin/**。下面是一张常见的文件布局表你可以拿它来对照压缩包里的目录位置内容说明src/main/java/.../controller与小程序交互的 REST 接口返回 JsonResultsrc/main/java/.../service业务逻辑与事务控制处理下单、扣库存src/main/java/.../daoMyBatis Mapper 接口方法对应 XML 中的 SQLsrc/main/resources/mapperXML 文件复杂 SQL 写在 XMLsrc/main/resources/*.xmlSpring 与 MyBatis 配置扫包、数据源、事务管理器app.js/pages/小程序全局配置与页面决定小程序端如何调用后台这段描述看起来像老生常谈但真有不少同学打开压缩包后直接去找index.html页面根本没意识到这是前后端分离。所以拆包第一步先确认后台是不是 REST 风格如果是直接按接口联调来跑。2.2 数据模型设计用户、菜品、订单、购物车、评价校园订餐的业务边界很清晰表也不多但表关系如果不提前理清后面改需求时会让你头大。我通常先看六张核心表用户表、菜品表、分类表、购物车表、订单主表、订单明细表。有的压缩包里还会有积分表、优惠券表但都是围绕这六张表做扩展。下面是我在原 SQL 基础上精简过的核心建表脚本你可以直接复制到 Navicat 的查询窗口执行也可以用来对照压缩包里的表结构看看有没有字段缺失。-- 用户表学生、食堂管理员、超管 CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, role TINYINT DEFAULT 0, -- 0普通学生 1商家 2管理员 campus VARCHAR(50), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 菜品表 CREATE TABLE dish ( id INT PRIMARY KEY AUTO_INCREMENT, category_id INT, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT DEFAULT 100, image VARCHAR(255), status TINYINT DEFAULT 1, -- 1上架 0下架 create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 购物车表以(user_id dish_id)做唯一键 CREATE TABLE cart ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, dish_id INT NOT NULL, quantity INT NOT NULL DEFAULT 1, selected TINYINT DEFAULT 1, UNIQUE KEY uk_user_dish (user_id, dish_id) ); -- 订单主表 CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL, user_id INT NOT NULL, total_amount DECIMAL(10,2) NOT NULL, status TINYINT NOT NULL DEFAULT 0, -- 0待支付 1已支付 2已接单 3配送中 4已完成 5已取消 address VARCHAR(255), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, pay_time DATETIME ); -- 订单明细表 CREATE TABLE order_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, dish_id INT NOT NULL, dish_name VARCHAR(100), price DECIMAL(10,2), quantity INT NOT NULL );这里解释几个设计取舍我把sys_user的 password 字段设成 64 位是给 MD5 或 SHA-256 预留的。很多压缩包里的密码字段是varchar(32)如果你想改成加盐 SHA-256需要先扩字段否则登录校验会一直报错。status用 TINYINT 而不是ENUM因为 MyBatis 在映射ENUM时需要额外枚举类型TINYINT 可以直接映射成 Integer状态机逻辑写在 Java 枚举里改动时不用 ALTER TABLE。cart表唯一键uk_user_dish很重要它是“一个用户同一道菜只保留一条记录”的基础。插入或增加数量时利用这个唯一键做成 upsert存在则 update不存在则 insert性能比先 select 再判断要好。2.3 核心流程从加购物车到下单请求是怎么穿过三层的写过一个校园订餐项目后你会意识到后端最核心的接口其实就几个登录、查菜品、加购物车、结算下单、模拟支付、查订单。我们拿“加购物车”来走一遍全链路。小程序端在用户点击“”按钮后执行// pages/dish/dish.js addToCart(dishId) { wx.request({ url: getApp().globalData.baseUrl /cart/add, method: POST, data: { dishId, quantity: 1 }, header: { token: wx.getStorageSync(token) }, success: (res) { if (res.data.code 0) { wx.showToast({ title: 已加入购物车, icon: success }); } } }); }这一步要注意token必须放在 header 而不是 body 里很多压缩包里是写到 data 里的那会混在业务字段中后端取 token 还要额外解析。后端 Controller 用统一的RestController接收请求RestController RequestMapping(/cart) public class CartController { Autowired private CartService cartService; PostMapping(/add) public JsonResult add(RequestBody Validated CartAddDTO dto, HttpServletRequest request) { Integer userId UserContext.getUserId(request); cartService.addToCart(userId, dto.getDishId(), dto.getQuantity()); return JsonResult.ok(); } }代码逻辑说明RestController等价于Controller ResponseBody每个方法的返回值会自动 JSON 序列化。Validated会校验CartAddDTO里NotNull(message dishId不能为空)这类约束校验失败后 Spring 会抛异常由全局异常处理器统一处理不用在 Controller 里写if(!dto.valid())。UserContext.getUserId(request)是从 HttpServletRequest 里解析 Token 并返回用户 ID。这个工具类内部用 ThreadLocal 传递避免了每个方法都多传一个 userId 参数是 SSM 项目里常用的轻量级登录态方案。Service 层的实现Service public class CartServiceImpl implements CartService { Autowired private CartMapper cartMapper; Autowired private DishMapper dishMapper; Override Transactional(rollbackFor Exception.class) public void addToCart(Integer userId, Integer dishId, Integer quantity) { Dish dish dishMapper.selectById(dishId); if (dish null || dish.getStatus() ! 1) { throw new BusinessException(菜品不存在或已下架); } Cart cart cartMapper.selectByUserIdAndDishId(userId, dishId); if (cart null) { cart new Cart(); cart.setUserId(userId); cart.setDishId(dishId); cart.setQuantity(quantity); cartMapper.insert(cart); } else { cartMapper.increaseQuantity(cart.getId(), quantity); } } }关键点Transactional保证“查菜品-查购物车-新增/修改购物车”之间的每一步都处于同一个数据库事务任何一步失败都会回滚。increaseQuantity使用UPDATE cart SET quantity quantity #{quantity} WHERE id #{id}这是原子操作不需要先 select 再 update避免两个请求同时加购时互相覆盖。如果用户传的quantity是负数后端需要先做合法性校验否则会出现一个用户把购物车数量改成负数随后结算金额变成负数订单却生成成功。到这里你已经明白这个压缩包的基本运行路径了。下一步就是把整个工程从 zip 代码变成你电脑上能跑的进程。3. 本地跑通从导入数据库到微信开发者工具出首屏的完整步骤3.1 环境准备JDK 8 / Tomcat 8 / MySQL 5.7 的版本搭配SSM 项目对 JDK 版本敏感我建议直接用 JDK 8。用 JDK 11 时容易遇到cglib代理和 JAXB 模块被移除的报错。Tomcat 用 8.5不要一上来上 Tomcat 9很多项目里的 Spring 版本还是 3.x/4.x在 Tomcat 9 上跑会报NoClassDefFoundError: javax.servlet.ServletRegistration。MySQL 用 5.7 或 8.0 都行但连接驱动如果是com.mysql.jdbc.Driver在 MySQL 8 上会报加载失败要换成com.mysql.cj.jdbc.Driver。由于压缩包里多数是 5.7 时代写的先装 5.7 最省事。前端工具只有两个微信开发者工具和数据库管理工具。微信开发者工具用稳定版即可不用每周追版本。下面是一个环境版本表照着装大概率一次过组件推荐版本理由JDK1.8.0_202与 spring 4.x/5.x 兼容避免 JAXB 缺失Tomcat8.5.x支持 servlet 3.1控制在 9 之前的兼容区MySQL5.7.x与 SQL 脚本中的 utf8/utf8mb4 兼容最好Maven3.6.x拉依赖稳定3.8 对私有仓库配置更严格微信开发者工具稳定版预览小程序调试请求提示不建议一开始用 Docker 拉现成的 MySQL 镜像因为压缩包里的 SQL 脚本可能会带CREATE DATABASE语句容器环境和宿主机字符集配置不一致时排查中文乱码会多绕一圈。3.2 导入后台用 IDEA 打开 .zip 里的 SSM 工程修改 3 处配置IDEA 直接File - Open选压缩包解压后的后台目录如果是 Maven 项目打开后需要等右下角弹“Import Maven Project”。不要从压缩包里直接打开里面的 pom.xml会丢失目录结构。以下是我每次都要改的三个地方。首先是数据库连接jdbc.propertiesjdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/campus_order?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456参数说明characterEncodingutf8必须放在 URL 后面否则中文入库变成问号。如果 MySQL 是 8.0driver 改成com.mysql.cj.jdbc.Driver并且 URL 里的serverTimezone必须有否则驱动只会随机时区。端口默认 3306如果你用的面板工具比如 phpstudy、小皮面板注意可能被占用或被改到 3307。再改包扫描路径。在spring-mvc.xml里看这两行context:component-scan base-packagecom.campus.controller/ mvc:annotation-driven / bean classorg.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping/base-package改成压缩包里实际的包名。很多压缩包直接打包了别人学校的学生名字包名是com.example或者com.其他人的名字不改成自己电脑上的包路径会导致 Controller 扫描不到访问任何接口都是 404。这也是新人最容易“以为项目坏了”实际是包名没对齐的地方。第三个改的是 Tomcat 中的 artifact 名。IDEA 里 Deploy 的栏里Application context 通常会是/ssm_war_exploded每次请求都要带长尾巴特别难受。我一般是右键 Tomcat 配置把 Application context 改成/这样小程序端的 baseUrl 就可以直接写http://localhost:8080/不需要处理 contextPath。3.3 初始化数据库执行 .sql 文件与默认管理员账号数据库推荐用命令行导入比 Navicat 的图形导入更贴近脚本里自带的字符集定义。打开终端mysql -u root -p campus_order /path/to/campus_order.sql如果没有数据库要先建库注意字符集mysql -u root -p -e CREATE DATABASE campus_order DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;导入后检查一下sys_user表通常默认管理员是admin / 123456但很多压缩包里会写死 md5 后的密码例如21232f297a57a5a743894a0e4a801fc3对应admin。如果你想自己造数据先看表结构里密码字段是多少位32 位一般是 md5直接用你本机的 MD5 工具生成一个。这一步一定要做因为“小程序登录失败”这个坑一半原因是密码字段格式不对。3.4 小程序端跑起来在微信开发者工具里配置 request 合法域名和 IP小程序端解压后在微信开发者工具的“导入项目”中选择小程序端文件夹。注意导入时 AppID 可以用“测试号”。如果要求账号密码就用微信扫码登录。关键操作是在app.js里把全局 baseUrl 改成你电脑上的局域网 IP 或localhost// app.js App({ globalData: { baseUrl: http://localhost:8080/ } })在开发阶段这样写一般够用。但微信开发者工具默认对http://localhost是允许的对局域网 IP 会提示“不在 request 合法域名列表”。解决方法是在开发者工具右上角“详情-本地设置”里勾选“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”。这个勾选在真机调试时需要重新开启用同样的方式。然后打开用户登录页如果你能看到菜品列表加载出来说明后端和小程序已经通了。如果加载是空的先回来看 3.2 里的包名和 3.3 里的表名十次有九次是这些问题不是代码错了。3.5 验证最小闭环用户登录-浏览-下单登录成功后在菜品页点一个加号购物车角标加一去购物车页点击“去结算”后端会生成订单并把库存扣掉。这一步能跑通就说明 Controller、Service、Mapper 三个层都活了。我用一个 curl 命令快速验证订单接口curl -X POST http://localhost:8080/order/create \ -H Content-Type: application/json \ -H token: 你的登录token \ -d {cartIds:[1,2],address:12栋203}正常的返回是{code:0,data:{orderNo:20250614235941001}}。如果返回 500去 Tomcat 的 logs 目录看catalina.out最后一屏基本是 SQL 异常或类型转换错误。注意如果用 IDEA 控制台打印了 SQL可以用log4j.properties打开 MyBatis 的 log但正式部署记得把 SQL 日志关掉。4. 核心接口与参数购物车、订单状态机与支付回调的 5 个必调项4.1 购物车接口为什么要用数据库存并发减库存怎么处理很多人一开始图省事把购物车放在小程序端的 storage 里后端只保留菜品数量和价格。这在单人演示时没问题一遇到多人同时下单就会出现不一致购物车数据在用户换设备后丢失后端无法核对价格结算时重新查数据库又可能价格已经改。所以还是建议用数据库存cart表而不是纯前端。减库存是订餐项目里最容易出 bug 的点。常见错误是“查一下库存判断够不够然后执行 update dish set stock stock - 1”。表面看没有错但对同一道菜高并发下两步之间会被别人插入导致库存变负数。我一般会用 MyBatis 写一条带条件的 updateupdate idreduceStock UPDATE dish SET stock stock - #{quantity} WHERE id #{dishId} AND stock #{quantity} /update如果返回值是 0说明库存不够Service 层直接抛业务异常。这个原子操作比用乐观锁的 version 字段更省事也足够应付课程设计级别的并发。注意 SQL 中必须包含stock #{quantity}否则仍会出现超卖。4.2 订单状态机待支付→已支付→已接单→配送中→完成订单状态不要只放一个 String 字符串比如待支付在小程序端显示没问题但后端做统计要 group by 状态字符串开销大还容易打错。用 TINYINT 加字典表的方式前端展示时映射中文。下面是状态流转的校验代码public OrderStatus next(OrderStatus current, OrderAction action) { switch (action) { case PAY: if (current WAIT_PAY) return PAID; break; case ACCEPT: if (current PAID) return ACCEPTED; break; case DELIVER: if (current ACCEPTED) return DELIVERING; break; case COMPLETE: if (current DELIVERING) return COMPLETED; break; case CANCEL: if (current WAIT_PAY) return CANCELED; break; } throw new BusinessException(非法状态跳转 current - action); }这个枚举式的状态机比在 Controller 里写四层 if 要清楚得多。状态机的好处是每次订单更新都必须在orders表加一个旧状态到新状态的记录方便前后端对齐而不是直接改 status 字段。如果你拿到手的压缩包里没有这个枚举我推荐你自己补上否则后面接支付回调时会出现同一条订单被重复支付后状态错乱。4.3 支付回调模拟支付与签名校验校园订餐的支付基本都是模拟支付但“模拟”也要有回调流程。前端调起支付接口后端返回一个支付参数比如二维码链接或直接 mark 已支付然后客户端轮询或后端主动回调。常见做法是后端提供一个payMock接口直接修改订单状态为已支付同时让前端跳到支付成功页。这样课程设计演示足够但如果要做到像真实小程序支付就要用微信支付服务商的参数。这里给一个模拟支付的安全坑不要在后端接口里让客户端直接传orderId并把订单改成已支付因为用户可以随便调用。即使不是真实微信支付也至少加一个一次性支付 tokenPostMapping(/pay/mock) public JsonResult mockPay(RequestParam(orderNo) String orderNo, RequestParam(payToken) String payToken) { // 从 Redis 或内存 cache 中校验 payToken 是否与 orderNo 配对用后即删 if (!payTokenCache.containsKey(orderNo) || !payTokenCache.get(orderNo).equals(payToken)) { throw new BusinessException(非法支付 token); } orderService.pay(orderNo); payTokenCache.remove(orderNo); return JsonResult.ok(); }参数说明payToken是后端在创建订单时生成的随机串写入缓存只能使用一次。orderService.pay内部用SELECT ... FOR UPDATE锁住订单行避免并发重复支付。如果压缩包里没有这个 cache至少把pay方法加上synchronized或数据库行锁不然重复点击支付按钮会出现两条已支付记录。4.4 分页加载列表页“加载更多”的 page/size 参数与总数适配小程序端下拉到页面底部会触发onReachBottom这是微信原生 API。后端如果不做分页菜品多了以后首屏会卡死而且图片加载特别耗流量。常规做法是定义请求参数public class PageQuery { private Integer page 1; private Integer pageSize 10; public Integer getOffset() { return (page - 1) * pageSize; } }然后在 MyBatis mapper 中使用 limit 参数select idselectDishList resultTypecom.campus.entity.Dish SELECT * FROM dish WHERE status 1 ORDER BY id DESC LIMIT #{offset}, #{pageSize} /select注意MyBatis 的#{}会自动加单引号但LIMIT后不能加引号所以用Integer类型传参不要用字符串拼接。前端loading more时要判断返回列表长度是否小于 pageSize小于就停止加载避免发无意义请求。offset必须在PageQuery里算好不能在 service 里传两个参数又忘了减一。4.5 常用注解与参数校验RequestBody、PathVariable、ValidatedSSM 项目里 Controller 的参数绑定是常出问题的地方。RequestBody用于接收 JSONPathVariable用于获取 URL 路径上的 id。下面这个例子把两个组合起来PostMapping(/dish/{id}/update) public JsonResult updateDish(PathVariable(id) Integer id, RequestBody Validated DishUpdateDTO dto) { dto.setId(id); dishService.update(dto); return JsonResult.ok(); }Validated配合 DTO 上的NotNull(message 菜品名称不能为空)使用Controller 里不需要再写一行if (name null)。SpringMVC 会自动绑定错误并抛出MethodArgumentNotValidException然后在全局异常处理器里统一返回 JsonResult。这是 SSM 里一个很值得提的“参数落位”细节很多压缩包里的老代码都是用HttpServletRequest挨个取值改造成 DTO 后维护效率高很多。5. 避坑SSM 校园订餐小程序最常见的 6 个翻车现场这一章我按真实翻车频率排序每一条都来自跑通这个压缩包时最常踩到的坑。5.1 现象小程序请求接口一直 404但 Postman 能通这种问题大多是后端 contextPath 或包扫描配置不对。Postman 能通说明接口其实存在开发者工具里 404 说明路径里少了 contextPath比如后端部署在/ssm下小程序里只写了/order/create。解决办法在app.js里统一拼上 contextPath或者干脆把 Tomcat 的 Application context 设成/两边都清爽。还有一个隐蔽原因spring-mvc.xml里的包名写的是com.xxx.controller而你实际代码放在com.yyy.controller扫描不到Controller 等于没注册。检查时看 Tomcat 控制台启动时是否打印了 RequestMappingHandlerMapping并带出接口路径。5.2 现象录入中文菜品名称MySQL 保存成问号数据库、表、连接 URL 三层字符集有一环不对就会这样。常见做法是三个都统一成 utf8mb4。建表时把 TABLE 属性也改成 utf8mb4连接 URL 加characterEncodingutf8并确认 MySQL 服务端配置项character_set_server为 utf8mb4。这里我不建议只改jdbc.url因为如果 SQL 脚本里的建表语句写的是DEFAULT CHARSETlatin1建好的表仍会保留 latin1你必须重新ALTER TABLE dish CONVERT TO CHARACTER SET utf8mb4;后才能生效。5.3 现象同一个订单重复扣库存没有事务或没有行锁导致。两个请求同时读到库存 5分别扣 2最后写回 3造成卖超。解决方法就是前面说的UPDATE dish SET stock stock - #{quantity} WHERE id#{id} AND stock #{quantity}。如果这个 SQL 返回 0订单就不能创建。还有一个隐蔽问题如果你只在 Service 方法上写了Transactional但类没有被 Spring 管理事务不会生效检查类上是否有Service注解。5.4 现象微信开发者工具不能发送请求显示“不在以下 request 合法域名列表中”标准解决步骤是在开发者工具右上角“详情-本地设置”里勾选“不校验合法域名”。但这个选项只对开发工具生效真机预览时依然需要你在后台配置 request 合法域名而且必须是 HTTPS。开发阶段可以用“预览”扫二维码调试把 BaseUrl 改成电脑局域网 IP同时让手机和电脑在同一 Wi-Fi。如果你用的是云服务器就直接用服务器的公网 IP如果只有本地 IP别忘关闭电脑防火墙或放行 8080 端口。5.5 现象修改了后端 Java 代码前端调用还是老逻辑Tomcat 没有热部署或没重新部署 war 包。SSM 项目在 IDEA 里用 debug 模式启动有时修改 Controller 后需要Build - Recompile或重启 Tomcat。建议不要开 “Build project automatically”太频繁的重启会让你分不清到底是代码没保存还是环境问题。我自己的习惯是每改一次后端代码手动重启 Tomcat再用 curl 验证一下对应接口确认没问题后再到小程序上看效果省得两边同时怀疑。5.6 现象小程序页面列表下拉加载更多时数据重复或缺失分页参数边界不对。常见的是第 2 页跟第 1 页重叠因为前端把 page 从 0 开始后端从 1 开始或者offset计算少减了一个页大小。解决后端返回total总数前端判断pageSize * page total时停止。同时把onReachBottom里加if (this.data.loading) return防抖。这里有一个血泪经验onReachBottom在用户快速滚动时会连续触发没有防抖的话你会在数据库日志里看到一堆重复的 SELECT。6. 进阶用 Charles 抓包调试小程序请求附缓存与并发优化技巧6.1 用 Charles 抓包定位前后端对接问题Charles 抓包是排查小程序请求问题最直接的手段。微信开发者工具自己也能看 network但真机上的流量导入 Charles 能看到更多细节。设置方法是电脑打开 Charles顶部 Proxy 开启 SSL 代理并设置*对所有域名解密手机手动代理到电脑 IP:8888然后安装 Charles 根证书到手机接着小程序里发起一个请求Charles 会列出完整的 request header 和 response body。这样当后台返回一堆奇怪的 JSON 时你不用靠 log 猜直接能看到状态码、具体报错和耗时。注意抓包时如果接口走 HTTPS证书信任不了是常见问题在手机上把“证书有效期校验”关掉或者用开发者工具的“真机调试”替代。6.2 缓存与并发让订餐与支付环节更稳除了抓包我还建议你在交付前把小程序的请求缓存做好。菜品列表不经常变动可以在用户第一次进入后缓存到 storage设置 5 分钟过期这样刷新时先显示缓存再请求网络。后台那层缓存如果压缩包里没有完全可以不加用内存 Map 做简单缓存也行但要记得设过期时间防止内存涨满。订单并发方面前面已经说过 SQL 原子扣库存再补充一点创建订单时把订单主表插入和明细插入放在同一个 Service 方法中用Transactional包裹同时给orders表加索引idx_user_id_status(user_id, status)这样统计“我的订单”时WHERE user_id? AND status?能走索引不会全表扫。最后给你一个我的习惯每次拿到「基于SSM的校园订餐小程序.zip」第一件事不是运行代码而是把jdbc.properties和app.js的请求路径通读一遍花十分钟把环境变量对齐。所有的运行问题百分之八十是路径、版本、字符集这三个黑匣子。希望帮到你。本文还有配套的精品资源点击获取