news 2026/10/17 5:08:55

SSM家庭健康管理系统:轻量级健康指标管理与阈值告警平台

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SSM家庭健康管理系统:轻量级健康指标管理与阈值告警平台

简介:这是一套基于SSM(Spring+SpringMVC+MyBatis)框架开发的家庭健康管理系统实战项目,面向Java初学者与Web开发入门者,聚焦家庭场景下的健康指标数字化管理需求。系统涵盖家庭管理、成员档案维护、健康数据上报、指标动态跟踪及可视化健康报告生成等核心功能,兼顾业务逻辑完整性与Web前后端协作实践。资源包共107个文件,含34个Java后端类(实现Controller/Service/DAO分层)、21个JSP页面(含Bootstrap响应式前端界面)、17个XML配置文件(Spring与MyBatis整合关键),辅以JS交互脚本、CSS样式及SQL建表语句,整体仅806KB,轻量易部署。目前已有42人学习下载,读者可直接运行调试,获得完整MVC结构工程、带日期选择器的健康数据录入界面、多角色用户权限基础框架,以及清晰的模块化目录组织,是理解健康管理类Web应用从需求分析到落地实现的优质教学参考。

1. 一个家庭健康数据“中枢”:不是体检报告归档工具,而是能联动预警、支持多角色协同的轻量级健康指标管理平台

你有没有遇到过这种场景:家里老人每月测三次血压,数据记在小本子上;孩子学校发来视力筛查结果,PDF藏在手机相册里;自己健身App导出的心率曲线,和医院体检单上的空腹血糖完全对不上——所有健康数据散落在不同载体、不同时间点、不同人手里,真要查某人半年趋势,得翻三台设备、问五个人。这个基于SSM框架的家庭健康管理系统,就是为解决这个“数据孤岛+人工拼图”问题而生的。它不追求替代专业医疗系统,也不堆砌AI诊断模型,而是用极简的Java Web技术栈(Spring + SpringMVC + MyBatis),把家庭成员的体重、血压、血糖、心率、睡眠时长、运动步数等核心健康指标,统一收口到一个可登录、可分权、可图表化、可设阈值告警的Web界面里。适合社区卫生站做家庭医生签约辅助、中小学校健康管理台账、或普通家庭三代同堂的日常健康监护。项目结构清晰、无第三方云依赖、MySQL本地部署即可跑通,新手照着文档30分钟能启服务,熟手两天内可接入智能手环API或扩展慢病随访模块。


2. 技术栈与工程结构解析:SSM不是过时标签,而是可控、可调试、可落地的务实选择

这个项目没追Spring Boot自动配置的“爽感”,也没上微服务拆分的“体面”,它用最经典的SSM组合,把每个技术组件的职责边界划得清清楚楚——这恰恰是现场交付、二次开发、甚至排查线上问题时最需要的确定性。下面从源码包解压后的根目录开始,一层层剥开它的骨架。

2.1 项目目录结构:标准Maven Web工程,但关键配置全在眼皮底下

解压后你会看到典型的src/main/java、src/main/webapp、src/main/resources三层结构。重点不在层级,而在几个“一眼定位”的文件:

  • src/main/webapp/WEB-INF/web.xml:Servlet容器入口,定义了DispatcherServlet拦截/路径,以及字符编码过滤器——这是整个MVC链路的起点;
  • src/main/resources/spring-mvc.xml:SpringMVC配置,明确声明了@Controller扫描包、视图解析器(前缀/WEB-INF/jsp/,后缀.jsp)、静态资源放行规则(/static/**);
  • src/main/resources/spring-mybatis.xml:MyBatis核心配置,包含数据源(com.mysql.cj.jdbc.Driver)、连接池(org.apache.commons.dbcp.BasicDataSource)、Mapper扫描路径(mapper/**/*.xml);
  • src/main/resources/jdbc.properties:数据库连接参数独立外置,方便部署时替换,内容就四行:jdbc.driver=com.mysql.cj.jdbc.Driver、jdbc.url=jdbc:mysql://localhost:3306/health_db?useSSL=false&serverTimezone=Asia/Shanghai、jdbc.username=root、jdbc.password=123456。

提示:jdbc.properties里的serverTimezone=Asia/Shanghai是MySQL 8.0+必须项,漏写会导致启动时报The server time zone value 'XXX' is unrecognized——这是新手第一道墙,别跳过。

2.2 核心包结构:按业务域分层,Controller层直击高频操作

src/main/java下是标准的com.health.*包体系,但命名不玩抽象,直接对应功能:

  • com.health.controller:只有5个Controller类,FamilyMemberController(管理家庭成员增删改查)、HealthRecordController(核心指标录入与查询)、DashboardController(首页聚合图表)、AlertRuleController(告警阈值设置)、LoginController(登录登出);
  • com.health.service:接口+实现分离,如HealthRecordService接口定义List<HealthRecord> getRecordsByMemberIdAndDateRange(Long memberId, String startDate, String endDate),其实现类HealthRecordServiceImpl里调用Mapper并做简单业务逻辑(如日期格式校验);
  • com.health.dao:纯Mapper接口,无XML以外的SQL,例如HealthRecordMapper.java里只有一行List<HealthRecord> selectByMemberIdAndDate(@Param("memberId") Long memberId, @Param("startDate") String startDate, @Param("endDate") String endDate);;
  • com.health.mapper:XML文件与DAO接口同名,SQL写得非常直白,比如HealthRecordMapper.xml中<select id="selectByMemberIdAndDate">SELECT * FROM health_record WHERE member_id = #{memberId} AND record_date BETWEEN #{startDate} AND #{endDate}</select>——没有动态SQL嵌套,没有复杂<choose>,全是可读、可调试的硬SQL。

这种结构意味着:你要改一个血压录入逻辑,路径就是HealthRecordController → HealthRecordService → HealthRecordMapper → HealthRecordMapper.xml,链路长度固定为4,没有中间代理、没有AOP横切、没有响应式流——代码在哪里、问题就在哪里。

2.3 数据库设计:6张表撑起全部功能,字段命名拒绝“玄学缩写”

建库脚本health_db.sql在src/main/resources/sql/目录下,共6张表,每张表字段数控制在10个以内:

表名关键字段说明
family_memberid,name,gender,age,relation,create_timerelation存“父亲/母亲/子女/配偶”,字符串而非枚举ID,降低前端渲染复杂度
health_recordid,member_id,record_date,systolic_bp,diastolic_bp,blood_sugar,heart_rate,weight,sleep_hours,steps所有健康指标字段均为DECIMAL(8,2)或INT,无VARCHAR存数字,避免后续计算转型
alert_ruleid,member_id,metric_type,min_value,max_value,enabledmetric_type为'systolic_bp'、'blood_sugar'等字符串,直接映射前端选项,不建字典表
userid,username,password,role,member_idrole为'admin'或'family_user',权限控制粒度到页面级,非按钮级
login_logid,user_id,login_time,ip_address记录登录行为,用于基础审计
system_configid,config_key,config_value,description存'default_chart_days'='30'这类全局配置,方便后期不改代码调参

注意:health_record.record_date类型为DATE,不是DATETIME。因为家庭健康数据按天汇总足够,存精确到秒反而增加索引体积、拖慢范围查询。我一般会强制要求所有日期类字段用DATE,除非业务真需要“某日第几次测量”。

2.4 前端资源组织:JSP+jQuery+Bootstrap,拒绝“前端黑匣子”

src/main/webapp/下没有node_modules,没有webpack.config.js,只有干净的三类资源:

  • /static/css/bootstrap.min.css+/static/js/jquery-3.6.0.min.js+/static/js/bootstrap.min.js:CDN下载后本地化,版本锁定,杜绝线上因CDN失效白屏;
  • /WEB-INF/jsp/:所有JSP页面,命名即功能——dashboard.jsp(首页图表)、record_list.jsp(指标列表)、record_form.jsp(录入表单);
  • /static/js/pages/:每个JSP配一个同名JS文件,如dashboard.js负责加载ECharts图表、record_list.js处理表格分页与搜索。

以record_form.jsp为例,表单元素<input type="text" name="systolic_bp" class="form-control" placeholder="收缩压(mmHg)">,其提交目标是HealthRecordController.addRecord(),Controller里直接用@RequestParam接收,不做DTO转换——简单,但足够。所有AJAX请求都用原生jQuery$.post(),返回JSON,错误统一走if (data.code !== 200) { alert(data.msg); },不搞Promise链或Vuex状态管理。这种“土法炼钢”方式,让前端同学改个按钮颜色、后端同学调个接口参数,都能在5分钟内完成并验证。


3. 启动与运行实操:从零部署到首页可见,绕不开的3个关键动作

项目不是解压即用,但也不需要你重装JDK或重配Tomcat。以下步骤基于Windows/Mac通用流程,Linux环境仅需将路径分隔符\\换成/,命令一致。

3.1 环境准备:JDK 8 + MySQL 5.7/8.0 + Tomcat 8.5,版本锁死是底线

  • JDK:必须是JDK 8(java -version输出含1.8.0_xxx),JDK 11+会因javax.servlet包路径变更导致编译失败。若已装高版本,请单独下载JDK 8u202(Oracle官网或Adoptium),并在IDE中为本项目指定JDK路径;
  • MySQL:推荐MySQL 8.0.26+,因jdbc.url中已启用serverTimezone=Asia/Shanghai。若用5.7,请注释掉该参数,并确保MySQL时区为+08:00(执行SET GLOBAL time_zone = '+08:00';);
  • Tomcat:必须8.5.x(如8.5.94),Tomcat 9+默认禁用web.xml中的<jsp-config>,会导致JSP页面无法解析。下载地址选Core: zip (pgp, sha512),解压后记住路径(如D:\apache-tomcat-8.5.94)。

提示:不要用IDE内置Tomcat!IntelliJ或Eclipse的“Add Framework Support”会自动生成web.xml副本或覆盖context.xml,导致部署后404。务必用解压版Tomcat手动部署。

3.2 数据库初始化:执行SQL脚本,但注意字符集与引擎

进入MySQL命令行(或Navicat/HeidiSQL),执行以下三步:

-- 1. 创建数据库,显式指定字符集,避免中文乱码 CREATE DATABASE health_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 2. 切换数据库 USE health_db; -- 3. 执行建表SQL(路径:src/main/resources/sql/health_db.sql) -- 注意:该SQL文件开头有 DROP TABLE IF EXISTS,确保每次执行都是干净状态 source D:/your-project-path/src/main/resources/sql/health_db.sql;

执行后检查表是否创建成功:

SHOW TABLES; SELECT COUNT(*) FROM family_member; -- 应返回0,初始无数据

关键点:utf8mb4是必须的,utf8在MySQL中实际是utf8mb3,不支持emoji和部分生僻汉字;InnoDB引擎已在SQL中指定,勿手动改成MyISAM——后者不支持事务,健康数据修改必须ACID保障。

3.3 项目部署:WAR包生成与Tomcat手动发布,拒绝IDE“一键部署”幻觉

不要点IDE的“Run on Server”。请严格按以下顺序:

  1. 清理并打包:在项目根目录(含pom.xml处)打开终端,执行:

    mvn clean package -Dmaven.test.skip=true

    成功后,target/目录下生成health-system-1.0.war(版本号以pom.xml中<version>为准)。

  2. 复制WAR包:将health-system-1.0.war复制到D:\apache-tomcat-8.5.94\webapps\目录下。

  3. 启动Tomcat:双击D:\apache-tomcat-8.5.94\bin\startup.bat(Windows)或./startup.sh(Mac/Linux)。观察控制台输出,直到出现:

    INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [xxxx] milliseconds INFO [main] org.apache.catalina.core.StandardContext.startInternal Container org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/health-system-1.0] has started
  4. 访问首页:浏览器打开http://localhost:8080/health-system-1.0/login.jsp。若看到登录页,输入默认账号admin/123456,成功跳转至仪表盘,则部署完成。

注意:首次访问可能较慢(约10秒),因Tomcat需解压WAR包并初始化Spring上下文。若卡在空白页,请立即查看D:\apache-tomcat-8.5.94\logs\catalina.out,搜索ERROR或Caused by:,90%问题在此日志中。

3.4 首页功能验证:5分钟走通一条完整数据链路

登录后,按顺序验证三个核心动作,确认系统活了:

  1. 添加家庭成员:点击顶部导航“家庭成员” → “新增”,填入姓名“张三”、性别“男”、年龄“65”、关系“父亲”,提交;
  2. 录入健康指标:点击“健康记录” → “新增”,选择成员“张三”,日期选今天,填收缩压“142”、舒张压“88”、空腹血糖“6.3”,提交;
  3. 查看图表趋势:回到首页(“仪表盘”),选择成员“张三”,图表自动加载近7天血压折线图——若线条出现,且鼠标悬停显示数值,则后端查询、前端渲染、ECharts集成全部通畅。

这三步验证了:数据库写入(INSERT)、跨表关联查询(JOIN family_member & health_record)、前端图表渲染(AJAX → JSON → ECharts)整条链路。任何一步失败,都比“首页打不开”更精准地定位问题模块。


4. 避坑指南:那些让我重启三次Tomcat、重装两次MySQL才搞懂的5个致命细节

这个项目看似简单,但SSM老框架的“隐性契约”极多。以下是我部署时踩过的坑,按发生频率排序,每条都附带复现方式和根治方案。

4.1 现象:启动Tomcat后,访问/login.jsp报404,但/health-system-1.0/目录下确有该文件

原因:web.xml中<welcome-file-list>未配置,或Tomcat未识别web.xml为有效描述符。常见于IDE误操作导致web.xml被移到错误目录,或<web-app>根标签缺少version="3.0"属性。
解决:打开src/main/webapp/WEB-INF/web.xml,确认首行是<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_0.xsd" version="3.0">;同时检查web.xml物理路径是否为WEB-INF/web.xml(不能是WEB-INF/config/web.xml)。

4.2 现象:登录时用户名密码正确,但跳转回登录页,无任何错误提示

原因:LoginController.login()方法中,session.setAttribute("user", user)后未return "redirect:/dashboard.jsp",而是写了return "dashboard",导致SpringMVC试图找名为dashboard的视图(JSP),但实际文件是dashboard.jsp。
解决:打开LoginController.java,找到login()方法,将return "dashboard";改为return "redirect:/dashboard.jsp";。注意redirect:前缀和.jsp后缀缺一不可。

4.3 现象:健康记录列表页显示“暂无数据”,但数据库health_record表里明明有记录

原因:HealthRecordController.listRecords()方法中,@RequestParam("memberId") Long memberId接收参数,但前端JSP表单提交时,<select name="memberId">的option值是字符串(如value="1"),而Spring MVC默认不自动将字符串转Long,导致memberId为null,查询条件WHERE member_id = null永远不成立。
解决:在HealthRecordController.java的listRecords()方法参数前加@RequestParam(value = "memberId", required = false) Long memberId,并在方法内加判空逻辑:

if (memberId == null || memberId <= 0) { model.addAttribute("records", new ArrayList<>()); return "record_list"; }

或者,更彻底的方案:前端<select>的option值改为value="${member.id}"(JSTL EL表达式),确保传的是数字字符串。

4.4 现象:血压图表显示为空白,浏览器F12看Network,/dashboard/data接口返回500

原因:DashboardController.getChartData()方法中,调用healthRecordService.getRecordsByMemberIdAndDateRange()时,传入的日期字符串格式为"2024-05-01",但MySQLDATE字段在BETWEEN查询中,若JDBC驱动版本低(如mysql-connector-java 5.1.x),会将字符串当作'2024-05-01 00:00:00'处理,导致时区转换错误。
解决:升级MySQL驱动。打开pom.xml,将<dependency>中mysql-connector-java版本从5.1.47改为8.0.33:

<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency>

同时确认jdbc.properties中jdbc.url包含serverTimezone=Asia/Shanghai。

4.5 现象:添加新成员后,在“健康记录”下拉框看不到该成员

原因:record_form.jsp中,<select>的<c:forEach items="${members}" var="m">循环,但HealthRecordController.toAddForm()方法未将familyMemberService.getAllMembers()结果放入Model。
解决:打开HealthRecordController.java,找到toAddForm()方法,在return "record_form";前添加:

model.addAttribute("members", familyMemberService.getAllMembers());

并确认FamilyMemberService.getAllMembers()方法在FamilyMemberMapper.xml中有对应SQL:<select id="selectAll">SELECT * FROM family_member ORDER BY create_time DESC</select>。

这些坑的共同点是:错误不抛异常,不打印ERROR日志,只表现为“功能缺失”。根源在于SSM各层间的数据契约(如参数类型、视图名、SQL字段名)必须100%精确匹配,差一个字母、一个空格、一个分号,就静默失败。所以我的血泪经验是:第一次跑通后,立刻用Postman逐个测试所有Controller接口,看返回JSON是否符合预期,而不是等前端点不动了再回头查。


5. 核心模块代码精讲:从血压录入到阈值告警,看SSM如何把业务逻辑“钉”在代码里

现在我们聚焦两个最高频、最体现业务价值的模块:健康指标录入与健康阈值告警。它们不是CRUD堆砌,而是SSM框架下“控制层接收、服务层编排、DAO层执行”的典型范式。我会逐行拆解关键代码,告诉你为什么这么写、参数怎么调、哪里能改。

5.1 健康指标录入:HealthRecordController.addRecord()——一行@Valid背后的防御式编程

这是用户每天点一次的功能,代码必须健壮。打开HealthRecordController.java,找到addRecord()方法:

@PostMapping("/record/add") @ResponseBody public Result addRecord(@Valid HealthRecord record, BindingResult result, HttpSession session) { // 1. 参数校验:@Valid触发HealthRecord实体上的@NotNull、@Min等注解 if (result.hasErrors()) { List<String> errors = result.getFieldErrors().stream() .map(e -> e.getField() + ": " + e.getDefaultMessage()) .collect(Collectors.toList()); return Result.fail("参数错误:" + String.join("; ", errors)); } // 2. 业务校验:防止录入未来日期 LocalDate today = LocalDate.now(); if (record.getRecordDate().isAfter(today)) { return Result.fail("记录日期不能晚于今天"); } // 3. 权限校验:确保当前登录用户有权为该成员录入 User currentUser = (User) session.getAttribute("user"); FamilyMember member = familyMemberService.getById(record.getMemberId()); if (!currentUser.getMemberId().equals(member.getId()) && !"admin".equals(currentUser.getRole())) { return Result.fail("无权为该成员录入数据"); } // 4. 执行保存 int rows = healthRecordService.addRecord(record); if (rows > 0) { return Result.success("录入成功"); } else { return Result.fail("录入失败,请重试"); } }

逻辑说明与参数说明:

  • @Valid HealthRecord record:Spring MVC自动将POST表单数据绑定到HealthRecord对象,并触发其字段上的JSR-303校验注解(如@NotNull(message="收缩压不能为空"))。这是防御第一关,比在Service里写if (record.getSystolicBp() == null)更优雅;
  • BindingResult result:必须紧跟@Valid参数之后,否则校验结果无法捕获。result.hasErrors()判断是否有校验失败项;
  • record.getRecordDate().isAfter(today):HealthRecord.recordDate类型为LocalDate(Java 8时间API),避免Date类的线程安全问题。LocalDate.now()获取系统本地日期,无需考虑时区;
  • session.getAttribute("user"):从Session取当前用户,User实体中memberId字段存储该用户对应的家庭成员ID,用于判断“子女能否录父母数据”——这是家庭健康系统的特有权限逻辑;
  • Result是项目自定义的统一返回类,结构为{code:200, msg:"success", data:null},前端AJAX统一处理data.code。

改造提示:若要支持“批量导入Excel”,只需在此方法内加一个@RequestParam("file") MultipartFile file参数,用Apache POI解析,再循环调用healthRecordService.addRecord()。不用动Service层,因为addRecord()本身已设计为单条插入。

5.2 告警阈值管理:AlertRuleController.saveRule()——动态SQL与业务规则的硬核结合

告警不是简单“超了就发邮件”,而是针对不同成员、不同指标、不同年龄段的差异化规则。AlertRule表的设计决定了它的灵活性,而AlertRuleMapper.xml里的SQL则体现了SSM对动态条件的掌控力。

AlertRuleController.java中saveRule()方法:

@PostMapping("/alert/rule/save") @ResponseBody public Result saveRule(@RequestBody AlertRule rule, HttpSession session) { User user = (User) session.getAttribute("user"); // 强制绑定当前用户可管理的成员 rule.setMemberId(user.getMemberId()); // 检查指标类型是否合法(防前端篡改) List<String> validTypes = Arrays.asList("systolic_bp", "diastolic_bp", "blood_sugar", "heart_rate", "weight"); if (!validTypes.contains(rule.getMetricType())) { return Result.fail("不支持的指标类型"); } int rows = alertRuleService.saveRule(rule); return rows > 0 ? Result.success("保存成功") : Result.fail("保存失败"); }

关键在AlertRuleMapper.xml的SQL:

<insert id="insertRule" parameterType="AlertRule"> INSERT INTO alert_rule (member_id, metric_type, min_value, max_value, enabled, create_time) VALUES ( #{memberId}, #{metricType}, <choose> <when test="minValue != null and minValue != ''">#{minValue}</when> <otherwise>NULL</otherwise> </choose>, <choose> <when test="maxValue != null and maxValue != ''">#{maxValue}</when> <otherwise>NULL</otherwise> </choose>, #{enabled}, NOW() ) </insert>

逻辑说明与参数说明:

  • <choose>标签是MyBatis的动态SQL,相当于Java的if-else。这里处理minValue和maxValue可能为null或空字符串的情况,避免插入''到DECIMAL字段报错;
  • #{memberId}是预编译参数占位符,防SQL注入;NOW()是MySQL函数,插入当前时间,不依赖Java端时间;
  • AlertRule实体中minValue和maxValue类型为BigDecimal,确保精度(如血糖6.35不能存成float丢失精度);
  • enabled字段为TINYINT(1),Java中用Boolean接收,MyBatis自动映射true→1、false→0。

改造提示:若要增加“告警通知方式”(短信/微信/邮件),只需在alert_rule表加notify_method VARCHAR(20)字段,AlertRule实体加String notifyMethod,AlertRuleMapper.xml中<insert>SQL加#{notifyMethod},Controller里加校验if (!Arrays.asList("sms","wechat","email").contains(rule.getNotifyMethod()))——改动集中在DAO和Controller,Service层无感知。

5.3 告警触发逻辑:HealthRecordService.checkAlerts()——定时任务与实时校验的双保险

告警不是“用户录入时立刻发短信”,而是分两层:实时校验(录入时检查并返回提示)+定时扫描(每小时扫一遍,补发遗漏)。checkAlerts()是后者的核心:

// 在HealthRecordService.java中 @Transactional public void checkAlerts() { // 1. 获取所有启用的告警规则 List<AlertRule> rules = alertRuleMapper.selectEnabledRules(); for (AlertRule rule : rules) { // 2. 查询该成员最近一条记录 HealthRecord latest = healthRecordMapper.selectLatestByMemberId(rule.getMemberId()); if (latest == null) continue; // 3. 根据指标类型取值,并与阈值比较 BigDecimal currentValue = getCurrentValue(latest, rule.getMetricType()); boolean alertTriggered = false; if (rule.getMinValue() != null && currentValue.compareTo(rule.getMinValue()) < 0) { alertTriggered = true; } if (rule.getMaxValue() != null && currentValue.compareTo(rule.getMaxValue()) > 0) { alertTriggered = true; } // 4. 若触发且未发送过,插入告警记录并通知 if (alertTriggered) { AlertLog log = new AlertLog(); log.setRuleId(rule.getId()); log.setRecordId(latest.getId()); log.setAlertValue(currentValue); log.setCreateTime(new Date()); alertLogMapper.insert(log); // 此处调用通知服务(伪代码) notificationService.sendAlert(rule.getMemberId(), rule.getMetricType(), currentValue); } } } // 工具方法:根据指标类型从HealthRecord取值 private BigDecimal getCurrentValue(HealthRecord record, String metricType) { switch (metricType) { case "systolic_bp": return record.getSystolicBp(); case "diastolic_bp": return record.getDiastolicBp(); case "blood_sugar": return record.getBloodSugar(); case "heart_rate": return record.getHeartRate(); case "weight": return record.getWeight(); default: return BigDecimal.ZERO; } }

逻辑说明与参数说明:

  • @Transactional保证整个扫描过程原子性,避免部分规则执行成功、部分失败导致状态不一致;
  • alertRuleMapper.selectEnabledRules()SQL很简单:SELECT * FROM alert_rule WHERE enabled = 1;
  • healthRecordMapper.selectLatestByMemberId()SQL用ORDER BY record_date DESC LIMIT 1,确保取最新一条,而非最大ID(ID不等于时间顺序);
  • currentValue.compareTo(rule.getMinValue()) < 0:BigDecimal必须用compareTo()比较大小,==或>会比较引用;
  • notificationService.sendAlert()是预留扩展点,目前为空实现,你可接入短信网关(如阿里云SMS)或企业微信机器人。

这个方法会被Spring的@Scheduled(cron = "0 0 * * * ?")每小时执行一次。若要改成“每15分钟”,把cron改为"0 0/15 * * * ?"即可。注意:生产环境务必加分布式锁,避免多实例重复扫描——这是你接手后的第一个优化点。


6. 二次开发实战:从接入智能手环API到支持多家庭隔离,我的3个落地改造技巧

这个项目不是终点,而是你搭建家庭健康应用的“最小可行底盘”。我接手过两个真实需求:某社区卫生站要对接华为手环API自动同步数据;某国际学校要求每个班级独立健康空间,互不可见。下面分享我落地时的3个具体技巧,不讲虚的,全是抄作业就能用的代码和配置。

6.1 技巧一:用Spring RestTemplate无缝接入手环API,避免“手动HTTP请求”玄学

华为手环开放平台提供GET /v1/user/{userId}/health-data接口,返回JSON格式的当日步数、心率、睡眠。传统做法是写HttpURLConnection,但容易出错。用Spring RestTemplate,5行代码搞定:

  1. 添加依赖(pom.xml):
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.3.31</version> <!-- 与项目Spring版本一致 --> </dependency>
  1. 配置RestTemplate Bean(spring-mvc.xml):
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate"> <constructor-arg> <bean class="org.springframework.http.client.HttpClientHttpRequestFactory"> <property name="httpClient"> <bean class="org.apache.http.impl.client.CloseableHttpClient" factory-bean="httpClientBuilder" factory-method="build"/> </property> </bean> </constructor-arg> </bean> <bean id="httpClientBuilder" class="org.apache.http.impl.client.HttpClients" factory-method="custom"/>
  1. 在HealthRecordService中写同步方法:
@Autowired private RestTemplate restTemplate; public void syncHuaweiData(String userId, String accessToken) { String url = "https://api.huawei.com/v1/user/" + userId + "/health-data"; HttpHeaders headers = new HttpHeaders(); headers.set("Authorization", "Bearer " + accessToken); HttpEntity<String> entity = new HttpEntity<>(headers); try { ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class); JSONObject json = new JSONObject(response.getBody()); // 解析JSON,构造HealthRecord对象 HealthRecord record = new HealthRecord(); record.setMemberId(getMemberIdByHuaweiUserId(userId)); // 你自己的映射逻辑 record.setRecordDate(LocalDate.now()); record.setSteps(json.optInt("steps", 0)); record.setHeartRate(new BigDecimal(json.optString("heartRate", "0"))); record.setSleepHours(new BigDecimal(json.optString("sleepHours", "0"))); healthRecordService.addRecord(record); // 复用原有录入逻辑 } catch (Exception e) { log.error("同步华为数据失败,userId:{}", userId, e); } }

关键点:RestTemplate自动处理JSON序列化、HTTP状态码、连接池复用;JSONObject来自org.json:json,轻量无依赖。不要用Jackson,会与Spring MVC的@ResponseBody冲突。

6.2 技巧二:用数据库Schema隔离实现“多家庭空间”,比ShardingSphere简单10倍

某学校要求:A班学生数据,B班老师看不到。SSM项目没上分库分表,但可以用MySQL Schema(数据库)隔离。原理:每个家庭(或班级)一个独立数据库,如health_db_class_a、health_db_class_b,共用同一套代码,只切换数据源。

  1. 修改spring-mybatis.xml,支持多数据源:
<!-- 定义两个数据源 --> <bean id="dataSourceA" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}_class_a"/> <!-- 动态拼接 --> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <bean id="dataSourceB" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}_class_b"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <!-- 配置AbstractRoutingDataSource --> <bean id="dynamicDataSource" class="com.health.config.DynamicDataSource"> <property name="targetDataSources"> <map key-type="java.lang.String"> <entry key="class_a" value-ref="dataSourceA"/> <entry key="class_b" value-ref="dataSourceB"/> </map> </property> < <p> <a href="https://download.csdn.net/download/XiongWinds/90270820" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 5:08:49

Burp Suite+Python脚本:CTF Web抓包找线索的高效流程

做CTF Web题&#xff0c;最怕的不是题目难&#xff0c;而是抓了一堆包&#xff0c;线索就在眼前却没认出来。Burp Suite抓包、Python脚本分析&#xff0c;这两样东西单拿出来大家都会&#xff0c;但真正把“抓包”变成“找线索”的高效流程&#xff0c;很多人其实没想明白。这篇…

作者头像 李华
网站建设 2026/10/17 5:07:25

实时控制与工业Agent:为何当前AI方案难破PLC确定性红线

1. 这个标题不是在唱衰AI&#xff0c;而是在划清工业现场的生死线“为什么我说‘实时控制的工业Agent’现在是伪命题&#xff1f;”——这句话我去年在某汽车焊装车间调试完第七台PLC后&#xff0c;在工程师休息室白板上写下的。当时旁边一位做机器视觉算法的同事盯着看了三秒&…

作者头像 李华
网站建设 2026/10/17 5:06:54

USDT支付系统源码解析:链上监听、自动回调与三级分销实现

简介&#xff1a;这是一套面向区块链支付开发者的USDT跑分支付系统完整源码&#xff0c;适合想研究数字货币收款、API监听与自动回调机制的中高级开发者&#xff0c;也可作为三级分销系统设计的参考案例。压缩包共约2000个文件&#xff0c;整体40.69MB&#xff0c;以609个js脚本…

作者头像 李华
网站建设 2026/10/17 5:06:48

零基础团队如何快速上手企业智能自动化?实在Agent实战解析

企业里问“有没有什么智能工具能帮我们省点人力”的次数&#xff0c;这两年明显变多了。尤其是看了几场所谓“AI Agent”的演示之后&#xff0c;老板们普遍有一种感觉&#xff1a;这东西好像不难&#xff0c;要不我们也上一套试试&#xff1f;可真到立项讨论的时候&#xff0c;…

作者头像 李华
网站建设 2026/10/17 5:06:47

Minimax H3视频模型本地部署ComfyUI全指南

1. 项目概述&#xff1a;Minimax H3 模型在 ComfyUI 中的本地化落地实践最近两周&#xff0c;我连续帮三位做AI视频生成的朋友部署 Minimax H3 模型进本地 ComfyUI 环境&#xff0c;过程中踩了至少七类典型坑——从显存爆表到节点报错&#xff0c;从模型加载失败到工作流输出模…

作者头像 李华
网站建设 2026/10/17 5:06:29

开源捐赠基金会:构建可持续资助模式的关键机制与实践

1. 开源项目生存现状&#xff1a;为什么需要一家捐赠基金会做开源的朋友应该都有一种共同的体感&#xff1a;写代码、提 PR、维护 Issue 是纯粹的“用爱发电”&#xff0c;但维持一个项目持续运转&#xff0c;远不只是写代码那么简单。域名要续费&#xff0c;CI 要跑&#xff0…

作者头像 李华