ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SpringBoot+Vue3+MyBatis实战:一个可复用的企业级后台管理系统拆解

SpringBoot+Vue3+MyBatis实战:一个可复用的企业级后台管理系统拆解 先说一下这个项目的定位。整套系统不是那种“跑通就完事”的demo而是一个可以拿去改造成真实业务后台的完整工程SpringBoot负责提供接口Vue3负责页面交互MyBatis管数据库访问MySQL存业务数据前后端通过JSON格式的RESTful API通信。单看“疫情隔离管理系统”这个名字很多人会觉得业务场景已经过时了但如果你把“隔离人员”换成“入住旅客”、“租客”、“学员”这套东西就是标准的酒店管理、公寓管理、教务管理系统。所以我更愿意把它理解成一个“以健康管理为业务载体的企业级后台模板”这也是为什么我还要专门写一篇长文拆它的原因。适合谁来参考想系统学习SpringBootVue3前后端分离开发的同学、准备做毕业设计或找工作项目经验的在校生还有需要快速搭一个带权限体系的管理后台的初级工程师。文章里所有的表结构、接口设计思路、权限控制方案和部署步骤我都尽量按“可以直接抄作业”的标准来写顺带把我自己踩过的坑也一并交代清楚。1. 整体设计与技术选型思路1.1 为什么是SpringBootVue3MyBatisMySQL这套组合先说后端SpringBoot几乎是目前Java后端事实上的标准起步框架。它解决了传统SSH或SSM时代最头疼的配置地狱问题内嵌Tomcat打一个jar包就能跑配合Actuator、Spring Security这些生态组件开发效率比裸写Servlet高一个量级。这个项目用SpringBoot不是因为“流行”而是因为它的自动装配机制能让开发重心放在业务上——写Controller、Service、Mapper不需要关心Bean怎么配、事务管理器怎么注册。前端Vue3则是另一个维度的选择。Vue2时代大量项目还在用Options API写业务逻辑组件一大同一个功能的代码被拆散到data、methods、watch、computed各个角落维护起来只能靠翻代码。Vue3的Composition API把同一业务的状态和行为聚合到一块用起来更像在写“有状态的函数”配合script setup语法糖代码量比Vue2少三分之一。这个管理系统页面多、表格多、表单多用Composition API做按业务域的抽离写起来非常顺手。MyBatis的选择理由就更直接了——它比JPA更贴近SQL本身。这种管理类系统有大量多表关联查询、动态条件筛选比如按姓名、状态、日期段过滤隔离人员用MyBatis的XML文件写动态SQL控制力极强。JPA虽然可以少写SQL但碰到复杂的统计报表查询要么拼JPQL要么写原生SQL还得绕一层反而别扭。MyBatis就是那种“把数据库控制权完全交给开发者”的框架配合MyBatis-Plus还能少写大量单表CRUD后面我会细说怎么用。MySQL则没什么好争论的中小型管理系统选它成本最低运维门槛也低。5.7这个版本到现在依然是生产环境的主力8.0在窗口函数和字符集上更友好。这个项目我在开发时用的是MySQL 8.0如果读者机器上装的是5.7.44也完全没影响核心SQL没有用到8.0专属特性。1.2 系统角色与核心业务流拆解在动手写代码前先把业务角色理清楚。这套系统的用户侧可以分成三类角色核心诉求典型操作系统管理员管理整个隔离点的人员、房间、物资创建隔离人员、分配房间、发放物资、审批解除隔离医护人员关注隔离人员健康状态查看每日健康上报、登记核酸结果、标记异常人员隔离人员普通用户提交每日健康信息、查看自己的隔离信息健康打卡、查看隔离开始/结束日期如果按真实的隔离点管理流程来走核心链路是这样的人员进入隔离点后由管理员登记基本信息、分配房间和隔离周期然后每天定时提交体温和症状信息医护人员根据上报数据决定是否需要重点关注核酸结果出来后录入系统隔离期满由管理员审批解除。整条业务链涉及“人员管理-日常上报-健康评估-解除隔离”四个关键节点每一个节点在数据库里都有对应的表去承接。这个业务模型的通用性很强。你把“隔离人员”换成“入住客户”把“健康上报”换成“服务请求”就变成了酒店PMS的核心模块把“核酸结果”换成“考试分数”就是学员管理系统。这也是我推荐读者把它当模板项目去学的原因——业务外壳会过时但里子里的设计方法论不会。1.3 前后端分离架构下项目怎么分层前后端分离不是单纯把代码分成两个目录关键在契约。前端只依赖后端接口文档后端只依赖数据库表结构两边通过RESTful API对接。在这个项目里我按下面这套约定来组织前端通过/api前缀访问后端接口生产环境由Nginx把/api转发到SpringBoot服务。后端接口统一返回{code: 200, message: success, data: ...}结构前端根据code判断业务是否成功。鉴权用JWT Token前端登录后把Token存在本地每次请求由Axios拦截器自动附加到请求头。后端工程的包结构我习惯这样分com.example.isolation ├── controller // 接口层只做参数接收和结果返回 ├── service // 业务层核心逻辑全部在这层 ├── mapper // MyBatis Mapper接口对应XML里的SQL ├── entity // 数据库实体对象 ├── dto // 接收前端参数的传输对象 ├── vo // 返回给前端的视图对象 └── common // 统一返回、全局异常、工具类分层的核心原则是Controller里不写业务判断Service里不出现SQL语句Mapper里只负责数据访问。这套规矩看着简单很多人为了省事会在Controller里直接调Mapper开始觉得效率高但一旦业务逻辑复杂起来事务控制和代码复用就会变成灾难。这个项目规模不算大坚持分层写下来后续扩展新功能时很舒服。2. 数据库设计与核心表结构2.1 六张核心业务表的字段设计数据库设计是这类管理系统最见功力的部分。表设计得合理写SQL和业务逻辑的时候才能顺畅。我针对这套业务设计了六张核心表用户表sys_user——存所有登录账号字段名类型说明idbigint主键自增usernamevarchar(50)登录名唯一passwordvarchar(255)BCrypt加密后的密码roletinyint角色1管理员2医护人员3隔离人员real_namevarchar(50)真实姓名phonevarchar(20)手机号statustinyint账号状态0禁用1启用create_timedatetime创建时间隔离人员表isolation_person——隔离业务主表字段名类型说明idbigint主键user_idbigint关联sys_user可为空访客登记场景id_cardvarchar(30)身份证号唯一索引namevarchar(50)姓名gendertinyint性别ageint年龄room_idbigint关联房间表start_datedate隔离开始日期end_datedate预计解除日期statustinyint状态1隔离中2已解除3异常关注contact_personvarchar(50)紧急联系人contact_phonevarchar(20)联系人电话remarkvarchar(500)备注健康打卡表health_report——每天上报数据字段名类型说明idbigint主键person_idbigint关联隔离人员report_datedate上报日期temperaturedecimal(4,1)体温coughtinyint是否咳嗽0否1是fatiguetinyint是否乏力other_symptomvarchar(200)其他症状描述create_timedatetime上报时间核酸记录表nucleic_acid_record——检测结果登记字段名类型说明idbigint主键person_idbigint关联隔离人员test_datedate检测日期test_resulttinyint结果1阴性2阳性3待复核test_orgvarchar(100)检测机构create_timedatetime登记时间房间表isolation_room——隔离点房间管理字段名类型说明idbigint主键room_novarchar(20)房间号floorint楼层room_typevarchar(20)单人间/双人间statustinyint0空闲1占用2消毒中物资表material_record——隔离点物资领用记录字段名类型说明idbigint主键person_idbigint领取人material_namevarchar(100)物资名称quantityint数量apply_timedatetime领取时间operator_idbigint经办人2.2 设计取舍逻辑删除、状态字段与索引规划有几个设计点值得展开讲讲。首先是逻辑删除。隔离人员的数据不能物理删除——今天删了明天审计要查记录怎么办所以业务表里我没放deleted字段而是用status状态字段做标记。如果非要保留删除操作建议加一个deleted字段做逻辑删除查询时统一加where deleted 0过滤。但这里有个坑如果每张表都加逻辑删除所有SQL都要记得带条件漏一处就会把“已删除”的数据查出来。我不建议每个表都做逻辑删除只对存在审计需求的表比如人员表保留就够了。其次是状态字段的设计。isolation_person.status我用了三个值表示隔离状态但实际业务里状态是靠start_date和end_date推导出来的——到了end_date如果没人操作解除状态字段还是“隔离中”。所以我在写查询逻辑时做了兜底查询列表时根据当前日期动态计算状态不只信任状态字段。这是管理类系统很经典的一个坑日期到了但状态没更新页面显示就错了。索引比很多人想象的重要。这个项目里查询频率最高的场景是隔离人员列表按姓名、身份证、状态查以及健康打卡按人员ID、日期查。我建了下面这些索引isolation_personid_card唯一索引、room_id普通索引、status普通索引health_reportperson_id report_date联合唯一索引——同一人同一天只允许一条上报记录这个联合唯一索引直接挡住了重复上报的脏数据nucleic_acid_recordperson_id普通索引2.3 建表脚本与时区设置的注意事项建表脚本直接执行没问题但有两点必须提醒。一是字符集和排序规则。建议统一用utf8mb4和utf8mb4_general_ci别用utf8——MySQL的utf8字符集压根儿不支持完整的Unicode遇到生僻字或者表情符号会直接变问号。二是时间字段统一用datetime别有的用timestamp、有的用varchar存字符串。时间字段类型不统一后续写SQL做日期范围查询的时候就是无穷无尽的转换麻烦。关于时区还有一个容易踩的坑。如果用的是MySQL 8.0连接串里建议加serverTimezoneAsia/Shanghai否则SpringBoot连数据库可能报“The server time zone value Öйú±ê׼ʱ¼ä is unrecognized”这类错误本质是数据库时区没设置对。5.7没有这么强制但建议也加上免得Java侧的LocalDateTime和数据库存的时间相差8小时。数据库这块我多说一句写建表SQL时字段注释一定要写清楚。这个项目表不多当时偷懒少写了一些注释结果后面前端同事问了我三四次某个状态值代表什么意思。字段注释写全、枚举值在注释里写明含义能省掉后面大量的沟通成本。3. 后端SpringBoot实现的核心环节3.1 工程初始化的版本选型这个项目开发时用的版本组合我认为比较稳SpringBoot 2.7.18、JDK 8、MyBatis-Plus 3.5.3、MySQL 8.0。SpringBoot 2.7是2.x系列的最后一个大版本还在维护期内对JDK8兼容性最好。如果选SpringBoot 3.x就要求JDK17起步很多老项目的代码需要适配对新手不够友好如果用JDK8却配了SpringBoot 3.x启动直接报错。创建工程有两种方式官网的Spring Initializr或者IDEA自带的New Project向导。我推荐用IDEA向导创建选好Spring Web、MyBatis、MySQL Driver三个依赖勾选Lombok减少实体类样板代码。Lombok这个东西争议一直存在但在这种业务系统里没什么可纠结的Data、Builder确实能少写几十行getter/setter。3.2 统一返回结果与全局异常处理后端接口的返回结构统一非常重要前端才能写一套通用的响应处理逻辑。我建了一个RT类Data public class RT { private Integer code; // 200成功其他失败 private String message; // 提示信息 private T data; // 业务数据 }所有Controller的返回值都包装成R同时配合RestControllerAdvice做全局异常处理。开发中最常见的情况是忘记处理空指针、参数格式错误如果每个接口都try-catch一遍代码就没法看了。全局异常处理器里我做了两件事捕获BusinessException业务主动抛出的异常返回对应的错误码捕获Exception统一返回500。前端拿到非200的code直接弹出message就行。这里有一个细节Service层抛出业务异常时必须用自定义的BusinessException而不是直接throw new RuntimeException。因为全局异常处理器里针对不同异常类型可以做不同的响应处理自定义异常可以携带错误码前端可以根据错误码做差异化提示比如密码错误和账号不存在返回不同的提示文案。3.3 JWT权限认证的实现细节这个管理系统有三类角色接口必须做权限控制。我用的是JWT方案实现思路不复杂登录接口验证用户名密码成功后用jjwt库生成TokenToken里存用户ID、用户名、角色。写一个JwtInterceptor拦截器继承HandlerInterceptorAdapter在preHandle里解析请求头中的Token校验有效性后把用户信息存入ThreadLocal。注册拦截器时用addPathPatterns排除登录、注册等公开接口。代码逻辑大致是这样public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 如果是预检请求OPTIONS直接放行 if (OPTIONS.equals(request.getMethod())) { return true; } String token request.getHeader(Authorization); if (token ! null token.startsWith(Bearer )) { token token.substring(7); } // 解析Token失败则抛出401异常 Claims claims JwtUtil.parseToken(token); UserContext.set(claims); return true; } }两个容易错的地方。第一OPTIONS请求必须放行。前端发跨域请求前会先发一个预检请求如果拦截器把这个请求拦了前端就永远收到401这是网上问得最多的问题之一。第二登录接口本身不能被拦截器拦住注册表配置时要excludePathPatterns(/api/auth/login, /api/auth/register)。3.4 MyBatis-Plus与XML动态SQL的配合战术这个项目在MyBatis基础上引入了MyBatis-Plus两者配合使用效率很高。单表CRUD全部交给MyBatis-Plus的BaseMapper避免为每张表写重复的insert/update/selectById代码。比如UserMapper extends BaseMapperSysUser之后selectById、insert这些基础操作就不用自己写SQL了。复杂的多表查询、动态条件筛选就写XML文件里的自定义SQL。隔离人员列表查询是全项目最典型的复杂查询按姓名模糊查、按状态精确查、按日期范围查三种条件还可能同时存在。用MyBatis的if标签拼动态SQL是最优解select idselectPersonList resultTypecom.example.isolation.vo.IsolationPersonVO SELECT p.*, r.room_no, r.floor FROM isolation_person p LEFT JOIN isolation_room r ON p.room_id r.id where if testname ! null and name ! AND p.name LIKE CONCAT(%, #{name}, %) /if if teststatus ! null AND p.status #{status} /if if teststartDate ! null AND p.start_date gt; #{startDate} /if if testendDate ! null AND p.end_date lt; #{endDate} /if /where ORDER BY p.create_time DESC /selectwhere标签会自动处理“第一个条件前的and会被去掉”的问题这是MyBatis最人性化的设计。分页用的MyBatis-Plus的分页插件PaginationInnerInterceptor配置一次全项目都能用Page对象接收分页结果。注意分页插件必须注入到MyBatis-Plus的拦截器链里不配置的话selectPage方法会查出全表数据这是个非常隐蔽的坑。3.5 事务控制与并发问题的处理健康上报和解除隔离这两个操作涉及多张表的联动更新必须加事务。以“解除隔离”为例要更新isolation_person的状态、可能新增一条系统通知、还要释放房间。三步操作任何一步失败前面的更新都得回滚。Service方法上直接加Transactional(rollbackFor Exception.class)就能搞定。注意rollbackFor必须写成Exception.class如果只写Transactional遇到受检异常默认不会回滚数据就会处于“半更新”状态。健康上报这个接口在真实场景下还有一个并发问题同一个用户同一天内重复提交。我在数据库层面加了person_id report_date的联合唯一索引同时在Service层做了先查再插的判断。两层防线数据库唯一索引保底Service层判断给用户更友好的提示——“今日已上报请勿重复提交”。只靠Service层判断会有并发窗口漏洞只靠数据库唯一索引用户会看到模糊的500错误两层配合才合理。4. 前端Vue3工程化实现重点4.1 Vite初始化的工程结构前端工程我用Vite来搭不用Vue CLI。Vite基于原生ESM开发环境冷启动速度比Webpack快一个数量级配置也比Webpack的vue.config.js直观得多。创建命令是npm create vitelatest isolation-web -- --template vue工程目录里我加了自己的分层src ├── api // 所有接口请求按模块拆分 ├── assets // 静态资源 ├── components // 公共组件 ├── router // 路由配置 ├── stores // Pinia状态管理 ├── utils // 工具函数axios封装在这里 └── views // 页面组件4.2 Composition API的实践心得Vue3最大的变化就是Composition API配合script setup写起来非常舒服。用健康打卡页面举例业务逻辑包括加载今日打卡状态、校验能否打卡隔离期内、提交表单。在Vue2里这些逻辑分散在data、created、methods多个选项里在Vue3里就是一段顺序执行的代码script setup import { ref, onMounted } from vue import { getTodayReport, submitReport } from /api/report const loading ref(false) const formRef ref(null) const form reactive({ temperature: 36.5, cough: false, fatigue: false, otherSymptom: }) async function loadTodayReport() { loading.value true try { const res await getTodayReport() if (res.code 200 res.data) { // 已经有上报记录回填表单并锁住 Object.assign(form, res.data) } } finally { loading.value false } } async function onSubmit() { // 表单校验…… const res await submitReport(form) if (res.code 200) { ElMessage.success(上报成功) } } onMounted(loadTodayReport) /script这段代码的逻辑是顺序展开的一眼就能看清楚页面加载后做了什么、提交时做了什么不用像Options API那样跳来跳去看。这也是我推荐Vue3做管理后台的最直接理由——业务代码的可读性提升了不止一个级别。4.3 Pinia和Vue Router的实现要点Vue3配套的状态管理库是Pinia相比Vuex它的API极其简洁没有mutations这一层直接改state就行。这个项目里Pinia主要用来存登录用户的Token和用户信息// stores/user.js import { defineStore } from pinia export const useUserStore defineStore(user, { state: () ({ token: localStorage.getItem(token) || , userInfo: null }), actions: { setToken(token) { this.token token localStorage.setItem(token, token) }, setUserInfo(info) { this.userInfo info }, logout() { this.token this.userInfo null localStorage.removeItem(token) } } })Token存localStorage还是cookie是个经典选择。这个项目前端是纯静态部署后端是独立服务localStorage更直接。Token放localStorage有XSS风险但管理后台本来就没有第三方脚本注入场景配好CSP策略问题不大。路由守卫的核心逻辑是用户未登录只能访问登录页登录后根据角色跳转不同首页越权访问统一重定向到403。在router.beforeEach里做判断router.beforeEach((to, from, next) { const userStore useUserStore() if (to.path /login) { next() return } if (!userStore.token) { next(/login) return } // 根据角色判断是否允许进入…… next() })4.4 Axios封装和拦截器的具体写法Axios封装是前端工程质量的关键点。我在utils/request.js里做了统一的axios实例核心有两点请求拦截器自动附加Token响应拦截器统一处理错误码和401状态。import axios from axios import { ElMessage } from element-plus import router from /router import { useUserStore } from /stores/user const service axios.create({ baseURL: /api, timeout: 10000 }) service.interceptors.request.use(config { const userStore useUserStore() if (userStore.token) { config.headers.Authorization Bearer ${userStore.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 { if (error.response error.response.status 401) { const userStore useUserStore() userStore.logout() router.push(/login) } ElMessage.error(error.message || 网络异常) return Promise.reject(error) } )所有请求都必须通过这个封装实例发出不能在页面里直接import axios from axios。把这个习惯立住后面做Token续期、接口日志上报、埋点只需要改动封装这一处工作量能省下一大截。4.5 Element Plus表格与表单的工程化封装隔离人员管理页面是这个系统里最复杂的页面顶部是筛选条件姓名、状态、日期范围中间是表格底部是分页。Element Plus的el-table、el-pagination、el-form组合使用。我把筛选表单和表格分页封装成了一个可复用的模块做法是写一个SearchTable组件接收“筛选字段配置”和“加载数据函数”两个props页面里就不用每个列表页重复写一遍“表单表格分页”的结构。不过这里有个重要提醒组件封装要适度。像这样一个项目的规模不用过度抽象把增删改查的公共逻辑抽成hooks就够了。我看到不少新手一上来就写七八层抽象组件最后改需求时谁都看不懂。工程化的核心是“把重复的事情自动掉”不是“把简单的事情复杂化”。图形展示方面健康统计用了ECharts的折线图和柱状图。Vue3里用echarts配合vue-echarts组件封装模板里直接写v-chart :optionchartOption /就行。ECharts这块只做了一个体温趋势图和各类状态人数的统计面板没有整得很复杂够用就好。5. 前后端联调、构建与部署5.1 开发环境跨域配置的两种方案前后端分离开发时前端跑在localhost:5173Vite默认端口后端跑在localhost:8080必然有跨域问题。我在开发环境用Vite的Proxy方案生产环境用Nginx反向代理方案两套都要配好。Vite的vite.config.js里这样配置export default defineConfig({ server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })这样前端请求/api/person/listVite开发服务器会把它代理到http://localhost:8080/api/person/list浏览器看到的请求是同源的跨域问题就消失了。生产环境Nginx配置是server { listen 80; server_name your-domain.com; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }try_files $uri $uri/ /index.html这行极其重要。Vue Router如果用的是history模式用户在页面内点击跳转没问题但直接刷新某个子路由比如/person/listNginx会去磁盘找这个路径对应的文件找不到就404。try_files会把所有不存在的路径都回退到index.html由前端路由接管才不会有刷新404的问题。5.2 SpringBoot jar包与前端静态资源的两种部署方式部署方式有两种看你的服务器条件。方案一前后端分开部署推荐。前端npm run build生成dist目录丢到Nginx的静态目录后端mvn clean package打成jar包用java -jar app.jar直接跑。这种方式的优势是前后端可以独立扩容、独立重启改前端不用动后端服务。方案二打成一体包中小项目也够用。把前端编译后的dist目录复制到SpringBoot的src/main/resources/static下再打包进jar。这样只有一个进程、一个端口部署成本最低。但这么做有个坑Vue Router用history模式时后端必须做地址回退否则请求/person/list会404。SpringBoot里要加一个Controller做转发Controller public class SpaForwardController { RequestMapping(value /{path:[^\\.]*}) public String forward() { return forward:/index.html; } }我自己在项目里用的方案一是分开部署因为后面还要加文件服务、消息推送拆开会灵活得多。5.3 数据库初始化与生产环境配置检查生产环境启动前有几项配置必须检查。第一个是MySQL连接串。生产环境不要用root账号单独建一个业务账号授权只限本应用需要的库。最小权限原则虽然听着像安全合规的套话但真出事的时候能挡住不少乱操作。spring: datasource: url: jdbc:mysql://localhost:3306/isolation_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: isolation_app password: your-password driver-class-name: com.mysql.cj.jdbc.Driver第二个是JWT密钥和生产配置的区分。密钥不要写在application.yml里然后提交到Git仓库应该用环境变量注入。我习惯在服务器上单独放一个application-prod.yml里面用${JWT_SECRET}占位符启动时通过环境变量传进来。第三个是日志配置。生产环境日志最少要按天切割保留30天。SpringBoot默认的日志输出全打到控制台进程一重启啥都没了。配置logback-spring.xml把error和info分开按天滚动后面排查线上问题才有的放矢。5.4 性能优化从数据库到前端静态资源系统上线后的性能优化按性价比排优先级是这样的一、数据库索引是最优先的。上线第一周观察慢查询日志把执行时间超过200ms的SQL捞出来用EXPLAIN看执行计划补缺失的索引。这个项目里最常见的慢查询是隔离人员列表按日期范围筛选补了一个start_date上的索引后扫描行数从几万降到几百。二、前端路由懒加载。Vue Router里用() import(/views/PersonList.vue)动态导入组件首屏只加载当前页面需要的JS而不是一次性把几十个页面全下载下来。这个改动对首屏性能提升非常明显特别是把ECharts这类重库放在独立页面里懒加载首页加载速度能快一倍。三、合理的HTTP缓存。Nginx里对/assets目录下的静态资源设置expires 30d带hash的文件名天然支持永久缓存只有内容变化才会生成新文件名浏览器就不用每次刷新都去服务器拉全量资源。6. 常见问题与踩坑排查实录6.1 MySQL报“Public Key Retrieval is not allowed”有些MySQL 8.0连接串会报这个错。原因是8.0默认用了caching_sha2_password身份验证插件JDBC首次连接时默认不允许从服务器拉取公钥。解决办法是连接串加allowPublicKeyRetrievaltrueuseSSLfalse。注意加了allowPublicKeyRetrievaltrue等于允许明文传输密码生产环境建议把数据库账号换成mysql_native_password插件或者确保连接走内网安全通道。6.2 SpringBoot项目启动时报“Invalid bound statement”这个错基本就是MyBatis的Mapper XML文件没被扫描到。常见的三种原因和排查顺序application.yml里没配置mybatis.mapper-locationsSpringBoot不知道XML文件在哪。配置项是mybatis.mapper-locations: classpath:mapper/*.xml。XML文件在src/main/java目录下没有在pom.xml里排除非resources目录下的xml。Mapper接口和XML文件的namespace写错包名不匹配。排查这个错误有个高效方法启动时加--debug参数看MyBatis启动日志里到底加载了哪些XML文件一目了然。6.3 Vue项目打包后放到服务器刷新页面404这个前面已经讲过Vue Router history模式配合Nginx时必须加try_files $uri $uri/ /index.html。很多人开发环境好好的因为Vite开发服务器内部处理了history回退一上线刷新就404就是漏了这一行。用hash模式不会出现这个问题URL会带个#但URL难看不推荐。6.4 前后端联调时前端永远收不到后端响应这个问题九成出在跨域配置上。开发环境先看浏览器Network面板请求有没有发出如果请求发出但状态是CORS error那就是服务端没配允许跨域。SpringBoot里可以写一个配置类实现WebMvcConfigurer的addCorsMappings方法也可以配合上面的Nginx代理解决。我的建议是开发环境用Vite proxy而不是后端开CORS因为CORS配置要是写得太宽比如allowedOrigins(*)生产环境也有风险。6.5 LocalDateTime返回前端变成了数组SpringBoot默认序列化LocalDateTime时会变成[2025, 5, 20, 12, 30, 45]这种数组格式前端根本没法直接用。解决办法是在application.yml里配置spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8如果前端需要单独的日期格式比如只要yyyy-MM-dd就在对应VO的字段上使用JsonFormat(pattern yyyy-MM-dd, timezone GMT8)。用配置做全局默认用注解做局部覆盖这是我实践下来最稳的组合。6.6 Element Plus表格的el-date-picker绑定值格式坑el-date-picker的v-model绑定值默认是Date对象但你后端接口要的是字符串。如果直接提交SpringBoot反序列化时偶尔会报格式错误。我在项目里的做法是给el-date-picker加value-formatYYYY-MM-DD这样v-model绑定的直接就是格式化后的字符串后端日期接收不需要任何额外处理。6.7 MyBatis查询结果有值但是Java对象的字段是null这个坑常发生在数据库字段用了下划线命名、Java实体用了驼峰命名而MyBatis的驼峰映射没打开的时候。基础配置里有一项mybatis: configuration: map-underscore-to-camel-case: true开了这个配置create_time就能自动映射到createTime字段不用每个字段都写resultMap。项目初始化的时候一定把这个配置先配上不然后面每写一条SQL都想着字段映射的事太折磨人。7. 这套系统后续可以怎么扩展系统目前覆盖了隔离点管理的核心闭环但离“生产可用”还有一段距离。我把后续可以扩展的方向列一下读者如果有需要可以继续在这个基础上做第一消息通知模块。健康打卡异常、隔离期满、物资不足这些场景都需要系统通知或短信提醒。后端可以引入消息推送或短信服务商SDK前端增加站内信模块。第二报表导出。隔离点管理员经常需要导出人员名单、每日健康汇总表。后端用EasyExcel可以轻松把查询结果导出成Excel前端一个下载按钮就能触发。这个功能在真实场景里比任何花哨的图表都实用。第三更细粒度的权限控制。目前权限是角色级别的如果隔离点有多个子区域、不同管理员只管自己片区的隔离人员就需要引入数据权限。做法是在用户表加区域字段查询SQL里自动拼接区域过滤条件。第四增加消息队列应对定时任务。比如每天早上8点自动推送健康打卡提醒可以用SpringBoot自带的Scheduled实现也可以引入消息中间件做任务分发。这个全看隔离点的规模几十个人一个隔离点定时任务就够了。第五把前端技术栈升级到更现代的写法。比如用TypeScript重写一遍或者引入unplugin-auto-import实现API自动导入。这些优化对项目功能没有影响但对代码可维护性有长远帮助。我在实际做这类管理系统项目时有个很深的体会技术选型其实不重要重要的是你有没有一套固定的开发模式。这套项目的代码结构、接口约定、异常处理、鉴权方式是我反复用过很多个管理后台之后沉淀下来的模板。你拿这个模板套到任何“信息登记状态管理报表统计”类需求上都能快速落地。所以这篇文字虽然是在拆“疫情隔离管理系统”但里面的方法论换成物业管理系统、车辆管理系统、实验室设备管理系统完全一样适用。把这个模板吃透再去接任何管理类项目都只是换层业务外壳而已。
返回列表