刚带完一个做师生健康信息管理系统毕业设计的学生,整套东西从搭建框架到最终答辩跑通,前后折腾了将近两个月。期间踩了不少坑,也总结了一些对新手比较友好的做法。这篇就把这个项目从头到尾拆开聊聊,把那些文档里不一定会写的东西一并讲清楚,给正在做类似选题的人做个参考。这个项目说白了就是一套典型的前后端分离系统:后端用 SpringBoot,前端用 Vue,数据库用 MySQL,核心场景是管理师生的日常健康信息,比如每日健康上报、体检记录、异常预警、统计分析这些。它解决的问题很具体——学校或者学院需要一套能替代纸质登记、Excel 汇总的健康信息管理工具。适合计算机相关专业拿来当毕业设计,也适合想系统学习前后端分离项目开发流程的初级开发者。1. 项目整体设计与技术选型思路1.1 先搞清楚这套系统到底要管什么动工之前一定要把需求边界划清楚,不然写着写着就收不住了。这套师生健康信息管理系统,核心角色和业务其实很明确:学生角色:每日健康上报、查看个人体检记录、查看系统通知。教师角色:健康信息查看、异常数据标记、所带班级的统计概览。管理员角色:用户管理、健康数据审核、全校/全院数据统计分析、系统配置。功能范围确定之后,系统的整体架构就清晰了。前端走 Vue + Element Plus 这套组合做管理界面,后端用 Spring Boot 提供 RESTful API,MySQL 负责持久化存储。前后端通过 JSON 格式交互,用 JWT 做身份认证。这个架构在当前的企业级开发里非常普遍,拿来作为毕业设计既有技术说服力,又能锻炼完整的项目开发能力。1.2 为什么选这套技术栈而不是别的很多同学会纠结要不要追新,比如用若依框架快速生成,或者上微服务那套东西。我的建议很直接:毕业设计的核心是让评委看到你对主流技术栈的掌握和思考,不是越复杂越好。SpringBoot + Vue + MySQL 这个组合有几个实实在在的好处:SpringBoot 简化了 SSM 时代的繁琐配置,内嵌 Tomcat,打 jar 包就能跑,部署门槛低。Vue 的响应式机制和组件化开发,和 SpringBoot 的 RESTful API 配合非常自然,前后端职责清晰。MySQL 成熟稳定,学习资料多,出问题也好排查。这套技术栈和实际企业招聘需求直接挂钩,写简历上说服力强。如果非要去追微服务、分布式那一套,反而容易陷入架构复杂度里,核心业务逻辑没写好,答辩时被问到底层原理就比较被动。1.3 系统核心模块怎么拆分系统的功能按角色和业务拆分,可以分为以下核心模块:模块主要功能涉及角色登录认证JWT 登录、角色权限校验所有用户每日健康上报体温、症状、行程信息填写和提交学生健康数据管理体检记录维护、健康档案查询学生、教师异常预警体温异常标记、症状跟踪教师、管理员统计分析健康数据报表、趋势图、导出管理员系统管理用户管理、角色管理、公告管理管理员这个模块划分看起来常规,但实际操作中要处理好两个容易忽略的点:一个是健康上报的"每日一次"限制逻辑,另一个是统计报表的维度设计。前者关系到学生端体验,后者关系到管理员端的数据决策价值。2. 数据库设计是整套系统的地基2.1 核心表结构与字段设计思路数据库设计这块值得多花时间,因为后面所有功能的实现都建立在表结构之上。这套系统的核心表我建议按以下思路设计:-- 用户表 CREATE TABLE `sys_user` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键', `username` VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名', `password` VARCHAR(100) NOT NULL COMMENT '加密密码', `real_name` VARCHAR(50) COMMENT '真实姓名', `role` TINYINT NOT NULL COMMENT '角色:1学生 2教师 3管理员', `dept_id` BIGINT COMMENT '所属部门/班级', `status` TINYINT DEFAULT 1 COMMENT '状态:1正常 0禁用', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';健康上报表的关键设计在于唯一约束,这个直接关系到业务逻辑:CREATE TABLE `health_report` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `user_id` BIGINT NOT NULL, `report_date` DATE NOT NULL, `temperature` DECIMAL(4,1) COMMENT '体温', `is_symptom` TINYINT DEFAULT 0 COMMENT '是否有症状:0无 1有', `symptom_desc` VARCHAR(255) COMMENT '症状描述', `location` VARCHAR(100) COMMENT '当前位置', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY `uk_user_date` (`user_id`, `report_date`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康上报表';uk_user_date这个联合唯一索引就是从数据库层面保证了"一个用户一天只能上报一次",这一条如果只靠后端逻辑去判断,高并发下容易出现重复数据,加了这个约束就万无一失了。2.2 权限相关的数据表怎么建用户与角色这里我没有单独建 role 表,而是用sys_user表里的role字段直接区分。这种设计在角色固定且不超过三四个的场景下完全够用,还能少写一张表的关联逻辑。如果是那种角色多、权限细到按钮级别的系统,才需要引入真正的 RBAC 五张表标准设计。真正需要用心设计的是班级和用户的关系。学生要关联到班级,班级要关联到教师,这里建议设计成:CREATE TABLE `dept` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `dept_name` VARCHAR(100) NOT NULL, `parent_id` BIGINT DEFAULT 0 COMMENT '父部门ID', `leader_user_id` BIGINT COMMENT '负责人,即班主任', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='班级/部门表';这样学生通过sys_user里的dept_id关联到班级,班级通过leader_user_id关联教师,层层关系一目了然,统计的时候也方便按班级聚合。2.3 建表的几个小提醒第一,所有表的字符集统一用utf8mb4,别用utf8,否则遇到生僻字或者 emoji 表情存不进去就麻烦了。第二,每张表都建议保留create_time和update_time字段,排查数据问题的时间线全靠它们。第三,字段注释一定要写,后面写论文的数据字典部分会省非常多的力气。3. 后端核心业务逻辑的落地技巧3.1 登录认证与权限拦截SpringBoot 项目里整合 JWT 做登录认证,这套流程虽然已经被写烂了,但很多新手还是会踩到拦截器放行路径的坑。配置拦截规则时,一定要把登录接口、静态资源、Swagger 文档这些路径放到放行列表里,否则前端还没登录就被拦在门外了。@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(jwtInterceptor) .addPathPatterns("/**") .excludePathPatterns( "/api/auth/login", "/api/auth/register", "/doc.html", "/webjars/**", "/favicon.ico" ); }这里有一个容易被忽视的细节:前端 OPTIONS 预检请求。跨域情况下,浏览器会先发一个 OPTIONS 请求,如果拦截器没有放行 OPTIONS,前端就会报 CORS 错误。处理方式是拦截器里直接判断请求方法:if ("OPTIONS".equals(request.getMethod())) { return true; }3.2 每日上报的防重复检查后端实现每日上报时,虽然数据库有唯一索引兜底,但业务层还是要有一次主动判断,这样能给出友好的业务提示,而不是直接抛数据库异常。这个逻辑放在 Service 层:public R report(HealthReport report) { LambdaQueryWrapper<HealthReport> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(HealthReport::getUserId, report.getUserId()) .eq(HealthReport::getReportDate, report.getReportDate()); long count = healthReportMapper.selectCount(wrapper); if (count > 0) { return R.error("今日已上报,请勿重复提交"); } healthReportMapper.insert(report); return R.ok(); }3.3 统计报表的聚合查询管理员端最常用的功能就是统计,比如按日期查看全校发热人数趋势、按班级对比上报率。这类需求用 MyBatis-Plus 的 LambdaQueryWrapper 不太好写复杂分组查询,更高效的做法是直接在 Mapper 层写自定义 SQL,用@Select注解或者 XML 都行:@Select("SELECT report_date, COUNT(*) AS total, " + "SUM(CASE WHEN temperature > 37.3 THEN 1 ELSE 0 END) AS fever_count " + "FROM health_report " + "WHERE report_date BETWEEN #{startDate} AND #{endDate} " + "GROUP BY report_date ORDER BY report_date") List<Map<String, Object>> getHealthTrend(@Param("startDate") String startDate, @Param("endDate") String endDate);返回List<Map<String, Object>>是因为聚合查询的结果没有固定的实体类对应,用 Map 接收最灵活。前端拿到这个数据后通过 ECharts 直接渲染折线图,交互体验很好,论文里还能配一张截图作为系统亮点。3.4 统一返回体与全局异常处理项目里一定要定义一个统一的返回体R,通常包含code、message、data三个字段。这样做的好处是前端可以统一处理响应状态,不用每个接口都单独做错误判断。全局异常处理建议用@RestControllerAdvice配合@ExceptionHandler,把业务异常、参数校验异常、系统异常分开处理。比如参数校验异常就返回 code 400,业务异常返回 code 500 加提示信息。这一套东西写好后,整个项目的代码整洁度会高一个档次,论文的"系统设计"章节也有内容可以写。4. 前端 Vue 部分的开发要点4.1 项目搭建与请求封装前端这边用 Vue 3 + Vite + Element Plus + Pinia + Vue Router 这套组合。Vite 比 Webpack 快得多,启动项目几乎秒开,开发体验很好。项目创建直接:npm create vue@latest health-frontend请求封装这一块是很多新手容易忽略却又特别重要的。直接用 axios ,但要封装一个 request 工具类,把 baseURL、请求拦截器(加 token)、响应拦截器(统一处理错误状态)都配好:import axios from 'axios'; const request = axios.create({ baseURL: '/api', timeout: 10000 }); // 请求拦截器:附加 token request.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); // 响应拦截器:统一处理错误 request.interceptors.response.use( response => { return response.data; }, error => { if (error.response && error.response.status === 401) { router.push('/login'); } return Promise.reject(error); } );4.2 动态路由与权限控制学生、教师、管理员看到的菜单和页面不一样,这个用动态路由实现最优雅。做法是登录成功后,根据返回的角色信息,动态添加对应的路由:const asyncRoutes = { student: [homeRoute, reportRoute, healthRecordRoute], teacher: [homeRoute, classStatsRoute, abnormalRoute], admin: [...allRoutes] }; function generateRoutes(role) { const routes = asyncRoutes[role]; routes.forEach(route => router.addRoute(route)); }配合 Vue Router 的beforeEach路由守卫做登录校验和角色校验,这样即使前端路径被人猜到,后端接口也有权限拦截兜底,双重保障。这一块实现好之后,答辩的时候基本被问不倒,因为涉及了前端工程化和权限控制两个核心考点。4.3 健康上报表单的关键处理健康上报表单是学生端最常用的功能,用户体验直接影响系统评分。这里有两个细节要处理好:第一是日期默认值,默认选中今天,而且如果今天已经上报过,进入页面就要给出提示并禁用提交按钮。这需要进入页面时先调一次查询接口看今天是否有记录。第二是体温的输入限制,体温范围合理控制在 35.0 到 42.0 之间,超过就给出异常提示。有人可能会说这算什么技术点,但实际开发中这种细节非常多,累积起来就是用户体验的差异。对于体温异常的情况,前端还可以做个视觉提示,比如体温超过 37.3 就红色高亮显示,一目了然。5. 部署上线的完整实操记录5.1 本地环境准备整个项目跑起来之前,先把环境准备好。我建议按这个清单对照检查:JDK 1.8 或 11,配置好 JAVA_HOME。Node.js 16 以上,npm 或者 pnpm 都行。MySQL 5.7 或 8.0。Maven 3.6 以上。5.2 后端打包后端打包用 Maven,有个细节很关键:打包之前要确保测试类不阻断构建。很多同学在项目里写了单元测试,然后打包时测试不通过,jar 包就生成不了。可以跳过测试:mvn clean package -DskipTests打包完成后,target 目录下会生成一个xxx.jar文件,这个是后端部署的核心产物。启动命令很简单:java -jar health-system.jar --server.port=80805.3 前端构建与 Nginx 部署前端开发模式下一艘是 npm run dev,但部署上线必须要走构建流程,把 Vue 项目编译成纯静态文件:npm run build构建完成后会生成dist目录,里面的文件就是前端最终部署的内容。推荐用 Nginx 进行托管,配置反向代理把/api开头的请求转发到 SpringBoot 服务上:server { listen 80; server_name localhost; root /opt/health/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # 解决前端路由 history 模式刷新 404 的问题 location / { try_files $uri $uri/ /index.html; } }try_files这一行非常关键,Vue Router 使用 history 模式时,刷新非首页路径会 404,加上这行配置后所有路由都回退到 index.html,由前端路由接管。5.4 初始化脚本与项目启动数据库这块,用 Navicat 导入项目里自带的health_system.sql就能把全部表和初始数据建好。建议管理员账号的密码通过 MD5 加盐存储,日志记录登录行为,这些安全细节在答辩时都是加分的点。启动顺序上,我建议按"数据库 -> 后端 -> 前端"的顺序来进行。后端进程起来之后先用 Postman 测几个接口确认能通,再启动 Nginx 服务前端页面,这样排查问题比较快。6. 常见问题与排查技巧实录6.1 后端接口单独访问正常,前端调用就报跨域这是前后端分离项目里最典型的一个问题。解决办法有两个层面:后端加全局跨域配置,或者前端用 Nginx 反向代理解决。开发环境下建议后端配置跨域,写一个WebConfig类实现WebMvcConfigurer即可;生产环境用 Nginx 反向代理,前后端同源,不存在跨域问题。@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }6.2 数据库连接报错 Access denied大概率是账号密码或者权限问题。确认一下 MySQL 里有没有创建对应的用户并授权:CREATE USER 'health'@'%' IDENTIFIED BY 'your_password'; GRANT ALL PRIVILEGES ON health_system.* TO 'health'@'%'; FLUSH PRIVILEGES;还有一种情况是 MySQL 8.0 的认证插件跟 JDBC 驱动版本不兼容,这时候要么升级 mysql-connector-java 版本,要么在连接 URL 后面加参数。6.3 前端页面加载正常但接口 404很可能是 Nginx 代理路径配置不对。检查 proxy_pass 是否写成了带路径的形式,比如proxy_pass http://127.0.0.1:8080/api/;,这样会导致请求变成/api/api/xxx或者路径重复。建议统一不带末尾/api/,后端接口路径本身已经带/api前缀。6.4 打包后启动 jar 包报端口占用先查端口被谁占了:netstat -ano | findstr 8080确认是旧进程占用就结束掉对应的 PID,或者换个端口启动。这里我一般直接改用--server.port=8081,省去杀进程的麻烦。6.5 系统上线后时间显示差了 8 小时典型的时区问题。MySQL 连接 URL 里要加上时区参数:spring.datasource.url=jdbc:mysql://localhost:3306/health_system?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai6.6 前端菜单正常但退出登录后再登录别的账号菜单没变这是前端常见的状态残留问题。退出登录时需要重置 Pinia 的 store,动态添加的路由也要清掉。可以这么处理:function resetRouter() { router.getRoutes().forEach(route => { if (route.name && route.name.startsWith('Role_')) { router.removeRoute(route.name); } }); }7. 照着做就能跑通的完整流程梳理为了方便直接上手,我把整个项目从零到跑通的过程压缩成一套可执行的清单:第一阶段:搭建骨架(约 2 天)创建后端 SpringBoot 项目,配置好依赖和 application.yml。创建前端 Vue 项目,引入 Element Plus 和 Pinia。导入数据库脚本,确认表结构完整。第二阶段:核心功能实现(约 3 周)登录认证 + 权限拦截。学生端每日上报功能。教师端健康数据查询与异常标记。管理员端用户管理 + 统计分析。第三阶段:界面完善(约 1 周)Element Plus 表格、表单、弹窗的组合使用。ECharts 统计图表的嵌入。第四阶段:测试与部署(约 3 天)后端打包,前端构建,Nginx 配置。全套流程跑测试用例记录 bug 并修复。根据测试结果截图,为论文准备素材。第五阶段:论文整理(约 1 周)背景意义、需求分析、系统设计、功能实现、测试五章的结构。画功能结构图、E-R 图、流程图,截图放关键功能页面。我自己带学生的过程中发现一个规律:很多人不是卡在某个技术难点上,而是被时间安排拖垮了。毕业设计最怕的是前期不着急,最后一个月疯狂赶工,这种情况做出来的东西质量和深度都很难保证。按上面的节奏稳步推进,整个过程从技术实现到文档整理都会从容很多。最后说一点关于论文和源码的关系。论文的截图和实现的功能一定要一一对应,评委很可能随便点一个界面问你对应的是论文里哪个章节的哪个功能点。系统里我强烈建议保留一段测试记录表,把测试用例、预期结果、实际结果列成表格放到论文的测试章节,这个细节会让整个论文显得非常扎实。这套系统做完之后,前后端分离的开发模式、JWT 认证、权限控制、统计分析这一整套主流技术方案就都亲手玩过一遍了。后续想扩展的话,体检报告上传、消息推送、移动端适配都是不错的方向。一个标准的信息管理系统要做到逻辑严谨、细节到位,二次开发的空间很大。
刚带完一个做师生健康信息管理系统毕业设计的学生,整套东西从搭建框架到最终答辩跑通,前后折腾了将近两个月。期间踩了不少坑,也总结了一些对新手比较友好的做法。这篇就把这个项目从头到尾拆开聊聊,把那些文档里不一定会写的东西一并讲清楚,给正在做类似选题的人做个参考。这个项目说白了就是一套典型的前后端分离系统:后端用 SpringBoot,前端用 Vue,数据库用 MySQL,核心场景是管理师生的日常健康信息,比如每日健康上报、体检记录、异常预警、统计分析这些。它解决的问题很具体——学校或者学院需要一套能替代纸质登记、Excel 汇总的健康信息管理工具。适合计算机相关专业拿来当毕业设计,也适合想系统学习前后端分离项目开发流程的初级开发者。1. 项目整体设计与技术选型思路1.1 先搞清楚这套系统到底要管什么动工之前一定要把需求边界划清楚,不然写着写着就收不住了。这套师生健康信息管理系统,核心角色和业务其实很明确:学生角色:每日健康上报、查看个人体检记录、查看系统通知。教师角色:健康信息查看、异常数据标记、所带班级的统计概览。管理员角色:用户管理、健康数据审核、全校/全院数据统计分析、系统配置。功能范围确定之后,系统的整体架构就清晰了。前端走 Vue + Element Plus 这套组合做管理界面,后端用 Spring Boot 提供 RESTful API,MySQL 负责持久化存储。前后端通过 JSON 格式交互,用 JWT 做身份认证。这个架构在当前的企业级开发里非常普遍,拿来作为毕业设计既有技术说服力,又能锻炼完整的项目开发能力。1.2 为什么选这套技术栈而不是别的很多同学会纠结要不要追新,比如用若依框架快速生成,或者上微服务那套东西。我的建议很直接:毕业设计的核心是让评委看到你对主流技术栈的掌握和思考,不是越复杂越好。SpringBoot + Vue + MySQL 这个组合有几个实实在在的好处:SpringBoot 简化了 SSM 时代的繁琐配置,内嵌 Tomcat,打 jar 包就能跑,部署门槛低。Vue 的响应式机制和组件化开发,和 SpringBoot 的 RESTful API 配合非常自然,前后端职责清晰。MySQL 成熟稳定,学习资料多,出问题也好排查。这套技术栈和实际企业招聘需求直接挂钩,写简历上说服力强。如果非要去追微服务、分布式那一套,反而容易陷入架构复杂度里,核心业务逻辑没写好,答辩时被问到底层原理就比较被动。1.3 系统核心模块怎么拆分系统的功能按角色和业务拆分,可以分为以下核心模块:模块主要功能涉及角色登录认证JWT 登录、角色权限校验所有用户每日健康上报体温、症状、行程信息填写和提交学生健康数据管理体检记录维护、健康档案查询学生、教师异常预警体温异常标记、症状跟踪教师、管理员统计分析健康数据报表、趋势图、导出管理员系统管理用户管理、角色管理、公告管理管理员这个模块划分看起来常规,但实际操作中要处理好两个容易忽略的点:一个是健康上报的"每日一次"限制逻辑,另一个是统计报表的维度设计。前者关系到学生端体验,后者关系到管理员端的数据决策价值。2. 数据库设计是整套系统的地基2.1 核心表结构与字段设计思路数据库设计这块值得多花时间,因为后面所有功能的实现都建立在表结构之上。这套系统的核心表我建议按以下思路设计:-- 用户表 CREATE TABLE `sys_user` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键', `username` VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名', `password` VARCHAR(100) NOT NULL COMMENT '加密密码', `real_name` VARCHAR(50) COMMENT '真实姓名', `role` TINYINT NOT NULL COMMENT '角色:1学生 2教师 3管理员', `dept_id` BIGINT COMMENT '所属部门/班级', `status` TINYINT DEFAULT 1 COMMENT '状态:1正常 0禁用', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';健康上报表的关键设计在于唯一约束,这个直接关系到业务逻辑:CREATE TABLE `health_report` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `user_id` BIGINT NOT NULL, `report_date` DATE NOT NULL, `temperature` DECIMAL(4,1) COMMENT '体温', `is_symptom` TINYINT DEFAULT 0 COMMENT '是否有症状:0无 1有', `symptom_desc` VARCHAR(255) COMMENT '症状描述', `location` VARCHAR(100) COMMENT '当前位置', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY `uk_user_date` (`user_id`, `report_date`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康上报表';uk_user_date这个联合唯一索引就是从数据库层面保证了"一个用户一天只能上报一次",这一条如果只靠后端逻辑去判断,高并发下容易出现重复数据,加了这个约束就万无一失了。2.2 权限相关的数据表怎么建用户与角色这里我没有单独建 role 表,而是用sys_user表里的role字段直接区分。这种设计在角色固定且不超过三四个的场景下完全够用,还能少写一张表的关联逻辑。如果是那种角色多、权限细到按钮级别的系统,才需要引入真正的 RBAC 五张表标准设计。真正需要用心设计的是班级和用户的关系。学生要关联到班级,班级要关联到教师,这里建议设计成:CREATE TABLE `dept` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `dept_name` VARCHAR(100) NOT NULL, `parent_id` BIGINT DEFAULT 0 COMMENT '父部门ID', `leader_user_id` BIGINT COMMENT '负责人,即班主任', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='班级/部门表';这样学生通过sys_user里的dept_id关联到班级,班级通过leader_user_id关联教师,层层关系一目了然,统计的时候也方便按班级聚合。2.3 建表的几个小提醒第一,所有表的字符集统一用utf8mb4,别用utf8,否则遇到生僻字或者 emoji 表情存不进去就麻烦了。第二,每张表都建议保留create_time和update_time字段,排查数据问题的时间线全靠它们。第三,字段注释一定要写,后面写论文的数据字典部分会省非常多的力气。3. 后端核心业务逻辑的落地技巧3.1 登录认证与权限拦截SpringBoot 项目里整合 JWT 做登录认证,这套流程虽然已经被写烂了,但很多新手还是会踩到拦截器放行路径的坑。配置拦截规则时,一定要把登录接口、静态资源、Swagger 文档这些路径放到放行列表里,否则前端还没登录就被拦在门外了。@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(jwtInterceptor) .addPathPatterns("/**") .excludePathPatterns( "/api/auth/login", "/api/auth/register", "/doc.html", "/webjars/**", "/favicon.ico" ); }这里有一个容易被忽视的细节:前端 OPTIONS 预检请求。跨域情况下,浏览器会先发一个 OPTIONS 请求,如果拦截器没有放行 OPTIONS,前端就会报 CORS 错误。处理方式是拦截器里直接判断请求方法:if ("OPTIONS".equals(request.getMethod())) { return true; }3.2 每日上报的防重复检查后端实现每日上报时,虽然数据库有唯一索引兜底,但业务层还是要有一次主动判断,这样能给出友好的业务提示,而不是直接抛数据库异常。这个逻辑放在 Service 层:public R report(HealthReport report) { LambdaQueryWrapper<HealthReport> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(HealthReport::getUserId, report.getUserId()) .eq(HealthReport::getReportDate, report.getReportDate()); long count = healthReportMapper.selectCount(wrapper); if (count > 0) { return R.error("今日已上报,请勿重复提交"); } healthReportMapper.insert(report); return R.ok(); }3.3 统计报表的聚合查询管理员端最常用的功能就是统计,比如按日期查看全校发热人数趋势、按班级对比上报率。这类需求用 MyBatis-Plus 的 LambdaQueryWrapper 不太好写复杂分组查询,更高效的做法是直接在 Mapper 层写自定义 SQL,用@Select注解或者 XML 都行:@Select("SELECT report_date, COUNT(*) AS total, " + "SUM(CASE WHEN temperature > 37.3 THEN 1 ELSE 0 END) AS fever_count " + "FROM health_report " + "WHERE report_date BETWEEN #{startDate} AND #{endDate} " + "GROUP BY report_date ORDER BY report_date") List<Map<String, Object>> getHealthTrend(@Param("startDate") String startDate, @Param("endDate") String endDate);返回List<Map<String, Object>>是因为聚合查询的结果没有固定的实体类对应,用 Map 接收最灵活。前端拿到这个数据后通过 ECharts 直接渲染折线图,交互体验很好,论文里还能配一张截图作为系统亮点。3.4 统一返回体与全局异常处理项目里一定要定义一个统一的返回体R,通常包含code、message、data三个字段。这样做的好处是前端可以统一处理响应状态,不用每个接口都单独做错误判断。全局异常处理建议用@RestControllerAdvice配合@ExceptionHandler,把业务异常、参数校验异常、系统异常分开处理。比如参数校验异常就返回 code 400,业务异常返回 code 500 加提示信息。这一套东西写好后,整个项目的代码整洁度会高一个档次,论文的"系统设计"章节也有内容可以写。4. 前端 Vue 部分的开发要点4.1 项目搭建与请求封装前端这边用 Vue 3 + Vite + Element Plus + Pinia + Vue Router 这套组合。Vite 比 Webpack 快得多,启动项目几乎秒开,开发体验很好。项目创建直接:npm create vue@latest health-frontend请求封装这一块是很多新手容易忽略却又特别重要的。直接用 axios ,但要封装一个 request 工具类,把 baseURL、请求拦截器(加 token)、响应拦截器(统一处理错误状态)都配好:import axios from 'axios'; const request = axios.create({ baseURL: '/api', timeout: 10000 }); // 请求拦截器:附加 token request.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); // 响应拦截器:统一处理错误 request.interceptors.response.use( response => { return response.data; }, error => { if (error.response && error.response.status === 401) { router.push('/login'); } return Promise.reject(error); } );4.2 动态路由与权限控制学生、教师、管理员看到的菜单和页面不一样,这个用动态路由实现最优雅。做法是登录成功后,根据返回的角色信息,动态添加对应的路由:const asyncRoutes = { student: [homeRoute, reportRoute, healthRecordRoute], teacher: [homeRoute, classStatsRoute, abnormalRoute], admin: [...allRoutes] }; function generateRoutes(role) { const routes = asyncRoutes[role]; routes.forEach(route => router.addRoute(route)); }配合 Vue Router 的beforeEach路由守卫做登录校验和角色校验,这样即使前端路径被人猜到,后端接口也有权限拦截兜底,双重保障。这一块实现好之后,答辩的时候基本被问不倒,因为涉及了前端工程化和权限控制两个核心考点。4.3 健康上报表单的关键处理健康上报表单是学生端最常用的功能,用户体验直接影响系统评分。这里有两个细节要处理好:第一是日期默认值,默认选中今天,而且如果今天已经上报过,进入页面就要给出提示并禁用提交按钮。这需要进入页面时先调一次查询接口看今天是否有记录。第二是体温的输入限制,体温范围合理控制在 35.0 到 42.0 之间,超过就给出异常提示。有人可能会说这算什么技术点,但实际开发中这种细节非常多,累积起来就是用户体验的差异。对于体温异常的情况,前端还可以做个视觉提示,比如体温超过 37.3 就红色高亮显示,一目了然。5. 部署上线的完整实操记录5.1 本地环境准备整个项目跑起来之前,先把环境准备好。我建议按这个清单对照检查:JDK 1.8 或 11,配置好 JAVA_HOME。Node.js 16 以上,npm 或者 pnpm 都行。MySQL 5.7 或 8.0。Maven 3.6 以上。5.2 后端打包后端打包用 Maven,有个细节很关键:打包之前要确保测试类不阻断构建。很多同学在项目里写了单元测试,然后打包时测试不通过,jar 包就生成不了。可以跳过测试:mvn clean package -DskipTests打包完成后,target 目录下会生成一个xxx.jar文件,这个是后端部署的核心产物。启动命令很简单:java -jar health-system.jar --server.port=80805.3 前端构建与 Nginx 部署前端开发模式下一艘是 npm run dev,但部署上线必须要走构建流程,把 Vue 项目编译成纯静态文件:npm run build构建完成后会生成dist目录,里面的文件就是前端最终部署的内容。推荐用 Nginx 进行托管,配置反向代理把/api开头的请求转发到 SpringBoot 服务上:server { listen 80; server_name localhost; root /opt/health/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # 解决前端路由 history 模式刷新 404 的问题 location / { try_files $uri $uri/ /index.html; } }try_files这一行非常关键,Vue Router 使用 history 模式时,刷新非首页路径会 404,加上这行配置后所有路由都回退到 index.html,由前端路由接管。5.4 初始化脚本与项目启动数据库这块,用 Navicat 导入项目里自带的health_system.sql就能把全部表和初始数据建好。建议管理员账号的密码通过 MD5 加盐存储,日志记录登录行为,这些安全细节在答辩时都是加分的点。启动顺序上,我建议按"数据库 -> 后端 -> 前端"的顺序来进行。后端进程起来之后先用 Postman 测几个接口确认能通,再启动 Nginx 服务前端页面,这样排查问题比较快。6. 常见问题与排查技巧实录6.1 后端接口单独访问正常,前端调用就报跨域这是前后端分离项目里最典型的一个问题。解决办法有两个层面:后端加全局跨域配置,或者前端用 Nginx 反向代理解决。开发环境下建议后端配置跨域,写一个WebConfig类实现WebMvcConfigurer即可;生产环境用 Nginx 反向代理,前后端同源,不存在跨域问题。@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }6.2 数据库连接报错 Access denied大概率是账号密码或者权限问题。确认一下 MySQL 里有没有创建对应的用户并授权:CREATE USER 'health'@'%' IDENTIFIED BY 'your_password'; GRANT ALL PRIVILEGES ON health_system.* TO 'health'@'%'; FLUSH PRIVILEGES;还有一种情况是 MySQL 8.0 的认证插件跟 JDBC 驱动版本不兼容,这时候要么升级 mysql-connector-java 版本,要么在连接 URL 后面加参数。6.3 前端页面加载正常但接口 404很可能是 Nginx 代理路径配置不对。检查 proxy_pass 是否写成了带路径的形式,比如proxy_pass http://127.0.0.1:8080/api/;,这样会导致请求变成/api/api/xxx或者路径重复。建议统一不带末尾/api/,后端接口路径本身已经带/api前缀。6.4 打包后启动 jar 包报端口占用先查端口被谁占了:netstat -ano | findstr 8080确认是旧进程占用就结束掉对应的 PID,或者换个端口启动。这里我一般直接改用--server.port=8081,省去杀进程的麻烦。6.5 系统上线后时间显示差了 8 小时典型的时区问题。MySQL 连接 URL 里要加上时区参数:spring.datasource.url=jdbc:mysql://localhost:3306/health_system?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai6.6 前端菜单正常但退出登录后再登录别的账号菜单没变这是前端常见的状态残留问题。退出登录时需要重置 Pinia 的 store,动态添加的路由也要清掉。可以这么处理:function resetRouter() { router.getRoutes().forEach(route => { if (route.name && route.name.startsWith('Role_')) { router.removeRoute(route.name); } }); }7. 照着做就能跑通的完整流程梳理为了方便直接上手,我把整个项目从零到跑通的过程压缩成一套可执行的清单:第一阶段:搭建骨架(约 2 天)创建后端 SpringBoot 项目,配置好依赖和 application.yml。创建前端 Vue 项目,引入 Element Plus 和 Pinia。导入数据库脚本,确认表结构完整。第二阶段:核心功能实现(约 3 周)登录认证 + 权限拦截。学生端每日上报功能。教师端健康数据查询与异常标记。管理员端用户管理 + 统计分析。第三阶段:界面完善(约 1 周)Element Plus 表格、表单、弹窗的组合使用。ECharts 统计图表的嵌入。第四阶段:测试与部署(约 3 天)后端打包,前端构建,Nginx 配置。全套流程跑测试用例记录 bug 并修复。根据测试结果截图,为论文准备素材。第五阶段:论文整理(约 1 周)背景意义、需求分析、系统设计、功能实现、测试五章的结构。画功能结构图、E-R 图、流程图,截图放关键功能页面。我自己带学生的过程中发现一个规律:很多人不是卡在某个技术难点上,而是被时间安排拖垮了。毕业设计最怕的是前期不着急,最后一个月疯狂赶工,这种情况做出来的东西质量和深度都很难保证。按上面的节奏稳步推进,整个过程从技术实现到文档整理都会从容很多。最后说一点关于论文和源码的关系。论文的截图和实现的功能一定要一一对应,评委很可能随便点一个界面问你对应的是论文里哪个章节的哪个功能点。系统里我强烈建议保留一段测试记录表,把测试用例、预期结果、实际结果列成表格放到论文的测试章节,这个细节会让整个论文显得非常扎实。这套系统做完之后,前后端分离的开发模式、JWT 认证、权限控制、统计分析这一整套主流技术方案就都亲手玩过一遍了。后续想扩展的话,体检报告上传、消息推送、移动端适配都是不错的方向。一个标准的信息管理系统要做到逻辑严谨、细节到位,二次开发的空间很大。