ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SSM框架与微信小程序健身助手:全栈联调与token会话实战

SSM框架与微信小程序健身助手:全栈联调与token会话实战 简介面向计算机专业毕业设计的微信小程序项目基于微信平台打造健身小助手后端采用SSM框架适合作为毕设、课程设计或工程实训的完整参考。资源主体包含小程序前端、管理后台、Java服务端、数据库SQL脚本及毕业论文文档源码经过严格测试可直接运行其中后台页面涵盖首页、密码修改等常见管理模块并提供一键安装、运行、构建的批处理脚本降低了环境搭建门槛。压缩包共1552个文件主要类型包括png图片、js逻辑脚本、vue后台页面、java后端代码以及wxml、wxss等小程序页面与样式文件同时包含json配置、xml映射、数据库相关文件整体大小约27.84MB。目前已有90人学习下载对于希望快速上手健身类小程序开发、理解SSM整合流程或需要毕设项目参考的学习者具有较高的借鉴价值和二次开发空间。1. 微信健身小助手SSM 后端与小程序端联调的架构起点打开小程序点开“训练打卡”页面选一个训练计划输入组数、重量、时长点提交不到 200 毫秒这条记录就写进了 MySQL再下拉一下列表最新一次卧推数据已经按日期排在最前面。这是我拿到这份“微信 285 基于微信平台健身小助手小程序”毕设资源后按 README 把 1-install.bat、2-run.bat、3-build.bat 依次跑完看到的第一个完整闭环。整套东西不是只给你看 UI 的项目而是小程序端、SSM 后端、SQL 脚本、配套论文都齐的毕设级产物项目本身拿过 98 分评审分适合正在做毕业设计或者想完整走一遍“微信端 → 后端接口 → 数据库”链路的人。下面我按后端分层、小程序端请求封装、核心功能联调、批处理脚本和 token 刷新技巧几个方向拆开讲跟着操作就能在自己的电脑上复现。2. SSM 后端分层、数据表设计与 SQL 脚本解读2.1 SSM 三层的职责边界与选型理由SSM 不是一个框架而是 Spring、SpringMVC、MyBatis 三个框架的组合。Spring 管对象生命周期和事务SpringMVC 管 HTTP 请求的路由和参数绑定MyBatis 管 SQL 与 Java 对象之间的映射。在这个健身小助手项目里三层各管一摊Controller 层只接收小程序端传来的 JSON不写业务逻辑Service 层处理打卡、计划增删改查这类业务规则Mapper 层对数据库做持久化操作。很多人在毕业设计里被问“为什么用 SSM 而不是 Spring Boot”一个能站住的回答是SSM 的配置是显式的你能说清楚每一个 Bean 是怎么被 Spring 装配进来的而 Spring Boot 大量依赖自动配置新手往往只会在配置文件里加依赖答不上底层机制。SSM 在配置层面天然逼着你理解 Spring IoC、AOP 事务、Mapper 代理扫描这些基础概念这是它至今仍是毕设高频选型的原因。在工程结构上项目按controller / service / mapper / entity / common分包。common 里放统一返回类和 JWT 工具。资源包里配套文件也印证了这一点后缀.bak的update-password.vue.bak、IndexMain.vue.bak这些是管理后台 Vue 源码的备份说明整个项目除了微信小程序端还带一个维护用的后台页面SSM 后端给两端同时提供接口。这点在写论文画系统架构图时很占优势。2.2 数据表设计用户表、训练计划表与打卡记录表SQL 脚本是整个资源的入口先把它读明白后面所有接口逻辑才有落点。健身助手核心涉及三张表用户基本信息表、训练计划表、训练打卡记录表。以打卡记录表为例它的字段设计能直接看出业务边界字段名类型说明record_idbigint主键自增user_idbigint关联用户表业务查询主条件plan_idbigint关联训练计划允许为空表示自由训练exercise_namevarchar(64)动作名称比如“杠铃卧推”“深蹲”weight_kgdecimal(5,2)重量保留两位小数repsint次数duration_minint时长单位分钟train_datedate训练日期按天统计用record_notevarchar(255)备注比如“最后一组力竭”create_timedatetime创建时间默认当前时间戳train_date单独用 date 类型而不是 datetime是因为列表页和统计页需要按天分组如果用 datetimeSQL 里还得包一层DATE()才能分组全表扫的时候这个函数的开销会放大。weight_kg用 decimal 而不是 float是为了避免浮点精度问题训练数据累计展示时0.1 的误差会被多次计算放大。plan_id是可空的用户完全可能不选计划直接打自由训练卡这个设计比外键强制非空更贴近真实使用习惯。2.3 从 SQL 脚本到本地库导入参数与字符集陷阱拿到.sql文件后我习惯先看文件头的建库语句再决定是直接导入还是手动建库。这份 SQL 里明确指定了utf8mb4字符集注意不是utf8。原因是utf8在 MySQL 里最多存 3 字节而用户备注里如果输入 Emoji 表情健身打卡里经常出现“训练到位”会直接报Incorrect string value错误。导入命令如下mysql -u root -p --default-character-setutf8mb4 fitness_miniprogram.sql--default-character-setutf8mb4参数的作用是让 mysql 客户端以 utf8mb4 编码解析整个 SQL 文件避免文件里的中文注释和预设数据在导入时变成乱码。如果执行过程中报错说Unknown database说明 SQL 里没有建库语句需要先手动建库再指定库文件也可以先打开 SQL 文件检查头几行CREATE DATABASE IF NOT EXISTS fitness_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE fitness_db;如果资源提供的 SQL 里已经有这两行直接执行即可。导入完成后用SHOW TABLES;确认表是否齐全。常见的导入报错集中在表之间的外键依赖顺序上比如先导入了带外键的t_train_record但被引用的t_user还没建立。解决方法是手动在 SQL 文件里把建表顺序调整为“先用户表、再计划表、最后打卡表”或者在每条建表语句后面去掉 FOREIGN KEY 约束、在全部表建完后再统一补外键。3. 微信小程序端请求封装、页面路由与 token 会话保持3.1 小程序目录结构与页面路由配置小程序端是原生微信小程序不是 uni-app 或 Taro 转译的所以目录结构里能看到标准的app.json、app.js、pages/、utils/、components/。app.json里注册了主要页面首页pages/index/index、训练计划页pages/plan/plan、打卡页pages/checkin/checkin、个人中心页pages/mine/mine最底部用tabBar配置了首页、计划、打卡、我的四个入口。一个值得注意的细节是项目里单独抽了utils/request.js而不是在每个页面里直接调用wx.request这个选择直接决定了后面 token 过期刷新好不好做。这里贴一段app.json中 tabBar 的核心配置{ pages: [ pages/index/index, pages/plan/plan, pages/checkin/checkin, pages/mine/mine ], tabBar: { list: [ { pagePath: pages/index/index, text: 首页 }, { pagePath: pages/plan/plan, text: 计划 }, { pagePath: pages/checkin/checkin, text: 打卡 }, { pagePath: pages/mine/mine, text: 我的 } ] } }tabBar的pagePath必须和pages数组里注册的路径保持一致否则真机预览时 tab 不显示。页面高度适配方面自定义导航栏时要注意胶囊按钮的高度通常用wx.getMenuButtonBoundingClientRect()拿到胶囊位置再动态计算顶部占位高度避免自定义头部遮挡页面内容。3.2 请求封装把 wx.request 包装成带 token 的 request 工具所有接口请求统一走utils/request.js这样做最大的收益是登录态管理和错误提示只需要写一次。封装代码如下// utils/request.js const BASE_URL http://192.168.1.100:8080/fitness/api; function request(path, method GET, data {}) { return new Promise((resolve, reject) { const token wx.getStorageSync(token); wx.request({ url: BASE_URL path, method: method, data: data, header: { Content-Type: application/json, Authorization: token ? Bearer token : }, success(res) { if (res.statusCode 200) { resolve(res.data); } else if (res.statusCode 401) { wx.navigateTo({ url: /pages/login/login }); reject(new Error(登录已过期)); } else { reject(new Error(res.data.message || 请求失败)); } }, fail(err) { reject(err); } }); }); } module.exports { request };BASE_URL要改成你自己电脑的局域网 IP注意微信开发者工具必须勾选“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”开发阶段才能访问本地接口。Authorization头用Bearer Token格式是业界常见做法后端在 SpringMVC 拦截器里解析这个头而不是让每个 Controller 手动读 token。这里把 401 统一处理为跳转登录页后续第五节再讲怎么改成静默刷新。3.3 微信登录的 code 换 token 完整链路小程序不能直接拿到用户手机号或密码登录链路是“用 code 换 token”小程序端先调用wx.login拿到一个临时凭证 code后端拿这个 code 去微信接口换 openid再拿 openid 查用户表、签发自定义 token 返回前端。这段逻辑在小程序端的写法是// pages/login/login.js wx.login({ success: async (res) { if (res.code) { const result await request(/auth/login, POST, { code: res.code }); wx.setStorageSync(token, result.data.token); wx.setStorageSync(userInfo, result.data.userInfo); wx.switchTab({ url: /pages/index/index }); } } });后端拿到 code 后调用微信的jscode2session接口换取 openid再生成 token// AuthController.java 核心逻辑 String url https://api.weixin.qq.com/sns/jscode2session?appid appid secret secret js_code code grant_typeauthorization_code;code 的有效期只有 5 分钟且只能使用一次所以前端不要缓存 code每次进入登录页都重新wx.login。后端换回来的 openid 是用户在微信生态内的唯一标识首次登录自动注册用户再次登录更新last_login_time即可。这套链路里最容易翻车的是 appid 和 secret 不匹配真机调试时报40013错误多半是小程序后台的 AppSecret 被重置过需要去 mp.weixin.qq.com 重新生成。4. 健身记录核心功能联调Mapper、Service、Controller 到 WXML 渲染4.1 后端接口设计以“新增训练打卡”为例新增打卡是整个系统使用频率最高的接口。前后端联调时约定统一返回格式{ code: 200, message: success, data: null }HTTP 状态码只表示传输层是否成功业务成功与否看 body 里的code字段。这样做的好处是后端业务异常不需要伪造 HTTP 状态码前端只需要判断 body.code。后端TrainRecordController对应代码如下RestController RequestMapping(/api/record) public class TrainRecordController { Autowired private TrainRecordService trainRecordService; PostMapping(/save) public Result save(RequestBody TrainRecord record, RequestAttribute(userId) Long userId) { record.setUserId(userId); trainRecordService.addRecord(record); return Result.success(); } }RequestAttribute(userId)里的 userId 不是前端传的而是 SpringMVC 拦截器在解析完 token 后塞进 request 的这样 Controller 层永远拿不到原始 token业务代码也不需要关心鉴权细节。新增打卡的请求参数如下参数名类型必填说明planIdLong否关联计划自由训练不传exerciseNameString是动作名称weightKgBigDecimal是重量repsInteger是次数durationMinInteger否训练时长trainDateString是训练日期格式 yyyy-MM-ddrecordNoteString否备注联调阶段最难排查的问题往往不是参数缺失而是前端传的trainDate格式是Fri Jul 12 2025后端用的是yyyy-MM-dd。遇到这种情况不用改代码在 SpringMVC 的applicationContext.xml里配置一个全局日期转换器比在每个 POJO 上加JsonFormat更省事。4.2 Mapper XML 动态 SQL按计划与时间段查询记录打卡记录列表不能每次都查全表用户可能某个月训练了 20 次也可能只练了 2 次。接口要支持按日期范围、按训练计划、按动作名称三个维度的组合筛选。如果为每一种组合写一条 SQL六个组合就是六条后续加一个维度又要翻倍。这份项目在TrainRecordMapper.xml里用了动态 SQL 来解决select idselectRecordList resultTypecom.fitness.entity.TrainRecord SELECT record_id, exercise_name, weight_kg, reps, duration_min, train_date, record_note FROM t_train_record where if testuserId ! null AND user_id #{userId} /if if testplanId ! null AND plan_id #{planId} /if if teststartDate ! null AND train_date gt; #{startDate} /if if testendDate ! null AND train_date lt; #{endDate} /if if testexerciseName ! null and exerciseName ! AND exercise_name LIKE CONCAT(%, #{exerciseName}, %) /if /where ORDER BY train_date DESC, record_id DESC /selectwhere标签会自动去掉第一个多余的AND这是 MyBatis 动态 SQL 最常用的技巧避免了用WHERE 11拼接字符串带来的性能损耗和 SQL 注入风险。gt;和lt;是 XML 里对和的转义直接用容易解析报错。LIKE CONCAT比LIKE %${exerciseName}%好在不会把用户输入拼进 SQL。对user_id和train_date这两个高频查询字段SQL 脚本里建了联合索引idx_user_date(user_id, train_date)联调时可以在查询前面加EXPLAIN看是否走索引避免慢 SQL 拖垮列表接口。4.3 小程序端提交记录与列表渲染页面拿到用户填的表单后提交逻辑如下// pages/checkin/checkin.js submitRecord() { const form this.data.form; if (!form.exerciseName) { wx.showToast({ title: 动作名称不能为空, icon: none }); return; } request(/record/save, POST, { planId: form.planId || null, exerciseName: form.exerciseName, weightKg: Number(form.weightKg), reps: Number(form.reps), durationMin: form.durationMin ? Number(form.durationMin) : null, trainDate: form.trainDate, recordNote: form.recordNote }).then(res { wx.showToast({ title: 打卡成功, icon: success }); this.loadRecordList(); }).catch(() { wx.showToast({ title: 提交失败, icon: none }); }); }这里把Number(form.weightKg)做了一次显式转换因为小程序input组件取出来的值一定是字符串后端接到的weightKg是67.5而不是67.5如果后端实体用 BigDecimal 接收字符串会直接报HttpMessageNotReadableException而且这个错误返回 400前端不是很好排查。列表渲染用wx:for遍历返回的数组要注意trainDate直接展示即可不需要转时间戳但排序必须在 SQL 里完成不要在 JS 里sort。4.4 联调异常定位从状态码到日志的排查顺序联调阶段遇到问题先看 HTTP 状态码再看后端日志最后才看前端控制台。这个顺序能省掉大量无效沟通。常见的几种异常要能在 30 秒内定位现象常见原因排查位置401token 缺失、过期或签名不对请求头 Authorization 是否携带404路径写错或 Controller 没加 RequestMapping后端控制台是否打印请求映射405前端用 POST后端接口是 GET检查 PostMapping/GetMapping500空指针或 SQL 异常IDEA 控制台异常堆栈定位到行号400请求体 JSON 与实体字段不对应核对参数名和前端实际传参我在联调时会在后端加一个OncePerRequestFilter把每个请求的 path、method、耗时、状态码打到日志里这样即使前端没抓到响应后端日志也能还原整个链路。使用 IDEA 时可以直接在applicationContext.xml里把 MyBatis 的 SQL 日志级别调到 DEBUGTRACE级别能看到 SQL 预编译参数对排查#{}和${}写错的问题非常有效。5. 环境批量脚本、联调验证与微信登录 token 刷新技巧5.1 读懂 install、run、build 三个批处理脚本的作用资源根目录下的1-install.bat、2-run.bat、3-build.bat是给答辩前快速复现用的批处理脚本。1-install.bat负责初始化环境连的是 MySQL 导入预设数据执行前先打开它确认脚本里数据库密码与你本机一致不改的话大概率会卡在登录认证。2-run.bat启动后端服务并自动弹出浏览器访问接口文档页面。3-build.bat是给前端管理后台用的内部执行npm install和npm run build。注意这些.bat脚本第一行通常有chcp 65001这行是解决中文乱码的不要删。5.2 token 过期静默刷新用 wx.login 重新换 token 的闭环token 过期是联调时最容易体验断裂的点用户正在写打卡备注突然跳到登录页回来内容全丢了。一个小程序端常见的优化是把 401 从“跳登录页”改成“静默换 token 后重放请求”。思路是判断错误码为 401 时先调wx.login拿到新 code再请求后端换取新 token更新本地 storage最后把刚才失败的请求重新发一遍。核心代码放在utils/request.js里// utils/request.js 增加 token 刷新重试逻辑 function requestWithRetry(path, method, data, retry false) { return new Promise((resolve, reject) { request(path, method, data).then(res { resolve(res); }).catch(err { if (err.message 登录已过期 !retry) { return refreshToken().then(newToken { wx.setStorageSync(token, newToken); return requestWithRetry(path, method, data, true); }); } reject(err); }); }); } function refreshToken() { return new Promise((resolve, reject) { wx.login({ success: res { if (res.code) { request(/auth/refresh, POST, { code: res.code }).then(res { resolve(res.data.token); }).catch(reject); } else { reject(new Error(wx.login 失败)); } } }); }); }注意refreshToken里不能再走全局封装的 request否则会陷入循环递归这里必须绕过 Authorization 头的注入直接调底层的wx.request。静默刷新只适合 token 过期但 refreshToken 接口还能用的情况如果后端把 refreshToken 接口返回的也是 401说明用户被踢下线此时才跳登录页。接口返回码要把“业务失败”和“登录态失效”区分开建议后端对 token 相关异常单独返回一个1001业务码前端只在遇到1001时才触发刷新其他错误正常提示避免 token 有问题时所有接口都在无意义地重试。本文还有配套的精品资源点击获取
返回列表