简介:基于 SSM+MySQL 的个人健康信息管理系统毕业设计文档,面向计算机相关专业学生与需要开发同类健康管理项目的开发者。文档系统梳理了健康管理平台的完整实现方案,依次覆盖项目背景意义、四项可行性分析(经济、人员、法律、操作)、功能需求设计(用户注册登录、个人信息管理、健康数据记录、健康提醒设置、数据分析报告)以及系统架构分层设计:前端 JSP 展示、SpringMVC 控制层、服务层、MyBatis 数据访问层。同时给出 MySQL 数据库表结构,包括用户表、健康数据表、健康提醒表、数据分析表,并补充功能、性能、安全性测试方案,有助于了解真实项目开发中的关键验证环节。资源包共 1 个 docx 文件,约 5.26MB,是一份可直接参考的完整设计报告,可用于毕业设计开题、论文撰写或系统开发。已有 77 人学习,适合正在准备 Java Web 方向毕设、希望快速掌握 SSM 整合流程的读者使用。
1. 个人健康信息管理系统:SSM+MySQL 这套组合能落地到什么程度
上周帮 A 同学调一套个人健康信息管理系统,数据库里躺着 6 张表、二十多个接口,界面能登录、能录血压、能看体检记录,但他问的第一句话是“这东西到底怎么跟别人讲清楚”。个人健康信息管理系统用 SSM(Spring + SpringMVC + MyBatis)加 MySQL 来实现,是 Java Web 学习路径里最典型的一类落地题目:它不碰算法和并发,核心是把一个人的体检记录、日常健康指标、运动和饮食数据,以清晰的字段结构存进 MySQL,再通过 SpringMVC 接收请求、MyBatis 操作数据库、前端页面把数据展示出来。适合三种人:正在做课设或毕设的在校生、想拿一套完整项目练手的初级开发者、以及要给家里或小团队做个内部健康数据管理工具的人。这篇我会从数据库设计讲到接口代码,再讲前端对接和五个高频坑,最后补上能直接上线的三个进阶动作。
2. 健康档案的数据模型设计:MySQL 建表与字段取舍
2.1 核心表拆解:用户表、健康指标表、体检记录表
个人健康信息管理系统里最忌讳一上来就堆字段。我见过有人把身高、体重、血压、体温、运动时长全塞进一张表,结果一张表四十多个列,一半是 NULL,查询越来越慢,代码里映射也越来越乱。常见做法是拆成“用户 + 多张业务记录表”。
第一张是用户表 user,管登录和基本资料。字段上只需要 id、username、password、real_name、gender、age、phone、create_time。password 字段长度给到 64,因为后续如果要接入 MD5 或 BCrypt 加密,32 位不够用。这里不建议把身高体重放进用户表,因为健康指标是随时间变化的,它们是“记录”而不是“属性”。
第二张是健康指标记录表 health_record,这是整套系统的核心。字段包括 user_id、record_date、height、weight、systolic、diastolic、heart_rate、blood_sugar、temperature、note、create_time。systolic 是收缩压、diastolic 是舒张压,英文直译不如 high/low 直观,但用 systolic/diastolic 更规范。每个指标都允许 NULL,因为有人只量血压不测血糖,强行 NOT NULL 会让录入界面变得很难用。
第三张是体检记录表 medical_record,存医院或体检机构的检查结果。字段有 check_date、organization、item_name、result_value、unit、reference_range、doctor_advice。这里注意不要把整个体检报告当一张表设计,因为体检项目是不固定的,把每个检查项拆成一行才是正确的做法。另外还有运动记录表 exercise_record 和饮食记录表 diet_record,结构类似,都是“某天 + 类型 + 数值 + 备注”的模式。
2.2 完整建表 SQL:从零创建数据库和 5 张核心表
直接给一份可以照着执行的建表脚本,编码统一 utf8mb4,存储引擎 InnoDB,字符集问题后面会专门讲。
-- 创建数据库,utf8mb4 是中文场景的标配 CREATE DATABASE IF NOT EXISTS health_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE health_system; -- 用户表 CREATE TABLE user ( id INT NOT NULL AUTO_INCREMENT COMMENT '用户ID', username VARCHAR(50) NOT NULL COMMENT '登录账号', password VARCHAR(64) NOT NULL COMMENT '登录密码', real_name VARCHAR(50) DEFAULT NULL COMMENT '真实姓名', gender TINYINT DEFAULT 0 COMMENT '性别: 0保密 1男 2女', age INT DEFAULT NULL COMMENT '年龄', phone VARCHAR(20) DEFAULT NULL COMMENT '手机号', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; -- 健康指标记录表 CREATE TABLE health_record ( id INT NOT NULL AUTO_INCREMENT, user_id INT NOT NULL COMMENT '所属用户', record_date DATE NOT NULL COMMENT '记录日期', height DECIMAL(5,2) DEFAULT NULL COMMENT '身高cm', weight DECIMAL(5,2) DEFAULT NULL COMMENT '体重kg', systolic INT DEFAULT NULL COMMENT '收缩压mmHg', diastolic INT DEFAULT NULL COMMENT '舒张压mmHg', heart_rate INT DEFAULT NULL COMMENT '心率次/分', blood_sugar DECIMAL(4,2) DEFAULT NULL COMMENT '空腹血糖mmol/L', temperature DECIMAL(4,2) DEFAULT NULL COMMENT '体温℃', note VARCHAR(500) DEFAULT NULL COMMENT '备注', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_date (user_id, record_date), CONSTRAINT fk_hr_user FOREIGN KEY (user_id) REFERENCES user (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康指标记录表';这是最核心的两张表。DECIMAL 用于身高体重和血糖,是因为浮点型 FLOAT/DOUBLE 在存储 55.5 这类数值时存在精度漂移,涉及健康数值时尽量用定点数。date 类型的 record_date 与时区无关,比 DATETIME 更适合“按天记录”这个场景。user_id 上没有只建普通索引,而建了联合索引 idx_user_date,因为最常见的查询是“某个用户某段时间的记录”,这种查询正好命中联合索引。物理外键在线上项目里常被省略,但课设和初学阶段建议保留,能让表关系更直观,也避免写出孤儿数据。
接着是体检、运动、饮食三张表。
-- 体检/就诊记录表 CREATE TABLE medical_record ( id INT NOT NULL AUTO_INCREMENT, user_id INT NOT NULL COMMENT '所属用户', check_date DATE NOT NULL COMMENT '检查日期', organization VARCHAR(100) DEFAULT NULL COMMENT '体检机构', item_name VARCHAR(100) NOT NULL COMMENT '检查项目', result_value VARCHAR(200) DEFAULT NULL COMMENT '检查结果', unit VARCHAR(20) DEFAULT NULL COMMENT '单位', reference_range VARCHAR(100) DEFAULT NULL COMMENT '参考范围', doctor_advice VARCHAR(500) DEFAULT NULL COMMENT '医生建议', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_date (user_id, check_date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='体检/就诊记录表'; -- 运动记录表 CREATE TABLE exercise_record ( id INT NOT NULL AUTO_INCREMENT, user_id INT NOT NULL, exercise_date DATE NOT NULL COMMENT '运动日期', sport_type VARCHAR(50) NOT NULL COMMENT '运动类型: 跑步/游泳/骑行', duration_min INT DEFAULT NULL COMMENT '运动时长分钟', calories INT DEFAULT NULL COMMENT '消耗热量千卡', note VARCHAR(200) DEFAULT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_date (user_id, exercise_date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='运动记录表'; -- 饮食记录表 CREATE TABLE diet_record ( id INT NOT NULL AUTO_INCREMENT, user_id INT NOT NULL, diet_date DATE NOT NULL COMMENT '记录日期', meal_type TINYINT NOT NULL COMMENT '餐次: 1早餐 2午餐 3晚餐 4加餐', food_desc VARCHAR(300) NOT NULL COMMENT '食物描述', calories INT DEFAULT NULL COMMENT '估算热量千卡', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_date (user_id, diet_date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='饮食记录表';这三张表的共性是“业务维度 + 数值 + 备注”。体检表把 result_value 设计成 VARCHAR 而不是 DECIMAL,是因为检查结果可能是“阴性”“++”这类文本,一棍子打死用数值类型反而会在插入时报错。docx 文档里如果只给了部分建表语句,补全这三张表是第一步,因为后面所有 Mapper 和页面都依赖这些字段名。
2.3 表关系选择:为什么一张宽表不如三张窄表
很多初学的人会把 user 和 health_record 合并成一张宽表,理由是“查询一次就能拿到所有数据,不用 join”。要是这个系统只用来存一个人的数据、永远不做扩展,那确实能跑,但一旦换数据库或者要加功能,麻烦就来了。
窄表设计的直接收益是写入灵活。用户今天只录了体重,明天只录了血压,宽表每一次都要补齐所有列,而窄表每行只写有意义的字段。另一个收益是统计方便,计算“这个月平均收缩压”只需要对 health_record 做 AVG(systolic),不需要关心其他列是否有 NULL。如果后面要加“腰围”指标,窄表只需加一列,宽表则要改表结构并回填历史数据。
提示:MySQL 5.7 及以上版本里,一张表有大量 NULL 列并不会像传言中那样“严重浪费空间”,但仍然会造成索引选择变差、代码可读性降低。设计时以“这个字段是否是这个实体的固有属性”为标准判断,比“会不会用到”更可靠。
3. 后端落地:SpringMVC 接收请求到 MyBatis 写库的全链路
3.1 项目结构与配置文件:SSM 工程目录怎么摆
SSM 项目的目录结构有固定套路,直接用 Maven 标准结构,包名按 controller/service/dao/entity 分层。这里给一个最小可运行的工程骨架:
health-system/ ├── pom.xml ├── src/main/java │ └── com/example/health │ ├── controller // SpringMVC 控制器,只做参数接收和结果返回 │ ├── service // 业务逻辑层,事务注解在这里 │ ├── dao // MyBatis Mapper 接口 │ ├── entity // 实体类,对应数据库表 │ └── interceptor // 登录拦截器等 ├── src/main/resources │ ├── spring-mvc.xml // SpringMVC 子容器配置 │ ├── applicationContext.xml // Spring 父容器配置 │ ├── mybatis-config.xml // MyBatis 全局配置 │ ├── db.properties // 数据库连接配置 │ └── mapper/ // MyBatis XML 映射文件 └── src/main/webapp ├── WEB-INF/web.xml └── jsp // 页面文件pom.xml 里需要引入的核心依赖是 spring-webmvc、spring-jdbc、mybatis、mybatis-spring、mysql-connector-java,以及 jackson-databind(处理 JSON 序列化)。一个容易忽略的依赖是 javax.servlet-api,Tomcat 8 和 9 对 servlet 版本要求不同,scope 记得设成 provided。
db.properties 里最容易翻车的是时区和驱动类名:
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/health_system?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false jdbc.username=root jdbc.password=yourpassworddriver 类名要注意:MySQL 5.x 用 com.mysql.jdbc.Driver,MySQL 8.x 必须用 com.mysql.cj.jdbc.Driver,写错启动就报 ClassNotFoundException。url 里的 serverTimezone=Asia/Shanghai 不加的话,连接 MySQL 8.x 大概率报 CST 时区错误。这些细节在 docx 文档里通常不会写,但跑不起来十有八九是这里的问题。
3.2 实体类与 Mapper:字段映射的三个坑
实体类字段名和数据库列名如果不一致,MyBatis 映射就会出问题。健康指标记录表的实体类写法如下:
public class HealthRecord { private Integer id; private Integer userId; private java.util.Date recordDate; private BigDecimal height; private BigDecimal weight; private Integer systolic; private Integer diastolic; private Integer heartRate; private BigDecimal bloodSugar; private BigDecimal temperature; private String note; private java.util.Date createTime; // getter/setter 省略 }数据库列名是 user_id、record_date,Java 字段是 userId、recordDate,这靠 MyBatis 的驼峰映射解决:在 mybatis-config.xml 里开启 mapUnderscoreToCamelCase。否则你会发现查询结果里 userId 一直是 null,不报错但数据出不来。这是第一个坑。
第二个坑是 time 类型的列。createTime 是 DATETIME,实体类用了 java.util.Date,这个组合没问题。但如果有人把实体类字段写成 java.sql.Date,Controller 层做 JSON 输出时序列化格式会非常诡异,后面专门讲。
第三个坑是 resultType 和 resultMap 的选择。简单字段映射用 resultType 就够了,但涉及关联查询(比如用户和其健康记录)时,resultMap 才能把嵌套对象映射出来。实操中建议统一在 mapper XML 里写 resultMap,养成习惯后改起来不费劲。
UserMapper.xml 的条件查询是整套系统里最常用的 SQL,贴出来看:
<select id="selectByCondition" resultMap="userResultMap"> SELECT id, username, password, real_name, gender, age, phone, create_time FROM user <where> <if test="username != null and username != ''"> AND username LIKE CONCAT('%', #{username}, '%') </if> <if test="realName != null and realName != ''"> AND real_name LIKE CONCAT('%', #{realName}, '%') </if> <if test="gender != null"> AND gender = #{gender} </if> </where> ORDER BY id DESC </select>标签会自动去掉第一个多余的 AND,比手写 where 1=1 干净,也避免了 SQL 注入式的写法。LIKE 查询用 CONCAT 拼接 %,而不是在 Java 里拼好再传进来,目的是让 MyBatis 的 #{} 预编译生效。gender 的判断写成 ,因为性别 0 是有效值,不能写成 test="gender != null and gender != ''",否则查性别为“保密”的用户时会漏掉。
3.3 Controller 参数绑定:前端传什么,后端怎么接
Controller 层最容易出现的问题是参数接收方式混乱。GET 请求用 @RequestParam,POST 提交 JSON 用 @RequestBody,这两者混用是新手重灾区。
给一个健康记录保存接口的完整示例:
@Controller @RequestMapping("/health") public class HealthRecordController { @Autowired private HealthRecordService healthRecordService; @PostMapping("/save") @ResponseBody public Result save(@RequestBody HealthRecord record) { if (record.getRecordDate() == null) { record.setRecordDate(new java.util.Date()); } healthRecordService.addRecord(record); return Result.success("保存成功"); } @GetMapping("/list") @ResponseBody public PageResult list(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, @RequestParam(required = false) Integer userId, @RequestParam(required = false) String startDate, @RequestParam(required = false) String endDate) { return healthRecordService.findPage(pageNum, pageSize, userId, startDate, endDate); } }@RequestBody 要求前端以 application/json 方式提交,如果前端用表单方式提交,这里会直接报 HttpMessageNotReadableException。比较省心的方案是:新增/编辑用 JSON,查询列表用 GET + 普通参数。分页参数 pageNum 和 pageSize 都给了默认值,这样前端就算漏传也不会报错。
还有一点,Controller 里尽量不要写业务逻辑。record.getRecordDate() 为空时兜底赋值为当前时间,这属于“补全数据”,勉强可以放 Controller。但如果涉及事务、关联更新,必须下沉到 Service 层,否则 @Transactional 注解形同虚设。
3.4 事务处理:新增健康档案时的数据一致性
保存一条健康记录时,除了往 health_record 插入数据,经常还要同步更新 user 表里的最近记录时间。这两个操作要么都成功,要么都失败,需要加事务。
@Service public class HealthRecordService { @Autowired private HealthRecordMapper healthRecordMapper; @Autowired private UserMapper userMapper; @Transactional(rollbackFor = Exception.class) public void addRecord(HealthRecord record) { healthRecordMapper.insert(record); userMapper.updateLastRecordTime(record.getUserId()); } }@Transactional 要加在 public 方法上,且不能同类内部调用,否则事务不生效。rollbackFor 设成 Exception.class 是为了让运行时异常和 checked 异常都能回滚,默认只回滚 RuntimeException,这一点容易踩坑。加事务之后一定要验证:故意让第二条 SQL 报错,看第一条是否回滚,不要等上线了才发现事务是假的。
4. 页面与接口对接:用 JSP + AJAX 把 SSM 数据端出来
4.1 列表页分页:PageHelper 和前端分页参数怎么对齐
后端分页有两种做法,手写 LIMIT 或者用 PageHelper 插件。手写 LIMIT 更直观,适合理解原理:
public PageResult findPage(Integer pageNum, Integer pageSize, Integer userId, String startDate, String endDate) { int offset = (pageNum - 1) * pageSize; List<HealthRecord> list = healthRecordMapper.selectPage(offset, pageSize, userId, startDate, endDate); Long total = healthRecordMapper.countByCondition(userId, startDate, endDate); return new PageResult(total, list); }对应的 Mapper XML:
<select id="selectPage" resultMap="healthRecordResultMap"> SELECT * FROM health_record <where> <if test="userId != null"> AND user_id = #{userId} </if> <if test="startDate != null and startDate != ''"> AND record_date >= #{startDate} </if> <if test="endDate != null and endDate != ''"> AND record_date <= #{endDate} </if> </where> ORDER BY record_date DESC LIMIT #{offset}, #{pageSize} </select>注意 XML 里大于号和小于号必须转义,> 写成 >,< 写成 <,这是 MyBatis XML 解析的硬性要求。前端拿到 PageResult 后,total 用来算总页数,list 渲染表格行。这里前端容易犯的错是把 total 当成“当前页条数”,导致分页器永远只有一页。
4.2 新增与编辑表单:日期控件和 JSON 格式化
页面上新增一条健康记录,最常见的提交方式是 jQuery AJAX:
function saveRecord() { var data = { userId: $('#userId').val(), recordDate: $('#recordDate').val(), systolic: $('#systolic').val(), diastolic: $('#diastolic').val(), heartRate: $('#heartRate').val() }; $.ajax({ url: '/health/save', type: 'POST', contentType: 'application/json;charset=UTF-8', data: JSON.stringify(data), dataType: 'json', success: function (res) { if (res.code === 200) { alert('保存成功'); loadPage(1, 10); } else { alert(res.msg); } }, error: function () { alert('请求失败,检查后端服务是否启动'); } }); }recordDate 来自,值是“2025-03-15”这种字符串。SpringMVC 把 JSON 字符串转成 java.util.Date 时,默认格式可能对不上,需要在 spring-mvc.xml 里配置日期转换器:
@Bean public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() { return builder -> { builder.serializerByType(java.util.Date.class, new SimpleDateFormat("yyyy-MM-dd").toFormat()); }; }或者直接在实体类的 recordDate 字段上加上 @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8"),这种方式更直观。如果用,前端给的值带 T,比如“2025-03-15T08:30”,后端接的时候要先替换 T 为空格,这是个小坑。
4.3 数据校验:前端校验和后端校验各管什么
前端校验负责体验,后端校验负责安全。很多 SSM 项目只做了前端校验,直接导致绕开浏览器用 Postman 提交脏数据。
后端手动校验最不容易出问题:
@PostMapping("/save") @ResponseBody public Result save(@RequestBody HealthRecord record) { if (record.getSystolic() != null && (record.getSystolic() < 60 || record.getSystolic() > 250)) { return Result.error("收缩压数值异常"); } if (record.getRecordDate() == null) { return Result.error("记录日期不能为空"); } healthRecordService.addRecord(record); return Result.success("保存成功"); }这里的原则是:前端校验做必填、格式、范围提示,后端校验做同样的逻辑,但容错要更宽,比如年龄范围 0-150,血压上限 250,不要因为校验过严把正常录入挡在门外。
5. 常见问题与排查:从跑不起来到数据错乱的五个高发点
5.1 中文乱码:JSP 到 MySQL 全链路排查
现象:页面上显示正常,但存进数据库后变成了问号;或者反过来,页面上显示乱码,数据库里正常。
原因:链路中某一个环节的字符集不是 utf8mb4。第一层是 JSP 页面本身没有指定编码,第二层是 Tomcat 没有配置 URI 编码,第三层是数据库连接 URL 缺少 characterEncoding=utf8,第四层是表结构默认字符集错误。
解决:按顺序检查。JSP 顶部写 <%@ page contentType="text/html;charset=UTF-8" %>;Tomcat 的 server.xml 里给 Connector 加 URIEncoding="UTF-8";数据库连接 URL 确保 useUnicode=true&characterEncoding=utf8;建表时明确 CHARSET=utf8mb4。改完前三处,重启 Tomcat,再插入一条中文数据验证。
5.2 数据库连接报错:时区、驱动版本、连接池参数
现象:启动项目时报 Cannot create PoolableConnectionFactory,或 Communications link failure,或 The server time zone value 'CST' is unrecognized。
原因:最常见是 MySQL 8.x 驱动与 URL 参数不匹配。MySQL 6.x 之后驱动类名变了,服务器时区也不能省略。
解决:驱动类统一用 com.mysql.cj.jdbc.Driver,URL 加上 serverTimezone=Asia/Shanghai&useSSL=false。如果项目里用的是 druid 连接池,Druid 版本不要在 1.1.10 以下,否则识别新驱动会有兼容问题。改完配置后不要只刷新应用,要重新执行 Maven 打包或重启,因为资源文件可能没有热更新。
5.3 实体类和表字段对不上:MyBatis 驼峰映射意外失效
现象:查询 user 表时,realName 返回 null,但 username 正常;或者 update 语句报 unknown column 'realName'。
原因:User 表里 real_name 带下划线,实体类里是 realName。如果 mybatis-config.xml 没有开启 mapUnderscoreToCamelCase,MyBatis 默认不自动转换。
解决:在 mybatis-config.xml 的 settings 标签里加配置,并且确认这是被 Spring 加载的那份配置文件。有些项目同时存在多份 mybatis-config.xml,改错了文件自然不生效。
<settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>还有一种容易被忽略的情况:XML 里写了 resultMap,但 resultMap 的 column 属性没有写,导致映射规则失效。写了 resultMap 后,驼峰映射不会自动接管,需要在 resultMap 里逐列声明。
5.4 日期类型报错:java.util.Date 和 java.sql.Date 的混用
现象:页面传来的“2025-03-15”保存成功,但列表页显示成“2025-03-15 00:00:00”或直接格式化报错。
原因:实体类字段在 DAO 层被写成了 java.sql.Date,在 Controller 层做 JSON 序列化时,序列化方式不一致。java.sql.Date 继承自 java.util.Date,但 toString 只会输出日期部分,序列化规则在不同版本 Jackson 下表现还不一样。
解决:实体类统一用 java.util.Date,Mapper XML 里不要做特殊 TypeHandler。数据库层面 DATETIME 和 DATE 的转换交给 JDBC 驱动处理。如果前端只需要日期,配合 @JsonFormat(pattern = "yyyy-MM-dd") 输出。
5.5 源码包不完整:docx 文档里常见的缺漏
现象:拿到手的文档里只有代码片段和部分 SQL,没有完整数据库脚本,或者 mapper XML 文件缺失,导致启动报 BindingException。
原因:docx 形式的源码大多是截图或节选,Word 文档本身不适合承载完整工程。缺 mapper 映射时,MyBatis 启动会报 Invalid bound statement (not found),缺数据库脚本时,项目能启动但查询就报 table doesn't exist。
解决:拿到这类文档的第一步,是先把文档里出现过的建表语句和实体类字段做成对照表,核对每个实体类字段在表里是否存在。缺了 mapper XML 的,根据接口方法名补 XML;缺了建表语句的,参照第 2 章的脚本结构反推。更重要的是自己新建一个空工程,把文档里的代码逐个复制进去验证,不要只在原工程上修修补补。原工程如果自带 target 目录或编译产物,先删掉再重新构建,能避开大量“改了代码不生效”的玄学问题。
6. 上线前的进阶打磨:登录拦截、健康趋势图与数据备份
SSM 系统跑通增删改查只是起点,真正能拿出去用还得补三件事。
第一件是登录拦截。现在很多页面直接裸奔,谁拿到地址都能访问。加一个 SpringMVC 拦截器,几行代码的事:
public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object loginUser = request.getSession().getAttribute("loginUser"); if (loginUser == null) { response.sendRedirect(request.getContextPath() + "/login.jsp"); return false; } return true; } }在 spring-mvc.xml 里注册拦截路径,把 /login 和静态资源排除掉,其余页面统一拦截。这一步做完,系统才算有个基本的访问控制。
第二件是趋势图。健康数据光有表格不够直观,给用户展示近 7 天的血压波动,用 ECharts 折线图效果最好。后端提供一个趋势接口,返回日期和收缩压、舒张压数组,前端渲染两条折线。这个功能对体检指标管理类系统是刚需,做完之后整个系统的完成度明显不一样。
第三件是备份习惯。健康数据是长期积累的,数据库一旦误删就没有后悔药。养成每天凌晨备份的习惯,一条 crontab 加一条 mysqldump 命令足够:
mysqldump -uroot -proot --default-character-set=utf8mb4 health_system > /backup/health_system_$(date +%Y%m%d).sql我在给 A 同学收尾时做了一件事:强制把原工程里的 target 和 .idea 目录删掉,用命令行重新 mvn clean package 打了一次包。这一步过滤掉了很多 IDE 带来的假环境问题,也让整个项目真正具备了“换一台机器就能跑”的能力。这套流程走完,SSM+MySQL 的个人健康信息管理系统才算不是玩具,而是一个能长期记录、能分析、能恢复数据的可用系统。这是我自己的习惯,也希望能帮到你。
本文还有配套的精品资源,点击获取