说实话,一开始拿到这个题目的时候,我心里是有点轻视的。不就是做一个填表、列表、审批的管理系统吗?等真正把需求整理清楚,才发现健康上报、异常预警、权限控制、数据统计之间是一环扣一环的,任何一环考虑不周,后面的联调就会翻车。我选择用 Java + SpringBoot + VUE 这套前后端分离技术栈来开发一个校园疫情防控平台,前后花了两个多月,最后顺利完成了演示和答辩。这篇内容就按我实际开发的顺序来写,适合正在做同类毕业设计、或者想练手前后端分离项目的同学,重点会放在那些教程不会细说、但实际特别容易踩坑的地方。
1. 需求拆解:不要把“疫情防控”做成普通填表系统
1.1 角色与权限:不是所有功能都该给所有人
我见过很多同类项目的设计方案,上来就是一个单表万物,所有用户都能看到所有页面。这样做出来的东西,演示勉强能看,但答辩的时候一问“如何防止学生看到别人的健康信息”,立刻就没法圆场。
做这个系统,第一件事不是写代码,而是把用户分成三类:学生、辅导员、系统管理员。三类人看到的页面和能做的操作完全不同。
| 角色 | 核心功能 | 说明 |
|---|---|---|
| 学生 | 每日健康上报、查看个人历史记录、提交出入校申请、查看通知 | 只操作与自己相关的数据 |
| 辅导员 | 查看管辖范围内的上报情况、处理异常预警、审批学生出入校申请 | 数据范围限制在班级或院系 |
| 系统管理员 | 用户管理、角色权限配置、全校数据总览、系统日志 | 管理维度,不参与日常业务 |
权限划分清楚之后,后端接口设计就有了依据。每个接口都要问一句:当前登录用户是谁,他有没有权限操作这份数据?学生只能查自己的,辅导员只能查自己管辖学生的,管理员才能看全局。这个思路看着简单,但实际开发中很容易被忽略。很多人的系统之所以答辩被问倒,就是权限判断写得太随意。
1.2 健康数据上报和异常预警的完整闭环
健康上报不是学生填一张表、数据入库就结束了。如果只做到这一步,那系统就缺少“业务价值”,答辩时也很难说出亮点。我最终梳理出来的核心闭环是这样的:
- 学生登录后进入健康上报页,填写当日体温、身体状况、是否在校、所在校区等必填项。
- 后端校验同一名学生当天只能上报一次,不能重复提交。
- 系统根据预设规则自动判断是否异常,例如体温高于 37.3℃ 或勾选了异常症状。
- 异常记录自动进入辅导员的待复核列表,同时生成一个异常事件。
- 辅导员对异常事件进行处理,填写处理意见,必要时把学生标记为“重点关注对象”。
- 被标记的重点关注对象,之后每天的连续上报记录会单独汇总给辅导员,直到解除关注。
这个闭环的好处是:数据不再是一潭死水。学生上报的数据会驱动后续的流程,异常事件有状态流转,辅导员有明确的工作台,整个过程是可追溯的。这个设计让我在答辩时讲业务逻辑非常顺畅,因为面试官或者评委问“这个异常之后怎么处理”,可以直接把状态流转过程讲给他听。
1.3 功能优先级:哪些功能可以果断砍掉
毕业设计最怕的一件事,就是需求无限膨胀。一开始我也想加人脸识别打卡、高德地图定位、微信消息推送,甚至还想做数据大屏。但冷静下来算一下时间,发现根本做不完,而且很容易把自己拖进各种第三方 SDK 的坑里。最后这些功能我全部砍掉了,只保留了最核心的闭环。
必须做的功能:用户登录认证、角色权限控制、健康上报、上报历史查询、异常事件处理、出入校申请审批、基础数据统计、用户管理。
可以砍掉的扩展功能:实时定位打卡、人脸识别、消息推送、自动问候机器人、复杂报表大屏。如果时间充裕,这些可以作为“后期展望”放在论文里,但不建议作为必做功能,否则很容易翻车。
需求边界一定要写到论文里,这样反而会让评委觉得你是一个有大局观的人,知道什么该做什么不该做。
2. 技术选型:Java+SpringBoot+VUE这套组合到底怎么敲定
2.1 后端:SpringBoot + MyBatis-Plus 是省心组合
后端框架我选的是 SpringBoot,这块基本没什么犹豫的。SpringBoot 自动配置能力强,内嵌 Tomcat,打 jar 包就能直接跑,对于毕业设计来说非常省事。相比传统的 Spring MVC 项目,省掉了大量 XML 配置,开发速度明显更快。版本我用的 SpringBoot 2.7,搭配 JDK 1.8,这是兼容性和资料丰富度都比较稳妥的组合。
持久层没有选择 Spring Data JPA,而是用了 MyBatis-Plus。原因很简单:单表 CRUD 几乎不用写 SQL,BaseMapper 已经把基本的增删改查封装好了;分页插件通过一个 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 开发环境统一版本,避免诡异问题
这类系统开发时最让人抓狂的问题,往往不是代码本身,而是环境不一致。我这里把自己使用的版本整理出来,供你参考。
| 组件 | 版本 | 备注 |
|---|---|---|
| JDK | 1.8 | 稳定,兼容性最好 |
| Maven | 3.8 | 依赖管理 |
| MySQL | 5.7 / 8.0 | 5.7 足够用 |
| Node.js | 14 / 16 | 太新版偶有不兼容问题 |
| Vue | 2.7 | 搭配 Element UI |
| Element UI | 2.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`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 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`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康上报表';unique 索引uk_user_date是防止重复上报的关键。这个约束会在数据库层面保证同一个学生同一天最多只能有一条上报记录。很多人只在代码里判断“今天是否已经上报”,但并发情况下代码判断并不可靠,必须靠数据库唯一索引兜底,这一点在第 6 章还会单独说。
3.2 统一返回体与全局异常处理,让前后端对接不再扯皮
前后端分离项目中,接口返回格式如果不统一,前端每次都要小心翼翼地猜后端返回了什么结构,这个项目就没法做了。我定义了统一的返回类 Result,所有接口返回结构保持一致。
public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("操作成功"); result.setData(data); return result; } public static <T> Result<T> error(int code, String message) { Result<T> 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 :model="form" :rules="rules" ref="reportForm" label-width="100px"> <el-form-item label="上报日期" prop="reportDate"> <el-date-picker v-model="form.reportDate" type="date" value-format="yyyy-MM-dd" placeholder="选择日期"> </el-date-picker> </el-form-item> <el-form-item label="体温" prop="temperature"> <el-input-number v-model="form.temperature" :precision="1" :min="34" :max="42"></el-input-number> </el-form-item> <el-form-item label="身体状况" prop="healthStatus"> <el-radio-group v-model="form.healthStatus"> <el-radio :label="1">正常</el-radio> <el-radio :label="2">异常</el-radio> </el-radio-group> </el-form-item> <el-form-item v-if="form.healthStatus === 2" label="症状描述" prop="symptomDesc"> <el-input type="textarea" v-model="form.symptomDesc"></el-input> </el-form-item> <el-form-item> <el-button type="primary" @click="handleSubmit">提交上报</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 打包后刷新页面 404:history 路由模式处理
前端开发时用 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 连接串加上serverTimezone=Asia/Shanghai,SpringBoot 配置文件中设置jackson.time-zone=GMT+8,数据库使用本地时间。
spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8日期字段如果只存日期,比如 health_report 的 report_date,用的是date类型,一般不会有时区问题。但如果存具体时间,比如 create_time,就一定要显式配置时区。这个细节说出来很小,但不提前配置,后期净化数据的时候会非常痛苦。
6. 毕业设计答辩最容易被追问的几个问题
6.1 JWT 和 Session 有什么区别?
毕业设计评委大概率会问认证方式。JWT 结构分为 Header、Payload、Signature 三部分,服务端签名后把 token 返回客户端,之后每次请求客户端带上 token,服务端验签通过就认为请求是可信的。Session 则是服务端把登录状态存在内存中,客户端保存一个 sessionId。
| 对比项 | JWT | Session |
|---|---|---|
| 存储位置 | 服务端不保存,客户端保存 | 服务端保存 |
| 扩展性 | 适合分布式系统 | 多节点共享麻烦 |
| 强制下线 | 需要额外手段 | 删除 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 拦截器和角色判断实现,防止普通学生调用辅导员接口;前端对用户输入做长度和格式校验,后端同样做参数校验,避免恶意脚本入库。
如果项目里涉及文件上传,还要额外注意上传文件类型过滤和大小限制,否则容易被传恶意文件。我在这个系统里没有开放文件上传功能,这也在需求设计阶段避掉了一个安全隐患。你如果增加了这个功能,一定要补好这两道防线。
写在最后:做这个项目的真实体会
如果只能分享一点经验,我想说:这类系统真正难的不是某一项技术,而是把散乱的需求串成一条完整业务链。在做健康上报、异常处理、出入校申请这三个模块时,我反复体会到一个词:“状态流转”。数据不是从表单到数据库就结束了,它必须继续流动,让审批、预警、统计这些环节动起来,系统才真正有“平台”的感觉。最后再给一个实际建议:不要在最后三天才联调,提前留出一周专门做前后端联调和部署测试,否则演示现场崩盘的概率会非常大。做这个项目虽然累,但跑通完整流程的一刻,你会觉得前面所有踩过的坑都值了。