☰
SpringBoot2+Vue3全栈开发实战:师生健康管理系统源码解析
2026/10/22 2:59:51 网站建设 项目流程

前阵子帮某高校的信息中心整理一套师生健康信息管理系统,拿到手的源码正好是SpringBoot2 + Vue3 + MyBatis-Plus + MySQL8.0这套组合。整套系统功能覆盖从师生基础档案、每日晨检记录、异常预警到统计报表的完整闭环,而且自带详细文档,可以直接作为课程设计、毕业设计或中小规模校园信息化项目的基础底座。

这类项目市面上其实不少,但大多存在几种通病:要么后端 Service 层写成一坨千行代码,要么前端还停留在 Options API + jQuery 思维,要么数据库脚本缺字段注释导致二次开发无从下手。这套源码我完整跑通并做了二次开发,过程中踩了不少坑,也梳理清了一些值得展开讲的设计细节。这篇博文就把我从"拿到源码"到"上线可用"的完整过程写出来,重点聊聊这套技术栈在校园健康场景里是怎么落地的,以及那些文档不会告诉你的细节。

1. 师生健康管理场景为什么需要这套系统

1.1 场景痛点:纸质晨检表和数据孤岛

很多人觉得"健康管理系统"不就是个增删改查吗,真去做才发现校园场景有其特殊之处。某高校此前用的是纸质晨检登记表,每天由班主任手动记录学生体温和异常症状,月底再统一汇总到校医室。这个流程最大的问题不是记录本身,而是数据无法形成闭环——晨检发现问题后没有追踪机制,校医室看不到班级维度的趋势变化,遇到传染病高发季只能靠人工翻表统计。

这类系统要解决的核心问题,其实是三件事:把分散的师生健康档案集中管理、把每日健康监测数据标准化录入和查询、把异常情况从"记录"升级为"预警+跟进"。这套源码对应的业务模块恰好覆盖了这三个维度,这也是我当初选中它的原因之一。

1.2 技术选型逻辑:为什么是这套组合

先说结论:这套组合不是最潮的,但一定是最适合课程设计和中小型项目的。SpringBoot2 相比 SpringBoot3,虽然 JDK 版本还停留在 8/11,但胜在生态资料丰富,遇到问题搜解决方案基本一搜一个准;Vue3 的组合式 API 让组件逻辑复用清晰,配合 Vite 开发体验比 Vue2 + Webpack 舒服太多。

MyBatis-Plus 在这套系统里承担的是持久层增强工作。它没有取代 MyBatis 的灵活性,而是在 BaseMapper 里预置了 insert/update/selectPage 这些通用方法,日常 CRUD 几乎不需要写 XML。配合分页插件,表格数据的列表查询效率很高。而 MySQL8.0 的好处在于 utf8mb4 字符集默认支持、窗口函数和 JSON 字段带来的扩展性——后面讲统计报表时你会看到我用窗口函数省了多少 Java 代码。

这两个配置文件是系统能跑起来的基础,后面会细讲。单从选型上,这套技术栈给出的是一个"下限不低、上限够用"的组合:业务复杂度不高时开发速度极快,而往后要接消息推送、对接体检系统时,SpringBoot 的生态也不会拖后腿。

2. 数据库设计拆解:五张核心表的建模思路

2.1 用户表与师生档案表:一张表还是两张表

打开数据库脚本,我首先关注的是用户体系设计。这套系统没有把"用户"和"师生信息"分成两个完全割裂的体系,而是采用了一张系统的sys_user表作为账号主体,再配合health_profile存放健康档案扩展信息。

sys_user表的核心字段包括:

  • id:主键,采用雪花ID策略生成
  • username:登录账号,通常是学号或工号
  • password:BCrypt 加密后的密码散列值
  • role:角色枚举(管理员 ADMIN / 教师 TEACHER / 校医 DOCTOR / 学生 STUDENT)
  • status:账号状态,0 表示正常、1 表示禁用

这里有个设计取舍:要不要把师生详细信息直接冗余在sys_user里?这套源码的选择是单独建health_profile表,保存血型、过敏史、慢性病史、紧急联系人、既往病史等健康字段。这个设计对健康管理系统是必要的——健康字段通常是低频修改、高频查询,和登录账号的更新频率完全不一样,拆开可以减少账号信息表的字段宽度,也方便以后对接外部体检数据时不污染核心账号表。

2.2 健康记录表和晨检表的存储策略

业务层面最核心的两张表是health_record和health_check。前者是完整健康档案记录(如校医录入的诊断、用药情况、随访计划),后者是每日晨检的快速登记。

晨检表health_check的字段设计很有讲究,我摘几个关键列:

CREATE TABLE health_check ( id BIGINT PRIMARY KEY, user_id BIGINT NOT NULL COMMENT '师生用户ID', check_date DATE NOT NULL COMMENT '检查日期', temperature DECIMAL(4,2) COMMENT '体温,单位摄氏度', is_abnormal TINYINT NOT NULL DEFAULT 0 COMMENT '是否异常:0正常 1异常', symptom_desc VARCHAR(255) COMMENT '异常症状描述', checked_by VARCHAR(64) COMMENT '登记人,通常是班主任', remark VARCHAR(255) COMMENT '备注', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, INDEX idx_check_date (check_date), INDEX idx_user_date (user_id, check_date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='每日晨检记录表';

注意idx_user_date这个联合索引,它是高频查询"某师生某天的晨检记录"和"某时间段内某人的记录趋势"的命脉。没有这个索引,数据量过十万后列表查询明显变慢。

2.3 MyBatis-Plus 在数据访问层的实际用法

代码里我的处理很简单。实体类上用@TableName映射表名,主键用@TableId(type = IdType.ASSIGN_ID)让 MP 自动生成雪花ID,CRUD 直接继承 BaseMapper:

public interface HealthCheckMapper extends BaseMapper<HealthCheck> { }

业务层查询每日异常记录时,直接用 LambdaQueryWrapper 构造条件,代码可读性比手写 SQL 好很多:

LambdaQueryWrapper<HealthCheck> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(HealthCheck::getCheckDate, date) .eq(HealthCheck::getIsAbnormal, 1) .orderByDesc(HealthCheck::getCreateTime); IPage<HealthCheck> page = healthCheckMapper.selectPage(new Page<>(pageNum, pageSize), wrapper);

分页插件在配置类里通过MybatisPlusInterceptor注册PaginationInnerInterceptor,这一步漏掉会导致分页不生效。我见过不少人抄代码只抄了依赖没抄配置,查出来的数据永远是全量。

3. 后端核心接口实现:登录鉴权与业务闭环

3.1 JWT 登录认证和角色权限的落地方式

这套系统的登录流程很典型:前端提交用户名密码,后端用 BCrypt 校验密码,通过后签发 JWT token,后续请求在请求头Authorization中携带 token,由拦截器统一校验。

我梳理了一下实现细节:

@Component public class JwtInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录接口 if (request.getRequestURI().contains("/login")) { return true; } String token = request.getHeader("Authorization"); if (token == null || !token.startsWith("Bearer ")) { throw new CustomException(401, "未登录或token已过期"); } // 解析token中的userId和role,存入ThreadLocal LoginUser loginUser = JwtUtil.parseToken(token.replace("Bearer ", "")); UserContextHolder.set(loginUser); return true; } }

权限控制没有引入 Spring Security,而是用角色字段配合拦截器做粗粒度判断,细粒度权限在 Controller 层通过自定义注解@RequireRole实现。对于课程设计和中小型系统,这个方案比 Spring Security 那套复杂的过滤器链更容易读懂和二次开发。

3.2 健康数据录入、查询、预警的接口链路

健康管理的核心链路是:每日晨检录入 -> 异常标记 -> 校医复核 -> 生成处理记录 -> 统计展示。

接口层面主要涉及:

  • POST /api/health/check:班主任提交晨检数据,支持批量导入全班体温
  • GET /api/health/records:分页查询师生健康记录,可按日期、是否异常、班级过滤
  • GET /api/health/records/{userId}/trend:查询某师生近30天体温趋势
  • POST /api/health/records:校医录入诊断与处理结果
  • GET /api/statistics/overview:统计面板核心数据

其中晨检数据批量导入的接口值得说。班主任面对全班四五十人,逐条点保存不现实,所以前端做了"按学号批量录入体温"的表格,后端接收一个 JSON 数组,循环插入时用 MyBatis-Plus 的saveBatch批量提交,实测一千条以内的 data 提交延迟很低,不会拖垮接口。

3.3 统计报表的 SQL 写法与性能取舍

统计模块最常用的两个维度是"按日异常人数趋势"和"按班级异常率排行"。这里我选了 SQL 聚合而非在 Java 里做循环统计,原因是数据量上来后 Java 循环逐人统计会产生大量查询,优化空间有限。

按日异常人数趋势的查询,核心 SQL 大概是:

SELECT check_date, COUNT(*) AS abnormal_count FROM health_check WHERE check_date BETWEEN #{startDate} AND #{endDate} AND is_abnormal = 1 GROUP BY check_date ORDER BY check_date;

配合 MySQL8.0 的窗口函数,还能进一步算班级异常率的连续趋势:

SELECT class_name, check_date, COUNT(*) OVER (PARTITION BY class_name ORDER BY check_date) AS cum_abnormal_count FROM health_check hc JOIN health_profile hp ON hc.user_id = hp.user_id WHERE is_abnormal = 1

这种写法把原本要写一大段 Java 分组统计的逻辑压缩成一条 SQL,响应速度更快。如果你的业务场景里有"导出某时间段内所有班级的异常趋势",这条查询就是现成模板。

4. Vue3 前端页面构建:从目录结构到核心页面

4.1 目录结构与状态管理

前端采用 Vite 构建,目录结构清晰,我拿到源码后几乎没有重构就接入了业务:

src/ ├── api/ # Axios 接口封装 │ ├── auth.js │ ├── health.js │ └── statistics.js ├── assets/ # 静态资源 ├── components/ # 通用组件(UploadExcel、DateRangePicker等) ├── router/ # Vue Router 配置与守卫 ├── stores/ # Pinia 状态管理 │ └── user.js ├── views/ │ ├── login/ │ ├── dashboard/ # 统计面板 │ ├── health/ # 健康档案与晨检 │ └── user/ # 用户管理 └── utils/ └── request.js # Axios 实例封装

状态管理用的是 Pinia,对比 Vuex 优势是 API 更简洁、对 TypeScript 支持好。stores/user.js里保存了 token 和用户信息,页面刷新后从 localStorage 恢复,路由守卫里通过userStore.token判断是否放行。

4.2 核心页面:档案列表、晨检录入、统计展示

健康档案列表页是我看得最多的一个页面。它用 Element Plus 的el-table展示数据,配合el-date-picker做日期范围筛选,右上角有"导出Excel"按钮。表格列配置里有一个值得注意的细节——异常状态列不是简单显示"是/否",而是用el-tag做了颜色区分:正常显示绿色"正常",异常显示红色"异常"并附上症状描述。

晨检录入页则做了两种模式切换:单条登记和批量录入。批量录入模式把全班学生按学号排列,每行一个体温输入框,前端自动校验体温范围(34.0-42.0),超出范围即标红提示。这个校验逻辑放在前端可以避免无效请求打到后端。

统计展示页使用了 ECharts 的折线图和柱状图,数据来自/api/statistics/overview。我在这部分的工作主要是调整图表配色和坐标轴刻度,以及把"异常率"的计算公式从前端挪到了后端返回——避免前端拿原始数据自己算导致口径不一致。

4.3 Axios 封装与接口联调的几个细节

utils/request.js里对 Axios 做了统一封装:

const service = axios.create({ baseURL: '/api', timeout: 10000 }) service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) config.headers.Authorization = 'Bearer ' + token return config })

响应拦截器里做了统一错误处理:后端返回 401 时清除登录状态并跳转登录页;其他业务错误直接ElMessage.error提示。开发环境通过 Vite 的 proxy 配置解决跨域问题,如果直接部署到 Nginx 上,需要配置对应的/api反向代理。

联调时最容易遇到的问题是后端接口接收的日期格式。这套系统的后端 JSON 序列化把 LocalDate 和 LocalDateTime 都统一成yyyy-MM-dd HH:mm:ss格式,前端表单如果用日期组件默认的yyyy-MM-dd传值,后端反序列化 LocalDateTime 时会直接报格式错误。解决办法是在application.yml里指定 Jackson 的日期格式,或者后端改用@JsonFormat注解固定格式。

5. 从零跑通这套源码:环境配置与踩坑记录

5.1 环境版本核对:JDK、Node、MySQL 的匹配问题

我按照文档建议先安装环境,结果发现文档没有强调版本兼容性的关键点:

组件推荐版本踩坑说明
JDK8 或 11SpringBoot2.7 用 JDK17 也能跑,但部分老依赖有兼容隐患
Maven3.6+版本太低拉不全依赖,建议 3.8 以上
Node.js16.20+ 或 18.xVite5 要求 Node18,Vite4 要求 Node16,注意看前端 package.json
MySQL8.0.x5.7 也能跑但某些窗口函数用不了
Redis(若启用)非必需本项目会话信息存在 JWT 中,不强制依赖 Redis

第一次启动后端时我直接报错,原因是 MySQL 连接串里的时区参数serverTimezone=Asia/Shanghai在数据库驱动版本过新时解析有变化。加在 JDBC 连接串里显式指定即可解决。

5.2 数据库初始化与配置文件修改

数据库脚本在/sql/health_system.sql里,直接用 Navicat 或命令行导入即可。导入后需要修改后端application.yml中的数据源配置:

spring: datasource: url: jdbc:mysql://localhost:3306/health_system?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver

这里有个细节:MySQL8.0 默认认证插件是caching_sha2_password,如果本地 MySQL 是 5.7 升级上来的,连接时可能报Public Key Retrieval is not allowed,需要在连接串里加allowPublicKeyRetrieval=true。

前端方面,/vite.config.js里配置了开发代理:

server: { port: 3000, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }

后端启动端口默认 8080,确认没有端口占用就能直接联调。

5.3 三个最常见的启动报错及修复方法

我把踩过的坑整理成表格,照着检查基本能解决大部分启动问题:

报错现象根因分析处理方式
java.lang.IllegalArgumentException: Failed to load driver class数据库连接串或驱动依赖缺失检查 pom.xml 是否引入mysql-connector-j,并核对驱动类名
Table 'health_system.xxx' doesn't exist数据库未导入完整脚本重新执行完整 SQL 脚本,注意脚本里可能包含跨库语句需要手动调整
前端Port 3000 is already in useVite 默认端口被占用在 vite.config.js 里换端口,或关闭占用进程

还有一个隐蔽的问题:如果后端用了 MyBatis-Plus 3.5.3 以上版本,分页插件的配置方式略有调整,旧版教程里写的是PaginationInterceptor,新版换成了PaginationInnerInterceptor(DbType.MYSQL)。我一开始照抄旧写法,分页数据只返回了 total 但 records 是空的,排查半天才反应过来是插件没注册。

6. 二次开发视角:这套源码的扩展方向

跑通基础功能只是起点。我在实际使用中总结了几个很自然的扩展方向,适合自己有进一步想法时参考:

第一个是消息通知模块。目前系统只在页面内展示异常预警,但实际场景里校医希望晨检发现异常后能第一时间收到推送。可以引入 WebSocket 或者接入已有的企业微信/钉钉机器人 Webhook,在health_check插入异常记录后由后端主动推送。

第二个是体检报告导入。师生年度体检数据通常是 Excel 或者 PDF 形式,给这套系统做一个"体检数据批量导入"功能并不复杂:前端上传 Excel,后端用 EasyExcel 解析,按学号匹配health_profile记录,把体检结果落入扩展字段或新增明细表。相比手工录入,这不是体验优化而是效率质变。

第三个是健康趋势的跨学期对比。目前的统计面板只支持按时间范围查询,如果加上"对比上学期同期"功能,SQL 上就是多一个同类聚合查询,前端用双折线呈现。别小看这个功能,校医和教务是很看重趋势对比的。

这些扩展方向都基于现有技术栈,不需要换框架或重写接口。SpringBoot2 的自动配置和 MyBatis-Plus 的灵活性足够应付,Vue3 的组件化也方便在不影响老页面的前提下新增模块。

回头看看整个落地过程,我对这套源码的总体评价是:结构规整、注释到位、文档完整,适合用来学习SpringBoot2+Vue3全栈项目的完整链路,也适合作为真实业务的起始骨架。尤其它的数据库设计和统计SQL部分,是很多项目教程里不会细讲的实战内容。如果你正准备选一套Java Web项目来做课程设计或毕业设计,这个题目的性价比很高——技术栈主流、业务场景清晰、可扩展点充足,做完之后能讲明白的东西特别多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询