资讯动态

SpringBoot+Vue前后端分离开发校园疫情防控平台:核心设计与实践

发布时间:2026/10/15 7:17:18 来源:尧图企业网站定制
说实话一开始拿到这个题目的时候我心里是有点轻视的。不就是做一个填表、列表、审批的管理系统吗等真正把需求整理清楚才发现健康上报、异常预警、权限控制、数据统计之间是一环扣一环的任何一环考虑不周后面的联调就会翻车。我选择用 Java SpringBoot VUE 这套前后端分离技术栈来开发一个校园疫情防控平台前后花了两个多月最后顺利完成了演示和答辩。这篇内容就按我实际开发的顺序来写适合正在做同类毕业设计、或者想练手前后端分离项目的同学重点会放在那些教程不会细说、但实际特别容易踩坑的地方。1. 需求拆解不要把“疫情防控”做成普通填表系统1.1 角色与权限不是所有功能都该给所有人我见过很多同类项目的设计方案上来就是一个单表万物所有用户都能看到所有页面。这样做出来的东西演示勉强能看但答辩的时候一问“如何防止学生看到别人的健康信息”立刻就没法圆场。做这个系统第一件事不是写代码而是把用户分成三类学生、辅导员、系统管理员。三类人看到的页面和能做的操作完全不同。角色核心功能说明学生每日健康上报、查看个人历史记录、提交出入校申请、查看通知只操作与自己相关的数据辅导员查看管辖范围内的上报情况、处理异常预警、审批学生出入校申请数据范围限制在班级或院系系统管理员用户管理、角色权限配置、全校数据总览、系统日志管理维度不参与日常业务权限划分清楚之后后端接口设计就有了依据。每个接口都要问一句当前登录用户是谁他有没有权限操作这份数据学生只能查自己的辅导员只能查自己管辖学生的管理员才能看全局。这个思路看着简单但实际开发中很容易被忽略。很多人的系统之所以答辩被问倒就是权限判断写得太随意。1.2 健康数据上报和异常预警的完整闭环健康上报不是学生填一张表、数据入库就结束了。如果只做到这一步那系统就缺少“业务价值”答辩时也很难说出亮点。我最终梳理出来的核心闭环是这样的学生登录后进入健康上报页填写当日体温、身体状况、是否在校、所在校区等必填项。后端校验同一名学生当天只能上报一次不能重复提交。系统根据预设规则自动判断是否异常例如体温高于 37.3℃ 或勾选了异常症状。异常记录自动进入辅导员的待复核列表同时生成一个异常事件。辅导员对异常事件进行处理填写处理意见必要时把学生标记为“重点关注对象”。被标记的重点关注对象之后每天的连续上报记录会单独汇总给辅导员直到解除关注。这个闭环的好处是数据不再是一潭死水。学生上报的数据会驱动后续的流程异常事件有状态流转辅导员有明确的工作台整个过程是可追溯的。这个设计让我在答辩时讲业务逻辑非常顺畅因为面试官或者评委问“这个异常之后怎么处理”可以直接把状态流转过程讲给他听。1.3 功能优先级哪些功能可以果断砍掉毕业设计最怕的一件事就是需求无限膨胀。一开始我也想加人脸识别打卡、高德地图定位、微信消息推送甚至还想做数据大屏。但冷静下来算一下时间发现根本做不完而且很容易把自己拖进各种第三方 SDK 的坑里。最后这些功能我全部砍掉了只保留了最核心的闭环。必须做的功能用户登录认证、角色权限控制、健康上报、上报历史查询、异常事件处理、出入校申请审批、基础数据统计、用户管理。可以砍掉的扩展功能实时定位打卡、人脸识别、消息推送、自动问候机器人、复杂报表大屏。如果时间充裕这些可以作为“后期展望”放在论文里但不建议作为必做功能否则很容易翻车。需求边界一定要写到论文里这样反而会让评委觉得你是一个有大局观的人知道什么该做什么不该做。2. 技术选型JavaSpringBootVUE这套组合到底怎么敲定2.1 后端SpringBoot MyBatis-Plus 是省心组合后端框架我选的是 SpringBoot这块基本没什么犹豫的。SpringBoot 自动配置能力强内嵌 Tomcat打 jar 包就能直接跑对于毕业设计来说非常省事。相比传统的 Spring MVC 项目省掉了大量 XML 配置开发速度明显更快。版本我用的 SpringBoot 2.7搭配 JDK 1.8这是兼容性和资料丰富度都比较稳妥的组合。持久层没有选择 Spring Data JPA而是用了 MyBatis-Plus。原因很简单单表 CRUD 几乎不用写 SQLBaseMapper 已经把基本的增删改查封装好了分页插件通过一个 PaginationInnerInterceptor 就能搞定代码生成器还能根据表结构一键生成实体、Mapper、Service省下大量重复工作。当然MyBatis-Plus 的核心是 MyBatis复杂关联查询仍然可以自己写 XML不会被框架限制住。顺带说一句不建议在这类系统中引入 Redis、消息队列、Spring Cloud 这类重组件。如果是为了展示能力可以在个别场景点一下名字说“这里可以用 Redis 优化某某逻辑”但真去搞一套微服务反而会占用大量写业务的时间。2.2 前端Vue 2.7 Element UI资料最全的组合前端我选的是 Vue 2.7 搭配 Element UI。现在 Vue 3 已经很普及Element Plus 也比较成熟了所以如果你的毕业设计明确要求使用 Vue 3选 Vue 3 Element Plus 也完全可行。我选择 Vue 2.7 的主要原因是大部分现有博客、教程、答辩资料都是基于 Vue 2 写的遇到问题搜索答案的效率更高对于时间紧张的毕设来说这点非常重要。Element UI 提供的表格、表单、弹窗、分页、日期选择器基本覆盖了管理后台九成以上的需求。尤其要注意日期组件的 value-format 属性不要直接提交 Date 对象给后端否则很容易出现时间格式不一致的问题后面前后端联调时会非常头疼。2.3 认证方案JWT 无状态认证适合前后端分离前后端分离项目最常见的认证方案就是 JWT。用户登录成功后后端生成一个 token 返回给前端前端把 token 存到 localStorage 里之后每次请求都在请求头里带上 Authorization。后端通过拦截器校验 token从 token 中解析出用户 ID再把当前用户信息放到 ThreadLocal 中后面的业务代码随时可以拿到当前用户。JWT 的核心原理稍后第 6 章会详细讲这里先提一下密码存储。用户密码绝不可以用明文存储我使用的是 BCrypt 加密算法Spring Security 的 crypto 工具类里可以直接使用 BCryptPasswordEncoder每次校验密码时调用 matches 方法即可不需要额外引入整套 Spring Security。2.4 开发环境统一版本避免诡异问题这类系统开发时最让人抓狂的问题往往不是代码本身而是环境不一致。我这里把自己使用的版本整理出来供你参考。组件版本备注JDK1.8稳定兼容性最好Maven3.8依赖管理MySQL5.7 / 8.05.7 足够用Node.js14 / 16太新版偶有不兼容问题Vue2.7搭配 Element UIElement UI2.15.x组件库如果你是在某高校的机房或者统一实验环境下开发建议先把这些版本固定下来环境统一之后很多玄学问题直接消失。3. 后端核心落地从数据表设计到接口实现3.1 数据库表设计先想清楚关系和约束数据表设计是整个系统真正的地基。我设计的时候没有搞太多表但每一张表的关系和约束都考虑得很清楚。核心表大概有 5 张。表名用途关键说明user用户表存放学生、辅导员、管理员的登录信息包含角色字段health_report健康上报表每个人每天一条记录用唯一索引防止重复abnormal_event异常事件表记录体温异常或症状异常的处理流转access_apply出入校申请表学生提交申请辅导员审批dict_data字典数据表存校区、症状类型等公共选项以最核心的 user 表和 health_report 表为例建表语句大概是这样的CREATE TABLE user ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键ID, username varchar(50) NOT NULL COMMENT 登录账号, password varchar(100) NOT NULL COMMENT BCrypt加密后的密码, real_name varchar(50) NOT NULL COMMENT 姓名, role tinyint NOT NULL COMMENT 角色1学生 2辅导员 3管理员, dept_id bigint DEFAULT NULL COMMENT 所属院系/班级ID, status tinyint DEFAULT 1 COMMENT 状态1正常 0禁用, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;CREATE TABLE health_report ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键ID, user_id bigint NOT NULL COMMENT 学生用户ID, report_date date NOT NULL COMMENT 上报日期, temperature decimal(4,1) NOT NULL COMMENT 体温, health_status tinyint DEFAULT 1 COMMENT 身体状况1正常 2异常, symptom_desc varchar(255) DEFAULT NULL COMMENT 异常症状描述, is_on_campus tinyint DEFAULT 1 COMMENT 是否在校1是 0否, campus_area varchar(100) DEFAULT NULL COMMENT 所在校区, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_user_date (user_id, report_date), KEY idx_report_date (report_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT健康上报表;unique 索引uk_user_date是防止重复上报的关键。这个约束会在数据库层面保证同一个学生同一天最多只能有一条上报记录。很多人只在代码里判断“今天是否已经上报”但并发情况下代码判断并不可靠必须靠数据库唯一索引兜底这一点在第 6 章还会单独说。3.2 统一返回体与全局异常处理让前后端对接不再扯皮前后端分离项目中接口返回格式如果不统一前端每次都要小心翼翼地猜后端返回了什么结构这个项目就没法做了。我定义了统一的返回类 Result所有接口返回结构保持一致。public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(操作成功); result.setData(data); return result; } public static T ResultT error(int code, String message) { ResultT result new Result(); result.setCode(code); result.setMessage(message); return result; } }与此同时配合一个全局异常处理器把业务异常、参数校验异常、未知异常拦截住统一转换成 Result 格式输出。这样前端请求拦截器只需要判断 code 是不是 200如果是 401 就跳转登录页其他情况弹错误提示整个过程非常清爽。3.3 健康上报接口并发安全和幂等处理健康上报的 Service 逻辑不难难在怎么防止重复提交。假如用户手抖点了两次提交或者网络慢导致连续请求后端如果收两条数据系统数据就脏了。我最后采用了“数据库唯一索引 捕获 DuplicateKeyException”的方案。Override public void submitHealthReport(HealthReportDTO dto) { HealthReport report new HealthReport(); report.setUserId(LoginContext.getCurrentUserId()); report.setReportDate(dto.getReportDate()); report.setTemperature(dto.getTemperature()); report.setHealthStatus(dto.getHealthStatus()); report.setSymptomDesc(dto.getSymptomDesc()); report.setIsOnCampus(dto.getIsOnCampus()); report.setCampusArea(dto.getCampusArea()); try { healthReportMapper.insert(report); } catch (DuplicateKeyException e) { throw new BusinessException(今日已上报请勿重复提交); } }代码先插入如果触发了唯一索引冲突数据库会抛出异常我们再把这个异常转换成友好的业务提示。这里并不是先 select 再判断是否存在因为两个请求同时到达时可能都查不到记录然后同时插入最终只有唯一索引能拦住重复数据。这个设计在答辩时是一个很好的加分项体现了对并发场景的思考。3.4 JWT 拦截器与接口权限控制后端权限控制我用的是最简单直接的方式一个 HandlerInterceptor在请求进来时校验 token再结合注解或者角色判断控制接口权限。核心逻辑如下public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token ! null token.startsWith(Bearer )) { token token.substring(7); } // 解析token失败则返回401 Long userId JwtUtil.parseToken(token); if (userId null) { response.setStatus(401); return false; } // 把当前用户放入ThreadLocal LoginContext.setCurrentUserId(userId); return true; } Override public void afterCompletion(...) { LoginContext.clear(); } }不需要把整个 Spring Security 引进来毕竟毕业设计业务量不大手写一个拦截器反而更容易讲清楚原理。具体接口的数据范围控制就在 Service 层根据当前登录用户的角色和数据权限去查询。比如辅导员查询学生列表时SQL 里带上 dept_id 条件保证只能看到自己管辖范围内的数据。这个逻辑一定要写清楚因为评委极大概率会问“如何防止越权查看他人数据”4. 前端Vue实现从页面骨架到接口封装4.1 项目目录结构提前规划好后面不迷路前端项目用 Vue CLI 创建目录结构保持了比较规矩的划分。一个前后端分离项目前端的核心目录大概是这样的src/ api/ // 按业务模块封装接口请求 health.js auth.js user.js access.js router/ // 路由配置 index.js store/ // Vuex 状态管理 modules/ user.js views/ // 页面组件 login/index.vue student/healthReport.vue student/myHistory.vue counselor/abnormalCheck.vue admin/userManage.vue utils/ // 请求封装、工具函数 request.js App.vue main.js我强烈建议后端模块和前端 api 目录保持一致比如健康上报模块对应 api/health.js页面调用时只从 api 模块里导入函数不直接在页面里写 axios 请求。这样后期接口地址变更时只需要改一个文件不会全局搜索到处替换。4.2 Axios 请求封装与路由守卫请求封装是前端工程化的第一道门槛。我在 utils/request.js 里创建了一个 axios 实例统一设置 baseURL请求拦截器自动加上 token响应拦截器统一处理错误状态。import axios from 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 }) service.interceptors.response.use( response { const res response.data if (res.code 200) { return res } else if (res.code 401) { localStorage.removeItem(token) window.location.href /login } else { return Promise.reject(new Error(res.message)) } }, error { return Promise.reject(error) } ) export default service路由守卫负责控制页面访问权限。如果用户没有登录哪些路由都不能进统一跳转到登录页。router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.path /login) { next() } else if (!token) { next(/login) } else { next() } })这里有个细节管理员、辅导员、学生看到的菜单是不同的。我做法是在登录后把用户的 role 存到 Vuex然后在侧边栏菜单上使用 v-if 根据角色过滤。后端接口有权限控制前端菜单只是为了体验真正安全还是靠后端兜底这一点答辩时要说得清楚。4.3 健康上报页面的实现逻辑健康上报页面是整个系统里最核心的表单页。我用 Element UI 的 el-form 实现字段包括日期、体温、身体状况、是否在校、症状描述。需要特别注意的是日期选择器要设置 value-format 为yyyy-MM-dd否则提交给后端的是一个 Date 对象JSON 序列化之后会出现时间格式问题。模板核心代码如下el-form :modelform :rulesrules refreportForm label-width100px el-form-item label上报日期 propreportDate el-date-picker v-modelform.reportDate typedate value-formatyyyy-MM-dd placeholder选择日期 /el-date-picker /el-form-item el-form-item label体温 proptemperature el-input-number v-modelform.temperature :precision1 :min34 :max42/el-input-number /el-form-item el-form-item label身体状况 prophealthStatus el-radio-group v-modelform.healthStatus el-radio :label1正常/el-radio el-radio :label2异常/el-radio /el-radio-group /el-form-item el-form-item v-ifform.healthStatus 2 label症状描述 propsymptomDesc el-input typetextarea v-modelform.symptomDesc/el-input /el-form-item el-form-item el-button typeprimary clickhandleSubmit提交上报/el-button /el-form-item /el-form提交方法里先调用 validate 校验表单通过后调用 api/health.js 里封装的 submitHealthReport 方法。成功后弹出成功提示然后重置表单。这里最常见的坑是表单校验规则里 numeric 类型和 el-input-number 的 value 类型不一致导致校验一直不通过解决方法是明确设置校验字段的类型。4.4 前端实现中最容易踩的细节第一个细节是 Element UI 日期组件默认值。如果设置:default-value为当天只是打开面板时默认显示并不是实际赋值。需要使用new Date()初始化 form.reportDate才能在用户不选的情况下直接提交当天日期。第二个细节是动态路由刷新后 404。有的项目会把菜单动态生成但刷新后路由丢失。为了避免这个坑我选择了静态路由配置所有角色共用一个路由表页面内用角色字段控制功能按钮和菜单显隐。虽然少了一点“动态化”的噱头但稳定得多。第三个细节是列表页的 loading 状态。如果在请求数据期间不显示 loading用户反复点击查询就会看到表格闪烁体验很差。所以每个 el-table 都配置了 v-loading在数据请求开始时置 true请求结束置 false。这些都是细节但写论文和答辩截图时很加分。5. 前后端联调与一体化部署这些坑几乎每届毕设都会踩5.1 开发环境跨域问题优先用后端代理前后端分离开发时前端运行在 8080 端口后端运行在 8081 端口直接请求必然遇到跨域。我在开发环境使用的是 Vue CLI 的 devServer.proxy 插件让前端把/api开头的请求代理到后端地址这样浏览器端感知不到跨域。// vue.config.js module.exports { devServer: { port: 8080, proxy: { /api: { target: http://localhost:8081, changeOrigin: true } } } }这样做比在后端直接开启 CORS 更干净因为所有接口请求在浏览器里看都是同源。如果后端有 Gateway 之类的组件也一样可以把代理链路做得更长。这里要注意代理只是开发环境用生产环境还需要在 nginx 里做同样的反向代理。5.2 打包后刷新页面 404history 路由模式处理前端开发时用 BrowserRouter 挺舒服但是打包之后部署到 nginx直接访问比如/student/history这个路径nginx 找不到对应的物理文件会返回 404。原因是前端路由是虚拟路径nginx 默认按真实文件路径找。解决办法是配置 nginx 的 try_files让找不到的静态资源请求回退到 index.html。server { listen 80; server_name localhost; root /var/www/dist; index index.html; location / { try_files $uri $uri/ /index.html; } }这段配置几乎是 Vue 项目部署的标配。如果没有它项目部署后点刷新按钮就白屏这是在答辩演示时最尴尬的场景。5.3 生产环境接口如何配置生产环境我不建议使用相对路径/api之外的方式。前端打包后的 dist 目录交给 nginx 托管后端 jar 包单独跑在一个端口上。nginx 把/api/前缀的请求反向代理到后端服务。location /api/ { proxy_pass http://127.0.0.1:8081; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }后端项目的 context-path 建议设置为/api这样所有接口路径天然带统一前缀前端 baseURL 也就固定了。整个部署流程就是前端打包、后端打包、上传服务器、启动后端、配置 nginx五步走。对于毕业设计来说一台小服务器加一个域名或者直接用 IP 端口访问就足够了。5.4 时区问题时间差 8 小时很容易被忽略数据库连接串、Jackson 序列化、MySQL 时区三个地方只要有一个不一致就会出现前端显示时间和后端存储时间相差 8 小时的情况。这是我当时卡了一个晚上的问题。后来统一做了三件事JDBC 连接串加上serverTimezoneAsia/ShanghaiSpringBoot 配置文件中设置jackson.time-zoneGMT8数据库使用本地时间。spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8日期字段如果只存日期比如 health_report 的 report_date用的是date类型一般不会有时区问题。但如果存具体时间比如 create_time就一定要显式配置时区。这个细节说出来很小但不提前配置后期净化数据的时候会非常痛苦。6. 毕业设计答辩最容易被追问的几个问题6.1 JWT 和 Session 有什么区别毕业设计评委大概率会问认证方式。JWT 结构分为 Header、Payload、Signature 三部分服务端签名后把 token 返回客户端之后每次请求客户端带上 token服务端验签通过就认为请求是可信的。Session 则是服务端把登录状态存在内存中客户端保存一个 sessionId。对比项JWTSession存储位置服务端不保存客户端保存服务端保存扩展性适合分布式系统多节点共享麻烦强制下线需要额外手段删除 session 即可CSRF 风险依赖 Authorization 头相对低依赖 Cookie需额外防护我们系统选择 JWT是因为前后端完全分离后端是无状态服务部署多个实例时不需要额外做 session 共享。同时为了弥补 JWT 不好强制下线的缺点客户端登录时保存 token后面如果需要做“踢人下线”可以再加一层 Redis 黑名单把失效的 token 列入黑名单。6.2 同一用户并发提交两次健康上报怎么办第一次被问到这个问题时我没有答好因为当时我还在用“先查询再插入”的写法。后来我改成数据库唯一索引兜底的设计就很好回答了无论前端怎么重复点击无论后端收到多少个并发请求数据库层面的唯一索引(user_id, report_date)会保证只有一条记录能插入成功其他请求会触发唯一索引冲突被全局异常处理器拦截下来。这个问题可以引出“先查再插”的缺陷两个请求同时查询发现都不存在然后同时插入结果数据就重复了。虽然概率很小但在分布式场景下完全可能发生。毕业设计能主动认识到这一点说明你对数据一致性是有概念的。6.3 Redis 在这个项目里是必要的吗我的项目里没有引入 Redis 作为核心组件但答辩时完全可以主动提一句哪些场景更适合用 Redis。比如登录验证码、token 黑名单、高频访问的字典数据缓存都可以用 Redis 优化。对于当前系统的访问量来说数据库直接扛得住不引入 Redis 是为了降低系统复杂度。如果在项目中已经使用了 Redis也请想清楚不要为了用而用。比如把健康上报的记录先写 Redis 再异步同步到 MySQL听起来高大上但一旦掉电丢数据后果反而更严重。答辩时如果主动说出“我评估过当前阶段不需要 Redis”评委通常会觉得你有取舍能力。6.4 系统安全怎么做安全这个问题也是评委关心的重点。我的回答分四个方面密码安全、SQL 注入、接口越权、XSS 过滤。密码使用 BCrypt 加密存储任何人都看不到明文持久层使用 MyBatis参数通过#{}预编译方式传递从机制上避免 SQL 注入接口权限通过 JWT 拦截器和角色判断实现防止普通学生调用辅导员接口前端对用户输入做长度和格式校验后端同样做参数校验避免恶意脚本入库。如果项目里涉及文件上传还要额外注意上传文件类型过滤和大小限制否则容易被传恶意文件。我在这个系统里没有开放文件上传功能这也在需求设计阶段避掉了一个安全隐患。你如果增加了这个功能一定要补好这两道防线。写在最后做这个项目的真实体会如果只能分享一点经验我想说这类系统真正难的不是某一项技术而是把散乱的需求串成一条完整业务链。在做健康上报、异常处理、出入校申请这三个模块时我反复体会到一个词“状态流转”。数据不是从表单到数据库就结束了它必须继续流动让审批、预警、统计这些环节动起来系统才真正有“平台”的感觉。最后再给一个实际建议不要在最后三天才联调提前留出一周专门做前后端联调和部署测试否则演示现场崩盘的概率会非常大。做这个项目虽然累但跑通完整流程的一刻你会觉得前面所有踩过的坑都值了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑