
接手这类项目最怕的不是写代码而是拿到需求后发现它远不止“增删改查”。隔离管理听起来就是一个简单的信息登记系统可真正把业务捋一遍会发现人员流转、每日健康监测、物资出入库、房间分配、角色权限、审批流程全都耦合在一起。我最终用SpringBoot2Vue3MyBatis-PlusMySQL8.0把这套系统完整落地后端依靠MyBatis-Plus的通用CRUD几乎没有手写几条SQL前端用Vue3的组合式API写起来也比Vue2顺手很多。这篇文章把我从需求拆解到部署配置、再到文档整理的完整过程写出来给正在做毕设、课设或者想拿一套经典前后端分离项目练手的朋友一个可复用的参考。1. 先理清隔离管理系统的业务边界与数据流1.1 核心角色与功能边界任何管理系统第一步都不是建表而是搞清楚谁在用、用他的核心动作是什么。这套系统里我划分了四类角色超级管理员、隔离点管理员、医护人员、普通坐席用于信息录入与查询。超级管理员管的是全局隔离点的新建与停用、管理员的账号分配、全系统数据查看。隔离点管理员是业务的核心使用者负责本点位的房间管理、隔离人员入点登记、物资出入库审核、解除审批。医护人员只做一件事每天录入隔离人员的健康指标包括体温、血压、血氧、症状描述。普通坐席更简单只读查询和数据录入不涉及任何审批动作。功能边界想清楚之后数据表的结构就自然出来了用户表、角色表、隔离点表、房间表、隔离人员表、健康监测记录表、物资分类表、物资出入库单、审批记录表。每张表对应一个完整业务闭环互相之间的外键关系在业务层维护而不是依赖数据库的物理外键这一点后文会展开说。1.2 一个隔离人员的完整状态流转我习惯在写代码前画出核心实体的状态机因为状态流转直接决定页面按钮的显隐和后端接口的权限判断。隔离人员的状态我定义为六个待入点、在点隔离、健康监测中、待解除、已解除、已转移。人员到达隔离点后管理员执行入点登记填写基础信息、分配房间状态变为“在点隔离”。每天医护人员录入健康指标当连续若干天指标正常、到达可解除条件时系统自动将该人员列入“待解除”列表。管理员发起解除审批审批通过后状态变成“已解除”房间空出。若中间出现健康异常则进入“已转移”状态由专门流程处理。状态机的好处是所有接口的权限校验都围绕它展开。比如“待解除”状态下普通坐席不能修改人员信息“已解除”状态下不能再录入健康监测数据。这些规则落在后端Service层做统一判断而不是散落在各个Controller里。1.3 为什么说这不是普通CRUD如果没有业务约束隔离人员表确实就是一张CRUD表新增、删除、改一下、查列表。但真实场景下要处理的是房间容量和已入住人数的联动、物资出库后库存字段的扣减、解除审批通过后人员状态和房间状态的同步更新。这些规则意味着事务边界必须被认真设计。我举一个具体例子入点登记时前端提交了一个人员JSON加一个房间ID后端做的事包括校验房间是否存在、容量是否已满、同一身份证是否已在点隔离然后插入人员记录、将房间已住人数加一、记录一条入点操作日志。这个操作必须在一个事务里完成任何一个环节失败都要整体回滚。我当时在Service层用Transactional包裹整个方法避免了多次调用Mapper造成的分布式一致性隐患。2. 技术栈为什么落在SpringBoot2Vue3MyBatis-PlusMySQL8.02.1 后端选型考量先说SpringBoot2。做这种中小型管理系统SpringBoot2的优势是生态极其成熟网上资料多到根本看不完遇到问题基本都能搜到现成答案。特别是毕业设计这个场景答辩老师大概率会问“为什么用SpringBoot”标准答案就是自动配置简化了Spring的XML配置、内嵌Tomcat让部署变成一条命令、配合Starter可以快速集成第三方组件。MyBatis-Plus在这套组合里承担的是数据访问层。选它的核心原因是多表CRUD和复杂查询的平衡单表操作完全不需要写SQL自带BaseMapper和IService接口LambdaQueryWrapper可以链式构造查询条件写出来的代码几乎不会出现SQL字符串拼接的安全问题。而真正复杂的统计报表SQL又可以手写XML塞进Mapper灵活性和开发效率两头都占了。MySQL8.0的选型则是顺理成章的。8.0默认字符集是utf8mb4对手机号、姓名、表情符号这类特殊字符的支持比5.7更省心窗口函数在做健康趋势分析时能用上云服务器或者Docker容器安装8.0的教程也最丰富踩坑成本低。2.2 前端技术栈考量前端我选了Vue3没有继续用Vue2原因很直接组合式API把同一逻辑的代码聚拢在一起写业务组件时不再需要把data、methods、computed拆到不同选项里代码可维护性明显更高。Vue3对TypeScript的支持也更友好虽然这个项目我全用了JavaScript但后续如果团队要上TS迁移成本很小。UI框架用的是Element Plus。它是Element UI的Vue3版本表格、表单、弹窗、上传、分页这些管理后台高频组件都是现成的样式一致性强不用自己做样式设计。状态管理用了Pinia比Vuex的API更简洁store定义几乎不需要模板代码。构建工具直接上Vite开发模式下热更新快到几乎无感比Webpack体验强一个级别。2.3 几个版本细节说明版本问题是最容易在开头就挖坑的地方。我当时项目用的是SpringBoot 2.7.x、MyBatis-Plus 3.5.x、Vue3.2.x、Element Plus 2.x、MySQL8.0.28。这里要特别提醒如果有条件JDK统一用1.8或11别直接上17因为部分老版本依赖在JDK17下会有模块访问问题。当时换到1.8后所有反射相关的警告都消失了。MyBatis-Plus从3.5.3开始分页插件写法变成了MybatisPlusInterceptor加PaginationInnerInterceptor老教程里的PaginationInterceptor已经废弃。Element Plus不再支持Vue2所以千万别在Vue3项目里装element-ui必须是element-plus。这是一个非常经典且容易踩的依赖混淆问题。版本锁定后我用一个pom.xml统一管理后端依赖版本前端用package.json锁死主版本这样任何一台新机器克隆下来都能跑起来不至于因为依赖漂移浪费半天。3. 后端落地从建表到通用CRUD和鉴权的一整套做法3.1 工程结构与依赖后端工程我按常见的分层结构组织controller、service、mapper、entity、common、config、dto、vo。entity是数据库表对应的实体dto负责接收前端参数vo负责返回给前端的数据这样避免直接把数据库实体暴露给接口调用方。核心依赖就四个spring-boot-starter-web、mybatis-plus-boot-starter、mysql-connector-java、jjwt。没有额外引入Spring Security因为这套系统的权限模型比较轻量我选择了更可控的方式登录成功后签发JWT写一个拦截器校验Token和角色权限代码量不大但完全够用。有答辩老师问到时我给出的解释是Spring Security功能强大但配置复杂度高对于角色数量少、接口权限明确的场景JWT加拦截器是更快的解法也更容易把权限逻辑讲清楚。3.2 实体类和通用CRUD实体类的写法直接决定后续开发效率。我给每张表都配了id、create_time、update_time、deleted四个公共字段。id用数据库自增实体上标TableId(type IdType.AUTO)deleted用TableLogic标注这样MyBatis-Plus做删除操作时自动改成update语句更新删除标记查询时自动追加deleted 0条件彻底不用自己拼SQL。一个典型的Service实现类长这样Service public class IsolatedPersonServiceImpl extends ServiceImplIsolatedPersonMapper, IsolatedPerson implements IsolatedPersonService { Autowired private RoomService roomService; Transactional(rollbackFor Exception.class) public void checkIn(CheckInDTO dto) { Room room roomService.getById(dto.getRoomId()); if (room null) { throw new BusinessException(房间不存在); } if (room.getOccupied() room.getCapacity()) { throw new BusinessException(该房间已满员); } long count lambdaQuery() .eq(IsolatedPerson::getIdCard, dto.getIdCard()) .count(); if (count 0) { throw new BusinessException(该身份证已登记请勿重复入点); } IsolatedPerson person new IsolatedPerson(); BeanUtils.copyProperties(dto, person); person.setStatus(IsolatedStatus.IN_QUARANTINE.getCode()); this.save(person); room.setOccupied(room.getOccupied() 1); roomService.updateById(room); } }ServiceImpl自带save、updateById、getById、lambdaQuery这些方法单表操作几乎没有模板代码。通过继承ServiceImpl、实现自定义接口再加Service三行声明就能获得整套通用方法这也是MyBatis-Plus最值钱的地方。3.3 自动填充与逻辑删除公共字段如果每次都手动set既啰嗦又容易漏。MyBatis-Plus提供了MetaObjectHandler做字段自动填充。我实现了一个组件在插入时自动填createTime和updateTime更新时自动填updateTime并把操作人的ID也顺手填进去这样后续审计追踪都不需要额外写逻辑。逻辑删除的坑在于如果某张表有联合唯一索引比如隔离人员表对id_card建了唯一索引一旦删掉一条记录这个身份证就永远无法重新登记了。解决办法是字段设计时预留一个delete_remark字段或者把唯一索引设计成(id_card, deleted)这样被逻辑删除的记录只是不再被查询出来但索引的互斥性还在。这个细节我在项目中期踩过排查了很久后来改表结构加联合索引才解决。3.4 分页与条件查询列表页是所有管理系统的重头戏。MyBatis-Plus的分页插件配置如下Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }配置完之后Service层写分页查询非常简单public PageResultIsolatedPersonVO pageQuery(PersonPageQuery query) { PageIsolatedPerson page new Page(query.getPageNum(), query.getPageSize()); LambdaQueryWrapperIsolatedPerson wrapper new LambdaQueryWrapper(); wrapper.eq(StrUtil.isNotBlank(query.getName()), IsolatedPerson::getName, query.getName()) .eq(query.getStatus() ! null, IsolatedPerson::getStatus, query.getStatus()) .orderByDesc(IsolatedPerson::getCreateTime); PageIsolatedPerson result this.page(page, wrapper); return convertToPageResult(result); }LambdaQueryWrapper用方法引用代替字段字符串代码里搜字段名时能直接跳转重构字段名也不会出现SQL报错。基于它实现的通用CRUD服务结合泛型还能进一步封装成BaseController把常见的增删改查接口抽到父类里子类只写业务扩展点。3.5 登录鉴权与全局异常处理登录流程是账号密码校验通过后用JWT生成一个包含userId和roleCode的Token设置24小时有效期返回前端。拦截器里校验Token合法性同时把解密后的用户信息放入ThreadLocal方便后续Service层获取当前操作人。角色权限的校验我写在自定义注解RequireRole(admin)上用拦截器统一处理避免了在Controller里到处重复判断。全局异常处理用RestControllerAdvice统一兜底。业务异常返回中文错误信息参数校验失败返回字段级错误未知异常返回友好提示并记录日志。这样做的好处是前端axios只看统一的code、message、data结构不需要为每个接口单独处理异常分支。4. 前端落地Vue3工程化、权限路由与动态表单4.1 项目脚手架前端工程我直接用Vite创建npm create vitelatest frontend -- --template vue然后安装element-plus、pinia、vue-router、axios。进入main.js后全量引入Element Plus虽然按需引入能减小打包体积但做后台管理系统时全量引用更省心首屏性能的损耗在这个体量下可以忽略。目录结构上我按api/、router/、stores/、views/、components/、utils/组织。每个业务模块在api/下建一个同名文件统一导出接口函数页面里不出现任何URL字符串。这样前后端接口路径发生变更时只需要改一个文件。4.2 axios封装与请求拦截axios封装是前端工程化的基本功。我在utils/request.js里创建了一个axios实例设置了baseURL和超时时间请求拦截器里从Pinia的store中取Token放进Header响应拦截器里统一处理三个情况HTTP 200且业务code为0时直接返回datacode为401时跳登录页并清除本地缓存其他code统一弹出错误提示。这里有个经验不要把后端返回的大JSON整体传给页面而是在拦截器里直接剥掉外壳页面拿到的是干净的response.data.data。页面代码看起来非常清爽谁的业务逻辑出错就找谁前端不需要关心后端的返回包装。后端被问“接口怎么不统一”时也有标准答案所有Controller返回ResultT结构分页返回PageResultT。4.3 权限路由与菜单菜单权限在前端有两种做法一种是后端返回菜单树前端动态生成路由另一种是前端把所有路由定义好后端只返回角色编码前端根据角色编码过滤菜单。我选了第二种理由是这个系统角色只有四类菜单层级固定动态生成路由反而引入不必要复杂度。具体实现是定义一份菜单配置每一项带roles字段登录后从接口拿到当前用户的角色前端用router.addRoute动态注册有权限的路由同时根据这份配置生成侧边栏菜单。核心判断逻辑写在router/permission.js里路由切换前检查Store中是否存在用户信息不存在就跳登录页。这套逻辑在答辩时很好讲前端的权限控制是体验层的拦截真正的安全校验在后端接口层两端都有兜底。4.4 页面落地难点在表单动态行列表页的开发模式是固定的搜索区、表格区、分页区、弹窗表单区。我先把ElTable和ElPagination拼好然后封装一个带搜索条件、分页变化、数据请求的loadData方法页面只需要调用一次搜索按钮和重置按钮分别触发条件变更和重新加载。比较棘手的是物资入库单这种包含多行明细的表单。用户希望点“新增一行”就能临时加一条物资记录这一需求在Vue3里可以用reactive数组加上模板循环来实现template div v-for(item, index) in form.items :keyindex classitem-row el-select v-modelitem.materialId placeholder选择物资 / el-input-number v-modelitem.quantity :min1 / el-button clickremoveRow(index)删除/el-button /div el-button clickaddRow新增一行/el-button /templateaddRow里form.items.push({ materialId: null, quantity: 1 })removeRow里splice(index, 1)。因为form是reactive创建的数组元素的增删会自动驱动视图更新。刚开始写Vue3的人容易把form.items也用ref包一层结果模板里要写form.value.items其实完全没必要。元素绑定用v-model配合index就能做到每行独立的数据通道提交时直接把整个数组传给后端。这页面上还用到watch监听房间ID变化后联动查询该房间的容量和已住人数动态禁用超出容量的选择项。Vue3的watch开销比Vue2更小也不再需要deep: true这种参数监控一个响应式对象的属性变化非常直觉。整个系统跑下来说明Vue3的这些API不是花架子而是真正能简化业务代码的。5. MySQL8.0部署配置里最容易卡住的地方5.1 Docker快速初始化本地开发最省事的办法是直接用Docker起一个MySQL8.0实例一条命令搞定docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD123456 \ -e TZAsia/Shanghai \ -v /opt/mysql-data:/var/lib/mysql \ mysql:8.0 \ --character-set-serverutf8mb4 \ --collation-serverutf8mb4_unicode_ci参数里的TZAsia/Shanghai很关键。如果不设置容器时区MySQL的系统时区默认是UTC而Java应用服务器在Asia/Shanghai两边一对比数据库里的NOW()和CURRENT_TIMESTAMP返回的时间会差8个小时。镜像启动时还可以顺手指定字符集和排序规则避免建库后忘记设置导致中文乱码。5.2 本机安装与连接工具如果你不想用Docker本机安装MySQL8.0也简单。Windows下下载安装包后选Developer Default组件集配置端口、root密码和字符集一样不能少。安装完在Navicat里测试连接如果提示Authentication plugin caching_sha2_password cannot be loaded说明客户端版本太老需要升级Navicat到16以上版本或者把MySQL密码认证方式临时改回mysql_native_password。还有一次遇到远程连不上数据库的情况防火墙把3306端口挡了。这种问题排查顺序要固定第一步telnet IP 3306测试连通性通了再看账号权限最后看SSL和认证插件。我习惯于给项目建专用账号并限定网段而不是拿root直连例如CREATE USER isolation_user% IDENTIFIED BY Isolation2024; GRANT SELECT, INSERT, UPDATE, DELETE ON isolation_db.* TO isolation_user%; FLUSH PRIVILEGES;这样即使账号密码泄露影响范围也仅仅在项目库不至于把整个MySQL实例交出去。5.3 JDBC连接串参数逐个排雷后端连接MySQL8.0时JDBC连接串里的几个参数必须搞清楚含义spring: datasource: url: jdbc:mysql://localhost:3306/isolation_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: isolation_user password: Isolation2024 driver-class-name: com.mysql.cj.jdbc.DriveruseSSLfalse本地开发不加密数据库连接避免MySQL8默认开启SSL导致握手失败。serverTimezoneAsia/Shanghai解决时区偏差问题。Java的“东八区”概念和数据库的系统时区对应不上时日期字段会出现整批偏移。allowPublicKeyRetrievaltrueMySQL8默认使用caching_sha2_password认证第一次连接时如果不允许公钥检索会直接报Public Key Retrieval is not allowed。driver-class-name必须是com.mysql.cj.jdbc.Driver老版本的com.mysql.jdbc.Driver在MySQL8下虽然能连但会持续打印弃用警告不如一次到位。5.4 建库建表与初始化数据的注意点建库时我推荐直接写SQL脚本而不是在数据库工具里手动点。脚本的好处是可重复执行团队协作时不会出现“我这能跑你那不行”的情况。初始化脚本里除了建表还要插入管理员账号、基础角色、物资分类、隔离点初始数据。比较适合演示的数据量是每个表有二三十条真实感强的测试数据不要只灌一条否则看分页和筛选效果时页面空荡荡。我写了一个data.sql里面包含了10个隔离点、各点位5个房间、每个房间1到2个隔离人员、近7天的健康监测记录这样演示人员列表和健康趋势时表格翻页、条件筛选、图表展示都有素材。这套初始化数据的脚本后来在客户现场演示时也起了大作用。6. 文档、演示答辩与二次开发扩展6.1 项目文档里真正有用的部分标题里写着“含文档”这份文档我分成四个部分需求说明书、数据库设计文档、接口文档、部署手册。需求说明书不是最复杂但最容易被忽略它把角色、功能模块、状态流转画成图表答辩时直接投影出来就能讲清楚业务理解。数据库设计文档包含每张表的字段说明、ER图、索引设计理由。接口文档我用Postman导出的格式再加中文注释前端开发时对着调用即可。部署手册写完直接扔给运维或老师照着敲命令就能跑。写文档有一个反常识的做法边开发边写而不是全部开发完再补。否则到项目后期你根本记不清某个接口为什么设计成那样写出来的文档会失真。我习惯每天开发完花半小时同步一次文档项目结束时文档已经接近完整只剩个别细节需要打磨。6.2 演示答辩怎么讲演示系统的核心是讲故事而不是挨个页面过功能。我给的演示路径是从登录开始讲角色权限进入管理员页面讲隔离点管理然后讲一个人从入点登记到健康监测、再到解除审批的完整生命周期最后打开物资模块讲库存和出入库。这条路径把状态机的设计、权限的区别、事务的实现全串起来了。答辩中被问最多的三个问题是为什么用MyBatis-Plus而不是JPA、为什么不用Spring Security、数据库为什么选MySQL8.0。我的回答分别落在开发效率、权限模型的复杂度匹配、以及字符集和性能特性上。回答时要有对比意识不要只说“好用”而是要说明你对比过什么为什么是这个选项。6.3 后续扩展方向系统跑通之后我闲着没事加了一个ECharts的健康趋势图组件直接统计某个隔离点近一周的体温和血氧走势。加上之后项目的可看性立刻提升了一个档次答辩时的演示效果也变得更好。类似的扩展还有导出Excel隔离人员台账、用Quartz写定时任务提醒待录入健康数据的房间、增加操作日志表记录所有敏感操作。这些扩展点对应的技术也正好是面试时经常被追问的方向。如果要继续往工程化方向走可以引入Spring Security OAuth2做更细粒度的权限控制把前端打包产物丢到Nginx下与后端分离部署再给MySQL配置主从复制提高可用性。这套骨架的底子够好换个业务场景比如酒店管理系统、小区物业系统只需替换业务表和对应的页面整体架构不需要大的改动。我个人做下来的最大感受是这套项目的价值不在“疫情隔离”这四个字的业务属性上而在于它完整覆盖了一个前后端分离管理系统从设计到落地的所有关键环节。事务、权限、状态机、通用CRUD、动态表单、数据库部署每一个拿出来都是面试会问、做项目必踩的点。如果你正在找一套能同时兼顾毕设交付和简历亮点的项目按这个技术组合去搭基本不会走弯路。最后分享一个小技巧数据库初始化脚本里一定要放几个真实感强的测试数据开发期联调和答辩演示都会轻松很多。