居民健康监测系统:Spring Boot与小程序的前后端契约实现

发布时间:2026/9/15 18:24:14
居民健康监测系统:Spring Boot与小程序的前后端契约实现 简介基于Spring Boot与微信小程序构建的居民健康监测系统是一份完整的Java毕业设计源码。系统围绕用户健康管理场景实现了微信授权登录、体温/心率/步数等健康数据采集、异常实时预警、可视化图表分析以及医生远程咨询等核心功能适合正在开展毕业设计或希望掌握前后端分离开发流程的开发者参考。资源包共包含1612个文件涵盖Java后台源码、Vue前端页面、小程序WXML/WXSS与JavaScript逻辑、JSON配置、SQL数据库脚本、Markdown说明及部署批处理脚本等类型压缩包整体约29.94MB目录结构清晰可作为小程序Spring Boot工程的组织范本。已有114人学习使用。读者可从中获取整套项目代码及资源文件对照理解Spring Boot接口开发、小程序端请求交互、数据持久化、图表展示以及健康预警策略的具体实现同时可参考其中的初始化脚本与部署配置快速搭起同类健康监测应用对完成毕业设计、课程实训或商业项目原型都有较高价值。1. 居民健康监测系统的实现问题不在“监测”而在前后端契约一个居民健康监测系统拆开看真正少见的是“居民”这个入口常见的是“健康数据”本身——血压、心率、血糖、体温、睡眠时长指标五花八门量纲、频率、参考区间各不相同。Spring Boot 在这里承担的是数据采集、指标计算、预警判断和趋势统计微信小程序承担的是录入、展示和提醒。两者之间最容易被忽视的是契约接口字段没对齐、时间格式差 8 小时、离线补传产生重复数据。做这类项目比较容易上手的路径是先画数据流再定接口然后才写业务代码。下面按这条链路逐步展开覆盖从表结构设计到小程序端补传的完整实现。2. 从健康数据采集到Spring Boot存储的链路设计2.1 先定数据流小程序采集、Spring Boot清洗、MySQL落库健康监测系统的数据流通常分四条用户手动录入或设备自动采集、小程序端组装请求、Spring Boot 接口接收并清洗、MySQL 落库后做统计与预警。血压、心率这类指标结构差异大同一张表的固定列只适合放通用字段指标明细应当放在 JSON 字段里否则每次加一种监测指标都要改表结构。2.2 建表与接口契约先行避免前后端各写一套2.2.1 health_record 表结构CREATE TABLE health_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, metric_type VARCHAR(32) NOT NULL COMMENT heart_rate / blood_pressure / blood_glucose / temperature / sleep, value_json VARCHAR(512) NOT NULL COMMENT 指标明细JSON字符串, measured_at DATETIME NOT NULL COMMENT 测量时间东八区, client_req_id VARCHAR(64) DEFAULT NULL COMMENT 客户端幂等ID, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_client_req (client_req_id), KEY idx_user_time (user_id, measured_at) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;2.2.2 上报接口的参数说明字段类型说明metricTypestring指标类型枚举见表注释valueJsonobject血压传 {systolic:135,diastolic:88}心率传 {value:75}measuredAtlong 时间戳小程序端生成单位为毫秒避免时区偏差clientReqIdstring小程序生成的 UUID用于弱网重传去重上报接口在 Spring Boot 侧对应 HealthRecordController核心逻辑是先按用户维度和指标类型做基础校验再把 valueJson 序列化后落库。常见做法是保存成功后立刻执行一次预警评估这样实时性最好后面预警逻辑会展开说明。2.3 处理多端时钟不一致的时间字段问题2.3.1 时间戳转换PostMapping(/record) public ResultLong upload(RequestBody RecordRequest req) { LocalDateTime measuredAt Instant.ofEpochMilli(req.getMeasuredAt()) .atZone(ZoneId.of(Asia/Shanghai)) .toLocalDateTime(); HealthRecord record new HealthRecord(); record.setUserId(req.getUserId()); record.setMetricType(req.getMetricType()); record.setValueJson(objectMapper.writeValueAsString(req.getValueJson())); record.setMeasuredAt(measuredAt); record.setClientReqId(req.getClientReqId()); healthRecordService.save(record); alertService.evaluate(record); return Result.ok(record.getId()); }这段代码最容易“看起来没问题”但实际错在时间上。小程序端 Date.now() 返回的是 UTC 毫秒数后端如果不转成东八区统计报表会整体偏移 8 小时。这里用 Instant 接收时间戳、再显式指定 Asia/Shanghai 时区字段语义就不会被环境变量带跑。3. 在Spring Boot中实现健康指标的统计与预警3.1 指标分级血压心率不能写死在 if-else 里3.1.1 参考区间表指标低值正常区间高值心率次/分6060-100100收缩压mmHg9090-139≥140舒张压mmHg6060-89≥90血糖mmol/L3.93.9-6.17.0阈值写在代码里一旦医生要调整就得重新发版常见做法是把规则拆成数据库表由运营后台动态维护。alert_rule 表包含 metric_type、field、operator、threshold_lo、threshold_hi、message、enabled 字段Spring Boot 侧只做匹配逻辑。3.1.2 动态规则评估public void evaluate(HealthRecord record) { ListAlertRule rules alertRuleMapper.selectList( new LambdaQueryWrapperAlertRule() .eq(AlertRule::getMetricType, record.getMetricType()) .eq(AlertRule::getEnabled, true)); if (rules.isEmpty()) return; JsonNode node objectMapper.readTree(record.getValueJson()); for (AlertRule rule : rules) { double value node.get(rule.getField()).asDouble(); if (compare(value, rule.getOperator(), rule.getThresholdLo(), rule.getThresholdHi())) { saveAlert(record.getUserId(), rule.getMessage()); } } }compare 方法的逻辑是operator 为 gt 时 value 大于 threshold_hi 才触发operator 为 between 时 value 落在 threshold_lo 和 threshold_hi 之间触发。规则表里同一指标可以配多条规则比如心率既配“100 偏快”也配“60 偏慢”每条规则独立评估、独立入库互不影响。3.1.3 预警冷却避免通知刷屏预警记录写入 alert_log 表但同一用户同一规则在 60 分钟内重复触发时只更新最后触发时间。实现时给 alert_log 加 (user_id, rule_id, status) 联合索引查询最近一条触发时间再做判断否则心率偏高的人每天会收到几十条通知。3.2 趋势报表的聚合查询3.2.1 按天聚合的 Mapper 写法SELECT DATE_FORMAT(measured_at, %Y-%m-%d) AS day, ROUND(AVG(CAST(JSON_EXTRACT(value_json, $.value) AS DECIMAL)), 2) AS avg_value, MAX(CAST(JSON_EXTRACT(value_json, $.value) AS DECIMAL)) AS max_value FROM health_record WHERE user_id #{userId} AND metric_type #{metricType} AND measured_at #{startTime} GROUP BY day ORDER BY dayJSON_EXTRACT 取出 value_json 中的数值CAST 转成 DECIMAL 后才能参与 AVG 聚合。这个查询跑在 (user_id, measured_at) 联合索引上千万级数据也能在几百毫秒内返回。难点在于 DATE_FORMAT 依赖 MySQL 时区数据库连接串里的 serverTimezone 必须和业务时区一致不然分组结果会按 UTC 日期错位。3.2.2 聚合数据组装的常见误区统计接口返回给小程序的数据结构里day 是字符串、avg_value 是 BigDecimal小程序端画折线图时不能直接用数组下标当横轴。常见做法是后端在返回前补全缺失日期比如某天没有数据就填 null前端图表才不会跳变。补全逻辑放在 Spring Boot 服务层不要扔给小程序端。4. 微信小程序对接Spring Boot的实现细节4.1 登录态code2session 换 openid再发自定义 token4.1.1 小程序侧登录代码// app.js 登录入口 wx.login({ success: (res) { wx.request({ url: https://api.example.com/api/auth/login, method: POST, data: { code: res.code }, success: (resp) { wx.setStorageSync(token, resp.data.data.token) wx.setStorageSync(openid, resp.data.data.openid) } }) } })4.1.2 Spring Boot 侧登录接口PostMapping(/auth/login) public ResultAuthVO login(RequestBody LoginRequest req) { String openid wechatService.code2Session(req.getCode()); String token UUID.randomUUID().toString().replace(-, ); tokenCache.put(token, openid, Duration.ofDays(7)); return Result.ok(new AuthVO(token, openid)); }code2Session 内部调用微信官方接口传入小程序的 appid 和 secret。这里有个关键点secret 只能放在后端小程序端一旦打包就相当于公开所以 4.1.1 里的请求地址必须是开发者自己的网关。token 用缓存管理不落数据库后续接口统一从这个 token 反查 openid。4.2 健康报告图表uCharts 在 canvas 上渲染小程序端展示趋势数据通常用 uCharts 或 ec-canvas。uCharts 包体积更小渲染性能在低端安卓机上表现更好。常见做法是把统计接口返回的 day 数组和 avg_value 数组直接传给 uCharts 的 categories 和 series。要注意 canvas 组件在 scroll-view 里滚动时2d 模式需要手动设置 canvas-id 和类型否则图表会出现空白。4.3 弱网补传本地缓存队列加幂等去重4.3.1 上报前先入本地队列// 上报健康数据先存本地再发送 function saveHealthRecord(record) { const records wx.getStorageSync(pendingRecords) || [] records.push({ ...record, clientReqId: generateUUID(), localTime: Date.now() }) wx.setStorageSync(pendingRecords, records) flushPendingRecords() } function flushPendingRecords() { const pending wx.getStorageSync(pendingRecords) || [] if (!pending.length) return const head pending[0] wx.request({ url: https://api.example.com/api/health/record, method: POST, header: { X-Token: wx.getStorageSync(token) }, data: head, success: () { wx.setStorageSync(pendingRecords, pending.slice(1)) flushPendingRecords() }, fail: () {} }) }4.3.2 后端用 clientReqId 去重队列方案保证数据不丢但网络抖动时可能出现服务端已写入、客户端响应超时的情况前端重发就会产生重复数据。数据库里给 client_req_id 加唯一索引命中即返回原记录这是最省事的幂等方案。generateUUID 用时间戳加随机数拼接即可不要用 Math.random 单独生成碰撞概率会明显上升。5. 用Mock数据和并发测试验证Spring Boot接口居民健康监测系统上线前建议做两类验证一类是 Mock 数据检查趋势图正确性一类是并发测试看接口在 30 个居民同时上报时是否稳定。# 生成 1200 条心率记录分布在 100 个用户、10 天里 for i in $(seq 1 1200); do uid$((i % 100 1)) ts$(date -u -d 2026-02-01 $((i % 10)) days %Y-%m-%dT%H:%M:%SZ) val$((60 i % 40)) curl -s -X POST http://localhost:8080/api/health/record \ -H Content-Type: application/json \ -H X-Token: test-token \ -d {\metricType\:\heart_rate\,\valueJson\:{\value\:$val},\measuredAt\:\$ts\} /dev/null done # 聚合验证 curl -s http://localhost:8080/api/health/stats?range10dmetricheart_rateuserId1脚本里的 uid 用固定取模计算ts 横跨 10 天val 限定在 60 到 99 之间。跑完后应看到 userId1 的聚合结果在第 1 天为 60 多、第 2 天为 61 多呈现单调递增说明时间字段和聚合 SQL 都没问题。若日期落在 UTC 与北京时间交界处导致某天没有数据就在 MySQL 里执行 SET time_zone 08:00 并重启连接池。并发测试建议用 JMeter 或 Locust30 线程各循环 50 次观察接口 99% 响应时间。常见坑有两个一是 Spring Boot 版本较高时默认 ObjectMapper 不认识带小数秒的时间字符串遇到 DateTimeParseException 就统一在请求里传时间戳二是 MySQL 连接池默认大小只有 1030 并发时会等待连接报 Connection is not available 的话需要把 spring.datasource.hikari.maximum-pool-size 调到 50并观察 active 数是否打满。跑完 Mock 数据后可以再用抓包工具检查小程序的请求体确认 valueJson 字段与后端实体一致这一条路的契约没断开整个居民健康监测系统的闭环就算验证通过了。本文还有配套的精品资源点击获取