简介:这是一套面向计算机相关专业毕业设计的师生健康信息管理系统完整项目,基于 Spring Boot 技术栈,系统分为前台与后台,包含用户端与管理端两种角色,覆盖学生/教师管理、数据收集、疫情问卷、问卷调查、返校信息管理等核心模块,可帮助读者快速理解高校健康管理业务的完整流程。资源共5个文件,压缩包内含项目源码、数据库脚本、万字论文文档、PPT演示文稿及说明文档,整体大小约13.91MB,其中sql文件可直接导入数据库,doc文档可供撰写或参考毕业设计论文,pptx适合答辩展示。目前已有32人学习下载,适合需要完成课程设计或毕业设计的同学对照学习。通过这份资料,读者可以获得一套可运行的前后台管理系统,梳理从数据表设计到问卷发布、填报与统计的实现思路,并结合实际代码理解用户与管理员的不同操作权限,借助万字文档完善自己的项目说明与答辩材料。
1. 师生健康信息管理系统:从数据模型到交付物的完整闭环
每年开学,校医室和班主任最头疼的不是校园防疫本身,而是健康数据散落在 Excel、问卷和纸质档案里,格式不统一,查起来全靠翻聊天记录。师生健康信息管理系统要解决的,就是把静态档案(师生信息、班级归属)和动态记录(晨检、体检、请假、异常追踪)放进同一个应用里。它是数据库课程设计里最典型的题目:实体关系、增删改查、统计报表、批量导入全都能练到,还能把课堂上的事务、索引、权限放进真实场景验证。
这套系统的标准交付物是源码、数据库脚本、万字文档和 PPT,四样东西其实是同一份设计的四副面孔。下面我按做这类系统最常见的方案,把模块边界、核心表结构、增删改查与预警代码、索引与迁移、文档和 PPT 的组织顺序完整过一遍。你拿到的源码可能是 Spring Boot + MyBatis,也可能是 JavaWeb + JSP 或 Django,只要数据模型和事务设计对了,换语言只是换写法。
2. 系统源码的模块边界与数据库表设计
2.1 先划边界,再谈源码结构
这类系统最容易踩的坑是一上来就写登录、菜单和一堆页面,健康业务反而不清晰。我一般先把健康业务拆成两条主线:静态档案(师生、班级、历史体检汇总)和动态记录(每日晨检、每次体检明细、就诊与请假、异常提醒)。源码包结构也按这两条线铺,常见的是 controller、service、mapper(或 dao)、entity、util 五层,controller 收参数,service 写业务规则,mapper 写 SQL。权限和系统管理单独放一个模块,不要和健康业务混在同一个包里,否则后面写文档和做 PPT 时很难讲清楚模块边界。
2.2 师生健康信息管理系统的核心表结构(MySQL)
结合数据库课程设计的需求,表结构就是这类系统的灵魂。以 MySQL 5.7/8.0 为例,我会建 class_info、student、teacher、health_record 四张核心业务表,外加 user 表做登录、notify 表做异常提醒。先看两个最常用的建表语句。
CREATE TABLE class_info ( class_id INT PRIMARY KEY AUTO_INCREMENT, class_name VARCHAR(50) NOT NULL, grade VARCHAR(20) NOT NULL, head_teacher VARCHAR(20) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='班级'; CREATE TABLE student ( student_no VARCHAR(20) PRIMARY KEY COMMENT '学号', name VARCHAR(20) NOT NULL, gender CHAR(1) COMMENT 'M/F', birthday DATE, class_id INT, height_cm DECIMAL(5,1), weight_kg DECIMAL(4,1), allergy VARCHAR(200) COMMENT '过敏史,逗号分隔', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_stu_class FOREIGN KEY (class_id) REFERENCES class_info(class_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生档案'; CREATE TABLE health_record ( record_id BIGINT PRIMARY KEY AUTO_INCREMENT, person_type CHAR(1) NOT NULL COMMENT 'S学生/T老师', person_no VARCHAR(20) NOT NULL, check_date DATE NOT NULL, temperature DECIMAL(3,1), symptom VARCHAR(100) COMMENT '发热/咳嗽/腹泻等', diagnosis VARCHAR(200), source CHAR(1) COMMENT '1晨检 2体检 3就诊', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_person_date (person_type, person_no, check_date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康记录';teacher 表结构跟 student 大同小异,只是主键用工号,部门字段代替班级字段,这里不重复贴。核心业务表的关系和用途可以用下面这张表快速说清,这张表直接复制到文档和 PPT 里都合适。
| 表名 | 核心字段 | 用途 | 与健康业务的关系 |
|---|---|---|---|
| class_info | class_id, class_name, grade | 班级维度统计 | student 的外键来源 |
| student | student_no, name, class_id | 学生档案 | 健康记录的主体之一 |
| teacher | teacher_no, name, dept_id | 教师档案 | 健康记录的主体之一 |
| health_record | person_type, person_no, check_date, source | 所有健康事件流水 | 系统的核心表 |
| exam_item | record_id, item_name, item_value | 体检细项拆分 | 一对多挂在 health_record 下 |
| notify | person_no, status, create_time | 异常提醒 | 由健康记录触发写入 |
表设计时有两个容易被忽略的点。一是 person_type + person_no 的组合,比分别建 student_health_record 和 teacher_health_record 两张表更省事,晨检、体检、就诊都按"人 + 日期"定位。二是 health_record 里的 source 字段必须保留,后面做统计时要区分数据是校医录的、体检导入的还是晨检同步的。有人把体温、身高、体重全拆成字段,我反而建议保留通用字段加文本诊断,这样不同学段加新检查项时不用改表。
2.3 关系说明与范式取舍
student 归属 class_info,是带外键的弱实体;health_record 对 student 和 teacher 是"多对一",但只存 person_no 字符串而不建外键,原因是它要同时关联两张父表,数据库级外键写起来很别扭。这种设计属于适度反范式,代价是没有数据库级约束,所以 service 层必须校验 person_type 和 person_no 的组合真实存在。另一个典型取舍是体检明细:把肺活量、视力、血压等几十个项目全加在 health_record 上,表会膨胀到 30 列以上,而且不同年级检查项不同。
常见做法是把体检项目提到一行一条,用 exam_item 表(record_id, item_name, item_value, unit, standard_value)按 record_id 关联 health_record 中 source='2' 的记录。这个取舍直接决定后面"异常指标标红"好不好实现。设计完表之后,可以快速用一条统计 SQL 验证三大来源的记录数,哪类为 0 就说明哪块功能还没接上。
3. 用源码把健康记录增删改查与异常预警跑起来
3.1 登录与权限:能少写就少写
课程设计场景下,我不会引入完整的 Spring Security 加 JWT 那套,权限模型简单到"三种角色、一个菜单表"就够:管理员管全院、校医管记录、老师只看本班。Java 源码里最常用的做法是登录成功把 userId 和 role 写进 session,用拦截器判断 Controller 方法上的注解。用户名密码存数据库时至少做一次 MD5 加盐,不要明文落库。把权限点收在 3 个以内,PPT 上讲"按角色控制数据范围"时也更容易自圆其说。
3.2 健康记录的 service 层校验与事务
新增健康记录是源码里最能体现水平的一段。下面这段代码是我在课程设计中最常用的写法,放在既有 Java 工程里可以直接抄。
@Transactional(rollbackFor = Exception.class) public Long addHealthRecord(HealthRecordDTO dto) { // 1. 校验 person_no 对应的人必须存在 Integer cnt = personMapper.countByTypeAndNo(dto.getPersonType(), dto.getPersonNo()); if (cnt == null || cnt == 0) { throw new BizException("该编号不存在:" + dto.getPersonNo()); } // 2. 按规则判断是否触发异常预警 boolean abnormal = new HealthChecker().evaluate(dto); // 3. 插入主记录 HealthRecord record = new HealthRecord(); record.setPersonType(dto.getPersonType()); record.setPersonNo(dto.getPersonNo()); record.setCheckDate(dto.getCheckDate()); record.setTemperature(dto.getTemperature()); record.setSymptom(dto.getSymptom()); record.setSource(dto.getSource()); if (abnormal) { record.setDiagnosis("待校医复核"); } healthMapper.insert(record); // 4. 异常时写提醒表,方便首页拉取待办 if (abnormal) { notifyMapper.insert(record.getId(), dto.getPersonType(), dto.getPersonNo(), dto.getCheckDate(), "体温异常或症状风险"); } return record.getId(); }逻辑说明:先查人,再算预警规则,再插入主记录,最后写提醒,四步在一个事务里,任何一步抛异常都会整体回滚。@Transactional(rollbackFor = Exception.class) 指的是即使抛出的是自定义业务异常也回滚;如果只写 @Transactional,默认只对 RuntimeException 回滚,对 checked 异常不回滚,这是数据库课程设计里最常掉的一个坑。参数说明:personType 用 'S' 和 'T' 区分师生;temperature 是 DECIMAL(3,1),最大 99.9 度,录入时会自动四舍五入到一位小数;source 取值 1 是晨检、2 是体检、3 是就诊。abnormal 的具体判定在 HealthChecker 里,阈值见 3.3。
3.3 异常阈值配置与批量导入
体检数据批量导入是文档和 PPT 里都要重点讲的功能。我一般用 EasyExcel 或 POI 读取文件,逐行转成 DTO,再调用上面那个 service 方法。阈值参数建议放进 sys_config 表而不是硬编码在 if 判断里,答辩时评委大概率会问"不同年级标准不一样怎么扩展",把阈值按 grade 维度拆开就解释得通。
| 指标 | 触发条件 | 单位 | 说明 |
|---|---|---|---|
| 体温 | >= 37.3 | ℃ | 晨检和入校检测通用 |
| BMI | 年龄 + 性别对应百分位 P95 以上 | kg/m² | 不同年级阈值不同 |
| 视力 | 任一裸眼 <= 4.8 | — | 需区分左右眼字段 |
| 血压 | 收缩压 >= 130 或 舒张压 >= 85 | mmHg | 一般针对初中以上 |
| 请假跟踪 | 连续 3 个工作日未销假 | 天 | 跟踪复课状态 |
批量导入时,失败处理策略建议是"行号 + 原因"逐条返回,而不是整体失败。我在代码里用一个 List 收集错误信息,形如"第 12 行:学号 20210012 不存在",导入结束后把失败清单导出成 Excel 给校医核对。500 人的学校一次导入完,页面上能直接看到"成功 497 条、失败 3 条",这比任何讲解都有说服力。导入逻辑说明:先做列头校验和日期格式校验,再逐行查 student 或 teacher 是否存在,存在才落库,不存在就记失败原因,最后统一返回汇总结果。
4. 师生健康信息系统的数据库索引、事务与迁移
4.1 索引设计:别把外键索引和查询索引混在一起
表建完,查询一慢就不知道加什么索引,这是"查询数据库"类搜索最常见的问题。健康记录表的高频查询只有两种:一个是"某个学生某段时间内的记录",另一个是"某天全校体温异常名单",对应两条核心索引,加上其他表的常用索引,汇总成下面这张表。
| 表 | 推荐索引 | 理由 |
|---|---|---|
| health_record | idx_person_date(person_type, person_no, check_date) | 覆盖"查某个人的历史记录" |
| health_record | idx_check_source(check_date, source) | 覆盖"某天某来源的批量筛选" |
| student | class_id | 班级维度统计的关联查询 |
| notify | status, person_no | 首页异常待办查询 |
| exam_item | record_id | 体检明细按主记录加载 |
索引字段顺序有讲究:等值条件放前面,范围条件放后面。person_type 和 person_no 是等值,check_date 是范围,所以三列按这个顺序排。如果调整成 check_date 开头,MySQL 也能走索引,但过滤性会下降。复合索引最左前缀原则在课程设计文档里写一段,比空泛的"索引能加速查询"有用得多,因为这直接对应"你为什么这么建索引"的提问。
4.2 慢查询排查与安全修改表结构
排查慢查询,第一步是用 EXPLAIN 验证执行计划。拿一条真实查询举例。
EXPLAIN SELECT person_no, check_date, temperature FROM health_record WHERE person_type = 'S' AND check_date BETWEEN '2024-09-01' AND '2024-09-30' ORDER BY check_date DESC;看执行结果里的 key 字段,如果是 NULL,说明没用到索引;type 字段出现 ALL,说明全表扫描。加上 idx_person_date 之后再跑一遍,type 会从 ALL 变成 range,Extra 里也不再出现 Using filesort,说明排序也走索引了。修改表结构时,使用 ALTER TABLE 而不是删表重建,例如给 student 表追加"是否住宿"字段:
ALTER TABLE student ADD COLUMN is_boarding CHAR(1) DEFAULT '0' COMMENT '是否住宿' AFTER class_id;如果要保证重复执行不报错,更稳的办法是先查 information_schema.COLUMNS 判断字段是否存在,存在就不执行 ADD COLUMN,而不是依赖 MySQL 8.0 里并不支持 IF NOT EXISTS 的 COLUMN 语法。
4.3 事务边界:哪里必须包事务,哪里别包
除了新增健康记录,还有三个场景必须检查事务:批量导入体检数据(几百条记录要么全成功、要么能回滚到导入前)、多天晨检补录、角色权限变更(改角色加清缓存)。事务边界的原则是"一个用户操作对应一个事务"。不要在循环里开 200 个小事务,也不要一个事务包住整个文件导入导致锁时间过长。折中方案是每 500 条提交一次,配合 SAVEPOINT,失败时回滚到最近一个批次保存点。
START TRANSACTION; SAVEPOINT batch_500; -- 循环批量插入 health_record INSERT INTO health_record(person_type, person_no, check_date, temperature, source) VALUES ('S', '20210001', '2024-09-10', 36.8, '1'); -- 出错时 ROLLBACK TO SAVEPOINT batch_500; -- 批次处理完 COMMIT;这段 SQL 的效果是:任何一个批次出错,只回滚当前批次,前面已提交的批次保留。配合 JDBC 的 setAutoCommit(false) 使用,控制粒度在 service 方法里。文档里写清楚"为什么不全量回滚",比只贴一段事务代码更能体现数据库功底。顺便说一句,健康记录的场景并发量很低,不需要隔离级别调优,默认的 REPEATABLE READ 足够,不要为了显示懂得多去改成 READ UNCOMMITTED。
4.4 数据库迁移与备份:MySQL 到国产库
达梦数据库、Oracle 在高校和政企项目里出现频率很高。这类系统换库的第一痛点不是复杂 SQL,而是三个小点:分页写法、自增主键、大小写敏感。MySQL 分页是 LIMIT offset, size,Oracle 和达梦用 ROWNUM 或 FETCH FIRST;自增列 MySQL 是 AUTO_INCREMENT,达梦是 IDENTITY。所以源码里凡是写 SQL 的地方,把分页和主键生成抽成两个独立方法,换库只改两个文件,其他地方的业务代码不用动。
备份命令可以直接放进文档附录,通用于 MySQL 的课程设计演示环境。
mysqldump -uroot -p --databases health_school --single-transaction --quick \ > health_school_$(date +%Y%m%d).sql--single-transaction 对 InnoDB 生效,导出期间不锁业务表,演示环境随时可以跑。恢复时用 mysql -uroot -p health_school < 备份文件.sql。迁移前要确认两边的字符集都是 utf8mb4,否则恢复后中文乱码的概率很大,这个问题比语法兼容出现得更早。
5. 万字文档与 PPT 的组织方式:验证交付物的最后一步
5.1 文档按"能照着重做一遍"来写
万字文档最常见的结构是四部分:需求分析(用例加业务流程图)、数据库设计(ER 图加表结构加索引清单)、系统实现(各模块核心代码片段与逻辑说明)、测试与部署(测试用例、部署步骤、常见问题)。文档面向的不是"看代码的人",而是"要复现项目的人"。所以我会把每个表的字段含义和 DEFAULT CURRENT_TIMESTAMP 这类细节写全,再单独给一节数据字典,把 sys_config 里的异常阈值和规则字段列进去,文档的字数和实用性就都上来了。每个模块的数据库增删改查操作要和页面截图放到一起,一张截图配一段代码配三行说明,比大段复制代码有用。
5.2 PPT 按一条数据流组织
PPT 不用做 20 页。我习惯的页数分布是:背景与目标 2 页、架构与功能 3 页、数据库设计 3 页(ER 图加三张关键表)、核心功能演示截图 3 页、测试与性能对比 2 页、总结 1 页。重点是"体检异常从导入到预警到通知"这一条端到端的数据流,用三张页面截图串起来,讲的时候就说这一条链路,其他功能一句话带过。PPT 最后一张放部署环境与复现步骤:JDK 版本、MySQL 版本、初始化顺序(先建库、再导表结构、再导数据),保证答辩老师按步骤能跑起来。
5.3 造数据与一致性验证
演示之前一定要造数据。下面这个存储过程生成 3 个月每个学生每天的晨检记录,用来看统计报表。
DELIMITER // CREATE PROCEDURE sp_gen_checkin_data(IN days INT, IN class_list VARCHAR(100)) BEGIN DECLARE i INT DEFAULT 0; WHILE i < days DO INSERT INTO health_record(person_type, person_no, check_date, temperature, source) SELECT 'S', st.student_no, DATE_SUB(CURDATE(), INTERVAL i DAY), ROUND(36.0 + RAND() * 1.0, 1), '1' FROM student st WHERE FIND_IN_SET(st.class_id, class_list); SET i = i + 1; END WHILE; END// DELIMITER ;参数说明:days 是生成天数,class_list 是逗号分隔的班级 ID,比如 '1,2,3';FIND_IN_SET 用来做列表匹配。RAND() 生成 36.0 到 37.0 的体温,想演示异常预警,单独把几条 UPDATE 成 37.5 就行。数据生成后,用两条 SQL 做一致性检查:一条统计各 source 的记录数必须都大于 0,验证导入逻辑覆盖完整;另一条检查健康记录的最大日期不能大于今天,防止测试数据越界。这两条 SQL 直接放进文档的测试小节,配合一个"清理造数数据"的 DELETE 脚本,交付时既有演示效果又有收尾手段,整份交付物的可信度就落在最后一个动作上。
本文还有配套的精品资源,点击获取