SpringBoot2+Vue3社区疫情信息管理系统开发复盘
这套系统做完已经有段时间了源码和文档都整理齐全正好写一篇完整的复盘。技术栈是 SpringBoot2 Vue3 MyBatis-Plus MySQL8.0面向的场景是中小社区的疫情信息管理覆盖居民台账、健康申报、出入报备、通知公告和数据统计这些核心业务。如果你正准备做类似的Java Web方向毕设或者公司里需要一个轻量级的基层信息采集与上报系统这篇文章应该能帮你省掉不少调研和踩坑的时间。我会把从需求拆解、表结构设计、后端接口开发到前端页面联调、最终部署上线的完整过程都讲一遍包括我实际遇到过的问题和对应的排查思路。先说一句这套系统不只是一个花架子页面。它按真实使用场景设计了三类角色居民、社区工作人员、系统管理员每一类角色看到的菜单、能调的接口都不一样。文档里包含了需求文档、数据库设计说明和部署手册拿到源码之后照着部署文档配好环境基本一两小时内就能跑起来。下面我按照做项目的实际顺序来拆解。1. 项目全貌与需求拆解1.1 这套系统到底解决什么问题中小社区的信息管理痛点往往不是技术而是数据太散。今天有人说自己从外地回来明天有人说体温偏高后天又要统计谁打过疫苗如果全靠微信群接龙加Excel登记信息根本没法核对更别提给街道上报数据。这个系统的核心价值就是把“人、时间、地点、状态”四条线统一管理起来。我把需求拆成了四个模块。人员台账是底座每一条居民记录都要归属到楼栋和网格后台可以按楼栋筛选导出健康申报是居民端最主要的交互入口每天填报体温、症状、是否接触过风险人群出入报备记录每一次跨区域流动的时间和去向通知公告由社区工作人员发布居民登录后就能看到。最后所有数据汇总到统计页面按日期、楼栋、状态做可视化展示。这样拆分的好处是每个模块之间边界清晰开发时可以切分成独立任务并行推进也能对应到毕业设计的几个核心功能点。评审老师问起来每一块都有明确的业务价值和实现逻辑。1.2 技术选型背后的取舍选这套技术栈不是因为它热门而是每一环都有对应的理由。后端用 SpringBoot2 而不是 SpringBoot3主要原因是生态更稳定。很多老牌工具库、教程、排查方案都是基于 SpringBoot2 的遇到问题网上能查到大量现成答案。而 SpringBoot3 强制要求 JDK17且部分第三方组件的兼容性还没完全跟上来对毕设和中小项目来说没必要冒这个风险。ORM 框架选了 MyBatis-Plus说白了就是看中它“少写重复代码”的能力。常规的单表增删改查、分页查询、条件拼接它能直接给到现成方法省掉了写大量XML映射文件的时间。这一点后面我会详细展开。前端用 Vue3 是因为组件化开发效率高配合 Vite 冷启动速度很快开发调试体验比传统 JSP 加 jQuery 好太多。版本上 Vue3.4 加 Vite5 搭配 Element Plus 2.x官方文档和社区资料都比较全。MySQL8.0 在性能和功能上都比5.7强窗口函数、公用表表达式这些特性虽然在这个项目里没用到但后续扩展SQL分析场景时不需要换库。数据库选型这块初期就要想清楚字符集和时区的问题后面会专门说。1.3 系统角色与核心模块清单角色权限是这类管理系统的骨架。我设计了三张核心表来支撑权限模型用户表、角色表、用户角色关联表。没有做细粒度的按钮级权限控制一套基于RBAC思想的“角色-菜单”模型够用了复杂度控制在合理范围内而且实现起来不容易出错。居民的日常操作集中在健康申报、出入报备、消息查看三个页面。社区工作者端则包含居民管理、审核报备、发布公告、数据统计。系统管理员多一个用户管理和角色配置权限。菜单不是写死的而是根据角色动态渲染这个点在答辩时通常是个加分项。模块清单固定下来之后前后端并行开发就有了依据。后端任务按模块拆前端按页面拆最后统一联调整个节奏就很顺。2. 数据库设计与后端核心实现2.1 表结构设计与MySQL 8.0初始化数据库一共设计了10张表核心几张分别是居民信息表、用户表、健康申报记录表、出入报备表、通知公告表、楼栋网格表。其中居民信息表和用户表通过关联字段绑定健康申报表以居民ID作为外键关联。建表时有两个细节必须注意。第一是字符集和排序规则如果当时没用 utf8mb4后面插入表情符号或者生僻字会直接报错。建库语句我建议在最开始就用CREATE DATABASE IF NOT EXISTS community_health DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;第二是时区问题。MySQL8.0 默认时区经常导致 JDBC 连接报错或者存入的时间比本机时间少8小时。我的解决办法是在连接参数里显式指定 serverTimezoneAsia/Shanghai同时数据库端设置时区为东八区。这两个动作同时做了就不会出现时间对不上的诡异现象。关于表设计我想多说一句设计经验状态字段不要用字符串随便写建议统一用数字枚举。比如申报状态 0正常、1异常、2待复核代码里定义常量或枚举类前端用字典映射显示中文。这样后续写统计SQL时直接对数字字段做分组统计可比对字符串高效得多也不容易因为命名不统一而埋bug。2.2 用MyBatis-Plus把重复CRUD写少一点MyBatis-Plus 在整个项目里承担了大部分数据访问层的工作。最常用的几个能力我梳理一下内置 BaseMapper 提供 insert、deleteById、selectById、updateById 等现成方法LambdaQueryWrapper 解决条件查询时字符串硬编码问题分页插件只需配置一个拦截器逻辑删除用 TableLogic 注解删除操作自动变成 update 语句分页插件配置是关键之一不配置的话分页查询实际不会生效只是查出全量数据再在内存里切一刀。正确的配置方式如下Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); PaginationInnerInterceptor pagination new PaginationInnerInterceptor(DbType.MYSQL); pagination.setMaxLimit(500L); interceptor.addInnerInterceptor(pagination); return interceptor; } }setMaxLimit 是我额外加的防止有人把 pageSize 传成很大的数字导致数据库压力过大这个细节属于上线后才会意识到的隐患。条件查询用 LambdaQueryWrapper 写起来非常直观。举个例子查询某栋楼某天异常申报的记录LambdaQueryWrapperHealthReport wrapper Wrappers.lambdaQuery(); wrapper.eq(HealthReport::getBuildingId, buildingId) .ge(HealthReport::getReportDate, startDate) .le(HealthReport::getReportDate, endDate) .eq(HealthReport::getStatus, 1) .orderByDesc(HealthReport::getReportDate);这种写法不用手写SQL也不容易因为字段改名后忘记同步字符串而报错。条件特别复杂的统计场景直接上 Select 注解写原生SQL依然保留在Mapper接口里不会和通用方法冲突。2.3 统一返回与全局异常后端代码的“门面”刚开始写接口时最容易犯的毛病是每个Controller各返回各的格式有人直接返回实体对象有人返回Map前端联调非常痛苦。我一开始就定了统一返回结构所有接口都返回 Result 包含 code、message、data 三部分。这样前端拿到响应后先判断 code 是否为 200再做后续处理。登录失效返回 401业务异常返回 400系统错误返回 500规则统一之后Axios 拦截器里写逻辑就非常清晰。全局异常处理用 RestControllerAdvice 实现处理三类异常自定义业务异常、参数校验异常、兜底的系统异常。自定义业务异常是用的最多的一类比如“该居民还未绑定账号”“申报时间已过”这类提示都在业务代码里抛出异常处理器统一转成标准返回格式。这里有个容易被忽略的点参数校验不要全写在Controller里手动if判断。我在实体类字段上加了 NotBlank、Size 这类校验注解Controller 参数用 Validated 触发校验异常处理器统一接住。代码干净很多也显得有工程素养。3. 登录鉴权与权限控制3.1 为什么没直接套Spring Security项目初期我也纠结过要不要引入 Spring Security JWT 那套组合。后来评估下来这套系统只有三种固定角色接口数量也不多Spring Security 的过滤器链配置反而增加了学习成本和排查难度。我自己实践下来选了更轻的方案手动写一个登录接口登录成功后签发 Token再写一个拦截器统一校验。毕业设计答辩时评委更看重的是你能把鉴权原理讲清楚而不是配置有多复杂。3.2 Token设计与拦截器实现Token 我用的方案是 UUID 加 Redis 存储登录成功后把 Token 作为 key、用户信息作为 value 写入 Redis并设置过期时间后续请求带上 Token拦截器去 Redis 查询。这样做的好处是服务端能主动让 Token 失效用户修改密码或管理员强制下线时直接删除 Redis 里的对应 key 就行。拦截器的核心逻辑其实很简单public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (StringUtils.isBlank(token)) { throw new BusinessException(401, 未登录); } LoginUser loginUser redisService.get(token); if (loginUser null) { throw new BusinessException(401, 登录过期); } UserContextHolder.set(loginUser); return true; }UserContextHolder 是个基于 ThreadLocal 的工具类请求进入时写入用户信息响应结束后记得清理否则线程池复用会导致数据串号。这个坑我亲眼见过网上也有很多案例加一个 afterCompletion 清理方法就能避免。角色权限校验我放在需要做二次校验的接口上用了自定义注解 RequireRole 配合拦截器。比如删除居民的操作只有管理员和社区工作人员能执行。这部分不难重点是拦截器注册时注意排除登录接口、静态资源路径。3.3 前端路由守卫与用户状态后端做鉴权前端也不能把所有页面都挂出来。Vue Router 里配置了动态路由登录后根据后端返回的菜单列表生成路由表用 addRoute 动态挂载。用户刷新页面时从 Pinia 里读不到菜单数据会导致路由空白所以我写了初始化逻辑页面刷新后先调一次“获取当前用户信息”的接口重建路由再进入目标页面。前端路由守卫里的处理大概是校验本地有没有 Token没有就跳登录页有就请求用户信息成功则放行失败就清空 Token 回到登录页。这套流程至少能让用户感知层面做到“该进的页面进得去不该进的看不到菜单”。4. Vue3前端实现与前后端联调4.1 工程初始化与目录规划前端工程用 Vite 创建命令也简单npm create vitelatest frontend -- --template vue装依赖时建议直接把 Element Plus、Axios、Pinia、Vue Router、ECharts 一起装上。构建完基础工程后第一件事是规划目录。我按功能分包api 目录放接口请求views 目录放页面组件router 目录放路由配置store 目录放全局状态utils 目录放工具函数components 目录放公共组件。这个目录规划花不了十分钟但后期维护时非常省脑子。找接口去 api 里翻公共弹窗去 components 里找页面组件各自独立不会出现文件越堆越乱的问题。4.2 Composition API 封装与Axios拦截Vue3 里我全面用的组合式 API也就是 script setup 写法。相比选项式 API它的优势在逻辑复用。比如表格页的“加载列表、搜索、分页、重置搜索”四件套我抽成了一个 composable 函数 useTable每个页面只需要传接口函数和查询参数返回表格数据和操作函数代码量直接缩减一大半。Axios 封装是整个前端最值得花时间写的一块。我在 utils/request.js 里统一了 baseURL、超时时间、请求拦截器和响应拦截器。请求拦截器给每个请求带 Token响应拦截器统一处理 code 码为 200 的情况401 跳转登录页其他错误弹 ElMessage 提示。baseURL 这块有个关键点开发环境不要写死完整后端地址用环境变量vite 的 .env.development 文件里配置 VITE_API_BASE_URL。这样后面部署到测试环境、生产环境只需要改环境变量文件代码一行都不用动。4.3 核心页面与图表展示居民管理页面是最典型的“表格搜索弹窗表单”组合Element Plus 的 el-table、el-pagination、el-dialog 基本能满足需求。刚开始写表格时建议把列配置和数据请求解耦列的显示逻辑单独维护后面加字段、调整宽度不用去翻数据逻辑。健康申报统计页面用 ECharts 做可视化我做了三个图按日期折线图展示每日申报数量变化按楼栋的柱状图展示异常申报分布饼图展示状态占比。ECharts 在 Vue3 里的接入也很顺在组件 onMounted 里 new 图表实例然后 watch 数据变化调用 setOption 更新。这里给个实用建议图表数据不要前端自己拼复杂结构让后端提供已经聚合好的接口比如按日期返回 [{date: 2024-01-01, count: 128}]。前端只负责渲染职责边界清晰也方便以后换图表库。4.4 开发期联调Vite代理与跨域前后端分离项目开发期最烦人的就是跨域。Vite 自带 proxy 配置把前端的接口请求转发到后端服务浏览器里看请求是同源的不需要后端配 CORS。配置在 vite.config.js 里server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }注意 target 里的端口要和后端 application.yml 里 server.port 保持一致。这个方案我只在开发环境用生产环境是 Nginx 统一反向代理前后端部署在同一域名下压根不存在跨域问题。这也是我建议所有前后端分离项目采用的方式。5. 部署上线与问题排查实录5.1 构建打包与服务器部署后端打包如果用的是 Maven一条命令搞定mvn clean package -DskipTests打包产物是 target 目录下的 jar 包。运行命令是nohup java -jar community-system.jar --spring.profiles.activeprod app.log 21 生产环境的配置放在 application-prod.yml 里数据库地址、Redis 地址都改成真实服务器的。需要注意日志配置要单独加否则长时间运行单个日志文件会变得很大排查问题时很不方便。我在这里加了 logback 配置按天滚动生成日志文件保留最近30天。前端打包更简单npm run build之后生成 dist 目录把整个目录传到服务器Nginx 配置好 root 路径和 try_files 重写规则就行。唯一容易漏的是刷新页面后白屏这是因为 Vue Router 用了 history 模式Nginx 需要把未知路径都转发到 index.html location / { try_files $uri $uri/ /index.html; }5.2 后端常见问题速查我把这次项目里遇到过的后端问题整理成了一张速查表每个问题都是实际触发过的现象原因解决方案启动报 Unable to load authentication plugin caching_sha2_passwordMySQL8.0 默认认证插件与旧驱动不兼容升级 mysql-connector-java 到 8.x 版本时间比实际少8小时数据库时区未设置JDBC 参数加 serverTimezoneAsia/Shanghai数据库设置时区东八区分页查询不生效返回全量数据没配置 MyBatis-Plus 分页插件添加 PaginationInnerInterceptor 到 MybatisPlusInterceptor更新数据时部分字段变 nullupdateById 只更新非null字段传了null的字段被忽略需要更新null字段时用 UpdateWrapper 显式 set逻辑删除后唯一索引冲突逻辑删除的数据仍然占着唯一索引值唯一索引字段设计中添加删除标记或采用其他策略最后一条是个隐蔽问题。比如居民的手机号做了唯一索引用户删除后记录还在表里再新增同手机号的人就会冲突。我当时通过把逻辑删除字段也拼入唯一索引解决例如唯一索引改为“手机号deleted 字段”的组合索引。这个设计值得你在建表时就想清楚。5.3 前端常见问题速查前端踩到的坑相对少一些但也都很有代表性现象原因解决方案接口 404开发环境 Vite proxy 没生效确认请求前缀是否匹配代理配置的 /api刷新页面后白屏动态路由没重新生成刷新时先请求用户信息再重建路由跨域报 CORS 错误前端地址和后端地址不同源开发用代理生产用 Nginx 同源部署打包后图片资源 404静态资源引用路径错误Vite 配置 base: ./ 使打包路径变相对路径表格数据量多时页面卡顿一次性渲染太多 DOM分页之外配合虚拟滚动或限制默认每页条数关于“Vite 代理没生效”这个问题我再强调一下经常有人把 axios 请求路径写成了绝对地址http://localhost:8080/api/...代理配置就没意义了。接口路径必须以/api开头走相对路径才能命中代理规则。5.4 留给后来者的几条实战心得这套系统从立项到上线我最大的体会有三点。第一数据库设计一定要认真做。表结构基本决定开发效率的上限。字段命名统一用下划线风格状态字段用枚举数字公共字段如 create_time、update_time 每个表都带上这些习惯能省非常多的事。第二接口联调一定要前置。不要等后端全部写完才开始写前端开发和接口文档同步推进才能尽早暴露格式不一致的问题。我在项目中期用 Apifox 维护接口文档后端完成一个接口就标记一个前端照着调效率高了不少。第三文档不只是给答辩或者交付用的更是给你自己保命的。部署文档里我记录了完整的服务器环境配置、初始化命令、常见报错处理方案过三个月再回头维护这个项目翻文档比翻代码回忆快多了。最后再分享一个小技巧开发期后端热部署加前端热更新都配置好后改代码到看到效果基本是秒级反馈这个体验会大幅提升你的开发效率也更愿意频繁小步迭代而不是攒一大坨代码一次性调试那样出了问题定位都费劲。如果你也准备做类似的 Java Web 管理系统这套技术栈选型我认为依然值得参考。它没有追逐最新版本但每一样都是久经考验的成熟方案组合在一起稳定可靠且资料丰富。照着我上面说到的设计和实现思路走下来你可以少踩不少坑最后交付一个能真正跑起来、讲得出亮点的系统。