这段时间陆陆续续处理过好几套“运动健康小程序 + SpringBoot”的源码项目,每次遇到的情况都差不多:源码能看、结构清楚,但一运行起来各种小问题就冒出来了。Java环境不对、SpringBoot版本冲突、数据库连不上、小程序真机预览白屏、手机号按钮点了没反应……相信很多做毕业设计或者接外包的朋友都被这些坑折磨过。
我手上这套项目,工程名就叫 weixin196,主题是运动健康管理,后端用的 SpringBoot,前端是微信小程序原生开发。功能上覆盖了用户登录、个人资料、运动打卡、健康数据记录、目标设置这类运动健康App最常见的场景,属于典型的“前端小程序+后端接口”分离结构。如果你正在找一个能改、能讲、能跑通的课设/毕设项目,或者接了个同样的单子不知道怎么下手,这篇内容应该能帮你省很多时间。
我不打算把源码逐行贴出来,那没有意义。我更想把整个项目从设计到落地涉及的思路、关键代码、配置方式和坑点讲透,让你拿到手之后不是只会启动,而是真的知道每一项为什么要这么做。
1. 项目拆解:运动健康小程序到底在做什么
1.1 一句话说清系统边界
这个系统给用户提供的核心价值,就是“记录运动和健康数据,并围绕数据进行提醒和展示”。后端 SpringBoot 负责处理业务逻辑、权限校验、数据持久化,小程序端负责用户交互和数据采集展示。两者通过 HTTP JSON 接口通信,没有页面回流,也没有模板渲染,是现在比较主流的前后端分离写法。
具体到功能模块,这套源码大致包含这么几块:
- 用户模块:微信登录、获取手机号绑定、个人资料维护(性别、身高、体重、生日)
- 运动记录模块:记录每日步数、运动时长、运动类型、消耗卡路里
- 健康数据模块:录入和查看心率、睡眠时长、体重变化等健康指标
- 目标管理模块:设置每日步数目标、运动天数目标
- 数据展示模块:用列表或者简单图表展示历史记录和趋势
可以看出,它的定位不是那种很重的医疗级产品,而是一个轻量化的健康管理工具。也正因如此,它的数据库设计、接口数量、权限逻辑都停留在“够用、能讲、易扩展”的合理范围,处理起来不复杂,适合用来学习或者二次开发。
1.2 功能模块和典型用户流程
用一条完整的用户路径来理解这个项目,比背功能清单有用得多:
用户打开小程序 → 自动执行 wx.login 拿到临时 code → 后端把 code 换成 openid → 判断该用户是否已注册 → 未注册则跳转到手机号绑定页面 → 用户点击“微信手机号快捷绑定”按钮 → 后端通过手机号快速验证接口获取真实手机号 → 创建账号 → 前端进入首页 → 用户填写身高体重 → 设置每日目标 → 之后每次打开小程序会自动登录,直接展示今日步数、目标完成度、运动记录列表。
整套流程里最核心的判断点有两个:一个是“怎么确认用户身份”,也就是 openid 的获取和保存;另一个是“怎么拿到用户手机号”,这是中国区微信小程序做用户体系绕不开的一步。很多源码跑不通,问题基本都出在这两块。
1.3 为什么适合直接用这个方案起步
我自己给很多学生讲过的一个观点是:不要迷信那些功能特别多、界面特别炫的源码项目。功能越多,意味着坑越多,答辩或验收时被问到的问题也越刁钻。这套运动健康项目麻雀虽小但五脏俱全,登录有鉴权、业务有CRUD、数据有统计、前端有交互,已经足够支撑一篇合格的毕业设计。
更重要的是,它涵盖了一个通用业务系统最标准的骨架:用户身份认证、业务实体建模、接口权限隔离、前端状态管理。把这些东西吃透,再去做商城、社区、预约类项目,你都会发现套路是一样的。也就是说,这个项目不只是运动健康场景的项目,它是一套能复用的后端设计模板。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型:SpringBoot + 小程序的组合为什么能打
2.1 后端选SpringBoot而不是SSH/Node的原因
很多人在技术选型时会纠结,为什么这套源码选的是 SpringBoot,而不是 SpringMVC + JSP,或者 Node.js、PHP。我的看法是:SpringBoot 是目前 Java 后端就业和教学覆盖面最广的框架,没有之一。
它的核心优势可以概括成三点。
第一,自动装配极大降低了配置成本。传统 SSM 项目需要写一堆 XML 配置,SpringBoot 直接用 starter 依赖和最简配置就能把 Web、数据库、Redis 组件串起来。对源码阅读者来说,不用再花大量时间在环境搭建上。
第二,生态成熟,遇到问题时能搜到大量解决方案。比如 MyBatis 兼容、MySQL 连接、拦截器配置、全局异常处理,这些在 SpringBoot 里都有非常成熟的套路。你哪怕是照着别人的例子改,也基本不会走偏。
第三,和内嵌容器的结合非常适合接口项目。小程序不需要后端返回页面,只需要返回 JSON 数据,SpringBoot 内嵌 Tomcat、项目打成 jar 包就能运行,对部署环境的要求非常低,这也降低了上线成本。
2.2 小程序端的技术栈取舍
小程序端在这套源码里采用的是原生微信小程序框架,没有用 uni-app 或者 Taro。这个选择其实是很有讲究的。
原生小程序的好处是:无需额外编译链,微信开发者工具直接打开就能预览;生命周期、路由、组件这些概念和微信官方文档一一对应,学习成本相对可控;对源码二次开发时,加页面、加组件就是复制目录结构的小事。
缺点是代码复用性差,将来如果要做成 App 或者 H5,需要重写界面逻辑。但如果你现在是在做课设、毕设,或者接一个要求快速交付的小程序项目,原生开发的效率反而是最高的。等你以后真需要多端发布,再切换到 uni-app 也不迟,核心逻辑在后端接口,前端替换成本并没有想象中那么大。
2.3 登录与手机号获取:最容易被忽略的坑
这个项目里最容易让人混乱的就是登录相关的代码,因为小程序登录其实包含了两套不同的凭证。
第一套是 wx.login 拿到的 code。小程序端调用 wx.login 后,后端拿这个 code 去微信接口 https://api.weixin.qq.com/sns/jscode2session 换取 openid 和 session_key。openid 是用户在当前小程序下的唯一标识,session_key 用于后续解密敏感信息。这两者里最重要的是 openid,它在整个用户体系里就是账号概念。
第二套是“获取手机号”按钮给出的 code。新版微信把手机号信息获取方式改成了动态令牌机制,用户点击 button 后,通过 bindgetphonenumber 事件可以拿到一个加密后的 code,后端需要拿这个 code 调用微信接口换取真实的手机号信息,然后再绑定到用户表。
很多源码因为版本比较老,还在用旧的 encryptedData 解密方式。如果微信开放平台已经停止了对旧接口的支持,那么你怎么试都拿不到手机号。正确做法是检查后端接口是否已经用了 getuserphonenumber 流程。下面这段伪代码就展示了正确的处理方式:
java复制@PostMapping("/api/user/bindPhone")
public Result bindPhone(@RequestBody BindPhoneReq req) {
// req.code 来自小程序 button.getPhoneNumber 回调
String phone = wechatService.getPhoneNumber(req.getCode());
userService.bindPhone(req.getUserId(), phone);
return Result.success();
}
同时在小程序端,对应的按钮绑定写法是:
xml复制<button open-type="getPhoneNumber" bindgetphonenumber="onGetPhoneNumber">
微信手机号快捷登录
</button>
这套流程就是整个系统登录模块的地基,地基不稳,后面所有业务操作都无从谈起。
3. 核心设计与实现细节
3.1 数据库表设计:运动健康领域最少需要的表
拿到一个源码项目,我习惯最先看数据库脚本,因为表结构能直接反映业务边界。这套运动健康项目核心表大概四张起底:用户表、健康记录表、运动记录表、目标表。在此基础上有些源码会再加一张管理端管理员表或运动建议表,但主体就是这四张。
用户表一般叫 user 或者 member,字段至少包含 id、openid、phone、nickname、avatar、gender、height、weight、birthday、create_time、update_time。其中 openid 建议建唯一索引,因为这是用户登录时的查询条件,没有索引会随着数据量增长越来越慢。phone 可以后面绑定,所以暂时不设非空约束。
健康记录表 health_record 的字段一般有 id、user_id、record_date、step_count、sleep_duration、heart_rate、weight、calories、create_time。注意 record_date 最好用 date 类型而不是 datetime,否则同一天的记录很难做去重;每次保存前先判断是否已有当天记录,有就更新,没有就新增。这是一种非常典型的“UPSERT”操作。
运动记录表 sport_record 用于记录单次运动,字段包括 id、user_id、sport_type、duration、distance、calories、start_time、remark。sport_type 可以设计成字典值,1 代表跑步,2 代表骑行,3 代表游泳等,也可以直接用字符串。对课设项目来说字符串更直观,按类型统计时也够用。
目标表 health_goal 用来存用户的个性化目标,例如 daily_step_target、weekly_sport_days、target_weight。一个用户只保留一条最新目标记录,修改时直接更新,不需要保留历史版本。
这几张表的关系很简单:user_id 作为外键逻辑关联,不一定要在物理上建立外键约束,但在代码写多表联查时要保证字段语义一致。用逻辑外键的好处是删除数据灵活,不会因为约束卡住测试流程,这也符合多数真实项目的习惯。
3.2 接口设计与数据返回约定
接口设计上,这套项目用了目前主流的 RESTful 风格,结合实际业务可以分成几个分组:
- /api/user/register、/api/user/login、/api/user/update、/api/user/info
- /api/health/save、/api/health/list、/api/health/statistics
- /api/sport/record、/api/sport/list、/api/sport/statistics
- /api/goal/get、/api/goal/save
最需要注意的是一套统一返回结构。我强烈建议所有接口都返回同样的 JSON 包装,不要一会返回 {"code":200,"data":...},一会返回 {"success":true,"rows":...}。这样前端封装的 request 方法只需要处理一种格式,排查问题时也能少一半工作量。
举个例子,项目里的 Result 类通常会长这样:
java复制public class Result<T> {
private Integer code;
private String msg;
private T data;
public static <T> Result<T> success(T data) {
// code=200, msg=success
}
public static <T> Result<T> error(String msg) {
// code=500
}
}
所有 controller 层接口都返回 Result 类型,全局异常处理器统一捕获未预料的 RuntimeException,避免后端一报错就把一大堆异常堆栈直接吐给小程序端。小程序那边只需要判断 code 是否为 200,不是 200 就弹 toast,非常省事。
3.3 Token鉴权:怎么做到一次登录多次请求不重复授权
小程序和传统的网页 Session 机制不太一样。虽然小程序也支持 Cookie,但微信官方环境对 Cookie 的支持不如浏览器那么完善,在部分 webview 场景下还会出现丢失问题。所以更普遍的做法是服务端生成 Token,小程序把 Token 存到 storage 里,每次请求通过请求头带到后端。
常见的 Token 方案有两种:一种是基于 Redis 的随机 Token,另一种是 JWT。这套源码如果依赖模块简单,一般会选择后者,因为 JWT 不需要额外的存储服务,登录成功后后端根据 openid 和用户ID生成一串带过期时间的签名 Token,前端拿到后存起来,后端通过拦截器解析 Token,就能知道请求来自哪个用户。
我摘一段核心思想:
java复制public class JwtUtil {
public static String createToken(Long userId, String openid) {
// JWT.create()
// .withClaim("userId", userId)
// .withClaim("openid", openid)
// .withExpiresAt(new Date(System.currentTimeMillis() + 7 * 24 * 3600 * 1000L))
// .sign(Algorithm.HMAC256(secret));
}
public static Long getUserId(String token) {
// 解析token,返回userId
}
}
但要提醒一句:JWT 的过期时间不要设置太长,7 天是常见值。小程序用户流失率很高,真要长期使用,可以加一个“refresh_token + 自动续期”的机制,不过对课设项目来说没必要,7 天足够覆盖日常使用周期了。
在 SpringBoot 里实现请求拦截,通常有两种方式。第一种是实现 HandlerInterceptor 的 preHandle 方法,在进入 controller 之前校验请求头里的 token;第二种是过滤器 Filter,在更早期处理。对普通接口项目来说,拦截器方式就够了。配置类里写好拦截规则,排除登录、注册、手机号绑定这类免鉴权接口,其余接口放行前先解析 token。
java复制registry.addInterceptor(new JwtInterceptor())
.addPathPatterns("/api/**")
.excludePathPatterns("/api/user/login")
.excludePathPatterns("/api/user/register")
.excludePathPatterns("/api/user/bindPhone");
这个细节非常重要,很多项目跑起来后小程序一请求就报 401 或 500,大概率就是拦截器排除了不该排除的路径,或者 token 解析的 key 和小程序端存储的 key 不一致。
4. 从源码到跑通:本地开发环境搭建全流程
4.1 后端启动的完整操作序列
拿到源码后不要急着写代码,先按固定顺序做环境检查和启动操作。我自己的习惯分五步走。
第一步,确认 Java 版本。SpringBoot 2.x 系列用 JDK 8 或 JDK 11 都可以,SpringBoot 3.x 则要求 JDK 17 以上。现在很多人电脑上默认装了 17 甚至 21,如果源码是 2.7 的老项目,运行起来经常会出现各种 javax 包找不到的问题,因为 SpringBoot 3.x 已经把 javax 替换成了 jakarta。反过来,如果源码用了 SpringBoot 3.x,你还拿 JDK 8 去跑,启动时基本会直接报 UnsupportedClassVersionError,连编译这关都过不了。所以第一步永远是先 pom.xml 看 spring-boot 父版本。
第二步,检查 Maven 仓库依赖能否正常下载。国内网络环境建议把阿里云镜像配到 Maven 的 settings.xml 里,避免依赖拉取超时。如果源码里带了 mvnw 脚本,优先用它,版本和仓库配置不会乱。
第三步,创建数据库并导入初始化 SQL。打开 MySQL 后执行 init.sql,确认四张核心表和必要的索引都已创建。注意数据库的字符集统一设为 utf8mb4,否则小程序端中文内容存进去会出现乱码。
第四步,修改 application.yml 里的数据源配置。把 url 里的数据库名、用户名、密码改成你本机的值,再确认下 mysql-connector 的版本和 MySQL 服务版本是否匹配。MySQL 8 之后的驱动类名是 com.mysql.cj.jdbc.Driver,老项目里如果写成 com.mysql.jdbc.Driver 会提示加载失败。
第五步,启动类右键运行,等待控制台出现 “Started Application in x seconds”。这个时候再打开浏览器访问 http://localhost:8080/api/health 之类的路径,看到 JSON 返回就说明后端已经稳了。
很多同学在第五步卡住,控制台报错五花八门,但本质无非就是端口被占用、数据库连接不上、依赖缺失这几种。建议用 IDEA 自带的结构窗口先看右边 Maven 面板里有没有红色报错,有就处理依赖,没有再看日志前几行,一般 error 信息会直接指出问题方向。
4.2 小程序端配置项逐项说明
小程序端拿到源码后,第一件事不是点预览,而是检查 project.config.json 里的 appid 和你自己的 appid 是否一致。如果你用的测试号,那么单选框、隐私授权这些功能会受限制,最好注册一个个人小程序账号,在“开发管理”里拿到自己的 AppID 填进去。
然后要改后端接口地址。小程序端一般会有一个专门的配置文件,比如 config.js 或 request.js,里面写了一个 baseUrl。本地开发时,这个地址应该改成你的局域网 IP,而不是 localhost。因为真机调试时手机访问不了你电脑上的 localhost,必须用后端启动机器的局域网地址 http://192.168.x.x:8080。
如果你的后端在云端服务器上,那 baseUrl 直接改成服务器的公网地址或已备案域名。这里有一个很容易忽略的点:小程序要求所有请求必须是 HTTPS,而且域名要在小程序后台配置白名单。但在本地开发阶段,可以先在微信开发者工具的“详情-本地设置”里勾选“不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书”。不然你连本地调试都过不去,请求一发起就会被拦截。
信息填写界面也要留意隐私协议更新。2023 年之后微信加强了对用户隐私的保护,在小程序管理后台需要配置“用户隐私保护指引”,申明收集手机号、微信昵称、头像等信息的目的。如果你的源码里没有隐私弹窗处理,提交审核时会被驳回,真机调试时 getPhoneNumber 也会提示接口不支持。
4.3 本地调试利器:真机预览与抓包配合
小程序开发中光看控制台是不够的,很多问题只在真机环境复现。我能给出的建议是:先在开发者工具里跑通基础流程,然后用“真机调试”功能,让它生成一个预览二维码,手机扫码后在授权弹窗里允许调试。
真机调试模式下,微信开发者工具的 Network 面板会展示手机端发出的所有请求。你可以在 Network 里看到 /api/user/login 的请求参数和返回结果,状态码、耗时、JSON 数据一目了然。这一步比很多同学理解的“抓包”要直接得多,因为微信开发者工具本身就能充当一个轻量抓包工具。
如果还需要看到更多请求细节或者后端响应超时,可以配合 Charles 或 Fiddler 工具抓 HTTPS 包,原理是设置代理后安装信任证书。但对于这个项目来说,开发者工具自带的 Network 面板基本够用,没有必要增加额外复杂度。
我梳理了一个比较标准的调试流程:
- 前端用 wx.login 获取 code,确认 code 不为空
- 调用后端 /api/user/login,后端返回用户信息和 token
- 将 token 保存到 storage,后续请求头部带上 Authorization: token
- 在 Network 面板里挑一个带鉴权的请求,点击预览返回 JSON,看是否出现业务数据
- 如果 401,看请求头是否漏带 token;如果 500,看后端日志,多半是 SQL 或空指针
这套排查顺序看起来基础,但能解决 80% 的联调问题。
5. 实际开发中我踩过的坑和排查方法
5.1 SpringBoot版本过高导致的数据访问异常
“SpringBoot版本太高”这句评论我在网上看到过很多次,自己也踩过。这个坑的典型表现是:依赖引入没问题,代码也没问题,但启动时疯狂报 Cannot determine embedded database driver class for database type NONE 或者 Failed to configure a DataSource。
这个报错有一个很常见的原因:SpringBoot 版本升级后,自动配置数据源的逻辑变了,或者你只引入了 spring-boot-starter-web 却没引入对应的数据访问驱动。某个依赖没进 classpath,SpringBoot 就跑不到数据库配置。如果你项目里同时用了 mybatis-spring-boot-starter,那版本之间的编排也要仔细对一遍。
我遇到过一个特别具体的例子:SpringBoot 2.7 项目升级到 3.0 后,mybatis-spring-boot-starter 还是旧的 2.x 版本,直接导致 SqlSessionFactory 构建失败。解决方式很简单,把 mybatis 相关依赖升级到 3.0.0 以上版本,再把所有 javax.* 包替换成 jakarta.* 包就好了。
如果你不想动源码里的代码路径,最稳妥的做法是严格按照 pom 里写明的 SpringBoot 版本来配置 JDK。源码里写 2.7.6,你就用 JDK 8/11;源码写 3.1.5,你就用 JDK 17。不要轻易“顺手升级到最新版”,这往往是项目崩坏的开始。
5.2 小程序登录获取手机号失败的常见原因
手机号绑定是运动健康小程序最核心的入口,也是最容易失败的一环。常见的有这几种情况。
第一种,开发工具里点了按钮但没有任何回调。这种情况大概率是基础库版本过低,或者登录按钮没有用 open-type="getPhoneNumber" 触发。检查一下页面的 json 文件,看有没有全局禁用某些组件。
第二种,回调里拿到了 code,但后端调用微信接口报 40029 或 40001。40029 代表 code 无效,很可能是同一个 code 被用了两次,或者 code 过期。手机号 code 的有效期很短,一般 5 分钟就失效,注意时序,不要在前端绕一圈再传到后端。40001 通常代表 access_token 无效,可能是小程序密钥配置错,或者后端获取 access_token 的缓存逻辑有问题。
第三种,接口返回成功但没有手机号字段。原因多半是前端传的 code 其实来自 wx.login,而不是 getPhoneNumber 回调。这两个 code 用途完全不同,传错了微信那边自然查不到手机号。我在阅读源码的时候经常发现新手把这两个 code 变量名混在一起,排查时务必要区分。
一个实操建议是:在手机号绑定接口入参里同时加上一个 bizType 字段,1 表示登录凭证,2 表示手机号凭证。后端根据 bizType 选择对应的微信接口,这样就能从参数上避免两个 code 互相污染。
5.3 域名校验、HTTPS和上线注意项
开发环境可以关闭域名校验,但一旦提交审核,微信官方会强制校验所有请求地址。如果你在小程序后台没有配置 request 合法域名,或者域名没有备案、没接入 HTTPS,提交审核时大概率会收到“类目不符”或“接口未配置”的反馈。
上线阶段,我有几个要点想重点说明:
- 域名必须与小程序后台配置的 request 合法域名完全一致,包括端口
- HTTPS 证书建议申请免费证书,过期时间是 90 天,记得设置到期提醒
- 服务器防火墙要放行 80、443 和 8080(如果直接用端口)的入方向流量
- 后端不要使用 http 明文协议,生产环境必须强制 HTTPS 跳转
- 小程序管理后台需要配置用户隐私保护指引,否则涉及手机号、头像昵称的接口会受限
部署时如果用的是宝塔面板,操作会简单不少。后端打成一个 jar 包,通过 Java 进程守护工具或 systemd 服务启动,再用 Nginx 做反向代理,把 /api/ 路径转发到本机的 8080 端口。小程序端 baseUrl 直接填 https://你的域名/api,这样所有请求都走标准域名,避免端口继续暴露在外面。
宝塔部署 SpringBoot 还有一个小技巧:如果你在站点运维里配置了反向代理,记得在 Nginx 配置里加上 Proxy 请求头设置。因为后端要通过 request.getRequestURI() 判断接口路径,代理丢失 URI 会导致所有接口 404,到时候排查半天还不如先看 Nginx 日志来得快。
6. 这套源码后续还能怎么扩展
6.1 健康数据可视化
目前源码里的健康数据多以列表展示为主,如果想提升产品完成度,优先做数据可视化。小程序端可以使用 ec-canvas(ECharts 小程序版)组件,在首页增加一个步数和体重趋势图。后端对应增加一个统计接口,返回近 7 天或 30 天的数据序列,前端直接用折线图渲染。
这一步注意点在于:ECharts 的体积不小,会拖慢小程序加载速度。建议按需引入图表类型,只加载 line、bar、pie 这些你需要的图,不要全量引入。真机预览时如果发现白屏或图表不渲染,先检查 canvas 类型是否为 2d,以及组件的宽高是否设置明确数值。
6.2 多人运动与社交化
运动健康项目最自然的扩展方向是社交化。你可以增加“排行榜”功能,按照每日步数对全部用户进行排名;也可以增加“加入运动小组”的概念,一个人设置目标、拉上三五好友一起打卡。
后端实现上只需要新增两张表,一张是 group 表,一张是 group_member 表,然后对步数数据进行按月或按周聚合统计。接口设计上可以用一个复合查询,把运动记录表和排行榜数据在服务层组装,避免过度使用复杂的 SQL 嵌套。对学习者来说,这个扩展方向能体现你对业务理解的能力,远比堆砌 CRUD 有价值。
6.3 多端复用方案
如果未来想把这个项目从微信小程序扩展到支付宝小程序、抖音小程序或者独立 App,后端代码基本不用动,只要按各平台规则重写前端界面就行。这也是前后端分离最大的好处。
但如果你真想低成本做多端,可以考虑把前端工程迁移到 uni-app。原生的 微信小程序 页面迁移到 uni-app 时,模板语法有区别,但页面结构逻辑大体能对应。后端 SpringBoot 的接口定义不变,只需要修改小程序端的请求封装,以及将 storage 存储方式替换成 uni.setStorageSync。迁移工作量远比想象中可控。
我个人在实际操作中的体会是:这个项目最宝贵的部分永远不是那几行 CRUD 代码,而是“登录凭证区分”“统一返回结构”“拦截器鉴权”“异常全局处理”这些通用问题的决策过程。把它们研究明白,哪怕这套运动健康项目的业务与你无关,你也能迁移到任何其他系统上。以后你写商城、写预约、写管理系统,这些设计依然成立。
最后再分享一个小技巧:拿到任何 SpringBoot + 小程序的源码项目,先把小程序端所有请求同时抓出来,逐个对应到后端的 controller 方法里,把接口清单画一张表格。这个动作只需要半小时,但能让你对整个系统的理解提升一个台阶。项目能不能跑通是第一步,能不能把每个接口为什么存在讲清楚,才是答辩或者交接时真正拉开差距的地方。
