简介:一套针对Java毕业设计场景的SSM+微信小程序健身小助手项目包,面向需要完整可运行课题源码的高校学生及课程设计者,业务围绕健身管理展开,适合参考学习前后端分离的小程序开发模式。该项目已完成导师审核并通过答辩,评审分达97分,在Windows10/11环境中严格调试,部署包内含数据库初始化SQL、环境配置文件与启动脚本,下载后按文档说明即可运行。资源共1066个文件,压缩包32.55MB,主要类型包括Java后端源码、Vue管理端页面、小程序wxml/wxss结构、SQL数据库文件,以及答辩PPT、开题报告、任务书、使用文档、演示视频等全套资料;png/svg图片与图标用于界面展示,xml/yml等配置用于参数调整,整体目录分层清晰,便于按模块查阅和二次开发。目前已有160人学习下载,读者可据此快速理解健身类小程序的功能组织、数据库设计以及移动端与后端联调方法,是毕业设计开题、答辩与项目落地的优质参考。
1. 健身小助手这种"高分优秀"毕设包,打开之后第一步不是看代码
很多同学拿到 SSM + 微信小程序 + 源码文档演示视频一应俱全的健身小助手项目时,第一反应是赶紧翻代码,然后就被几十个类文件和一堆 XML 配置劝退。实际上标题里真正值钱的不是"健身"这两个字,而是 SSM 后端与小程序前端之间那套完整的联调链路,以及源码、数据库、开题报告、PPT、使用文档、演示视频组成的可验证闭环。这个方向能解决两类真实问题:一是毕设阶段需要一个既能现场演示、又能讲出业务深度的选题;二是你有 Java 基础但没完整做过小程序前后端联调,想找一个稳的骨架来改业务。它适合正在准备 Java 方向毕业设计的学生,也适合想快速建立 SSM + 小程序完整认知的开发者。这篇笔记直接按"拆结构 → 跑通 → 改业务 → 防踩坑 → 准备答辩"的顺序来写,照着走能省下至少一周的弯路。
2. 拆解项目骨架:SSM 后端、MySQL 业务表、小程序前端的分工与边界
拿到项目包,不要急着启动,先把三层结构看清楚。健身小助手这种项目,代码量不大,但结构是否清晰直接决定你后面改业务和答辩的难度。
2.1 后端三层到底怎么分:Controller 只做转发,Service 写业务,Mapper 只碰 SQL
SSM 三个框架的分工常被一句话带过:Spring 管对象、SpringMVC 管请求、MyBatis 管数据库。但真正打开源码之后,层与层之间的边界才是决定这个项目能不能讲清楚的地方。常见做法是按 controller / service / mapper 三层切包;我也见过不少翻车案例,把查询逻辑直接写在 Controller 里,一个方法堆两三百行,答辩时导师一问"事务怎么控制""异常在哪处理"就接不上话。
一个典型的打卡请求流转是这样的:小程序 wx.request 把 JSON 数据 POST 到 /api/checkin/add → DispatcherServlet 分发到 CheckinController → Controller 调用 CheckinService 接口的实现类 → Service 里做参数校验、重复打卡判断、事务控制 → 通过 CheckinMapper 接口操作数据库 → 结果逐层返回。这里的关键约束是:Controller 不写 SQL,也不处理事务,只负责拿参数、调服务、返回统一 Result 对象;事务注解 @Transactional 只出现在 Service 实现类上。这样拆的好处是业务变更时只改 Service 和 Mapper,接口路径不动,小程序端也不用跟着改。
如果你拿到的项目里把 Mapper 的查询结果直接返回给前端,要格外注意:实体类通常带着级联关系,直接序列化容易把关联对象整串带出去,甚至会触发 Jackson 的无限递归(这个问题第 5 章单独讲)。更稳的做法是 Controller 返回 VO(视图对象),把实体类留在服务层内部。能主动说出"为什么不用实体类直接返回"的人不多,这是答辩时一个很好的加分点。
2.2 数据库不是四张表的事:用户、打卡、计划、身体数据的字段设计与关联
健身小助手这类业务,核心表跑不掉这几类:用户表(绑定微信 openid)、打卡记录表、健身计划表。身体数据看似单独一张表,但对这个量级的项目,冗余在用户表里更实用——身高体重本身就变化缓慢,用户每次打开首页需要的是最新值,没必要为了所谓的"规范化"拆一张历史曲线表出来,只有当你打算做体重周趋势图时,才值得单开 body_record 表。设计的关键不是表多,而是字段粒度是否经得起提问。
CREATE TABLE `user` ( `id` INT NOT NULL AUTO_INCREMENT, `openid` VARCHAR(64) NOT NULL COMMENT '微信登录唯一标识', `nickname` VARCHAR(32) DEFAULT '健身用户', `gender` TINYINT DEFAULT 0 COMMENT '0未知 1男 2女', `height` DECIMAL(5,2) DEFAULT NULL COMMENT '身高cm', `weight` DECIMAL(5,2) DEFAULT NULL COMMENT '体重kg', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_openid` (`openid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';openid 是微信小程序登录后由微信接口返回的字符串,同一用户对同一小程序是固定不变的,所以必须加唯一索引。身高体重用 DECIMAL 而不是 FLOAT,因为 FLOAT 在比较和展示时容易出现精度尾差,DECIMAL(5,2) 能存 0.00 到 999.99,对体重和身高完全够用。create_time 用 DATETIME 并默认当前时间,减少业务代码里手动 new Date() 的重复劳动。
CREATE TABLE `checkin` ( `id` INT NOT NULL AUTO_INCREMENT, `user_id` INT NOT NULL COMMENT '关联user.id', `plan_id` INT DEFAULT NULL COMMENT '关联plan.id,可为空', `type` TINYINT NOT NULL COMMENT '1跑步 2力量 3瑜伽', `duration_min` INT DEFAULT 0 COMMENT '运动时长(分钟)', `calories` INT DEFAULT 0 COMMENT '消耗卡路里', `status` TINYINT DEFAULT 0 COMMENT '0正常 1已撤销', `checkin_date` DATE NOT NULL COMMENT '打卡日期', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_user_date` (`user_id`, `checkin_date`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='打卡记录表';checkin_date 用 DATE 而不是 DATETIME,这是很多人第一次建表容易踩的点:如果存 DATETIME,同一天多次打卡的时间不同,判断"今天是否已打卡"时就得用 DATE(checkin_date) 函数把字段包一层,写了函数的列索引会失效,查询性能打折且写法难看。用一个单独的 DATE 字段,直接等值查询就能命中联合索引 idx_user_date。status 字段用来做撤销而不是直接 DELETE 记录,这样统计报表里能保留历史,并且撤销可追溯。
CREATE TABLE `plan` ( `id` INT NOT NULL AUTO_INCREMENT, `user_id` INT NOT NULL, `goal` TINYINT NOT NULL COMMENT '1减脂 2增肌 3保持', `week_count` TINYINT DEFAULT 3 COMMENT '每周计划训练次数', `status` TINYINT DEFAULT 1 COMMENT '1进行中 2已完成 3已放弃', `start_date` DATE NOT NULL, `end_date` DATE DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_user_status` (`user_id`, `status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健身计划表';计划表的关键在于 status 的流转必须由后端 Service 控制,而不是小程序端随便传。常见做法是一个轻量的状态机:进行中 → 打卡次数达标 → 已完成;超过计划结束日期仍未达标 → 已放弃。字段只是存状态,状态怎么变由后端逻辑说了算,这点答辩时能展开讲两分钟。
2.3 小程序端不是只看页面:目录结构、请求封装与登录态的打开方式
小程序端最怕的是每个页面都直接 wx.request 裸调,路径写死、错误处理重复、token 手工拼。拿到项目先看 utils 或 api 目录,通常这里会有一个封装好的 request 方法,负责拼接 baseURL、注入 token、统一处理业务码和 401。
// utils/request.js const BASE_URL = 'http://192.168.1.100:8080'; // 开发环境用电脑局域网IP function request(path, method, data) { return new Promise((resolve, reject) => { const token = wx.getStorageSync('token'); wx.request({ url: BASE_URL + path, method: method || 'GET', data: data || {}, header: { 'Content-Type': 'application/json', 'token': token || '' }, success: (res) => { if (res.data.code === 0) { resolve(res.data.data); } else if (res.statusCode === 401) { // token 过期或失效,清掉并跳登录页 wx.removeStorageSync('token'); wx.reLaunch({ url: '/pages/login/login' }); } else { wx.showToast({ title: res.data.msg || '请求失败', icon: 'none' }); reject(res.data); } }, fail: () => { wx.showToast({ title: '网络连接失败', icon: 'none' }); reject(new Error('network error')); } }); }); } module.exports = { request };这里 BASE_URL 用 192.168.x.x 是关键:开发者工具里可以用 localhost,但真机预览时手机上的 localhost 指向手机自己,必然连不上电脑上的后端。请求被包成 Promise 后,页面里用 async/await 读代码会清爽很多。注意一个容易误判的细节:wx.request 只要请求发出去了,即使 HTTP 状态码是 401,也会走 success 回调而不是 fail,所以 401 的判断必须放在 success 里通过 res.statusCode 做分支,我见过有人把它放 fail 里,导致登录过期后永远没有跳转。
登录态这一环,小程序和普通网页登录不同:前端 wx.login 拿到临时 code 发给后端,后端拿 code 向微信接口换 openid,再生成自己的 token 返回前端。前端把 token 存进 storage,后续请求在 header 里带 token 识别用户。token 过期后的 401 统一处理放在 request 封装里,而不是每个页面各写一遍。
3. 把项目跑起来:环境搭配、数据库导入、双端联调的完整命令
如果说拆结构是"看懂",那跑通就是"落地"。这一章照着做,目标是在 30 分钟内让后端和小程序同时在你的机器上工作。
3.1 环境版本怎么搭才不会在启动时翻车
健身小助手是典型的老牌技术栈组合,版本搭配有一个稳的配方,我一般建议照这个来,不要追求新版本:
| 组件 | 推荐版本 | 备注 |
|---|---|---|
| JDK | 1.8 / 8 | SSM 项目在 JDK 8 下编译最稳,JDK 17 会碰到强封装报错 |
| Maven | 3.6.3 | 3.8+ 对仓库源有调整,个别私服镜像配置会失效 |
| Apache Tomcat | 8.5 | Spring 5 + Tomcat 9 也行,但 8.5 是最少折腾的组合 |
| MySQL | 5.7 | 8.0 也能用,但驱动要换 mysql-connector-java 8.x,注意时区参数 |
| 微信开发者工具 | 最新稳定版 | 只影响前端编译,基本无兼容问题 |
这个表格不是玄学,是有实际原因的。SSM 项目大多基于 Spring 5.x、MyBatis 3.5.x 这一代依赖,它们对 JDK 8 的支持最完整;JDK 11 也能跑,但反射相关的强封装在 JDK 17 会直接抛异常。数据库方面,MySQL 8 默认驱动类名从 com.mysql.jdbc.Driver 改成了 com.mysql.cj.jdbc.Driver,如果项目里的 jdbc.properties 还写着旧类名,启动必然报 class not found。所以拿到项目先核对这几处再动数据,别一上来就怀疑代码。
3.2 导入数据库和修改配置:最容易被忽略的三个位置
第一步不是启动 IDEA,而是把数据库建出来。项目里通常带一个 .sql 文件,用命令行导入:
mysql -uroot -p123456 -e "CREATE DATABASE IF NOT EXISTS fitness DEFAULT CHARSET utf8mb4" mysql -uroot -p123456 fitness < fitness.sql命令行导入的好处是编码可控。用可视化工具导入时,如果不注意选择 utf8mb4,经常出现表建出来了、中文注释全变问号的情况。导入后先验证核心表是否存在:
SHOW TABLES;然后改配置文件。SSM 项目的数据库配置一般在 jdbc.properties 或 application.properties:
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/fitness?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=123456这里三个位置必须检查:driver 是否与 MySQL 版本匹配(MySQL 8 要换 com.mysql.cj.jdbc.Driver,记得同步升级 mysql-connector-java 依赖);url 是否带 characterEncoding=utf8,缺了中文写入必乱码;serverTimezone 是否设置,MySQL 8 在某些时区配置下会在连接时报错。数据库密码只要包含特殊字符,在 properties 里就得做转义处理,省事起见直接把密码改成简单的数字字母组合。
最后是 Maven 导入。用 IDEA 打开项目顺序错了会很难受:先等右下角 Maven 依赖下载完成,再配置 Tomcat。配置路径是 Run Configurations → 新增 Tomcat Server → Local → Deployment 里把项目的 exploded artifact 加进去,然后启动。启动成功的标志不是控制台开头那段 Spring 日志,而是最后一行类似 "Deployment of web application archive ... has finished"。如果卡在这一步,先确认 Tomcat 版本和项目 web.xml 头部声明是否匹配:web 3.x 对应 Tomcat 7 以上,web 4.x 需要 Tomcat 9,声明和容器对不上会启动时报错。
3.3 小程序端联调:开发者工具三个设置一次搞定
后端起来了,小程序端打开项目目录,依次做三件事。
第一件,在微信开发者工具里关掉域名校验:详情 → 本地设置 → 勾选"不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书"。开发环境走 http 和局域网 IP,不勾这一项,请求直接 fail,页面永远白屏,而且不会告诉你具体原因。
第二件,改 BASE_URL 为电脑的局域网 IP:
const BASE_URL = 'http://192.168.1.100:8080';怎么拿到这个 IP?Windows 命令行执行 ipconfig,找到无线局域网适配器或以太网适配器下的 IPv4 地址;手机和电脑连同一个 WiFi,就能真机预览。这里最常见的问题不是 IP 填错,而是路由器开了 AP 隔离,手机和电脑互 ping 不通,表现就是网络层完全连不上,后面避坑章节还会提。
第三件,先验证后端接口独立可访问。在浏览器直接访问 http://192.168.1.100:8080/api/user/info 这类 GET 接口,能看到 JSON 返回,说明后端网络层是通的,问题就只在小程序配置侧。然后再打开开发者工具的 Network 面板,点一次登录请求,确认状态码和响应体。联调阶段的核心原则是:先证明后端独立可访问,再去怀疑小程序端。这个顺序能少排查一半问题。
4. 健身业务怎么落地:打卡防重、计划推荐、统计聚合三块核心代码
项目跑通只是开始,真正决定你能不能讲清楚业务深度的是这三块核心代码。它们恰好也对应答辩时最容易被追问的三个问题。
4.1 打卡接口的幂等设计:同一用户一天只能打一次卡
健身小助手最核心的业务动作是打卡。这个接口看着简单,但直接往表里 insert 一定被导师问住:用户手抖点了两次怎么办?网络重试呢?所以实现上要先查后插,把"重复"的判断放在 Service 层,同时配合数据库唯一索引兜底。
@Service public class CheckinServiceImpl implements CheckinService { @Override @Transactional(rollbackFor = Exception.class) public Result add(CheckinDTO dto) { // 1. 按用户和当天日期查是否已有记录 LocalDate today = LocalDate.now(); Checkin exists = checkinMapper.selectByUserAndDate(dto.getUserId(), today); if (exists != null) { return Result.fail(1001, "今天已经打过卡,不要重复提交"); } // 2. 构建记录并插入 Checkin checkin = new Checkin(); checkin.setUserId(dto.getUserId()); checkin.setType(dto.getType()); checkin.setDurationMin(dto.getDurationMin()); checkin.setCalories(dto.getCalories()); checkin.setCheckinDate(today); return checkinMapper.insert(checkin) > 0 ? Result.ok() : Result.fail(1002, "打卡失败"); } }这个实现有三个要点。第一,LocalDate.now() 用的是服务器时区,如果日后部署的服务器和客户端跨时区,会出现客户端显示是今天、服务端判定是昨天的情况,联调时把时区对齐成 Asia/Shanghai。第二,@Transactional 加在 Service 方法上,保证"查重 + 插入"两步同生共死,防止查重通过但插入异常时留下半截业务状态。第三,返回码设计成业务码 1001 和 1002 而不是 HTTP 500,前端拿 code 判断业务失败,拿 statusCode 判断网络失败,两者分开处理才不会被一个弹窗糊弄过去。
对应的 Mapper XML 里,查重语句要只查必要字段:
<select id="selectByUserAndDate" resultType="com.example.entity.Checkin"> SELECT id, user_id, type, duration_min, calories, status, checkin_date FROM checkin WHERE user_id = #{userId} AND checkin_date = #{date} LIMIT 1 </select>注意这里没有查 create_time,也没有查 plan_id。查询列只带回业务需要判断的字段,能避免 MyBatis 去触碰关联对象,省掉一次可能的懒加载触发点。检查一下 #{date} 传的是 java.time.LocalDate,MyBatis 会直接映射到 DATE 列,不需要额外转换。
考虑到并发场景,Service 层的先查后插存在一个极小的竞态窗口:同一用户连点两次,两个请求同时通过判空,然后各自插入一条。到最后严格兜底的是表上的联合唯一索引,这里我建议给 checkin 表再加一个UNIQUE KEY uk_user_date (user_id, checkin_date),如果项目里没加,自己补上,在答辩时主动讲出"接口幂等靠业务判断,数据一致性靠唯一索引兜底",这句话能让评委看到你理解到位的分层防线。
4.2 计划推荐:先做 BMI 分层,再按目标过滤,别一上来就谈算法
计划推荐是项目里最有"业务深度"的部分,也是最容易被过度设计的地方。一个毕设体量,不需要引入规则引擎或者跑来跑去的模型,把推荐逻辑清晰写在 Service 层的 if-else 里,反而最好讲、最好维护。
public List<PlanVO> recommend(Long userId) { User user = userMapper.selectById(userId); // 1. 计算 BMI = 体重(kg) / (身高(m)^2) double heightM = user.getHeight() / 100.0; double bmi = user.getWeight() / (heightM * heightM); // 2. 按国内常用参考线分层,决定目标类型 int goal; if (bmi >= 24) { goal = 1; // 偏重,推荐减脂 } else if (bmi < 18.5) { goal = 2; // 偏瘦,推荐增肌 } else { goal = 3; // 正常,推荐保持 } // 3. 排除用户已创建的计划,避免重复推荐 return planMapper.selectByGoal(goal, user.getId()); }BMI 大于等于 24 归为偏重、低于 18.5 归为偏瘦,这是国内常用的参考线。代码里写常量没问题,但答辩时最好主动说一句"这个阈值目前是常量,后续可以改造成配置表或写成系统参数"。这句主动陈述比贴十行代码更能展示设计意识。selectByGoal 的第二个参数传当前用户 id,目的是在 SQL 里排除已创建的计划,避免用户反复收到同一套推荐。
SQL 侧对应如下:
SELECT id, goal, week_count, start_date, end_date FROM plan WHERE goal = #{goal} AND user_id != #{userId} ORDER BY RAND() LIMIT 3这里 ORDER BY RAND() 在数据量很小的场景下没问题,能实现"每次进去推的内容不完全一样"的效果,足够演示。数据量上千以后 RAND() 会全表扫,但在这个项目的体量里完全不需要为此改成复杂的随机策略——这就是我要强调的"别过度设计":方案要和业务量级匹配,讲清楚取舍,比盲目堆技术更能说服人。
4.3 统计报表:一条 SQL 聚合出一周的运动量
统计页是演示效果最好的页面,它把打卡表按类型和日期聚合,前端画成柱状图。
SELECT type AS exercise_type, COUNT(*) AS total_times, SUM(duration_min) AS total_minutes, SUM(calories) AS total_calories FROM checkin WHERE user_id = #{userId} AND checkin_date BETWEEN #{startDate} AND #{endDate} AND status = 0 GROUP BY type ORDER BY total_minutes DESC这条 SQL 有三个值得对导师讲清楚的决策点。第一,BETWEEN 两端是 DATE 类型,所以边界是包含当天 00:00 到 23:59:59 的完整一天,这也是前面建表把 checkin_date 单独定为 DATE 字段的原因——如果这里用 DATETIME,统计周报时得小心处理下边界。第二,status = 0 把撤销记录排除在统计外,既保留了数据,又不污染报表。第三,GROUP BY type 之后的结果直接给前端渲染,Java 层不需要再做二次聚合。
如果项目里还需要按周、按月汇总,常见做法是在 Service 层根据传入的 startDate 和 endDate 调同一个方法,前端用日期选择器控制范围。至于要不要用存储过程或者物化视图,对这个演示场景完全没必要,反而会让导师觉得你把简单问题复杂化了。
5. 避坑指南:跑通健身小助手最常见的 5 个坑
这一章不是理论推导,是照着项目一步步跑才能真正遇到的坑。每一条都按"现象 → 原因 → 解决"来讲。
5.1 小程序请求一直 fail,后端却什么都没收到
现象:开发者工具里点"打卡",请求一直 fail,后端控制台没有任何日志,浏览器直接访问接口却正常返回 JSON。
原因:绝大多数情况是开发者工具的域名校验没有关闭,或者请求地址不是局域网 IP。开发环境的 http 接口和 IP 直连都会被微信的合法域名校验拦下,表现就是 fail,且不返回具体原因,很容易被误判成后端代码问题。
解决:勾选"不校验合法域名"后重新编译,同时确认 BASE_URL 是电脑的局域网 IP。真机预览时,手机必须和电脑在同一局域网。想确认是不是域名校验问题,可以把 BASE_URL 临时改成 https 的公网地址测一次,能通就说明问题出在校验配置,不是代码逻辑。
5.2 数据库中文乱码:字符串存进去变成问号
现象:用户昵称、计划名称等中文数据,在小程序端输入正常,写入数据库后就变成 ?? 或乱码。
原因:两层问题叠加。一是数据库连接串没带 characterEncoding=utf8,二是表或库的字符集不是 utf8mb4。只改其中一处,问题依旧。
解决:先确认连接串,再确认表结构。connection 串要带useUnicode=true&characterEncoding=utf8;建表语句统一DEFAULT CHARSET=utf8mb4。如果表已经建出来,执行下面语句修复:
ALTER TABLE `user` CONVERT TO CHARACTER SET utf8mb4;改完重启后端再测一次。这条的隐蔽之处在于:如果连接串带了对 charset 而表不对,录入时并不报错,只是存入的数据已经损坏,改完表结构后旧数据需要清理,不要留着。
5.3 启动报 NoClassDefFoundError:依赖冲突比缺依赖更隐蔽
现象:Tomcat 启动时抛 NoClassDefFoundError 或 ClassNotFoundException,但不是每次都出现,偶尔重启又好了。
原因:典型情况是项目 Web-INF/lib 下手动放了旧版本的 jar,同时 Maven 又拉了一份新版本,classpath 里出现重复类,加载顺序又不固定,表现就是时好时坏。SSM 老项目经常会有人图省事把整个 lib 目录拷进去。
解决:把 lib 目录下手动添加的 jar 清掉,依赖全部交给 Maven 管理。检查方法是在 IDEA 的 Maven 面板执行:
mvn dependency:tree看同一个 artifact 是否出现多个版本。如果某个依赖确实不在中央仓库,确保它只在 lib 里存在,pom 里不要再声一次。这个坑排查起来费时间,提前清理能帮你省下一整晚。
5.4 JSON 序列化死循环:返回关联对象直接栈溢出
现象:请求打卡列表时接口返回 500,控制台报 infinite recursion 或 StackOverflowError。
原因:实体类存在双向关联。比如 checkin 实体里有一个 User 对象,User 实体里又有一个 List<Checkin>,Jackson 序列化时 A 引 B、B 引 A,递归到栈溢出。
解决:在关联字段上加 @JsonIgnore,序列化时跳过该侧。
public class User { // 其他字段略 @JsonIgnore private List<Checkin> checkinList; }如果需要返回关联信息,就新建 VO 对象,只放需要的字段。顺手把不该暴露的用户内部字段一起过滤掉,等于补了个安全隐患,答辩时还能顺便讲一句"序列化对象和持久化对象分离"的设计原则。
5.5 token 过期后页面弹一堆报错弹窗
现象:登录态过期后,用户继续操作,每个页面弹一次"请求失败",接口返回 401,体验非常差。
原因:对 401 的处理逻辑散落在各个页面,每个页面单独 showToast,没有统一的过期跳转。
解决:按 2.3 的做法,在 request 封装里做统一拦截:statusCode 为 401 时,清除本地 token,wx.reLaunch 到登录页,页面层只处理业务错误码。改完验证一次:手动把 storage 里的 token 改错,操作任意接口,应该只发生一次跳转,而不是满屏报错弹窗。
6. 从"能跑"到"高分答辩":演示路径和两个加分验证技巧
项目能跑通只是及格线,答辩时导师想听的是"你为什么这么设计"和"你怎么证明它能工作"。这里给你两个现成的验证技巧。
第一个是数据对比演示。答辩前准备一个专门的演示账号,提前录入 3 到 5 天的打卡数据,答辩现场操作小程序再新增一条打卡,然后切到数据库执行:
SELECT checkin_date, type, duration_min, calories FROM checkin WHERE user_id = 1 ORDER BY checkin_date DESC;屏幕左边是小程序提交页面,右边是数据库查询结果,现场新增的记录实时出现在第一行。这个动作比PPT上十页截图都有说服力,它直接证明了接口链路是通的、数据落库是真实的,而不是录好的演示视频或者写死的 mock 数据。
第二个技巧是主动讲"为什么这样建表"。答辩时把三件事说清楚:checkin_date 用 DATE 而不是 DATETIME 的业务理由,status 字段保留撤销记录的意义,openid 唯一索引的必要性。这三点背下来,十分钟技术问答至少能稳稳接住一半。
演示顺序我一般建议:先展示小程序端完整操作流程,再打开后端控制台实时看日志输出,最后抛数据库查询求证。这个顺序让导师看到的东西从"页面能点"逐层深入到底层存储,覆盖全链路的证据链条。如果项目里有管理端,还可以加一步:在小程序里操作,管理数据列表同步变化,这样前后端联动就更直观。
最后说一点自己的教训。我做类似 SSM 项目的时候,最吃亏的就是只讲"能跑",没准备"为什么"。被导师问了一个很基本的问题——"这个打卡接口并发下会不会出问题"——当场答得支支吾吾。后来每次演示前,我把"重复打卡怎么防""统计为什么不用存储过程""为什么事务加在 Service 层而不是 Controller"这类问题写在一张纸上,对着镜子提前讲一遍。准备过的回答和临场编的答案,"在台下的观感完全是两回事"这句话,我是真信了。希望帮到你。
本文还有配套的精品资源,点击获取