Spring Boot+Vue前后端分离实战:防疫信息管理系统设计与开发全解析
写这个项目的时候我的第一反应是又是一个管理系统。没错Spring Boot Vue 的“XX信息管理系统”在毕业设计里几乎是标准答案的一部分。但真要把这套“防疫信息管理系统”做成一个能打、能答辩、能跟面试官聊上十分钟的项目关键不是把 CRUD 写出来而是把整个链路的技术点吃透从数据库建模到权限控制从接口封装到部署上线每一步都能讲清楚“为什么这么做”。这篇实战笔记就是冲着这个目标来的。我会用一套完整的业务模块做例子把后端接口开发、前端页面实现、联调部署的完整流程拆给你看包括那些踩过的坑和答辩时容易被追问的点。适合正在准备毕业设计的人也适合想系统性上手前后端分离项目的同学。这不是什么高深的东西但把细节做到位它就是一个能拿得出手的完整作品。1. 毕业设计选型为什么这套系统能成为模板级项目1.1 项目背景与需求定位先说清楚这个系统到底解决什么问题。站在毕业设计的视角防疫信息管理系统本质上是一个“多角色、多模块、数据闭环”的信息管理平台。典型的使用场景是企业、学校或社区内部普通人员需要每天填报健康信息、查看公告和通知管理员需要维护人员名单、审核登记数据、管理物资台账、生成统计报表。这里面的核心不是“防疫”两个字而是“信息管理”四个字——所有功能都落在数据的增删改查上。绝大多数毕业设计的评分逻辑非常一致功能完整度、业务流程闭环、界面友好程度、答辩时对技术点的理解深度。你不需要做出一套世界级的东西但需要在一个完整业务场景里把主流技术栈用对、用透。防疫信息管理系统的业务边界足够清晰模块划分自然数据表之间关系明确非常适合展示前后端分离开发的全部技术要点。有一个很现实的好处是这个主题自带“场景感”。答辩时老师问“你这个系统有什么实际意义”你可以很自然地回答减少手工登记、统一数据入口、告警信息实时推送、统计报表自动生成。这就比做一个泛泛的“图书管理系统”更有说服力。1.2 完全前后端分离架构的优势这个项目我推荐直接做成彻底的前后端分离。前端用 Vue 写单页应用后端用 Spring Boot 提供纯接口两者之间通过 JSON 交互互不干扰。前后端分离带来的第一个好处是开发效率。前端同学和后端同学或者你自己分别扮演两个角色可以并行开工只要提前把接口契约定好互相喂假数据也能同步推进。第二个好处是职责清晰。后端只关心业务逻辑和数据前端只关心页面渲染和交互出了问题排查范围小很多。第三个好处是部署灵活这在后面演示环节会让你省很多事。举个实际的例子用户登录后看到一个“今日健康登记”页面前端只负责把这个页面画出来然后向/api/health/record/save发送一个 POST 请求传temperature、healthStatus这些字段。后端校验参数、写库、返回Result.success(data)。前端拿到结果后弹出提示“登记成功”。这个过程里两端完全靠接口文档沟通这就是分离架构的意义。1.3 为什么这是企业级框架的“低配版”很多同学担心 Spring Boot Vue 是不是太简单了不如去学微服务、高并发这些听起来厉害的东西。我的建议很直接毕业设计阶段别碰微服务。你只需要把下面这张对应关系想明白就足够在企业里找到一份入门工作了。毕业设计用到的技术企业级对应的真实角色Spring Boot后端服务框架负责提供 REST APIMyBatis-Plus数据持久层框架替代了繁琐的 JDBC 代码MySQL关系型数据库存储业务数据Redis可选缓存中间件用来做验证码、热数据缓存JWT无状态登录令牌前后端分离下的标准方案Vue Element-Plus前端组件化开发对应企业里的中后台系统这个技术组合放在今天的就业市场上依然非常主流甚至很多中小企业内部系统就是这个配置。所以“低配”不是贬义而是说用最小的复杂度覆盖最核心的技能点。你把这套东西玩明白后面学 Redis、Spring Cloud 都是在这张地图上做增量。2. 系统设计与数据库建模先画好图纸再动手2.1 角色权限设计开工前第一件事是设计角色。毕业设计项目角色别设计太多三个正好管理员、部门/网格负责人、普通用户。管理员管人、管数据、看报表负责人做审批和统计普通用户只填自己那份资料。权限模型用标准的 RBAC基于角色的访问控制设计。核心是五张表用户表sys_user、角色表sys_role、菜单表sys_menu、用户角色关联表sys_user_role、角色菜单关联表sys_role_menu。用户和角色是多对多角色和菜单也是多对多。这套模型的好处网上已经说烂了但我要强调的是你自己一定要能讲清楚“为什么用户表里不直接放一个 role_id 字段”。因为实际业务里一个人可能既是某个部门的负责人又是普通填报人员单一角色字段根本不够用。RBAC 把角色独立出来权限分配就灵活了。答辩的时候把这段讲明白基本就能证明你不是只会复制代码的人。前端部分配合vue-router的路由守卫做页面级控制后端通过拦截器做接口级控制。具体做法后面专门讲。2.2 核心数据表设计详解除了权限那几张表业务核心表我建议按下面这个清单设计person_info人员信息表姓名、证件号、所属部门、手机号、居住地址。health_record健康登记表人员ID、体温、健康状况、是否接触风险区域、备注、登记日期。vaccine_record疫苗接种记录表人员ID、接种针次、接种日期、疫苗名称。detect_record检测记录表人员ID、检测日期、检测结果。material_info物资信息表物资名称、规格、库存数量、安全库存阈值。material_inout物资出入库记录表物资ID、出入库类型、数量、操作人、操作时间。notice_info公告通知表标题、内容、发布人、发布时间、状态。health_stat汇总统计表可选日期、填报人数、异常人数。拿health_record来说一张合格的表长这样CREATE TABLE health_record ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键ID, user_id bigint(20) NOT NULL COMMENT 用户ID, temperature decimal(4,1) DEFAULT NULL COMMENT 体温, health_status tinyint(1) DEFAULT 0 COMMENT 健康状况0正常 1异常, touch_risk tinyint(1) DEFAULT 0 COMMENT 是否接触风险区域0否 1是, remark varchar(255) DEFAULT NULL COMMENT 备注, record_date date NOT NULL COMMENT 登记日期, deleted tinyint(1) DEFAULT 0 COMMENT 逻辑删除标记, create_time datetime DEFAULT NULL COMMENT 创建时间, update_time datetime DEFAULT NULL COMMENT 更新时间, PRIMARY KEY (id), KEY idx_user_date (user_id,record_date) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4 COMMENT健康登记记录表;这里有几个设计细节值得你记住。逻辑删除字段deleted非常实用数据不真删恢复和追溯都方便。联合索引idx_user_date保证同一个用户同一天只能查出一条记录这是后面做“每日限填一次”的底层保障。业务字段用tinyint存状态值而不是字符串存储和查询都更高效。所有时间字段统一用datetime或date绝不混用否则排序会遇到一堆莫名其妙的坑。2.3 数据库设计的三个坑第一个坑是过度设计外键。毕业设计里物理外键能不用就不用靠 Java 代码控制关联逻辑就够了。理由很简单物理外键在删除和更新时约束太多你写代码的时候会被各种外键报错折磨演示的时候也容易翻车。表之间保持逻辑关联通过user_id这种字段在查询时 JOIN 即可。第二个坑是字段类型随意。手机号、证件号这种纯数字字符串必须用varchar而不用bigint因为可能带前导零还可能包含 X 这种字符。金额、数量要注意精度用decimal而不是float。能用date的不要用datetime比如登记日期它没有时分秒需求用date就够了。第三个坑是忽略时间字段的自动填充。MyBatis-Plus 提供了MetaObjectHandler可以自动填充createTime和updateTime别在业务代码里手动 set。写一遍两遍没事等到每个表的插入都想起来要 set 时迟早漏一个。3. 后端骨架搭建与接口开发全流程3.1 工程结构与依赖管理后端工程我的建议是直接建单体 Maven 项目别拆多模块。毕业设计的代码量还没到拆模块的规模单模块结构清晰、打包简单、答辩好讲。包结构按职责分层com.example.epidemic ├── controller // 接口层 ├── service // 业务接口 ├── service.impl // 业务实现 ├── mapper // MyBatis-Plus 的 Mapper 接口 ├── entity // 数据库实体 ├── dto // 前端请求参数封装 ├── vo // 前端展示数据封装 ├── config // 配置类分页插件、跨域、拦截器 └── common // 统一返回结果、异常类、工具类依赖方面按最低必要原则引入。spring-boot-starter-web是必须的mybatis-plus-boot-starter用来操作数据库mysql-connector-java连库lombok省了 getter/setterhutool-all做各种工具操作JWT 相关用java-jwt或者jjwt都可以。如果你要做 Excel 导出再加一个easyexcel。注意MyBatis-Plus 和 Spring Boot 有版本对应关系最好从官网看兼容版本别直接复制别人 pom 里最新的依赖坐标容易踩版本坑。3.2 统一响应与全局异常处理后端接口最忌讳每个方法自己拼一个 Map 返回。统一返回结构是基本功写一个ResultT泛型类包含code、message、data三个字段配合静态方法成功、失败两种构造。Data 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(String message) { ResultT result new Result(); result.setCode(500); result.setMessage(message); return result; } }有同学可能会问直接用 HTTP 状态码不行吗HTTP 状态码是给浏览器和代理看的接口内部业务是否成功是另一回事。业务上密码错误、参数缺失、无权限这些都不代表网络层出问题了。前端 axios 拦截器只看 HTTP 200具体业务成不成功看code字段这样前后端沟通成本最低。异常处理一定要做全局拦截。写一个类标注RestControllerAdvice用ExceptionHandler分别处理业务异常BusinessException、参数校验异常MethodArgumentNotValidException和兜底的Exception。好处是控制器代码非常干净只管正常流程异常统一在外部消化。3.3 JWT 登录鉴权实现前后端分离项目登录不能再用 Session因为后端接口可能部署在 A 机器、前端页面在 B 机器Session 天然不适合这种无状态场景。JWT 是标准方案它的核心思想是用户登录成功后后端生成一个包含用户身份信息的令牌返回给前端前端每次请求把令牌带到请求头里后端验签通过就放行。Component public class JwtUtil { Value(${jwt.secret}) private String secret; Value(${jwt.expire}) private Long expire; // 单位秒 public String generateToken(Long userId, String username) { return JWT.create() .withClaim(userId, userId) .withClaim(username, username) .withExpiresAt(new Date(System.currentTimeMillis() expire * 1000)) .sign(Algorithm.HMAC256(secret)); } public Long getUserId(String token) { return JWT.require(Algorithm.HMAC256(secret)) .build() .verify(token) .getClaim(userId) .asLong(); } }拦截器负责拦请求。写一个HandlerInterceptor在preHandle里从请求头Authorization拿 token解析失败就返回 401 状态码前端收到后自动跳回登录页。注意生产环境 JWT 密钥必须放到配置文件甚至环境变量里不能写死在代码中。毕业设计虽然没那么严格但你代码里把密钥写出来答辩老师看见了至少会追问一句到时候你就解释“配置文件分离”这个概念反而是加分项。对于首次接触的同学我强烈建议自己手写一遍 JWT 的生成和校验逻辑不要上来就套 Spring Security JWT 的全家桶。Spring Security 的学习曲线陡峭而且毕业设计的复杂度远没到需要它的程度。一个拦截器 JWT 工具类就够用了讲起来也清晰。3.4 核心业务接口示例健康登记模块下面用整个项目最重要的“健康登记”模块串一遍后端开发的核心链路。先写实体类Data EqualsAndHashCode(callSuper true) TableName(health_record) public class HealthRecord extends BaseEntity { private Long userId; private BigDecimal temperature; private Integer healthStatus; private Integer touchRisk; private String remark; TableField(record_date) private LocalDate recordDate; }Controller 层只做参数接收和结果返回业务全部下沉到 ServiceRestController RequestMapping(/api/health) public class HealthRecordController { Resource private HealthRecordService healthRecordService; PostMapping(/record/save) public ResultVoid save(RequestBody Validated HealthRecordDTO dto) { healthRecordService.saveRecord(dto); return Result.success(null); } GetMapping(/record/page) public ResultPageResultHealthRecordVO page(HealthRecordQuery query) { return Result.success(healthRecordService.queryPage(query)); } }Service 实现里有一个非常经典的业务场景限制一个用户每天只能登记一次。实现方式是先查当天是否已有记录有则抛出“今日已登记”的业务异常。这里要注意一个细节查重复必须走数据库不能依赖前端控制因为接口可以被绕过。参数校验用Validated注解配合 DTO 里的NotBlank、NotNull注解完成比在业务代码里写一堆 if 判断优雅得多前端拿到的错误信息也统一。这是你们平时最容易忽略但答辩时非常加分的地方。3.5 分页查询与多条件筛选管理后台页面几乎都是“查询表单 数据表格 分页”的结构。后端写一个通用分页查询方法既支持分页也支持多条件筛选。MyBatis-Plus 的Page模型天然支持分页配合LambdaQueryWrapper写条件非常舒服。public PageResultHealthRecordVO queryPage(HealthRecordQuery query) { PageHealthRecord page new Page(query.getPageNum(), query.getPageSize()); LambdaQueryWrapperHealthRecord wrapper new LambdaQueryWrapper(); // 姓名模糊查询通过关联人员表实现这里简化为按用户ID精确查询 wrapper.eq(query.getUserId() ! null, HealthRecord::getUserId, query.getUserId()); // 日期范围查询 wrapper.ge(query.getStartDate() ! null, HealthRecord::getRecordDate, query.getStartDate()); wrapper.le(query.getEndDate() ! null, HealthRecord::getRecordDate, query.getEndDate()); // 状态筛选 wrapper.eq(query.getHealthStatus() ! null, HealthRecord::getHealthStatus, query.getHealthStatus()); // 按登记时间倒序最新的记录排最前 wrapper.orderByDesc(HealthRecord::getRecordDate); PageHealthRecord result healthRecordMapper.selectPage(page, wrapper); // 注意这里需要把实体转换成VO避免把userId这种内部字段直接暴露给前端 ... }注意一个很容易踩的坑分页查询不要直接在 Mapper 里 return 实体列表给前端。真实的业务开发里实体类字段和前端需要的字段经常不一致。换成 VOView Object再返回这也是分层设计的一部分。毕业设计里很多人不分 DTO/VO全用实体打天下功能没问题但面试官一眼就能看出你缺少工程意识。3.6 Excel 导出给答辩加分的隐藏技能CRUD 大家都写得出来但“导出 Excel”这个功能一做出来整个项目的完成度瞬间上一个台阶。我这里推荐用 EasyExcel它是阿里开源的工具封装了 POI 的复杂性代码量非常少。public void exportHealthRecord(HttpServletResponse response, HealthRecordQuery query) { ListHealthRecordVO list healthRecordService.queryAll(query); try { response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); response.setCharacterEncoding(utf-8); String fileName URLEncoder.encode(健康登记数据.xlsx, UTF-8); response.setHeader(Content-Disposition, attachment;filename fileName); EasyExcel.write(response.getOutputStream(), HealthRecordVO.class) .sheet(健康登记) .doWrite(list); } catch (IOException e) { throw new BusinessException(导出失败); } }前端触发方式最简单就是 window.open 一个带参数的下载地址或者用 axios 的 blob 响应类型做文件下载。我建议用 axios 做因为可以在响应拦截器里统一处理导出失败的情况。答辩的时候你现场演示从几十条数据里点击导出然后 Excel 打开看到规整的表格那画面感是嘴上千言万语都换不来的。4. 前端页面开发与前后端联调4.1 前端工程初始化前端技术栈我建议 Vue 3 Vite Element-Plus Pinia Vue Router Axios。相比 Vue 2 ElementUI这套是当前主流也是新项目默认组合。工程结构按分析师惯例给一个参考src ├── api // 接口请求定义 ├── assets // 静态资源 ├── components // 通用组件 ├── router // 路由配置 ├── stores // Pinia 状态管理 ├── utils // 工具函数与 axios 封装 └── views // 页面组件axios 封装是整个前端工程最关键的公共部分。一定要做三件事创建统一实例、请求拦截器注入 token、响应拦截器统一处理状态码。const service axios.create({ baseURL: /api, timeout: 10000 }) service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] token } return config }) service.interceptors.response.use( response { const res response.data if (res.code ! 200) { // 业务失败统一弹提示 ElMessage.error(res.message) return Promise.reject(new Error(res.message)) } return res }, error { // 401 未认证跳转登录页 if (error.response error.response.status 401) { localStorage.removeItem(token) router.push(/login) } ElMessage.error(网络异常) return Promise.reject(error) } )这段代码逻辑很简单但它决定了你后面几十个接口能不能顺畅地联调。很多同学遇到的问题比如“明明登录成功了但页面还是跳回登录”“接口报错信息怎么统一弹出来”都出在这个封装没做好的地方。4.2 登录页面与路由守卫登录页本身代码没什么特别的一个表单、一个按钮、一个接口调用。重点是登录成功后干两件事把 token 存起来然后跳回原来想去的页面。路由守卫的核心逻辑是“未登录不能进后台页面”router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (!token to.path ! /login) { next(/login) } else { next() } })这里要提醒你一个小细节如果你用了动态菜单根据用户角色生成不同的菜单和路由路由守卫逻辑会比这个复杂。毕业设计我建议菜单在前端静态配置根据用户角色控制显示哪些菜单项即可不要上动态路由。动态路由是加分项没错但它会引入“路由刷新后丢失”的一堆问题花费大量时间调试还不一定拿得稳性价比不高。4.3 核心 CRUD 页面实操健康登记管理管理后台最核心的页面长什么样我直接说代码思路。页面结构分三层第一层是查询条件区用一个el-form设置为inline排列放三个控件日期选择器起止日期、状态下拉框、查询按钮和重置按钮。日期范围查询可以用el-date-picker的daterange类型一次拿两个日期后端拆成 startDate 和 endDate。第二层是操作按钮区放“新增登记”“导出 Excel”按钮。注意新增按钮要根据角色判断是否显示普通用户就不用看到这个按钮管理员才有全部操作权。第三层是el-table列表 el-pagination分页。表格列与后端返回的HealthRecordVO字段一一对应。体温、健康状况这些字段可以用el-tag展示状态颜色比如“异常”红色、“正常”绿色做出来的视觉效果比干巴巴的文本好很多。新增和编辑用一个el-dialog弹窗内部套el-form加校验规则。这句“复用弹窗而不是写两个页面”是很多人搞不懂的点。编辑时打开弹窗判断如果row有值则回填表单数据新增时row为空清空表单。这样一个弹窗就同时服务新增和编辑两个场景代码量和维护成本直接减半。删除操作一定加确认提示。用el-popconfirm在删除按钮上包一层让用户点两次才能删掉。这个设计不仅是体验问题更是防止误操作的底线。前端代码里还有一个容易被追问的点分页数据怎么管理。我的做法是把查询条件放一个queryParams对象里表格绑定的tableData单独一个变量分页绑定的total一个变量。切换页码时把queryParams.pageNum改掉再调用fetchList()方法。思路简单但很多人在这些状态之间理不清关系导致“切页后条件失效”“删掉最后一页数据后还停在一个空页面”这些经典问题。4.4 联调阶段最值得警惕的五个问题前后端联调期是 Bug 高发期我对常见问题做个总结你可以直接当成检查清单用。问题原因解决方案接口返回 404后端 Controller 的 RequestMapping 路径和前端请求路径不一致前后端严格按接口文档核对路径别靠猜跨域报错前端端口 5173后端端口 8080不同源开发环境用 Vite proxy 代理转发 /api 请求日期变成时间戳Jackson 默认把 LocalDate 转成数组或复杂格式后端配置 Jackson 统一日期格式为 yyyy-MM-dd上传文件报错Spring Boot 默认限制单文件大小 1MB配置 multipart 的 max-file-size 参数接口响应慢分页查询没有走索引或者查询了不必要的字段查 SQL 执行计划确保 WHERE 条件字段有索引这里面跨域问题是新手最容易卡住的。开发环境下最省事的方案是配 Vite 代理前端所有/api请求都被转发到http://localhost:8080这样浏览器看到的是同源请求根本不会触发跨域。等部署上线之后再用 Nginx 做反向代理后端接口路径统一以/api/开头Nginx 根据路径规则转发完全避开了跨域这个坑。所以记住一个原则永远不要在前端代码里把后端地址写成http://localhost:8080要一律使用/api这种相对路径。5. 部署上线让别人能在局域网访问你的系统5.1 打包配置演示和答辩的时候不可能一直开着 IDEA 和 Vite 开发服务器。正式演示场景应该是后端打成 jar 包跑起来前端打包成静态文件放到 Nginx 里让访问者通过一个干净的 URL 直接使用系统。前端打包特别简单npm run build会生成dist目录里面全是静态文件。后端打包用 Maven 的package命令产物是一个可执行 jar 包通过java -jar就能跑起来。这里要提醒你一个常见的打包问题后端打包前一定确认application.yml里的数据库地址、端口等配置是演示环境可用的别把本地数据库地址打包进去换台机器就连接失败。5.2 Nginx 部署方案Nginx 在这个项目里承担两件事托管前端静态文件反向代理后端接口。核心配置示例如下server { listen 80; server_name localhost; # 前端页面 location / { root /opt/epidemic/dist; index index.html; try_files $uri $uri/ /index.html; # 解决 Vue Router 刷新 404 问题 } # 后端接口代理 location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }那行try_files $uri $uri/ /index.html特别关键。Vue Router 用的是 history 模式页面地址如/system/user在服务器上并不存在真实文件刷新时如果没有这行配置Nginx 会直接返回 404。加上之后请求统一回退到 index.html让前端路由接管问题就解决了。5.3 部署演示时的环境清单我见过太多同学代码没问题结果在答辩现场因为环境没准备好直接翻车。给你一份实战 checkList照着准备绝对不会出问题一台可用的电脑或服务器预装 JDK 1.8、MySQL 5.7、Nginx。数据库脚本提前导入并且确保数据里有几个可用的测试账号。后端 jar 包路径和日志路径固定用nohup java -jar xxx.jar app.log 21 方式启动。前端 dist 目录确定存在Nginx 配置里 root 路径一定要写正确。现场演示前先完整走一遍流程登录到退出、增删改查、导出全程跑通了再拷贝环境。演示时一个小技巧提前用浏览器把系统打开在“健康登记列表”页面答辩时直接进入主题“下面我演示核心功能”鼠标点几下就能展示曲线。千万不要现场输密码再等接口响应任何一次意外都会分散评审老师的注意力并放大你的紧张感。6. 答辩准备与项目扩展建议6.1 答辩常见问题清单答辩的核心逻辑不是看你代码敲得多快而是看你对自己项目的理解有多深。下面这些问题是这个题目大概率会被问到的提前把答案理清楚现场才能撑得住。问为什么用 JWT 而不用 Session答Session 依赖服务端保存状态在前后端分离、后端多实例部署的场景下不好扩展。JWT 是无状态的服务端不需要保存登录信息很适合接口服务场景。缺点是 token 一旦签发难以主动失效所以过期时间要控制好。问MyBatis-Plus 和 MyBatis 有什么区别为什么选它答MyBatis-Plus 在 MyBatis 基础上提供了通用的 CRUD 方法、条件构造器、分页插件等能力单表操作不用写 SQL能减少大量重复代码。复杂查询仍然可以用 XML 写标准 SQL两者不冲突。问系统权限是怎么控制的答后端用拦截器对需要鉴权的接口做 token 校验同时通过角色信息判断是否有特定接口的操作权限这个通过 RBAC 角色权限模型实现。前端用路由守卫控制页面访问再通过菜单渲染控制操作按钮的显示。问数据量大了分页查询会慢你怎么优化答这个问题有两种答法。初级答法是加索引、避免select *、只查需要的字段。高级答法能聊到覆盖索引、延迟关联、分库分表但对毕业设计来说能答清楚索引的力气和作用就很稳了不要给自己挖坑。问如果某个接口在高并发下被频繁请求你怎么保护后端答可以加接口限流比如用 Redis 做计数器实现简单的滑动窗口限流或者用 Guava RateLimiter 做单机限流。这个回答能体现你有性能意识对于本项目来说属于超纲加分项。6.2 让项目从“及格”变“优秀”的五个扩展如果你的时间还有富余或者想稳拿优秀下面五个扩展点可以按优先级做一个或几个第一个是数据可视化。用 ECharts 做一个首页统计大屏展示今日填报人数、异常人数趋势、部门填报率。这个功能视觉冲击力极强答辩时一展示整个项目的档次直接提升而且 ECharts 官方文档案例很多调用方式简单。第二个是 WebSocket 实时通知。有人提交异常健康数据时后端通过 WebSocket 给管理员推送一条实时消息。这个扩展点能体现你对“实时通信”这个知识点的理解与轮询的对比也讲得出东西。第三个是 Excel 批量导入。在刚才导出功能的基础上增加通过上传 Excel 批量导入人员信息的接口。用 EasyExcel 的read方法监听器解析每一行数据校验后写入数据库。这个和导出是一对讲起来自成体系。第四个是操作日志审计。用 AOP 写一个切面对关键接口的调用记录操作人、操作时间、方法名、参数等信息。这能体现企业对系统的合规性要求面试时也是能聊的点。第五个是 Redis 缓存。登录验证码或者热点统计数据放 Redis设置过期时间减少数据库压力。这个不需要集成特别多东西但能把 Redis 这个高频技术点写进简历。注意扩展功能不要贪多。我见过同学做了五个扩展结果每一个都是半吊子连登录都偶尔报错答辩翻车。把一个扩展打磨到“演示顺畅、逻辑清晰、能回答追问”的程度收益远大于做五个半成品。6.3 学习路径建议如果你是从零开始接触这套技术栈我建议的学习顺序是Java 基础语法 → Spring Boot 快速上手 → MyBatis-Plus 增删改查 → Vue 基础语法与组件 → Element-Plus 中后台页面 → 本项目完整开发。不需要把 Java 学透了再动手那样大概率学不到一半就放弃。以项目为单位边做边补基础效率要高得多。开发过程中遇到问题怎么排查一个重要习惯是看日志。后端看控制台输出的异常堆栈前端按 F12 看 Network 和 Console。很多同学的“代码没错啊”只是没看日志而已日志会告诉你真正的错误原因比盲猜靠谱一百倍。最后再说一个很多人关心的事这个项目可以直接拿去当毕设吗我的回答是实践上完全没问题但你心里要清楚系统本身是工具你从开发过程中学到的东西才是真正能带走的东西。哪怕功能一模一样能讲清为什么这么设计、遇到过什么坑、怎么排查解决的人和只写出来能跑的人答辩得分是两回事。7. 写在最后我个人做这个项目的体会是真正让我成长的不是最后那个能运行的系统而是中途为了搞清楚一个跨域问题、一个分页插件配置、一个路由守卫反复试错的过程。这些经历才是你和“只会照着视频敲代码”的人拉开差距的地方。如果你时间紧代码可以直接拿来参考学习或重构但答辩前一定要把每个核心问题自己过一遍比如登录流程走到了哪一步、token 存在哪里、分页条件是怎么传给后端的。脑子里过一遍才算真正掌握。最后再分享一个实际演练的小技巧写完系统后找一位同学或朋友扮演评审老师让他随机选一个模块反复提问。你会发现很多你以为懂了的地方被别人一问就卡壳。趁这个卡壳发生在演习阶段而不是答辩现场。准备好的系统和准备好讲系统的人才是毕业设计真正完整的交付物。