☰
SpringBoot学生疫情信息管理系统:从需求分析到部署上线的完整实战
2026/10/6 21:03:12 网站建设 项目流程

这篇由标题驱动的毕设项目,正好是我很熟悉的领域。这类“XX管理系统”是计算机专业毕业设计里最常见的一类选题,每年都有大量学生选它。今天不聊怎么水论文,也不聊那些花里胡哨的PPT演示,就从一个真正打算把这个系统做出来、跑起来、能答辩的人的视角,把整个springboot学生疫情信息管理系统从需求到上线、从踩坑到优化都掰开揉碎讲一遍。

整套技术栈其实就是Java后端开发里最主流的那一套:SpringBoot做后端接口、MyBatis-Plus做数据持久层、MySQL存数据、Vue做前端页面,再配上JWT做登录认证。项目不大但五脏俱全,把学生管理、健康打卡、异常上报、预警通知、数据统计、公告管理这些功能完整做下来,基本上一套标准的Web应用开发流程就全走通了。

这个系统能做什么?往大了说它可以用于学校对在校学生健康状况的日常监测管理,往实际了说,它就是咱们日常做企业级项目那套增删改查加上业务流转的逻辑。对毕设而言,它的价值在于:既有清晰的业务场景,又有足够的技术深度可供挖掘。适合正在准备毕业设计的计算机相关专业学生,或者刚学完SpringBoot基础课程、想拿一个完整项目练手的初级开发者。

先说几个我觉得最关键的设计决策,再一点点拆开讲。

1. 项目整体设计与需求拆解

做管理系统的第一步,永远不是写代码,而是把角色和流程想清楚。

1.1 三类角色与一条完整业务闭环

我见过很多学生的毕设代码,功能东一块西一块,原因是没把角色想清楚。这个系统从用户维度看,至少应该拆成三种角色:学生、辅导员/管理员、还有校医/学院负责人。

学生这边的核心痛点是什么?是每天要上报自己的健康状态、体温与行程信息,同时要接收学校发布的各类通知。辅导员或管理员这边,痛点则是信息太碎,需要时刻知道谁填了、谁没填、谁填的数据异常。校医这个角色往往被忽略,但疫情信息管理系统中它其实是核心,因为只有它能对异常上报做最终处置与归档。

这样一条闭环就出来了:学生提交健康打卡 -> 系统校验并记录数据 -> 自动或人工触发异常预警 -> 辅导员核实并初步处置 -> 校医/负责人复核并归档 -> 各类角色在首页看到统计结果。

很多毕设论文写得烂,核心问题就是流程没闭环。比如学生可以填表单,但提交之后没人管,也没有异常通知,那这个系统就只是个网页表单生成器,谈不上“管理”。所以做需求阶段,我建议先在纸上把这几个闭环节点画出来,再对着每个节点定功能范围。

1.2 模块边界与数据库表结构设计

基于上面的业务流程,功能模块可以拆为:

  • 系统登录与用户认证(区分角色和权限)
  • 学生健康信息填报(每日打卡、体温、行程、症状、备注)
  • 异常信息预警与处置(自动预警、人工标注、流程记录)
  • 数据统计与可视化(班级/学院维度的填报率、异常率、趋势)
  • 公告与通知管理(发布、查看、指定接收人)
  • 学生信息管理(导入、导出、年级/班级维护)

数据库设计上,我推荐最省事的6张表起步:

用户表(包含学生基本信息、角色字段)、健康打卡表、异常处置表、公告表、班级表、操作日志表。

这里有几个我只在实际开发中体会到的设计细节,专门说明一下。

  1. 用户表要冗余班级信息。很多新手习惯学生表外挂班级ID,每查一次都要JOIN。其实对于毕设规模的数据量,冗余一个班级名字段在用户表里非常方便,查询列表直接拿来用,省掉大量联表操作。当然如果追求生产级别的范式设计,那另说。

  2. 打卡表建议按天建立唯一索引。student_id + date做联合唯一索引,这样后端接口只需要捕获数据库的DuplicateKeyException就能判断重复打卡,比先select再insert的写法稳得多也快得多。

  3. 异常类型用状态机而不是单一字段。比如异常的流转状态:待处置、处置中、已结案、已归档。用status字段管理状态跳转,比用一堆布尔字段清晰得多。

数据库建表语句里有两个字段我觉得是必加且常被忽略的:一个是每个表要有create_time,甚至update_time,用MyBatis-Plus的自动填充功能维护它,统计时经常要用;另一个是在打卡表上建议加一个report_source字段,标记数据是正常填报还是异常申诉,这在后面做数据汇总时非常有用。

2. 核心技术选型与工程配置要点

技术选型不是越新越好,而是越稳越好。这个项目我推荐稳定版组合,下面把选型逻辑讲透。

2.1 为什么前后端分离,而不是传统的JSP加SpringBoot

很多毕设要求文档里会说“基于SpringBoot”,但没说一定不能用模板引擎。我的建议是尽量用前后端分离方案,即后端只写JSON接口给前端。原因有三个:

第一,前后端分离在论文里能多写一章“系统架构设计”,技术含量看起来高;第二,分离模式下,前端的页面展示和后端的数据逻辑独立,调试方便,遇到问题定位更快;第三,SpringBoot最主流的开发模式就是Web API,答辩时接口设计能聊的东西比渲染页面多得多。

前端框架选Vue的版本是个经典纠结。我的建议是直接选Vue 3加Element Plus。有些人担心Vue 3没学过,实际上如果只写管理系统页面,Vue 2和Vue 3的写法差异微乎其微,而且Vue 3的组合式API写起来逻辑更紧凑。类比一下,Vue 2像手动挡,Vue 3像自动挡,管理系统的业务深踩油门就行了,不用整天换挡。

2.2 SpringBoot核心依赖与配置文件的现场讲解

pom.xml里加上MyBatis-Plus的依赖是常规操作。注意版本别乱用最新的,到我写这篇文章的时候,SpringBoot 2.7.x配MyBatis-Plus 3.5.x是极其成熟稳定的组合,网上资料多,报错也好搜。SpringBoot 3.x在启动底层和javax/jakarta命名上变化不小,除非你想展示自己在跟前沿,否则没必要在毕设里给自己挖这个坑。

配置文件是我看毕设代码的重灾区。一份能直接用的 application.yml 大概长这样:

server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/health_mgr?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false username: root password: 123456 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0

我单独解释两个关键点。

数据库连接URL里的serverTimezone=Asia/Shanghai很关键。很多本地MySQL是8.0以后,驱动版本变了,如果时区不设置成Asia/Shanghai,插入时间字段时后端报错或者时间差8小时。这个坑几乎每个第一次用新版驱动的人都踩过,答辩时被问到处理思路,刚好是个讲细节的机会。

mybatis-plus的日志配置,开发阶段一定要保留StdOutImpl。很多人调试SQL不方便,总说是SQL写错了,其实连日志都没打开。控制台能直接打印出完整的SQL语句和参数,排查问题效率翻倍。该项目后期部署上线,把这行注掉即可,不然日志文件增长很快。

2.3 自动装配原理简单理解法

SpringBoot面试题最爱问自动装配原理,实际开发时这也是理解配置文件背后行为的关键。我不会背源码,我只讲一个理解模型:

SpringBoot在启动时,会去读取META-INF/spring.factories或新版里的AutoConfiguration.imports,这里面列了一堆自动配置类。这些配置类上有很多条件注解,比如@ConditionalOnClass,意思是“当项目里存在某个类的时候,才自动配置这套东西”。

用生活化的例子说,这就像吃自助火锅。调料台上有几十种调料,你拿了一碟麻酱和韭菜花,厨房就知道你要涮羊肉;你拿了花生碎、香菜和辣椒油,厨房就知道你在配凉菜。你不需要告诉厨房每个调料分别怎么放,厨房靠“看到什么调料组合”来推断你的需求。SpringBoot就是靠类路径里有没有DataSource、有没有JdbcTemplate来判断要不要帮你配一个数据源。所以引入依赖和写配置是配套动作,缺一个系统就启动不了或跑错行为,这就是全部原理。

3. 核心功能实操:从登录认证到每日打卡

这个章节是项目的重头戏,拿三个核心链路手把手演示一遍。

3.1 登录认证与权限控制:JWT方案落地

登录认证如果还在用Session复制粘贴,答辩基本解释不清楚并发场景。我推荐用JWT(JSON Web Token),而且实现起来也不难。

核心思路是:用户登录时服务端验证账号密码正确性,验证通过后生成一个加密好的JSON字符串传回前端。这个字符串里可以塞用户ID、用户名、角色信息,并带过期时间。前端之后每次请求都在Header里带上这个Token,后端写一个拦截器统一校验Token是否有效、是否过期。

用通俗的比喻就是,传统Session方式相当于每次进园区都刷身份证然后园区记录在案,JWT则像给你发一张带有效期的限时手环,园区工作人员看你手上晃一眼手环颜色就知道你能去哪层楼。

关键代码不难:

// 生成Token @RequestMapping("/login") public Result login(@RequestBody LoginDTO dto) { // 1. 查用户,校验密码 User user = userService.getByUsernameAndPassword(dto.getUsername(), md5(dto.getPassword())); if (user == null) { return Result.error("账号或密码错误"); } // 2. 生成JWT String token = JwtUtil.createToken(user.getId(), user.getRole(), 24 * 60 * 60 * 1000L); // 3. 返回给前端 return Result.success(new HashMap<String, Object>() {{ put("token", token); put("userInfo", user); }}); }

注意密码不建议明文存储,用MD5加盐或者BCrypt都行。这里尤其要说明,我见过太多毕设代码里用户表直接存明文密码,答辩时老师只要查一下数据库截图就能看出问题。至少用DigestUtils.md5Hex(password + salt)这个级别。

拦截器端,关键点是放行登录接口和静态资源请求。用Spring的拦截器只管校验请求头里有没有合格的Token,其他的逻辑不用管。

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { if (request.getMethod().equals("OPTIONS")) return true; String token = request.getHeader("Authorization"); // 校验token,如果失败则返回401 return JwtUtil.checkToken(token); }

3.2 每日健康打卡:重复提交与异常预警

打卡接口业务上就两件事:一是保存一条打卡记录,二是如果打卡数据触发了预警条件,需要生成一条异常记录通知辅导员。

先说防重复打卡。我刚才提到了联合唯一索引,实际编码时可以这么做:

public Result doClock(@RequestBody ClockForm form) { // 补全字段 StudentClock clock = new StudentClock(); clock.setStudentId(LoginUser.getId()); clock.setDate(LocalDate.now()); clock.setTemperature(form.getTemperature()); clock.setSymptoms(form.getSymptoms()); try { clockService.save(clock); } catch (DuplicateKeyException e) { return Result.error("今日已打卡,请勿重复提交"); } // 触发异常检测 iClockHandler.triggerException(clock); return Result.success(); }

预警条件可以设计得非常简单:体温大于或等于37.3摄氏度就算异常;存在咳嗽、乏力等症状也算异常;行程码有风险地区就属于重点观察对象。这个规则如果写成一堆if-else,后期想改条件很麻烦。这里我建议做一个简单的策略模式或者规则表,把预警阈值放进数据库规则表里,用后台管理界面就能改。

public void triggerException(StudentClock clock) { // 查询规则表里的阈值 Rule rule = ruleService.getByName("temperature_abnormal"); if (clock.getTemperature() >= new BigDecimal(rule.getThreshold())) { // 生成一条异常记录,状态为“待处置” anomalyService.create(clock, "体温异常"); // 推送通知给该生所属辅导员 notifyService.pushToCounselor(clock.getStudentId(), "体温异常提醒"); } }

异常处置这块的逻辑类似工单系统,辅导员可以看到自己学院或班级的异常列表,点击处理时写几句处置意见,填下状态字段,校医角色可以查看完整链路并结案。这一整块功能做完,论文的系统功能设计章节就有东西写了。

3.3 前端联调与难点解决日志

前端用Vue 3加Element Plus,管理界面通常包括一个Layout布局:左边是侧边栏菜单,右边是内容区域。页面按角色动态渲染菜单项,学生看到的是“每日打卡”“我的记录”“通知公告”,管理员看到的是“学生管理”“打卡统计”“异常处置”等。

开发时最常遇到的问题就是跨域。前端开发服务器跑在5173端口,后端在8080端口,前端直接请求后端接口会报CORS错误。解决方案有两种:一是在后端加全局CORS配置,二是在前端配置开发代理。我推荐两个都了解,但实际用第二种。在vite.config.js里这样写:

export default defineConfig({ server: { port: 5173, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, '') } } } })

这样前端请求/api/login会被代理到http://localhost:8080/login,既隐藏了后端地址,又绕开了跨域。部署到服务器时再用Nginx做反向代理,同一套逻辑。这个点前后端联调与生产部署用的是一套思路,答辩时能完整说清楚,是加分项。

axios封装也养成好习惯,用一个请求拦截器统一在Headers里加Token,用响应拦截器统一处理401跳转登录页、后端业务码非0时自动弹出错误消息。不然每个页面都写一段重复的请求处理,代码量膨胀且细节维护痛苦。核心代码如下:

import axios from 'axios' import router from '@/router' const request = axios.create({ baseURL: '/api', timeout: 10000 }) request.interceptors.request.use(config => { const token = sessionStorage.getItem('token') if (token) config.headers.Authorization = token return config }) request.interceptors.response.use( res => { if (res.data.code !== 200) { ElMessage.error(res.data.message) return Promise.reject(res.data) } return res.data }, err => { if (err.response.status === 401) router.push('/login') return Promise.reject(err) } )

小提示一个:BASE_URL我建议直接改成用相对路径而不是写死IP,因为本地开发走代理,上线后也走反代,写死IP会导致环境切换时改一堆代码。

4. 部署上线干货与常见报错排查实录

这章是纯经验局。毕设做完才是第一步,能跑起来、能展示、能应对答辩老师的追问才是最终目的。

4.1 从Jar包到服务器:一键启动全流程

我推荐把后端打包成Jar包部署在服务器上,前端打包成静态文件交给Nginx托管。这个方案的好处是轻量、易解释、成本低。

打包命令如下:

mvn clean package -DskipTests

打出来的Jar在target/目录下。服务器上只要装了JDK,执行:

java -jar health-system.jar --spring.profiles.active=prod

就能启动。如果想后台运行不占用终端,用nohup或者直接写一个开机自启的systemd服务。生产环境我建议用systemd,出问题后看日志非常方便。简单写一个health.service放/etc/systemd/system/下:

[Unit] Description=Health Manager System After=network.target [Service] User=root ExecStart=/usr/bin/java -jar /opt/app/health-system.jar SuccessExitStatus=143 Restart=always RestartSec=10 [Install] WantedBy=multi-user.target

注意配置了Restart=always,这样服务挂了会自动拉起,展示给老师看至少是有运维思维。

前端打包命令很简单:

npm run build

打包完的dist目录移动到服务器的Nginx静态目录,比如/usr/share/nginx/html,再配置一个反向代理把/api请求转到8080端口。

Nginx配置最简版长这样:

server { listen 80; server_name localhost; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /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; } }

这里有一个很扎心的坑:Vue是单页应用,前端路由用history模式时,刷新某个子页面Nginx会返回404,所以try_files那行必须加上,让所有请求回退到index.html。如果没有这行,刷新学生管理页面直接白屏,答辩现场很尴尬。

4.2 常见报错速查表与排查思路

我把做这类项目最常见的报错整理成一个表,大家可以直接对照查。

报错现象根本原因解决办法
启动报Access denied for user数据库账号密码或host不对检查application.yml数据源配置
启动报Port 8080 was already in use端口被占用Linux用netstat -tunlp | grep 8080查PID后kill,或者改端口
控制台执行SQL后报Unknown column 'deleted'逻辑删除字段未建用户表和打卡表都加deleted字段,类型tinyint
前端请求接口报404proxy代理路径不一致或后端context-path多配了检查Vite里rewrite与后端接口路径是否对齐
前端请求接口报401Token过期或未传检查登录后sessionStorage是否存了token,检查拦截器Header名是否一致
后端返回的Date字段变成数组未配Jackson时间格式在application.yml配置spring.jackson.date-format和time-zone
中文乱码数据库连接未指定utf8或表编码不对URL加useUnicode=true&characterEncoding=utf8,建库时用utf8mb4
上传文件到服务器后报file size exceedSpring默认单文件1MB限制在yml里配spring.servlet.multipart.max-file-size: 20MB

其中前端请求接口报404这个坑我特别想多说两句。很多人本地联调得好好的,一旦按我的建议把baseURL换成相对路径/api,Vite的rewrite如果写错了,就会变成请求http://localhost:5173/login,而不是反代到后端,控制台里能看到请求地址就是不对。排查顺序优先看Network面板里的完整请求URL,再对照proxy配置的target和rewrite规则。

4.3 系统性能与并发量补全要点

疫情信息管理系统在校园场景下有集中打卡的流量特征。早晨上课前那一小时内,几千学生同时提交打卡。不做任何处理,数据库每秒可能被打进上百个请求。这里不用生产级分布式架构,但能做好几件事,就足以在答辩时从容应对。

第一,Redis做缓存。首次启动时把学院、班级等基础字典数据全量加载进Redis,后续读取就不用反复连MySQL。打卡结果也先写Redis的Set集合,通过判断Set中是否有今天的日期来快速判定是否重复打卡,只有首次打卡才落库。

Boolean first = redisTemplate.opsForSet().isMember("clock:" + studentId, LocalDate.now().toString()); if (Boolean.TRUE.equals(first)) { return Result.error("今日已打卡"); } // 落库后加入集合 redisTemplate.opsForSet().add("clock:" + studentId, LocalDate.now().toString()); redisTemplate.expire("clock:" + studentId, 2, TimeUnit.DAYS);

第二,写接口用异步线程池处理通知推送。打卡成功后要发异常通知到辅导员,这个操作如果占据请求线程,响应时间会明显变长。用Spring的@Async注解配合一下线程池,让主事务只管保存记录,通知推送异步执行。

第三,统计页面使用预聚合表。统计每天填报率时,如果实时去count一年的打卡记录,SQL查询压力大。我通常在每天凌晨跑一个定时任务,把前一天的打卡汇总信息生成一行统计记录,白天前端页面查询直接查汇总表,速度飞快。这就是毕设论文“数据统计模块设计”章节里最有说服力的设计思路。

5. 项目扩展与答辩加分思路

这套系统做完主干功能后,如果时间允许,可以从下面三个方向扩展,都能在简历上写一笔,答辩也更有的聊。

5.1 接入消息推送:从站内信到实时通知

目前系统通知只是在数据库里存一条记录,学生进入系统才能看到。换个思路,如果接入企业微信机器人或者钉钉机器人,异常预警时直接用Webhook推送消息给对应管理员。开发量不大,相当于是对一个HTTP接口,但产生的体验差异很直观,而且用的是真实互联网公司常用的方案。

将来能表现更好的是利用消息队列。集中打卡时段大量学生提交数据,异常信息要转发给各学院管理员,如果把通知逻辑放进请求线程里做,数据库压力极大。引入一个简单的内存队列或者RabbitMQ就能把数据削峰填谷,稳定地消费并推送。对毕设来说,能把“为什么要引入消息队列”讲清楚,面试官和答辩老师都会觉得思路很清晰。

5.2 数据看板与可视化增强

光有报表表格太单调,多数管理系统都在向大屏看板靠拢。前端适配一套ECharts可视化大屏:校园总体填报趋势折线图、各学院填报率热力图、异常类型分布扇形图、今日健康状态总览卡片。

这套大屏做完,答辩演示环节非常出效果——一打开页面先放一张色彩丰富的数据大屏,老师的第一印象就稳了。后端的统计接口无非是返回几个聚合后的List,前端用ECharts接收后渲染,难度并不高,但展示效果远超普通的表格页面。

5.3 安全管理与日志留痕

生产级考虑,所有敏感操作建议都记录操作日志。谁在什么时间修改了学生的打卡记录、谁把异常状态从待处置改成了已结案,这些都必须有迹可循。我一般用AOP注解方式实现,在需要记录的方法上打一个@OperationLog("修改异常状态"),切面自动把操作人、操作内容、IP、时间全部落库。

论文可以单独开一节讲系统安全性设计,内容包括:密码加密存储、基于JWT的无状态认证、越权操作拦截、操作日志留痕。这四点加在一起,就算答辩老师问“如果现在学校需要正式部署这套系统,你觉得还有什么安全隐患?”也能理直气壮地回答核心部分已经考虑了。

6. 一些代码设计上的私人心得

写到最后想掏点个人经验。

我接手过不少学生毕业设计和初级开发者的代码,这个管理系统最大的问题通常不是“功能没实现”,而是“代码全堆在一个Service里”。比如打卡接口里同时写了校验用户、写打卡表、发异常通知、更新统计表。第一次写觉得挺爽,但答辩时一旦老师追问“你发通知失败会影响主流程吗?”,就答不上来了。

我实际开发中哪怕是小项目,也习惯按Controller -> Service -> Mapper的标准三层分层,另外抽一个处理器专门处理“打卡成功后的一系列动作”。核心逻辑与副作用解耦之后,后期维护和新功能扩展都非常舒服。这个习惯延续到生产项目里,能省掉很多重构成本。

还有一个日常开发中容易被忽略的点是返回值统一。我规定所有接口正常返回code=200,业务异常返回自定义错误码,系统异常返回code=500。前端统一拦截器看到这些码就知道怎么处理。这个约定我在团队的联调文档里写了三次,因为它真的能避免大量无意义的沟通。前端与后端各自独立开发时,只要把返回值结构定义为先遣约定,联调时就能完全并行。

最后聊一句关于毕设心态的话。软件这行有个特点:会做的人很容易低估自己做的项目,总觉得技术太简单、不够高大上。但实际上,一个把登录认证、角色权限、核心业务流转、数据统计、异常处理、日志、部署全流程都跑通的系统,已经覆盖了一个完整后端工程师日常工作中80%的常见场景。更重要的是,你能把每个设计点后面的为什么讲清楚——为什么选JWT、为什么加唯一索引、为什么用异步通知——这比堆一个自己都讲不明白的微服务架构,在答辩和面试中更打动人。

毕竟,写代码是一时的,而做事的方法和解决问题的思路,是一直跟着你的。

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

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

立即咨询