news 2026/10/22 6:21:41

SpringBoot + 微信小程序健康管理系统实战:从登录鉴权到部署上线

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot + 微信小程序健康管理系统实战:从登录鉴权到部署上线

做互联网开发这几年,医疗健康类的项目我接触过不少,但真正从零完整做完一套“SpringBoot + 微信小程序”的健康管理系统,还是有很多值得沉淀的内容。这个系统功能上并不算复杂:用户在小程序端录入自己的体征数据,比如体重、血压、血糖、睡眠时长,后端负责存储和计算,再按规则给出健康建议,管理员后台还能维护一些健康科普内容。可把这条链路完整跑通,背后涉及的东西远比想象中多:微信登录的 session 管理、体征数据的表结构设计、安全拦截器的配置、小程序端各种机型适配、包括最后部署时的 HTTPS 和域名校验,每一环都能踩出坑来。

这篇文章我按实际项目的脉络来复盘:从需求拆解讲到数据库设计,从后端核心接口写到小程序端实现,最后再说上线阶段遇到的典型问题和排查思路。如果你正准备做一个类似的快速原型项目、毕业设计,或者公司内部的健康管理小工具,这篇文章可以直接当参考模板用。

1. 项目概述与需求拆解

1.1 健康管理系统到底在解决什么问题

很多人一听“健康管理系统”,第一反应是做一套很重的医院系统。实际上,市面上真正跑得动的健康管理小程序,做的都是“轻量记录 + 异常提醒 + 趋势分析”这件事。这次项目的目标用户是社区健康管理场景下的普通用户,核心需求列出来其实很清晰:

  • 用户注册登录:不需要复杂账号体系,直接微信授权登录,降低使用门槛。
  • 体征记录:记录体重、BMI、血压、血糖、心率、睡眠时长这些常见指标。
  • 记录查询:支持按时间范围查看记录列表,并展示趋势变化。
  • 健康建议:根据最新体征数据自动生成提示,比如“体重偏高、建议增加运动”这类文本。
  • 健康科普内容:管理员在后台上传文章,小程序端分类展示。
  • 我的健康档案:展示个人基础信息、历史统计摘要。

这个需求范围如果直接开做,后端至少二十张表打底。但实际项目我做了收敛,把核心表控制在 8 张以内,先把主链路跑通,再考虑扩展。

1.2 技术选型:为什么是 SpringBoot + 微信小程序

先说结论:这个组合非常适合“中小型健康应用”的快速落地。

SpringBoot 的优势不用多讲,自动配置、内置容器、生态成熟。我用的是 2.7.x 版本,搭配 JDK 1.8,这个组合在云服务器上跑起来内存占用低、稳定性好。有朋友问我为什么不用 SpringBoot 3.x,因为项目上线环境是老 CentOS,JDK 8 可能没法直接升级,而且很多第三方 starter 还停留在 2.x 版本,没必要冒兼容性风险。

微信小程序端我选了原生开发,没有上 uniapp。原因是这个项目只需要服务微信生态,原生框架调试方便,分包、真机预览都很直接,和小程序的 API 对接没有中间层,减少一层抽象就少一层出问题的概率。

数据库使用 MySQL 8.0,缓存用了 Redis,主要用于存储微信 session 状态和 token 黑名单。整体架构就是经典的前后端分离:小程序端通过 HTTPS 请求访问后端 RESTful 接口,后端统一返回 JSON 数据,认证机制采用 JWT + Redis 双重校验。

2. 系统整体架构与数据库设计

2.1 分层架构与项目目录规划

后端项目结构我不是按“controller/service/dao”这种三层一刀切,而是采用功能模块划分,每个模块内再分层。这样在功能演进时能快速定位,比如新增一个“运动记录”功能,只需要在module/exercise目录下新增文件,不会牵动全局。

com.healthmanage ├── common // 通用工具、常量、异常处理 │ ├── config // 拦截器、Redis配置、跨域配置 │ ├── util // JWT工具、AES加密工具、BeanCopy工具 │ └── constant // 业务常量、错误码 ├── module │ ├── auth // 登录鉴权模块 │ │ ├── controller │ │ ├── service │ │ ├── mapper │ │ └── domain │ ├── user // 用户与健康档案模块 │ ├── record // 体征记录模块 │ ├── advice // 健康建议模块 │ └── article // 健康科普模块 ├── quartz // 定时任务 └── HealthApplication.java

这种模块化结构特别适合中期维护,例如给健康建议模块增加“按季节生成建议”的逻辑时,完全不会碰其他模块的代码。

2.2 数据库表设计与核心字段说明

数据库设计是这类系统最容易返工的地方。健康数据有几个特点:同一用户的数据是时间序列数据、不同指标单位不同、不同指标的记录频率也不同(血压可能一天测两次,体重一周测一次)。所以我的方案是“一张主表 + 三张扩展字段表”,而不是把每个指标单独建一张表。

核心表结构如下:

用户表 t_user

CREATE TABLE t_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键', openid VARCHAR(64) NOT NULL COMMENT '微信openid', nickname VARCHAR(64) DEFAULT '' COMMENT '昵称', avatar_url VARCHAR(255) DEFAULT '' COMMENT '头像地址', gender TINYINT DEFAULT 0 COMMENT '性别 0未知 1男 2女', birthday DATE DEFAULT NULL COMMENT '出生日期', height DECIMAL(5,2) DEFAULT NULL COMMENT '身高cm,用于计算BMI', phone VARCHAR(20) DEFAULT NULL COMMENT '手机号', status TINYINT DEFAULT 1 COMMENT '状态 1正常 0禁用', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间', update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', UNIQUE KEY uk_openid (openid) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

openid 是用户在微信生态里的唯一标识,必须建唯一索引。注意一定不要把鞋带一样的 openid 直接暴露给前端,登录后要换成自己系统生成的 userId 和 token,后续接口都用 userId 操作,这样即使 openid 泄露也影响有限。

体征记录表 t_health_record

CREATE TABLE t_health_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL COMMENT '用户ID', record_date DATE NOT NULL COMMENT '记录日期', record_type VARCHAR(20) NOT NULL COMMENT '指标类型:weight/blood_pressure/blood_sugar/heart_rate/sleep', record_value DECIMAL(10,2) NOT NULL COMMENT '指标数值', systolic_pressure INT DEFAULT NULL COMMENT '收缩压,血压专用', diastolic_pressure INT DEFAULT NULL COMMENT '舒张压,血压专用', record_unit VARCHAR(10) DEFAULT '' COMMENT '单位', remark VARCHAR(255) DEFAULT '' COMMENT '备注,如:早晨空腹、运动后', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_user_date (user_id, record_date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='体征记录表';

这里是关键设计决策:用 record_type + record_value 存储不同类型的指标数据。如果每个指标建一张表,血压还需要收缩压和舒张压两个字段,后期新增“血氧”指标又得建一张表。用这种通用表结构,新增指标只需要在代码里加一个枚举值,不用改表结构。

但要注意:通用表在查询时会有一定的性能牺牲,因为只能按 record_type 过滤后查。我的做法是给(user_id, record_date, record_type)建联合索引,查询时把时间范围缩小到一个月甚至一周,实测一张表 50 万数据量下接口响应仍在 200ms 以内,完全够用。

健康建议表 t_health_advice

CREATE TABLE t_health_advice ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, advice_date DATE NOT NULL COMMENT '建议日期', advice_type VARCHAR(20) NOT NULL COMMENT '建议类型:bmi_weight/blood_pressure/blood_sugar/sleep', advice_title VARCHAR(100) NOT NULL COMMENT '建议标题', advice_content TEXT NOT NULL COMMENT '建议内容', is_read TINYINT DEFAULT 0 COMMENT '是否已读', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_user_read (user_id, is_read) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康建议表';

健康建议不是实时的,而是每天根据用户当天最新一条体征记录生成一次,存储后供小程序端展示。这样设计的好处是:用户看到的建议是稳定的,不会因为病情数据变化反复横跳,也方便后续做推荐效果的统计。

其余还有t_article(健康科普)、t_article_category、t_share_log(分享记录)等表,这里不再一一贴出。总的思路就是:业务表保持简单,能通过字段扩展解决的不要建新表,能通过联合索引解决的不要搞冗余表。

3. SpringBoot 后端核心实现

3.1 微信登录与 token 鉴权链路

健康管理系统的核心起点是登录。这里有一个非常重要的概念:小程序登录和传统网页登录不同,小程序端wx.login()拿到的 code 只能在后端通过微信接口二次换 session_key 和 openid,这个流程是微信安全体系的核心。

后端的登录接口实现如下:

@RestController @RequestMapping("/api/auth") public class AuthController { @Autowired private AuthService authService; @PostMapping("/login") public Result<LoginVO> login(@RequestBody @Validated LoginRequest request) { // request 中携带小程序端传来的 code String code = request.getCode(); return Result.ok(authService.login(code)); } }
@Override public LoginVO login(String code) { // 1. 调用微信接口换取会话信息 WxSessionResponse session = wxApi.code2Session(code); // 2. 根据 openid 查询本地用户,不存在则自动注册 User user = userMapper.selectByOpenid(session.getOpenid()); if (user == null) { user = User.builder() .openid(session.getOpenid()) .nickname("微信用户") .status(1) .build(); userMapper.insert(user); } // 3. session_key 存入 Redis,有效期 7 天,用于未来解密手机号 redisTemplate.opsForValue().set( "wx:session:" + user.getId(), session.getSessionKey(), 7, TimeUnit.DAYS ); // 4. 生成自己的 JWT token 返回给小程序端 String token = jwtUtil.generateToken(user.getId(), "user"); return new LoginVO(token, user.getId()); }

这个流程里我特别要强调 session_key 的存储问题。很多人拿到 session_key 之后不知道怎么处理,其实 session_key 不是每个接口都要用的,只有在解密微信手机号、运动数据等敏感信息时才需要。把它存到 Redis 里并设置 7 天过期,配合用户刷新登录的时机清理,不会有安全漏洞。

3.2 JWT 拦截器设计与权限控制

登录之后,如果没有一套完整的鉴权体系,用户 A 很容易就能通过改请求参数查用户 B 的数据。健康数据是高度隐私的,权限控制必须做到接口层面。我的做法是基于 SpringBoot 拦截器 + 自定义注解来做:

@Component public class LoginInterceptor implements HandlerInterceptor { @Autowired private StringRedisTemplate redisTemplate; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization"); if (StringUtils.isBlank(token)) { throw new BizException(401, "未登录"); } // 1. 解析 JWT token Long userId = jwtUtil.parseUserId(token); // 2. 校验 Redis 中是否存在,支持主动登出 String redisToken = redisTemplate.opsForValue().get("login:token:" + userId); if (!token.equals(redisToken)) { throw new BizException(401, "登录已过期"); } UserContext.set(userId); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { UserContext.clear(); } }

这里有个细节容易被忽略:JWT 是无状态的,一旦签发就无法在服务端主动让它失效。为了支持“用户退出登录”“管理员封禁账号后立即失效”这两个场景,我把 token 同时存了一份到 Redis,每次请求时不仅验证 JWT 签名,还要校验 Redis 里的值是否一致。这样当用户点退出或者后台封禁账号时,删掉 Redis 里的记录,token 就自然失效了。多了一次 Redis 查询,换来了服务端主动控制能力,在健康系统里这个取舍非常值。

资源的权限校验我通过自定义注解实现,比如查询用户详情、体检建议等接口,只允许本人访问:

@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RequireOwner { }
public class OwnerInterceptor extends HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { Long loginUserId = UserContext.getUserId(); Long targetUserId = Long.parseLong(request.getHeader("X-User-Id")); if (!loginUserId.equals(targetUserId)) { throw new BizException(403, "无权访问他人健康数据"); } return true; } }

在 controller 方法上加上@RequireOwner注解即可完成校验。注意拦截器只负责解析 token,资源权限校验放在另一个拦截器里,注意拦截器注册顺序,否则会出现UserContext还没设置就走到资源校验的情况。

3.3 体征记录接口与统计报表实现

体征记录接口的设计比较直接,核心是“保存记录”和“查询趋势”两个能力。

保存记录时需要注意指标值的校验,比如血压数值不可能只有 20。这个校验我放在 Service 层,通过一个指标校验策略类来实现。每种指标都有对应的校验规则和阈值范围:

@Component public class BloodPressureValidator implements IndicatorValidator { @Override public String type() { return "blood_pressure"; } @Override public void validate(HealthRecord record) { if (record.getSystolicPressure() == null || record.getDiastolicPressure() == null) { throw new BizException("血压记录必须包含收缩压和舒张压"); } if (record.getSystolicPressure() > 250 || record.getDiastolicPressure() > 150) { throw new BizException("血压数值超出正常范围,请检查后重试"); } // 收缩压必须高于舒张压 if (record.getSystolicPressure() <= record.getDiastolicPressure()) { throw new BizException("收缩压必须高于舒张压"); } } }

这样后续新增指标时,只需要实现 IndicatorValidator 接口并注册到 Map 中,Service 层代码零改动。

趋势查询接口返回两类数据:按天分的记录列表和按周/月聚合的统计值。统计部分我用 MyBatis 直接写 SQL,举个例子,查询最近 30 天内用户每天的平均体重:

<select id="selectWeightTrend" resultType="com.healthmanage.module.record.domain.vo.WeightTrendVO"> SELECT record_date AS recordDate, AVG(record_value) AS avgValue, MIN(record_value) AS minValue, MAX(record_value) AS maxValue FROM t_health_record WHERE user_id = #{userId} AND record_type = 'weight' AND record_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 30 DAY) AND CURDATE() GROUP BY record_date ORDER BY record_date ASC </select>

注意这里用了AVG聚合函数,但同一用户同一天可能记录了多组体重数据。业务上我允许这种情况(比如早晨和晚上各测一次),但统计时会取平均值。这样对用户来说直观,不会因为峰值数据导致误判。

3.4 健康建议规则引擎的设计与实现

生成健康建议是“管理系统”变成“智能系统”的关键。这部分我设计的不是复杂算法,而是一个可配置的规则决策器。

以 BMI 建议为例:

  • BMI < 18.5:偏瘦建议,增加营养摄入,补充蛋白质。
  • BMI 18.5 ~ 23.9:正常,鼓励保持现有生活方式。
  • BMI 24 ~ 27.9:超重,建议控制饮食、增加有氧运动。
  • BMI >= 28:肥胖,建议去医院营养科获取专业方案。

代码实现上我没有用 if-else 满天飞,而是用了一个规则表结构 + 决策引擎。规则表字段包括:指标类型、区间下限、区间上限、建议标题、建议内容模板、优先级。这样运营人员可以直接在后台配置规则,不用每次修改代码重新发布。

public class BmiAdviceRule implements AdviceRule { @Override public String type() { return "bmi_weight"; } @Override public AdviceResult evaluate(User user, HealthRecord latestRecord) { if (user.getHeight() == null || latestRecord.getRecordValue() == null) { return null; // 数据不足,不生成建议 } double heightInMeter = user.getHeight() / 100.0; double bmi = latestRecord.getRecordValue() / (heightInMeter * heightInMeter); // 区间判断 if (bmi < 18.5) { return new AdviceResult("bmi_weight", "体重偏瘦", "你的BMI为" + String.format("%.1f", bmi) + ",属于偏瘦范围。建议适当增加优质蛋白摄入,规律三餐。"); } // ... 其他区间 return null; } }

定时任务每天早上 8 点扫描前一天有体征记录的用户,生成健康建议并写入t_health_advice,小程序端可以模拟“每日健康日报”的感觉。这个功能上线后,用户粘性比单纯录数据高了不少——因为每天都有新的东西可以看。

4. 微信小程序端实现要点

4.1 登录流程与 token 管理

小程序端页面再多,登录永远是第一关。原生小程序里我用的是wx.login()获取 code,然后请求后端/api/auth/login获得 token,把 token 存在wx.setStorageSync中。这里有一个关键细节:不要每次冷启动都重新调 wx.login 换 token,正确的做法是启动时先看本地有没有 token,有就直接用;接口请求时如果返回 401,再重新走一遍登录流程。

会话保持方面的代码我封装了一个request公用方法:

const request = (url, method, data) => { return new Promise((resolve, reject) => { const token = wx.getStorageSync('token') wx.request({ url: BASE_URL + url, method: method, data: data, header: { 'Authorization': token, 'Content-Type': 'application/json' }, success: (res) => { if (res.data.code === 401) { // token 失效,重新静默登录 handleTokenExpired().then(() => { request(url, method, data).then(resolve).catch(reject) }) return } if (res.data.code !== 200) { wx.showToast({ title: res.data.msg, icon: 'none' }) reject(res.data) return } resolve(res.data.data) }, fail: (err) => { wx.showToast({ title: '网络异常,请稍后重试', icon: 'none' }) reject(err) } }) }) }

注意 header 里的Authorization值不要加额外的前缀,保持和后端拦截器解析逻辑一致。这个方案虽小,但在多页面跳转时能保证所有请求都走统一鉴权,不用每个页面单独处理。

4.2 表单录入与图表展示的细节优化

健康管理离不开数据录入。小程序端我用了微信原生的picker来做血压记录里的下拉选择,但体重和血糖这类需要精确数值的输入,我用的是 input 加数字键盘。一个细节是:小程序input type="digit"在不同机型上的表现不一样,iOS 上可以调出带小数点的数字键盘,安卓上有些版本没有。所以我干脆用type="number"配合placeholder提示用户“如果录入整数,小数位请补零”,保证数据格式一致性。

图表展示这块我刚开始用的是echarts-for-weixin,社区维护的 echarts 小程序版本,图表渲染效果好,但有个痛点是包体积太大,光 echarts 小程序代码就有几百 KB。后来项目上线时我把图表组件改成了 canvas 手绘折线图,核心代码不到 200 行,图标渲染性能反而更流畅。如果只是展示简单的趋势折线,我建议别再用 echarts 了,canvas 手绘完全够用,还省下载体积。

手绘折线图的关键代码如下,主要就是根据数据最大值最小值换算坐标:

drawLineChart(canvasId, dates, values) { const ctx = wx.createCanvasContext(canvasId, this) const width = 340 const height = 200 const padding = 20 const minVal = Math.min(...values) - 10 const maxVal = Math.max(...values) + 10 ctx.clearRect(0, 0, width, height) // 绘制背景参考线 ctx.setStrokeStyle('#e8e8e8') ctx.setLineWidth(1) for (let i = 0; i <= 4; i++) { const y = padding + (height - padding * 2) * i / 4 ctx.beginPath() ctx.moveTo(padding, y) ctx.lineTo(width - padding, y) ctx.stroke() } // 绘制折线 ctx.setStrokeStyle('#4a90d9') ctx.setLineWidth(2) ctx.beginPath() values.forEach((val, index) => { const x = padding + (width - padding * 2) * index / (values.length - 1) const y = padding + (height - padding * 2) * (maxVal - val) / (maxVal - minVal) if (index === 0) { ctx.moveTo(x, y) } else { ctx.lineTo(x, y) } }) ctx.stroke() ctx.draw() }

注意 canvas 在真机上的渲染可能存在视网膜屏模糊问题,需要在小程序配置canvas-id对应的canvas组件上开启type="2d"新接口。老接口wx.createCanvasContext在部分新机上已开始预警废弃,迁移到canvas.getContext('2d')是大趋势,建议新项目直接上 2D 接口。

4.3 顶部导航栏高度适配与页面布局

小程序页面在 iPhone 刘海屏、安卓全面屏、以及新增的灵动岛机型上,顶部导航栏的高度都不一样。如果直接写死一个height: 64px,在安卓手机上会显得特别挤,在 iPhone 上又会顶到状态栏。我封装了一个工具函数获取胶囊按钮的位置信息,动态计算导航栏高度:

const getNavBarInfo = () => { const systemInfo = wx.getWindowInfo() const menuRect = wx.getMenuButtonBoundingClientRect() const statusBarHeight = systemInfo.statusBarHeight const navBarHeight = (menuRect.top - statusBarHeight) * 2 + menuRect.height return { statusBarHeight: statusBarHeight, navBarHeight: navBarHeight, menuRect: menuRect } }

页面布局时,自定义导航栏的高度直接取这两个数据拼接,内容区再根据导航栏高度设置padding-top。这个方案不仅能解决布局错乱问题,还能让自定义导航栏在视觉上和微信原生风格一致。另外,页面的page.json中要设置"navigationStyle": "custom",否则自定义导航栏会和系统默认导航栏叠加两层,这是新手最容易踩的坑。

健康报告页面的卡片式布局,配合scroll-view的滚动加载,在低端安卓机上我用的是wx.createIntersectionObserver监听滚动到底部来触发加载更多,而不是简单绑定scrolltoupper事件,这样性能上更可控。

5. 项目联调、部署与性能优化

5.1 本地联调环境配置

小程序和后台本地联调是很多新手搞不定的环节。小程序的wx.request默认要求合法域名,开发调试阶段可以在微信开发者工具里勾选“不校验合法域名”,同时开启“本地设置 - 不使用缓存”,这样才能连本地后端。但真机预览时,这个选项不生效,必须用手机和电脑在同一局域网,后端监听0.0.0.0:8080,并且小程序请求地址要写电脑的局域网 IP。

我在后端调试时经常遇到跨域问题。小程序端的请求并不是浏览器环境,所以传统 CORS 的 preflight 请求不会出现,但后端还是要在配置里放开所有源,避免某些 Web 端调试工具(比如页面版管理后台)联调时受阻:

@Configuration public class CorsConfig { @Bean public CorsFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); config.addAllowedOriginPattern("*"); config.addAllowedHeader("*"); config.addAllowedMethod("*"); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } }

这里提醒一下,生产环境不要用addAllowedOriginPattern("*")彻底放开,建议把域名白名单配置到配置文件里,用config.setAllowedOrigins(Arrays.asList(allowOrigins))来加载。

5.2 服务器部署与 HTTPS 配置

微信小程序正式版要求所有请求域名必须是 HTTPS 且经过 ICP 备案。这个流程绕不开,但实际操作时要注意:域名防护墙、证书更新、Nginx 反代配置,三个步骤顺序不能乱。

我的部署方案是:

  1. 在云服务器安装 Nginx 和 Docker。
  2. 后端应用打成 jar 包,用 Dockerfile 构建镜像,运行在 8080 端口。
  3. Nginx 监听 443 端口,配置 SSL 证书,将/api/路径下的请求反向代理到本机的 8080。
  4. 配置 HTTP 强制跳转 HTTPS。

Nginx 关键配置片段如下:

server { listen 443 ssl; server_name api.health.example.com; ssl_certificate /etc/nginx/cert/api.health.example.com.pem; ssl_certificate_key /etc/nginx/cert/api.health.example.com.key; location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 60s; } }

这里最容易出错的是proxy_pass的路径拼接问题。location /api/且proxy_pass http://127.0.0.1:8080;时,请求/api/user/list会被代理为http://127.0.0.1:8080/api/user/list,后端接口路径保持原样。但如果proxy_pass写成http://127.0.0.1:8080/;,路径里的/api/会被吃掉,后端就会出现 404。我的习惯是后端接口统一加/api前缀,Nginx 反向代理时保持路径不变,匹配规则简单明了。

5.3 性能优化与安全加固

健康管理系统的数据隐私级别很高,上线前我把安全性能做的比较重:

  • 接口加密:小程序端提交敏感数据(如血糖、血压)时用 AES 加密 JSON body,后端用密钥解密。这个方案防的是抓包工具直接获取明文,但要注意前端加密一定会被逆向,所以密码只作为一层基本防护,核心还是 HTTPS + 权限控制。
  • 敏感数据脱敏:查询列表接口不返回手机号等敏感字段,只在用户授权后单独接口获取。
  • 限流控制:登录接口和验证码接口加简单的 Guava RateLimiter 限流,防止被恶意刷。

性能层面,我重点优化了健康趋势接口。之前直接用select *查出所有记录,再在 Java 代码里做日期分组和统计,数据量小时没问题,用户记录 1000 条时响应时间到了 2 秒。改用 SQL 按日期分组聚合后,接口响应降到 200ms 以内。这里我体会到,能用 SQL 聚合解决的就不要拉到 Java 内存里算,数据库的聚合功能在健康记录这类数据上效率非常明显。

6. 常见问题与排坑实录

6.1 微信登录 session_key 失效问题

之前线上出现过用户登录后,晚上再打开小程序无法获取最新体征数据的情况。排查发现,是因为我把 session_key 的过期时间设成了 2 小时,而 Redis 里的 token 设置了 7 天。用户第二次请求时 token 还没过期,但 session_key 已经没了,解密手机号等接口就失败。

解决思路:session_key 的有效期不需要特别短,因为它的作用是配合解密接口,真正的访问控制由 token 完成。我把 session_key 过期时间重新调整为和 token 一致的 7 天,并在用户重新调用wx.login时刷新 Redis 里的值。同时增加一个修复逻辑:后端发现 session_key 缺失时,返回特定错误码,小程序端收到后重新执行wx.login。

6.2 小程序真机与模拟器的行为差异

这个项目让我最头疼的是wx.getWindowInfo()在模拟器和真机返回的statusBarHeight不一致。在开发者工具里显示正常的高度,到 iPhone 14 Pro 上就出现刘海屏遮挡。后来统一改为用wx.getMenuButtonBoundingClientRect()拿胶囊按钮位置动态计算,不再依赖模拟器的固定值。这个适配方案在真机上验证了 20 多款机型,基本没有出现明显偏移。

另一个差异是本地存储。模拟器里wx.setStorageSync写入的数据在代码更新后还在,容易让人误以为数据清空逻辑没问题,真机上用户注册并退出后,旧数据没有清除会导致下一个用户登录后看到上一个用户的记录。这是因为我在用户切换时只清理了 token,没有清理本地缓存的 userId 和健康数据。修复很简单,登录成功时写 userId,退出时wx.removeStorageSync把所有本地缓存都清掉,还要在后端强校验每个请求的 userId 都是来自 token,而不是请求参数,避免越权风险。

6.3 请求超时与连接池配置问题

项目刚上线那阵,有个用户反馈在弱网环境下健康数据提交经常失败。排查后发现不是后端的问题,而是小程序默认请求超时时间是 60 秒,但我后端连接池和数据库连接池超时设置太短,Redis 读取 session_key 超时只有 200ms,在服务器负载高的时候容易触发超时异常。

我调整后的配置:

spring: datasource: hikari: connection-timeout: 30000 maximum-pool-size: 20 minimum-idle: 5 redis: timeout: 5s

同时在小程序端优化了弱网体验:提交按钮做了防重复点击,请求前先检查wx.getNetworkType(),网络类型为 none 时直接提示用户检查网络,而不是让用户白等 60 秒。

6.4 部署上线后的小程序审核注意事项

健康管理系统归类上容易涉及“医疗器械”边界,如果小程序名字里包含“医疗”“诊断”这类词,微信审核会要求提供相关资质。我这次把小程序命名为“XX健康助手”,主要功能定位为“健康数据记录与建议”,避开了医疗诊断类目。另外,涉及用户隐私的《微信小程序用户隐私保护指引》配置务必在后台填写完整,收集的用户信息(手机号、健康数据)必须声明用途,否则审核大概率被驳回。

个人开发者的账号限制也要注意:首次发布小程序需要个人身份认证,部分健康类目可能要求企业主体,这个在项目启动前就要确认,否则开发完上线时发现类目不对就很被动了。

在这个项目的收尾阶段,我个人最大的感受是:健康管理系统的技术栈虽然不是最前沿的,但做好它需要认真对待三件事——数据安全、权限边界、以及移动端适配的细节。SpringBoot 和微信小程序的组合已经非常成熟,项目的成败更多取决于对业务场景的理解和对细节的把控。比如 session 的持久化管理、按指标类型设计的通用表、动态导航栏高度适配,这些单拎出来都是很基础的东西,串起来才构成了一个稳定可用的系统。

如果你也准备做类似的小程序项目,我建议先把“用户登录 + 一项核心体征记录 + 列表展示”这条最小闭环跑通,再逐步叠加健康建议和科普内容。最后再分享一个小技巧:把健康建议的规则配置界面做出来之后,你会发现运营人员自己调整规则文案和阈值,比你每次改代码发版要省下大量时间,这才是管理系统里“管理”二字真正的价值所在。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 14:00:08

宫颈癌YOLOv5检测数据集实战:从数据校验到训练调参全流程

简介&#xff1a;面向宫颈癌检测的YOLOv5格式高质量数据集&#xff0c;类别为cancer&#xff0c;面向计算机视觉、深度学习和医疗影像分析方向的研究者与开发者&#xff0c;可直接用于目标检测模型训练、小目标检测算法验证及标注效果评估等环节。压缩包体积约180.22MB&#xf…

作者头像 李华
网站建设 2026/10/17 9:17:46

水果识别系统毕设实战:四大模型微调与Web部署全流程

简介&#xff1a;本资源是一套完整的Python高分毕业设计项目&#xff0c;面向计算机、人工智能、电子信息等专业本科生及深度学习初学者&#xff0c;聚焦水果图像智能识别这一典型CV任务。项目基于VGG16、ResNet50、MobileNetV2和DenseNet121四大主流模型&#xff0c;采用迁移学…

作者头像 李华
网站建设 2026/10/17 0:37:19

IAR新版本全面解析:界面焕新、许可证重构与Cortex-M工程迁移实践

用了这么多年嵌入式工具链&#xff0c;我早以为IAR的界面已经焊死在经典风格上了。结果这次打开新版本&#xff0c;冷不丁发现整个IDE的皮都换了一副——从默认字体、图标风格到工程导航栏全变了&#xff0c;第一眼还真有几分陌生感。但适应两周之后回头再看&#xff0c;又觉得…

作者头像 李华
网站建设 2026/10/20 9:06:46

从拨号到光纤到卫星:一张图看懂互联网接入演进

前几天一个做室内设计的朋友问我&#xff1a;家里宽带从百兆升到千兆&#xff0c;可看视频偶尔还是转圈&#xff0c;到底差在哪儿&#xff1f;我反问他&#xff1a;你知道自己家的数据是怎么从路由器钻出去、跑到视频网站机房的吗&#xff1f;他愣了几秒说&#xff0c;不就是网…

作者头像 李华
网站建设 2026/10/21 21:10:05

pstack + Claude Code:AI辅助分析线程堆栈的实战指南

1. 先聊清楚&#xff1a;pstack-claude 到底是个什么活儿看到“pstack-claude”这个组合&#xff0c;很多人的第一反应是&#xff1a;这俩东西怎么凑到一起了&#xff1f;pstack 是老牌的系统级调试工具&#xff0c;用来抓进程的线程堆栈&#xff1b;Claude 是当前热门的 AI 编…

作者头像 李华
网站建设 2026/10/16 0:48:58

MySQL连接实战:驱动选型、参数调优与高频坑排查

简介&#xff1a;这是一份面向VB.NET初学者的MySQL连接示例工程&#xff0c;核心演示数据库连接与注册登录模块的实现。压缩包共15个文件&#xff0c;主要包含7个VB源码文件、2个资源描述文件&#xff0c;以及项目配置、解决方案等管理文件&#xff0c;覆盖窗体设计、项目配置与…

作者头像 李华