SpringBoot2+Vue3疫情隔离管理系统开发实战:从零搭建到部署全流程
最近把一个疫情隔离管理系统从零到一完整做了一遍技术栈就是标题里那套——SpringBoot2 Vue3 MyBatis-Plus MySQL8.0。说实话这类系统放在平时看起来不复杂无非就是人员登记、健康监测、房间管理、物资管理几块业务但真正从设计表结构到前后端联调再到打包部署、写文档准备交付整个过程里踩过的坑远比想象中多。这篇文章不聊虚的就把我做这个项目的完整思路、表结构设计、后端核心封装、前端页面交互、环境部署实测记录以及那些文档里绝对不会写的排查经验全部摊开讲清楚。项目源码附带的说明文档我看了写得中规中矩但还不够细所以我决定自己重新整理一篇记录更接近真实开发过程的经验。不管你是准备拿这个项目做课程设计、毕业设计还是单纯想练手SpringBoot2 Vue3的组合玩法这篇内容都能帮你省下不少摸索时间。基础要求不高会一点Java和JavaScript语法就能跟上我会把关键代码和配置直接贴出来你照着做就能跑起来。1. 业务梳理与技术选型背后的逻辑1.1 这个系统到底解决什么问题疫情隔离管理这个场景核心痛点在于信息分散。隔离人员入住登记靠纸质表格每日体温监测靠人工汇报房间占用情况靠打电话问物资消耗靠月底盘库存。一旦人数上来数据一多管理就完全失控。所以这个系统的定位就是把这些分散操作集中到一个平台里让不同角色在各自权限范围内完成自己的工作。管理员负责全局配置和数据查看医护人员负责录入隔离人员的健康监测信息普通用户或隔离人员则可以通过系统查看自己的状态和通知。核心业务流程其实只有一条人员登记入驻、分配房间、每日健康记录、到期解除隔离。围绕这条链路再延伸出物资管理、公告通知、数据统计等辅助模块。1.2 技术栈选型不追新选成熟为什么选SpringBoot2而不是SpringBoot3我的考虑很简单。SpringBoot2.7.x配合JDK8是当前最稳定的企业级组合网上资料多遇到问题基本一搜就有答案。SpringBoot3强制要求JDK17如果你的电脑只装了JDK8那就要多一道环境配置的槛。对于课程设计和毕业设计这种场景稳定性优先于新版本特性另外MyBatis-Plus对SpringBoot2.x的支持也是最成熟的代码生成器、分页插件、条件构造器都是直接可用。Vue3选择了Composition API Element Plus的组合。Vue3对比Vue2最大的变化就是组合式API逻辑复用更清晰配合Vite构建速度极快开发体验比webpack时代好太多。Element Plus是Vue3生态里最完整的UI组件库后台管理系统的表格、表单、弹窗、标签页都能一站解决。MySQL8.0在这个项目里其实发挥了几个关键特性。默认字符集是utf8mb4中文存储不会出现乱码问题窗口函数和公共表表达式这些新特性虽然在这个项目里没用上但以后做数据分析报表扩展时直接就能用另外MySQL8.0对JSON字段类型支持很好后面如果要扩展自定义字段也方便。最重要的是学校机房和企业内部环境现在普遍装的就是8.0选它意味着部署不折腾。1.3 软件架构与核心模块划分整个系统采用经典前后端分离架构。后端提供RESTful API前端通过HTTP请求调用两者之间通过JSON交换数据。后端SpringBoot负责业务逻辑和数据库操作前端Vue3负责页面渲染和用户交互。系统功能模块我划分为六大块模块核心功能设计说明用户管理登录注册、角色分配、账号维护采用JWT做无状态认证角色分管理员/医护人员/普通用户人员管理隔离人员登记、解除、查询核心业务入口包含入住和解除两个关键动作房间管理楼栋/房间信息维护、分配状态房间状态驱动分配逻辑空闲/占用/消杀三种状态健康监测体温记录、异常标记、连续监控按人员维度记录每日健康数据支持历史曲线查询物资管理物资出入库、库存预警核心是库存台账的准确性预警阈值可配置公告管理通知发布、列表查看面向所有角色支持按发布时间排序模块之间的依赖关系也很清晰人员管理依赖房间管理来确定入住房间健康监测依赖人员管理来确定记录归属统计报表又依赖前面所有模块的数据。设计时把公共字段创建时间、更新时间、逻辑删除标记统一放进基础实体类避免每个表都重复写一遍。2. 数据库设计与初始化脚本要点2.1 核心表结构设计数据库是整套系统的地基。我在设计表结构的时候一切都是围绕“业务状态如何流转”来做的。最终确定的六张核心表是用户表、隔离人员表、房间表、健康记录表、物资表、公告表。这里挑几张关键表详细说一下。隔离人员表是最复杂的一张表它承载了整条业务主线的状态。字段包括姓名、证件类型、证件号码、手机号、来源地、入住时间、计划解除时间、实际解除时间、入住房间号、状态。其中状态字段是关键设计我用了tinyint类型的status0表示待入住1表示隔离中2表示已解除。为什么不直接用字符串因为后端做状态条件查询时整型比较比字符串匹配效率更高而且配合MyBatis-Plus的枚举类型就能在Java代码里做统一转换。房间表的设计重点在状态字段和编号规则。房间编号我采用了“楼栋号-楼层-房间号”的组合格式比如2-3-05表示2栋3层05号房。这样设计的好处是前端展示时不用额外拼接后端做楼栋统计时也能通过模糊查询实现。健康记录表相对简单但索引设计很关键。由于系统要按人员查历史记录我创建了联合索引人员ID记录日期否则数据量上来之后按人员查询会全表扫描。物资表的重点在于库存字段的约束检查。库存不能为负数我在数据库层直接加了CHECK (quantity 0)的约束在应用层用事务控制出入库操作的原子性双保险。2.2 建库建表与初始化数据脚本示例数据库名称定义为isolation_db字符集指定为utf8mb4。我个人习惯在MySQL8.0上把排序规则设为utf8mb4_unicode_ci这个排序规则对中文文本比较友好。CREATE DATABASE IF NOT EXISTS isolation_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;用户表建表语句我直接给出来注意密码字段不要用varchar(50)因为BCrypt加密后的密码长度是60位长度不够会导致认证失败CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键ID, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录账号, password VARCHAR(100) NOT NULL COMMENT BCrypt加密后的密码, nickname VARCHAR(50) COMMENT 用户昵称, role VARCHAR(20) NOT NULL DEFAULT USER COMMENT 角色: ADMIN/DOCTOR/USER, status TINYINT NOT NULL DEFAULT 1 COMMENT 状态: 1启用 0禁用, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, deleted TINYINT NOT NULL DEFAULT 0 COMMENT 逻辑删除标记, INDEX idx_username (username) ) ENGINEInnoDB COMMENT系统用户表;这里有一个非常容易被忽略的坑MySQL8.0默认的认证插件是caching_sha2_password而很多老版本驱动只支持mysql_native_password。如果你在连接数据库时报认证插件错误要么改用8.0对应的驱动版本要么在建用户时指定认证插件。我的做法是在项目初始化时统一使用mysql-connector-j8.0.x版本代码里指定驱动为com.mysql.cj.jdbc.Driver。2.3 初始化管理员的正确姿势初始数据里必须要有一个管理员账号。我用BCrypt加密工具生成密码串而不是明文存入数据库。具体做法是写一个CommandLineRunner在SpringBoot启动时检测sys_user表是否为空如果为空就自动插入默认管理员。Component public class AdminInitializer implements CommandLineRunner { Autowired private SysUserMapper userMapper; Override public void run(String... args) { Long count userMapper.selectCount(null); if (count 0) return; SysUser admin new SysUser(); admin.setUsername(admin); admin.setPassword(new BCryptPasswordEncoder().encode(admin123)); admin.setNickname(系统管理员); admin.setRole(ADMIN); userMapper.insert(admin); log.info(默认管理员账号已创建: admin / admin123); } }这样设计的好处是别人拿到源码后不需要手动执行SQL插入数据就能启动系统体验会好很多。3. 后端核心实现SpringBoot2 MyBatis-Plus的实战技巧3.1 项目骨架与统一结果封装后端项目的标准包结构是这样的controller、service、mapper、entity、common、config。controller只做参数接收和结果响应service写业务逻辑mapper就是MyBatis-Plus的BaseMapper接口entity对应数据库表common放通用工具类和结果封装config放配置类。所有接口返回统一的数据结构这是前后端协作质量提升的关键一步。封装的返回体包含三个字段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.code 200; result.message 操作成功; result.data data; return result; } public static T ResultT error(String message) { ResultT result new Result(); result.code 500; result.message message; return result; } }统一的返回格式解决了前后端沟通成本问题。前端axios拦截器只需要判断code是否为200再决定是走业务逻辑还是弹出错误提示代码逻辑会非常干净。3.2 基于MyBatis-Plus的通用CRUD服务封装MyBatis-Plus这个框架最大的价值不是帮你写SQL而是帮你省掉那些无状态的单表增删改查代码。基于BaseMapper接口你不需要写任何SQL就能完成单表的插入、更新、条件查询、分页查询。但如果每个Service都重复写一套selectList、insert、updateById那也没有真正省事。我的做法是封装一个通用Service接口和实现类把常见的CRUD操作抽象出来。这个思路在项目里效果非常明显六个业务模块的Service层加起来不到两百行业务代码。public interface CommonServiceT { boolean save(T entity); boolean update(T entity); boolean removeById(Long id); T getById(Long id); ListT list(WrapperT queryWrapper); IPageT page(PageT page, WrapperT queryWrapper); }通用实现类基于IServiceT来做业务模块的Service只需要继承这个通用实现类再补充各自独有的业务方法即可。值得注意的是通用CRUD虽然方便但只适合单表简单操作。多表关联或复杂统计我会在mapper里写自定义SQL使用Select注解或XML文件实现。千万不要所有业务都用通用CRUD硬套比如物资出库时要同时扣减库存并新增出入库记录这种就必须用事务方法处理。3.3 分页查询的正确打开方式MyBatis-Plus分页需要先配置分页插件这个步骤容易漏漏了之后你会发现page方法返回的数据永远是全量的。Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }配置完成后Service层调用分页前端只需要传递当前页码pageNum和每页条数pageSize两个参数即可。分页插件会自动生成count查询和limit语句你不需要手动写SQL。除了分页MyBatis-Plus的LambdaQueryWrapper也很好用。比如做人员列表的条件查询姓名和状态都可能为空用条件构造器配合StringUtils.hasText()判断就能避免拼接SQL时空条件的坑。LambdaQueryWrapperIsolationPerson wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.hasText(personName), IsolationPerson::getName, personName) .eq(personStatus ! null, IsolationPerson::getStatus, personStatus) .orderByDesc(IsolationPerson::getCreateTime);用LambdaQueryWrapper还有一层好处就是编译期能检查字段名字段写错了会直接编译报错不会等到运行期才告警这一点对开发效率影响非常大。3.4 登录鉴权与角色权限的控制实现认证方案我选了JWT无状态方案没有用Session主要考虑到前后端分离架构下Session跨域处理麻烦而JWT天然无状态前端存储token每次请求在请求头里携带即可。流程是这样用户登录成功后后端生成token返回给前端前端后续请求在请求头中加入Authorization: Bearer token后端拦截器解析token获取用户ID和角色放行或拒绝请求。生成token我用的是hutool工具包的JWTUtil省去手写JJWT的繁琐代码。token里只放user id和role两个字段过期时间设置为24小时。public class JwtUtils { private static final String SECRET your-secret-key; public static String generateToken(Long userId, String role) { return JWT.create() .setPayload(userId, userId) .setPayload(role, role) .setExpiresAt(DateUtil.offsetHour(new Date(), 24)) .setKey(SECRET.getBytes()) .sign(); } public static JWTValidator validateToken(String token) { return JWTValidator.of(token).validateAlgorithm(JWTSignerUtil.hs256(SECRET.getBytes())); } }权限控制通过拦截器实现拦截所有/api/**请求白名单放行登录接口。拦截器里解析token后把用户信息放入ThreadLocal后续Service层就能直接获取当前操作人是谁。说实话JWT的坑也有。最典型的问题是token无法主动失效如果用户修改了密码旧token在24小时内仍然有效。我的解决方案是引入一个token_version字段用户修改密码时版本号加1JWT中携带版本号拦截器对比数据库中的版本号不一致就拒绝访问。这个设计在文档里没有但我觉得对于真实交付系统来说很必要。3.5 核心业务状态流转的实现隔离人员从登记到解除状态流转必须严谨。我的设计是状态字段加上操作时间的联动。登记入住时如果房间为空闲就同时执行三个操作更新人员状态为隔离中更新房间状态为占用写入一条操作日志。这三个操作必须在一个事务里否则就会出现人员已登记但房间没被占用的数据不一致问题。Transactional(rollbackFor Exception.class) public ResultVoid checkIn(CheckInRequest request) { // 校验房间状态 Room room roomMapper.selectById(request.getRoomId()); if (!FREE.equals(room.getStatus())) { return Result.error(该房间当前不可用); } // 登记入住 IsolationPerson person new IsolationPerson(); person.setName(request.getName()); person.setRoomNo(room.getRoomNo()); person.setStatus(1); person.setCheckInTime(LocalDateTime.now()); isolationPersonMapper.insert(person); // 占用房间 room.setStatus(OCCUPIED); roomMapper.updateById(room); // 写日志 OperationLog log new OperationLog(); log.setContent(人员【 person.getName() 】入住房间【 room.getRoomNo() 】); operationLogMapper.insert(log); return Result.success(null); }Transactional注解强调了事务边界。如果这里不加事务第二步房间更新失败后第一步已经写入了数据库后面再做解除隔离、统计报表数据就会错乱。这类状态型业务一定要把事务放在Service层在Controller层加事务注解是无效的因为事务要通过Spring代理生效Controller交给Service调用的入口在代理对象内部多层的调用链容易出现代理失效。解除隔离的流程同理需要同时更新人员状态为已解除、房间状态为消杀中、生成解除记录。我把解除逻辑和登记逻辑做了对称设计保证状态流转闭环。4. 前端实现Vue3 Element Plus怎么把后台页面做顺手4.1 Vite初始化与项目目录划分前端我用Vite创建Vue3项目命令是npm create vitelatest isolation-admin -- --template vue。选Vite不选webpack的原因很直接Vite的开发服务器冷启动几乎秒开热更新速度也快Vue3官方推荐的脚手架就是Vite。项目目录结构我按后端模块做了对应划分views下面每个模块一个文件夹每个文件夹里放index.vue和对应的子组件。这样维护起来非常直观后端的接口路径和前端页面的目录结构一一对应。src/ ├── api/ # 接口请求封装 ├── assets/ # 静态资源 ├── components/ # 通用组件 ├── router/ # 路由配置 ├── stores/ # Pinia状态管理 ├── views/ # 页面组件 │ ├── login/ │ ├── dashboard/ │ ├── person/ │ ├── room/ │ ├── health/ │ ├── material/ │ └── notice/ └── utils/ # 工具方法4.2 路由守卫与权限控制后台管理系统的路由不能随便访问没登录的人直接访问首页必须被拦截跳转到登录页。这个逻辑放在路由守卫里实现。router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.path /login) { next() } else if (!token) { next(/login) } else { next() } })除了登录状态角色权限我用了前端按钮级控制。比如只有管理员能看系统用户管理页面医护人员只能看到健康管理和自己的工作台。方案是在Pinia的userStore里存当前用户的role然后页面上通过v-if判断是否渲染特定按钮或菜单项。后端的接口也要做同样的权限校验前端控制只是提升体验真正保证安全的是后端。4.3 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 Bearer ${token} } return config }) service.interceptors.response.use(response { const res response.data if (res.code 200) { return res } else { ElMessage.error(res.message) return Promise.reject(new Error(res.message)) } }, error { if (error.response error.response.status 401) { localStorage.removeItem(token) router.push(/login) } else { ElMessage.error(网络请求异常请稍后重试) } return Promise.reject(error) })这里重点说下401处理。JWT过期后后端拦截器会返回401状态码前端此时要做的就是清掉本地token并跳回登录页。如果不在这个统一拦截器里处理那每个页面都要单独判断登录失效代码量会成倍增加。统一拦截器一次性解决这个问题这也是后端返回状态码要统一的原因。接口管理上我把每个接口导出为独立的函数页面上只关心中间调用逻辑不关心URL拼接。这种把接口层、页面组件层、状态管理层分离的做法在系统功能扩展时会非常受益。比如以后后端接口地址变了只需要改api目录下对应文件就行页面代码完全不用动。4.4 核心页面的交互设计以人员登记与健康上报为例人员登记页面是这个系统里最复杂的表单。字段有姓名、证件号、手机号、来源地、房间选择、计划解除时间。表单验证直接用Element Plus的表单校验规则手机上必须符合11位数字证件号按身份证格式做正则校验。房间选择用级联选择器实现先选楼栋再选楼层最后选具体房间。关键点是房间下拉框的数据应该只显示空闲状态的前端实时请求房间列表根据status字段过滤。房间列表数据量一般不大直接查询全部再在前端过滤也能接受但更标准的做法是后端提供room/freeList接口只返回空闲房间。健康上报页面的重点在于连续记录。前端做成一个日期选择器加表单的结构用户选择日期填报体温、是否咳嗽、是否乏力。后端保存时会自动关联当前人员和登录用户不需要前端传用户ID。这个细节很重要如果前端能把用户ID传给后端攻击者就能伪造别人的健康记录。所以后端一定要从token里取用户信息不能相信前端传过来的任何用户标识。数据统计页面我用了ECharts绘制体温趋势折线图和每日新增隔离人员柱状图。ECharts的Vue3封装库叫vue-echarts使用起来非常方便。要注意的是ECharts图表的容器必须有明确高度否则图表初始化后高度为0什么都看不到。这个是ECharts最常见的坑比配置项出错还多。4.5 前端联调的跨域问题处理开发阶段前端跑在5173端口后端跑在8080端口两者之间跨域。最方便的方案是在Vite的配置文件里配代理而不是在后端开CORS。代理的好处是前端请求的URL是相对路径后面部署到同一域名下时不需要改任何代码。server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }后端也可以配置CorsFilter但我在项目里还是推荐用Vite代理。原因很简单开发环境用代理生产环境把前端打包后的dist目录和后端jar包放在一起由SpringBoot统一提供静态资源服务天然就没有跨域问题。前后端分离部署另说但课程设计和毕设项目一般都部署在一台机器上代理方案最省心。5. 环境准备与部署实测记录5.1 MySQL8.0安装与初始化记录我这次用的是在Docker中安装MySQL8.0的方式部署起来非常干净。Docker安装MySQL8.0的关键参数是端口映射、root密码、数据目录挂载。docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDyourpassword \ -e TZAsia/Shanghai \ -v /opt/mysql-data:/var/lib/mysql \ mysql:8.0这个命令有几个值得注意的地方。-e TZAsia/Shanghai指定时区不加的话MySQL默认使用UTC时区这样会导致数据库存储的当前时间和北京时间差8个小时。数据目录挂载到宿主机是为了容器删除重建时数据不丢失。当然如果你本机已经装了MySQL直接用本机的也可以。我的建议是装原生版还是Docker版看你自己情况关键是数据库版本必须是8.0因为MyBatis-Plus的DbType.MYSQL和driver的配置都跟版本强相关。初始化数据库时执行建表脚本然后记得确认用户表权限。如果用的是root用户连接一般没问题但如果新建了专门用户需要授权CREATE USER isolation% IDENTIFIED BY isolation123; GRANT ALL PRIVILEGES ON isolation_db.* TO isolation%; FLUSH PRIVILEGES;5.2 后端配置文件实测清单后端配置文件application.yml是项目能跑起来的命脉我把关键配置项逐项列出来。首先是数据源配置MySQL8.0的driver-class-name是com.mysql.cj.jdbc.Driver不是老版的com.mysql.jdbc.Driver写错就启动失败。URL里必须带serverTimezoneAsia/Shanghai否则连接会报时区错误。server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/isolation_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: yourpassword jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0mybatis-plus.configuration.log-impl这里我开了SQL日志打印开发阶段能直观看到每条执行的SQL语句排查问题非常方便。等打包上线前可以注释掉避免日志刷屏。逻辑删除配置也很关键deleted字段配合逻辑删除配置后MyBatis-Plus在执行delete操作时会自动转为update语句更新deleted为1查询时会自动加上deleted0条件。这个机制避免了物理删除导致的历史数据无法追溯问题。5.3 前端打包与服务集成开发完成后前端需要打包成静态资源然后放到后端工程里统一提供服务这样整个系统只需要启动一个Java进程就能访问。npm run build构建完成后dist目录下就是打包产物。把dist目录里的内容复制到SpringBoot项目的src/main/resources/static目录下重新打包后端即可。mvn clean package -DskipTests java -jar target/isolation-system.jar启动成功后访问http://localhost:8080就能看到登录页面。前端静态资源交给SpringBoot托管后路由需要做处理。Vue Router默认使用history模式直接访问/person会返回404。我的做法是使用hash模式URL带#号虽然不太美观但最大的好处是不需要额外的服务器配置。毕设答辩阶段演示系统时hash模式是最稳妥的选择。部署过程中实测需要特别留意的就是jar包运行时不能直接双击打开要用命令行java -jar启动。如果想在服务器后台运行用nohup java -jar target/isolation-system.jar app.log 21 把日志输出到文件中方便排查问题。6. 常见问题与排查技巧实录6.1 MySQL8.0连接报错实战排查我在联调阶段遇到最多的问题就是数据库连接失败。总的来说有三类错误最典型。第一类是Public Key Retrieval is not allowed解决方案是连接URL加allowPublicKeyRetrievaltrue另外把useSSLfalse也加上测试环境能省很多麻烦。第二类是Unknown database isolation_db说明数据库没创建或名字拼错在SQL客户端中执行SHOW DATABASES;确认。第三类是Access denied for user说明用户名密码错误或授权不到位重新授权即可。时区错误也是高频问题错误信息里包含serverTimezone字样。出现这个问题说明连接URL里没带时区参数在上面5.2节的配置里已经解决了。6.2 MyBatis-Plus的常见误用第一个坑是分页插件没配置查出的数据不是分页结果而是全表数据。这属于配置遗漏加上PaginationInnerInterceptor就好了。第二个坑是逻辑删除配置了但实体类字段名不匹配提示找不到deleted字段检查实体类是否标注了TableLogic注解或全局配置中logic-delete-field是否和实体字段名一致。第三个坑是条件构造器直接new QueryWrapper后暴露SQL注入风险我建议统一使用LambdaQueryWrapper或LambdaUpdateWrapper用方法引用代替字符串列名既安全又能编译期发现问题。还有一个小技巧MyBatis-Plus的selectCount方法传入null参数表示查询全表但没人管的话清空表数据很危险。我对这个项目中所有统计类操作都显式传入条件构造器而且加上了deleted0条件避免把逻辑删除的数据也算进去。6.3 前后端跨域与鉴权联调问题前端页面打开后接口报跨域首选方案就是检查Vite的proxy配置确认target地址是否正确端口是否对应后端实际启动端口其次确认后端Controller的RequestMapping前缀是否和proxy的/api前缀一致不一致会导致代理转发后404。另外还要检查SpringBoot的context-path设置如果配置了context-path后端接口的实际访问路径会变长代理也要相应调整。登录接口调用成功但拿不到用户信息优先检查后端拦截器排除名单是否写对。如果拦截器拦截了/api/user/login登录请求直接401那问题就很明显。JWT解析报错通常是密钥不一致导致的检查前端拿到的token和后端生成token时用的密钥是否匹配。6.4 前端开发调试的那些坑Vue3开发过程中最容易出问题的是响应式数据的误用。如果你的数据在页面中修改后视图不更新多半是用普通变量代替了ref或reactive这是Vue3响应式原理的基本功。另外在Composition API中直接在setup函数里写setTimeout并修改数据对象属性不需要额外处理响应式但如果用了解构赋值把响应式对象拆成普通变量那修改就失效了。表格数据加载不出来时先用浏览器开发者工具的Network面板看接口是否返回正常数据重点看后端返回的data字段结构是否符合el-table的prop绑定。常见错误是后端返回字段名是createTime前端绑定的是create_time两者不一致导致表格列全空。Element Plus组件库版本要注意和Vue版本匹配。Element Plus不支持Vue2必须用Vue3项目。如果引入后页面空白且控制台报组件未注册错误检查是否忘记了app.use(ElementPlus)。7. 这套源码里的隐藏价值与后续扩展建议说实话这套系统最值得研究的部分不是功能有多花哨而是它完整展示了SpringBoot2 Vue3从零搭建一个后端管理系统的标准流程。表结构设计、统一结果封装、通用CRUD抽象、JWT鉴权、前端路由守卫、Axios拦截器这些都是以后不管做什么管理类系统都会反复用到的技术点。把这套源码吃透你就能举一反三。如果后续想给这个项目加分我个人建议优先扩展三个方向。第一个是Excel导入导出功能人员批量登记、健康记录导出用EasyExcel就能实现这个功能在答辩演示时非常出效果。第二个是ECharts大屏展示把隔离人员趋势、房间占用率、物资消耗汇总做成可视化看板整个系统的档次一下就上去了。第三个是Redis缓存优化给用户权限和房间列表加上缓存提升接口响应速度。技术学习这块做完整个项目之后我的最大感受是一定要自己动手把整个流程走通一遍。看别人分享的crud封装、分页配置、jwt拦截器看的时候觉得很简单真正动手时才发现一堆细节问题。特别是事务边界放在哪一层、Vite代理怎么配、静态资源如何统一部署这些问题只有自己操作一次才能真正记住。最后再分享一个小技巧项目开发过程中每完成一个模块记得把运行效果截图保存下来。无论是写课程设计文档还是答辩PPT这些截图都是最有说服力的素材。另外数据库设计文档用Navicat或DataGrip导出一份Word版连注释一起导出这个文档比你自己重新画表结构快得多而且格式规范导师看了也会觉得你做得扎实。