news 2026/10/11 4:28:59

基于SpringBoot2+Vue3+MyBatis-Plus的隔离管理系统开发实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于SpringBoot2+Vue3+MyBatis-Plus的隔离管理系统开发实践

接手这类项目,最怕的不是写代码,而是拿到需求后发现它远不止“增删改查”。隔离管理听起来就是一个简单的信息登记系统,可真正把业务捋一遍,会发现人员流转、每日健康监测、物资出入库、房间分配、角色权限、审批流程全都耦合在一起。我最终用SpringBoot2+Vue3+MyBatis-Plus+MySQL8.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. 技术栈为什么落在SpringBoot2+Vue3+MyBatis-Plus+MySQL8.0

2.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 ServiceImpl<IsolatedPersonMapper, 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 PageResult<IsolatedPersonVO> pageQuery(PersonPageQuery query) { Page<IsolatedPerson> page = new Page<>(query.getPageNum(), query.getPageSize()); LambdaQueryWrapper<IsolatedPerson> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(StrUtil.isNotBlank(query.getName()), IsolatedPerson::getName, query.getName()) .eq(query.getStatus() != null, IsolatedPerson::getStatus, query.getStatus()) .orderByDesc(IsolatedPerson::getCreateTime); Page<IsolatedPerson> 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 vite@latest 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时直接返回data;code为401时跳登录页并清除本地缓存;其他code统一弹出错误提示。

这里有个经验:不要把后端返回的大JSON整体传给页面,而是在拦截器里直接剥掉外壳,页面拿到的是干净的response.data.data。页面代码看起来非常清爽,谁的业务逻辑出错就找谁,前端不需要关心后端的返回包装。后端被问“接口怎么不统一”时也有标准答案:所有Controller返回Result<T>结构,分页返回PageResult<T>。

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" :key="index" class="item-row"> <el-select v-model="item.materialId" placeholder="选择物资" /> <el-input-number v-model="item.quantity" :min="1" /> <el-button @click="removeRow(index)">删除</el-button> </div> <el-button @click="addRow">新增一行</el-button> </template>

addRow里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_PASSWORD=123456 \ -e TZ=Asia/Shanghai \ -v /opt/mysql-data:/var/lib/mysql \ mysql:8.0 \ --character-set-server=utf8mb4 \ --collation-server=utf8mb4_unicode_ci

参数里的TZ=Asia/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 'Isolation@2024'; 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?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username: isolation_user password: Isolation@2024 driver-class-name: com.mysql.cj.jdbc.Driver
  • useSSL=false:本地开发不加密数据库连接,避免MySQL8默认开启SSL导致握手失败。
  • serverTimezone=Asia/Shanghai:解决时区偏差问题。Java的“东八区”概念和数据库的系统时区对应不上时,日期字段会出现整批偏移。
  • allowPublicKeyRetrieval=true:MySQL8默认使用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、动态表单、数据库部署,每一个拿出来都是面试会问、做项目必踩的点。如果你正在找一套能同时兼顾毕设交付和简历亮点的项目,按这个技术组合去搭,基本不会走弯路。最后分享一个小技巧:数据库初始化脚本里一定要放几个真实感强的测试数据,开发期联调和答辩演示都会轻松很多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/11 4:27:55

Webpack与Vite文件处理机制深度对比:从构建模型到HMR

如果你在两套构建工具之间切换过&#xff0c;一定感受过这种撕裂感&#xff1a;同样的项目&#xff0c;webpack 冷启动可能要等二三十秒&#xff0c;改一行代码热更新也要晃两下才反应过来&#xff1b;换成 vite 之后&#xff0c;冷启动往往不到一秒&#xff0c;保存代码的瞬间…

作者头像 李华
网站建设 2026/10/11 4:26:24

内网离线安装Docker全栈方案:运行时+依赖+配置+审计一体化交付

简介&#xff1a;本资源专为内网隔离环境下的Linux系统运维人员设计&#xff0c;解决无外网连接时无法在线安装Docker及Docker-Compose的核心痛点&#xff0c;适用于政企、金融、教育等强安全管控场景的CentOS 7离线部署需求。压缩包共22个文件&#xff0c;含20个适配el7的RPM依…

作者头像 李华
网站建设 2026/10/11 4:24:39

C++ static关键字全解:存储期、链接性与线程安全初始化

如果要我在C里挑一个最容易被低估、却又最容易让人翻车的关键字&#xff0c;我大概率会选static。你去看C&#xff1a;static这种标题&#xff0c;总觉得是个入门知识点&#xff0c;好像谁都会 &#xff0c;可真要在项目里用稳了&#xff0c;能把人折腾到半夜去查链接错误、初始…

作者头像 李华
网站建设 2026/10/11 4:23:59

某宝商品搜索列表结果爬取开发指南及代码

某宝搜索结果页爬虫开发某宝列表页搜索结果提取工具采集工具&#xff0c;开发完了人家不要了&#xff0c;闲置&#xff0c;未发布软件&#xff0c;交流一下1.支持关键字、销量、信用、价格排序搜索采集2.不限量、无限翻页3.导出CVS文件无缝对接上架平台4.一键采集商品全量信息5…

作者头像 李华