
简介一套面向Java学习者与课程设计者的家庭健康管理系统完整项目基于SSMSpringSpringMVCMyBatis框架围绕家庭管理、成员管理、健康指标管理、指标上报和健康报告等功能设计覆盖从数据库建模到前后端交互的典型业务闭环适合作为毕业设计或项目实践参考。压缩包共107个文件总大小约806KB其中包含34个Java源码文件、21个JSP页面、17个XML配置以及JS、CSS等前端资源并附带SQL脚本可初始化数据库解压后能清晰看到后台逻辑、页面模板与静态资源的组织方式。当前已有42人学习/下载整体项目轻量但结构完整适合快速读懂SSM整合流程和家庭健康场景下的指标管理实现思路。通过源码可以掌握用户管理、家庭成员维护、指标上报及健康报告生成等模块的编码方式同时获得可直接运行的系统原型便于二次扩展或用于答辩展示。1. 切入点家庭健康管理为什么需要一个 SSM 项目家里长辈的血压、血糖、心率这些数据现在大多记录在纸质的本子上或者散落在微信聊天记录里。时间一长想找某个月的记录对比看看趋势翻半天也翻不全。这个基于 SSM 框架Spring Spring MVC MyBatis的家庭健康管理系统解决的就是这个“数据分散、难以追踪”的问题——它把家庭成员的健康指标统一录入、存储、展示支持按时间维度查看趋势还能对异常指标给出基本判断。适合两类人一是想给家里老人做健康记录管理的开发者二是刚开始接触 SSM 整合、需要一个完整 Java Web 项目来练手的学习者。它不是一个大而全的医疗系统而是一个目标明确、结构清晰的健康指标管理工具正好可以作为从“会写单模块代码”到“能整合一个完整项目”的过渡案例。2. 技术选型与整体架构SSM 三层结构是怎么串起来的2.1 为什么选 SSM 而不选 Spring Boot这个项目用的是经典的 SSM 框架组合也就是 Spring Spring MVC MyBatis。现在很多新项目直接上 Spring Boot但 SSM 依然有它的价值它把配置过程完全暴露出来你能看到每个 Bean 是怎么装配的、事务是怎么控制的、SQL 是怎么和 Java 方法绑定的。对于理解 Java Web 底层机制来说这种“手动档”反而是一种很好的学习材料。从架构上看项目分了标准的三层表现层Controller、业务层Service、持久层Mapper/DAO。Spring 负责管理对象的生命周期和依赖注入Spring MVC 负责接收 HTTP 请求并路由到对应的 ControllerMyBatis 负责把 Java 方法和 SQL 语句映射起来处理数据库的读写操作。前端是 JSP HTML CSS JavaScript通过 Ajax 调用后端接口拿 JSON 数据渲染页面。2.2 工程目录结构拿到源码先看哪里这里用一个通用的 Maven 工程结构来说明。实际项目中目录名可能有差异但分层思路是一致的family-health-manager/ ├── pom.xml # Maven 依赖配置 ├── src/main/java │ ├── com/health │ │ ├── controller/ # 表现层处理前端请求 │ │ │ ├── UserController.java │ │ │ ├── HealthDataController.java │ │ │ └── LoginController.java │ │ ├── service/ # 业务层接口 │ │ │ ├── UserService.java │ │ │ └── HealthDataService.java │ │ ├── service/impl/ # 业务层实现 │ │ │ ├── UserServiceImpl.java │ │ │ └── HealthDataServiceImpl.java │ │ ├── mapper/ # MyBatis 数据访问接口 │ │ │ ├── UserMapper.java │ │ │ └── HealthDataMapper.java │ │ ├── entity/ # 实体类对应数据库表 │ │ │ ├── User.java │ │ │ └── HealthData.java │ │ └── interceptor/ # 登录拦截器等 │ │ └── LoginInterceptor.java │ ├── resources │ │ ├── spring/ # Spring 配置文件 │ │ │ ├── applicationContext.xml │ │ │ └── spring-mvc.xml │ │ └── mapper/ # MyBatis XML 映射文件 │ │ ├── UserMapper.xml │ │ └── HealthDataMapper.xml │ └── jdbc.properties # 数据库连接配置 └── src/main/webapp ├── WEB-INF │ └── jsp/ # JSP 视图 │ ├── login.jsp │ ├── index.jsp │ └── health/list.jsp ├── static/ # 静态资源 │ ├── css/ │ └── js/ └── index.html # 入口页面第一次拿到源码的人我建议按这个顺序捋先看 pom.xml 了解项目依赖再看 jdbc.properties 改成本地数据库配置然后从 LoginController 开始沿着“请求 → Service → Mapper → SQL”这条链路读一遍整个项目就通了。2.3 数据库表设计用户和健康指标怎么建模这个系统的核心是两张表用户表t_user和健康指标记录表t_health_data。用户表负责存储家庭成员的账号信息包括用户名、密码、姓名、年龄、角色等字段。密码存储用的是 MD5 加密后的密文这是 SSM 项目里很常见的做法虽然现在更推荐 BCrypt但作为教学项目 MD5 仍然随处可见。健康指标记录表则是整个系统的核心一条记录对应一次测量数据。字段大致包括记录 ID、用户 ID外键、体重、收缩压、舒张压、血糖、心率、体温、测量时间、备注。把血压拆成收缩压和舒张压两个字段而不是存成一个字符串是为了后续做数值范围判断和趋势图展示时可以直接参与运算这个设计在开发时值得沿用。3. 核心模块拆解从登录认证到健康指标录入的完整流程3.1 登录认证拦截器 会话控制怎么配合这个系统有一个登录拦截器LoginInterceptor它实现了 Spring MVC 的 HandlerInterceptor 接口在请求进入 Controller 之前先检查 Session 里有没有用户信息。如果没有就重定向到登录页有就放行。这里直接看核心代码public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 从 Session 中获取当前登录用户 User user (User) request.getSession().getAttribute(currentUser); if (user null) { // 未登录跳转到登录页面 response.sendRedirect(request.getContextPath() /login); return false; } // 已登录放行请求 return true; } }这段代码的逻辑很直接从 Session 里取用户对象取不到就跳回登录页。关键在于这个拦截器需要注册到 Spring MVC 的配置里并且要指定哪些路径需要拦截、哪些路径放行——通常登录接口本身、静态资源CSS/JS/图片是需要排除的否则会出现“登录页自己都加载不了样式”的问题。在 spring-mvc.xml 里注册拦截器的常见写法是mvc:interceptors mvc:interceptor !-- 拦截所有请求 -- mvc:mapping path/**/ !-- 放行登录相关接口和静态资源 -- mvc:exclude-mapping path/login/ mvc:exclude-mapping path/static/**/ mvc:exclude-mapping path/css/**/ mvc:exclude-mapping path/js/**/ mvc:exclude-mapping path/images/**/ /mvc:interceptor /mvc:interceptors这里有一个很容易踩的坑如果你把/login的 POST 提交路径也拦截了用户输入账号密码点击登录后请求根本到不了 Controller而是被重定向回登录页界面上看不出任何报错只会觉得“登录按钮点了没反应页面闪了一下”。排查方式是在浏览器开发者工具里看网络请求的响应状态码如果是 302 并且 Location 指向登录页那基本就是拦截路径配置的问题。3.2 健康指标录入Controller、Service、Mapper 三层协作录入一条健康指标数据是贯穿整个项目架构的典型操作。前端页面把表单里的各项指标通过 Ajax 提交到后端后端经过三层处理后写入数据库。先看 Controller 层的处理方法Controller RequestMapping(/health) public class HealthDataController { Autowired private HealthDataService healthDataService; RequestMapping(value /add, method RequestMethod.POST) ResponseBody public Result add(RequestBody HealthData healthData, HttpSession session) { // 从 Session 中获取当前登录用户作为这条记录的归属 User currentUser (User) session.getAttribute(currentUser); if (currentUser null) { return Result.error(登录已过期请重新登录); } // 将用户 ID 绑定到健康数据对象上 healthData.setUserId(currentUser.getId()); // 调用业务层执行新增 boolean success healthDataService.addHealthData(healthData); if (success) { return Result.success(记录成功); } else { return Result.error(记录失败请检查数据格式); } } }注意到 Controller 用RequestBody接收前端传过来的 JSON 字符串Spring 会调用 Jackson 库把它自动转换成 HealthData 实体对象。这里有一个隐含的要求前端传的字段名必须和实体类的属性名一致否则框架会抛异常或者字段全部为 null。比如前端传systolicPressure: 120实体类里的属性必须叫systolicPressure如果你写的是systolic那这个值就丢了。Service 层的实现则承担了数据校验和业务规则判断的职责Service public class HealthDataServiceImpl implements HealthDataService { Autowired private HealthDataMapper healthDataMapper; Override public boolean addHealthData(HealthData healthData) { // 基本参数校验关键指标不能为空 if (healthData.getSystolicPressure() null || healthData.getDiastolicPressure() null) { throw new RuntimeException(血压数据不能为空); } // 合理性校验收缩压范围 70-250舒张压范围 40-150 int systolic healthData.getSystolicPressure(); int diastolic healthData.getDiastolicPressure(); if (systolic 70 || systolic 250 || diastolic 40 || diastolic 150) { throw new RuntimeException(血压数据超出合理范围); } // 如果没有指定测量时间默认取当前时间 if (healthData.getMeasureTime() null) { healthData.setMeasureTime(new Date()); } // 调用持久层写入数据库 int rows healthDataMapper.insert(healthData); return rows 1; } }这段代码展示了一个合格开发者在业务层会做的事不只是简单地把数据往数据库里塞而是在入口处做了两层校验——空值校验和合理性校验。合理性校验里的血压范围是根据常识设定的一个人的收缩压如果显示 300那大概率是录入时多打了个数字。这种在业务层拦截错误数据的方式比让数据库报错要友好得多。最后看 Mapper 层的 XML 映射文件它是 MyBatis 的核心。把 Java 方法insert和一条 SQL 语句绑定insert idinsert parameterTypecom.health.entity.HealthData INSERT INTO t_health_data ( user_id, weight, systolic_pressure, diastolic_pressure, blood_glucose, heart_rate, temperature, measure_time, remark ) VALUES ( #{userId}, #{weight}, #{systolicPressure}, #{diastolicPressure}, #{bloodGlucose}, #{heartRate}, #{temperature}, #{measureTime}, #{remark} ) /insert注意 SQL 里的字段名是下划线风格systolic_pressure而#{}里写的是实体类的属性名systolicPressure。如果数据库连接配置里设置了mapUnderscoreToCamelCasetrueMyBatis 会自动把下划线风格映射成驼峰风格如果没有设置查询时的 ResultMap 就要手动做字段映射否则查出来的对象属性全部是 null。这个配置项在 mybatis-config.xml 里往往也是新手最容易忽略的细节。3.3 历史记录查询与趋势展示数据怎么变成可视化的图表有了录入的数据下一步就是把它们查出来展示。这里的查询不是简单的“查出所有记录”那么简单而是带了筛选条件按时间范围查、按家庭成员查、按指标类型查。Controller 层的查询接口接收userId和dateRange两个参数组装后传给 Service 层。Service 层按条件查询后返回一个ListHealthData。前端拿到这个列表后通过 ECharts 或者 Chart.js 这类图表库把数据渲染成折线图。以 ECharts 为例核心的 JavaScript 代码大致是这样的// 从后端接口获取健康指标历史数据 $.ajax({ url: /health/list, data: { userId: $(#userId).val(), startDate: 2025-01-01, endDate: 2025-03-31 }, dataType: json, success: function (res) { if (res.code ! 200) { alert(加载数据失败 res.message); return; } // 准备图表数据 var dates []; var systolicData []; var diastolicData []; res.data.forEach(function (item) { dates.push(item.measureTime.substring(0, 10)); systolicData.push(item.systolicPressure); diastolicData.push(item.diastolicPressure); }); // 初始化 ECharts 折线图 var chart echarts.init(document.getElementById(trendChart)); chart.setOption({ tooltip: { trigger: axis }, legend: { data: [收缩压, 舒张压] }, xAxis: { type: category, data: dates }, yAxis: { type: value, name: mmHg }, series: [ { name: 收缩压, type: line, data: systolicData }, { name: 舒张压, type: line, data: diastolicData } ] }); } });这段代码从接口拿回数据后做了一次数据转换把后端返回的 JSON 数组拆成 ECharts 需要的两个数据系列。这里有一个实际的开发经验后端返回的数据最好按测量时间排序否则折线图上会看到日期乱序造成的折线交叉回跳看起来像数据错了实际上只是排序问题。如果接口没有排好序可以在 SQL 里加ORDER BY measure_time ASC前端排序也可以但在 SQL 里处理更干净。4. 项目运行从零到跑通的全流程操作4.1 环境准备JDK、Tomcat、Maven 和数据库的版本组合要把这个项目跑起来需要准备一套 Java Web 开发环境。推荐的版本组合是这样JDK 1.8SSM 项目的标配高版本 JDK 可能会有兼容性问题、Maven 3.6管理项目依赖、Tomcat 8.5 或 9.x部署 Web 应用。数据库方面用 MySQL 5.7 及以上版本或者 MariaDB 也可以。为什么强调版本组合因为 SSM 框架是十几年前开始流行的技术栈很多老项目在 JDK 11 以上的环境中会碰到反射报错或 JSP 编译失败的问题。这不是代码的问题而是版本兼容性问题。所以如果本地装了高版本 JDK建议单独准备一个 1.8 环境或者用 IDE 里自带的 JDK 切换功能别在版本上折腾太久。4.2 建库建表初始化数据库脚本项目源码里一般会附带一个init.sql或者database.sql脚本文件。拿到的第一件事就是在 MySQL 里执行这个脚本创建数据库和表结构。如果没有附带脚本按照实体类手写建表 SQL 也不难核心表结构大致如下CREATE DATABASE IF NOT EXISTS family_health_db DEFAULT CHARACTER SET utf8mb4; USE family_health_db; -- 用户表 CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, real_name VARCHAR(30), age INT, role TINYINT DEFAULT 0 COMMENT 0-家庭成员 1-管理员, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 健康指标记录表 CREATE TABLE t_health_data ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, weight DECIMAL(5,1) COMMENT 体重kg, systolic_pressure INT COMMENT 收缩压mmHg, diastolic_pressure INT COMMENT 舒张压mmHg, blood_glucose DECIMAL(4,1) COMMENT 血糖mmol/L, heart_rate INT COMMENT 心率bpm, temperature DECIMAL(3,1) COMMENT 体温℃, measure_time DATETIME COMMENT 测量时间, remark VARCHAR(255) COMMENT 备注, FOREIGN KEY (user_id) REFERENCES t_user(id) );字符集用 utf8mb4 而不是 utf8是因为 utf8mb4 能完整支持一些特殊字符和 emoji虽然这个系统里不太可能输入 emoji但这是当前数据库建表的默认习惯。外键约束在建表时就声明好防止出现没有归属用户的孤儿数据。4.3 配置调整与启动三步把项目跑起来第一步修改jdbc.properties文件里的数据库连接信息把 URL、用户名、密码改成自己本地的这是必须做的一步。很多新手在这一步容易漏掉数据库密码或者把 URL 里的数据库名写错导致启动时数据源初始化失败。第二步用 Maven 打包。在项目根目录执行mvn clean package如果配置了阿里云等国内镜像仓库依赖下载会快很多。打包完成后在target目录下会生成一个 WAR 包这个包就是可以部署的完整应用。第三步把 WAR 包扔到 Tomcat 的webapps目录下启动 Tomcat访问http://localhost:8080/项目名/就能看到登录页面。或者更简单的方式是在 IDEA 里直接配置 Tomcat点击运行按钮一键部署。这里以 IDEA 配置 Tomcat 为例要点是在 Run Configuration 里选择 Tomcat Server - LocalDeployment 选项卡里点击加号选 ArtifactApplication context 填/或/family-health然后启动。启动日志里看到Deployment of web application archive的字样就说明部署成功了。4.4 运行验证怎么确认系统真的正常跑起来之后用几个典型操作验证系统是否正常。首先注册一个新用户然后登录登录成功后应该能看到管理首页。接着录入一条健康指标数据比如血压 120/80、心率 72、血糖 5.6保存后去历史记录页面查这条数据是否存在。最后再看趋势图页面确认折线图上有对应的点。如果登录后访问任何页面都被弹回登录页说明 Session 没有正常写入或者拦截器配置有问题如果录入数据时提示“记录失败”优先打开浏览器开发者工具看网络请求找到对应的 POST 请求查看响应信息里的具体错误提示如果页面样式全乱了那是静态资源路径配置有问题检查spring-mvc.xml里是否配置了mvc:resources映射。5. 避坑指南SSM 项目最常见的五个坑与排查方法5.1 查询结果全部是 null驼峰映射没有开启现象登录功能正常但查询健康指标列表时前端收到的数据里每个字段都是 null。数据库里明明有数据接口也没有报错。原因数据库表字段是下划线风格systolic_pressure实体类属性是驼峰风格systolicPressureMyBatis 默认不会自动把下划线映射成驼峰。如果查询时没有配置 ResultMap也没有开启驼峰映射Java 对象就接收不到数据库返回的值。解决在mybatis-config.xml的settings节点里加上setting namemapUnderscoreToCamelCase valuetrue/重新启动服务查询结果就正常了。这个配置项对 insert 不影响对 select 影响极大拿到项目第一件事就可以检查它。5.2 登录接口被拦截导致无法登录现象输入正确的用户名密码点击登录后页面刷新了一下又回到登录页没有任何错误提示。后台日志里也没有任何登录相关的请求记录。原因登录提交路径/login被拦截器拦截了。注册拦截器时写了mvc:mapping path/**/但没有把/login添加到mvc:exclude-mapping里导致 POST 请求在到达 Controller 之前就被拦下。解决在 spring-mvc.xml 的拦截器配置中把登录接口路径和静态资源路径都加到排除列表里。如果登录接口是/user/login那exclude-mapping就写/user/login注意路径要完全匹配不要写/login去排除一个/user/login的接口那样照样会被拦截。5.3 前端传日期字符串报 400 错误现象录入健康指标时测量时间字段传的是2025-03-20 08:30:00这样的字符串后端接口直接返回 HTTP 400控制台报错信息是 JSON 解析失败。原因Spring MVC 在把 JSON 字符串转换成 Java 对象时默认使用 Jackson 的格式。Jackson 对java.util.Date类型的默认解析格式是 ISO 8601 的时间戳格式yyyy-MM-dd HH:mm:ss这种带空格的标准日期格式不在默认支持范围内所以直接解析失败。解决有三种常见做法。第一种是前端把日期字符串改成时间戳new Date().getTime()转换成毫秒数再传给后端第二种是在实体类的日期字段上加JsonFormat(pattern yyyy-MM-dd HH:mm:ss, timezone GMT8)注解第三种是在 Spring MVC 配置里注册一个全局的日期转换器统一处理所有日期格式的转换。第二种最省事推荐优先使用。5.4 静态资源无法访问页面只有纯文本现象登录页能打开但完全没有样式也没有任何图片查看页面源代码能看到link标签引用的是/static/css/style.css但浏览器访问这个路径返回 404。原因Spring MVC 的前端控制器DispatcherServlet映射了/它会拦截所有请求包括静态资源。如果配置里没有显式放行静态资源Tomcat 默认的静态资源处理器就不会工作CSS、JS、图片全部 404。解决在 spring-mvc.xml 里配置静态资源映射把/static/**映射到实际资源目录mvc:resources mapping/static/** location/static// mvc:resources mapping/css/** location/static/css// mvc:resources mapping/js/** location/static/js//这样浏览器访问/static/css/style.css时Spring MVC 会直接把文件返回给浏览器。配置完成记得重新启动 Tomcat因为部分 IDE 热部署不会自动重载 XML 配置文件。5.5 中文乱码从 JSP 到数据库全链路排查现象页面录入中文备注信息保存到数据库后查看变成了问号或乱码。原因这一般是多个环节的编码不一致导致的。可能是 JSP 页面本身没有声明 UTF-8 编码可能是 Spring MVC 的 CharacterEncodingFilter 没有配置也可能是数据库连接 URL 里没有指定characterEncodingutf8三个环节任何一个断了中文就乱了。解决按链路逐个排查。JSP 页面顶部的pageEncoding设为 UTF-8web.xml 里配置 CharacterEncodingFilter 的init-param设为 UTF-8 并设置force-encodingtrue/force-encodingjdbc.properties 里的连接 URL 加上?useUnicodetruecharacterEncodingutf8。三个都做齐乱码问题基本不会再出现。6. 二次开发指南给这个健康管理系统加一个“用药提醒”模块这个项目的结构清晰分层明确做二次开发只需要顺着现有的模式往下加功能。下面以“用药提醒”为例完整体验一次从建表到前后端联通的开发流程。6.1 加表加实体按现有模式扩展数据模型先建一张用药提醒表字段包含提醒 ID、用户 ID、药品名称、用药剂量、用药时间、是否已服药。SQL 直接模仿t_health_data的风格保持命名规范一致CREATE TABLE t_medication_reminder ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, medicine_name VARCHAR(50) NOT NULL, dosage VARCHAR(30) COMMENT 用药剂量如1片/次, remind_time VARCHAR(10) COMMENT 提醒时间格式HH:mm, status TINYINT DEFAULT 0 COMMENT 0-未服药 1-已服药, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES t_user(id) );再创建对应的实体类MedicationReminder.java字段和表结构一一对应。注意不要只用基本类型 int而是用包装类型 Integer这样数据库里的 NULL 值可以被正确接收不会因为自动拆箱引发空指针异常。6.2 照着现有链路写三层代码最省事的做法是直接照着HealthDataController、HealthDataServiceImpl、HealthDataMapper三个文件的代码风格把“用药提醒”对应的三个类写出来。不要去改动已有的 Controller、Service 或 Mapper而是新增一套平行的代码这样不会影响现有功能。Mapper 的 XML 文件也新建一个MedicationReminderMapper.xml在里面写 insert、selectByUserId、updateStatus 三个方法对应的 SQL。写 Service 层的时候可以在updateStatus这个方法里加一个业务逻辑当用户把某条提醒标记为“已服药”时自动在t_health_data表里插入一条备注为“服药”的健康记录把两个模块串起来。这种小的业务联动就是体系的加分项。6.3 前端页面的接入方式JSP 页面新建一个reminder.jsp页面上用 Ajax 调用新增的三个接口。列表展示用selectByUserId查出当天所有提醒每条提醒旁边有一个“标记已服药”的按钮点击后调用updateStatus。提醒时间可以和业务层里获取当前时间做对比时间到了就在页面顶部弹出一条提示这个用 JavaScript 的setInterval定时轮询接口就能实现不用上 WebSocket。6.4 改造完成后的验证方法新模块开发完验证不能只靠“录入一条数据看看成功没”。我习惯的做法是走一遍异常流程先不登录直接访问提醒列表接口确认拦截器返回 401再登录后新增一条提醒确认数据落库把提醒时间改成当前时间确认前端提示生效最后把一条提醒标记为已服药检查t_health_data表里是否新增了一条联动记录。四条路径全部走通这个模块才算真的开发完成。这个项目的可扩展点其实还有不少引入 ECharts 仪表盘展示身体综合评分、加一个上传体检报告的附件管理功功能、用定时任务实现微信推送提醒等。每一条都是沿着“Controller 新增接口 Service 新增业务逻辑 Mapper 新增 SQL JSP 新增页面”的模式走架构本身不会成为瓶颈。做二次开发这几年我翻车最多的情况不是代码逻辑写不出来而是改完代码没有走全验证流程就直接交付。特别是拦截器这种“全局生效”的模块加一个路径配置可能影响所有请求。从那以后我每次改完拦截器或 Spring 配置都强制把登录、退出、带参数查询、无参数查询这几条路径重新走一遍。希望这篇拆解能帮你在跑通这个项目的同时也把这套验证意识带进自己的开发习惯里。本文还有配套的精品资源点击获取