ARTICLE DETAIL

资讯详情

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

Spring Boot+Vue实验室管理系统实战:从权限设计到部署上线

Spring Boot+Vue实验室管理系统实战:从权限设计到部署上线 1. 实验室管理系统的核心需求拆解从台账到流程的真实痛点如果你问十个实验室管理员实验室管理最头疼的是什么大概率得到的答案不是设备不够或场地太小而是根本不知道现在谁在用哪台设备、试剂还剩多少、上次保养是什么时候。实验室的日常管理基本靠Excel、微信群和口头约定三类工具叠加在一起产生的不是协同而是混乱。我做这套Java基于Spring BootVue的实验室管理系统最初就是被一个高校实验室的老师反复吐槽你们搞软件的能不能把那个破Excel给我换了才决定从零开始搭一整套可落地的管理系统。项目最终覆盖了设备管理、预约借用、耗材库存、实验项目管理、人员与权限、数据统计六大模块整套系统跑通之后我能给到的最核心建议是实验室管理系统本质上不是数据登记系统而是流程管控系统。为什么这么说你仔细想一下实验室的实际工作场景。设备管理员的核心诉求不是我能查设备清单而是我能知道谁借走了示波器、什么时候还、坏了算谁的。老师的诉求不是我能录入项目信息而是我能看到这学期哪些学生在做哪些实验用了哪些设备。采购员的核心诉求也不是我能看到库存数量而是试剂快用完的时候能否自动提醒我补货。这些诉求背后的公共点全都是流程预约流程、审批流程、领用流程、归还流程、报废流程。所以系统功能模块的设计是围绕全生命周期管理来做的。设备从申购、验收、入库、领用、使用记录、保养维修到报废每一步都留痕耗材从采购入库、领用出库、库存预警到盘点核销每一笔都走单人员从学生、教师、管理员到院级领导每一类角色都有不同的操作边界。这套系统说到底做的不是技术上的炫技而是把实验室里那些线下靠吼、线上靠Excel的流程平移到Web端并且加上权限控制、状态流转和数据分析。1.1 角色权限的建模为什么要分五层而不是三层很多初学Spring BootVue的人做权限习惯性就是管理员、普通用户两个角色顶多加一个超级管理员。但做实验室这种场景三层是绝对不够的。原因很简单实验室的管理权责不是一条直线而是一张网。我在项目里最终实现了五类角色每一类的数据边界和操作边界完全分开角色核心职责可操作范围院级领导查看统计报表、审批大型设备购置全部数据的只读权限审批流终审节点实验室管理员设备/耗材/场地全流程管理本实验室范围内的所有业务单据授课教师创建实验项目、发起预约申请本人创建的项目及名下学生的申请学生预约设备、填报使用记录本人相关记录系统运维用户管理、字典维护、日志审计不碰业务数据只管系统配置这个角色的划分不是拍脑袋而是基于实际的权责边界。比如学生不应该看得到全校所有实验室的设备库存只能看到自己可选预约的设备以及自己的预约历史。老师需要能审批学生发起的申请但是不能改管理员录的设备档案。管理员能够把所有流程推到底但是不能改系统里的角色配置。在实现层面我建议后端用Spring Security JWT做认证和授权权限控制粒度直接做到按钮级别而不是仅仅控制路由。页面上的按钮显隐是前端通过自定义指令判断角色但真正兜底的一定是后端的接口权限校验。前端隐藏菜单只是体验优化后端按角色做Authority校验才是安全底线。1.2 设备管理需求里的状态机思维设备管理是整个实验室系统里最值得细说的模块因为一台设备从其生命周期来看本质上是一个状态机。设备从录进系统开始可能经历的状态有在库可用、已被预约、领用中借出在途、维修中、报废停用。每一个状态都对应了系统里的一条业务单据在库可用基础档案完整无未完成的借用单已被预约存在一条处于待审批或已通过状态的预约单领用中存在一条已出库且未归还的领用单维修中存在一条维修工单且状态未闭环报废停用存在一条报废审批通过的记录这个状态机设计得好不好直接决定后面设备是否可预约的判断逻辑怎么写。如果只是用一个字段存状态值你会发现一旦某条业务记录状态没有同步设备档案的状态就会乱。我有一个比较实用的经验设备状态不直接存字段而是通过当前有效的业务单据实时计算出来。设备表里只存基本信息设备编号、名称、型号、位置、购置日期、责任人当前状态通过查询关联表获得。这样做有什么好处首先避免了多表状态不一致的问题其次任何状态的变更都强制走了对应业务单据的审批流最后做审计追踪的时候你直接查单据记录就能还原一台设备某个时间点到底在谁手里。代价是多了一次关联查询但实验室设备几千台上限的体量性能完全不是问题。设备模块的辅助功能还包括设备二维码打印每一件设备生成一个唯一二维码贴到实物上学生扫码就能看到设备信息并跳转预约。这块如果用Vue做前端可以用qrcode库生成二维码图片后端提供设备详情的H5页面接口扫码直接打开。1.3 耗材库存管理里的预警与批次逻辑耗材管理是实验室系统里业务最琐碎但最容易出彩的模块。很多人做库存系统只做简单的入库单和出库单加个数量字段就以为完事了。但实验室耗材有两个特殊点一是有效期管理二是批次管理。化学试剂、生物试剂都有有效期同一个试剂编号不同批次入库效期不同价格也可能不同。如果出库时不按批次FIFO先进先出扣减而是直接在所有批次的总量里减那会出现系统显示有库存实际库里全过期了的尴尬局面。我在设计耗材表的时候采取了主表批次子表的结构。主表存耗材的基本信息耗材编号、名称、规格、单位、分类、安全库存阈值批次子表存每一批入库的数量、单价、生产日期、有效期。出库操作直接针对批次子表扣减先扣最早过期的那一批。前端Vue实现出库单时选择耗材后会带出该耗材的有效批次列表管理员手动选择从哪个批次出库也可以一键按效期优先自动分配。安全库存预警我用了一个比较轻量的方式不额外引入定时任务框架直接用Spring的Scheduled注解每天凌晨扫描一次所有耗材的当前可用库存总量与安全阈值的差值低于阈值的写入预警表同时把消息推送到管理员的待办列表里。邮件通知或者短信通知属于增强功能我这里只是预留了通知渠道的接口方便后续接入。2. Spring Boot后端架构与数据库建模表怎么设计才能支撑流程技术选型方面后端Spring Boot前端Vue这套组合在Java技术栈里可以说是最成熟、资料最多的搭配。Spring Boot负责提供RESTful APIVue负责做交互界面两者通过JSON通信完全前后端分离。我们的后端技术栈最终确定如下并且这套选型在后面整个开发周期里没有换过技术组件选型选型理由核心框架Spring Boot 2.7.x稳定版本资料丰富兼容主流中间件安全框架Spring Security JWT无状态认证适合前后端分离ORMMyBatis-Plus单表CRUD免写SQL复杂查询手写XML数据库MySQL 8.0主流关系型数据库事务支持完善接口文档Swagger Knife4j前后端联调必备接口一键调试工具库Hutool、MapStruct减少重复代码对象拷贝更高效2.1 核心数据表的字段划分与关联关系数据库我总共设计了二十多张表核心的业务表不可能全部列出来但可以讲几个关键的设计决策这些都是踩完坑之后被验证过可行的方案。用户表与角色表之间我采用了标准的RBAC模型用户表、角色表、菜单表、用户角色关联表、角色菜单关联表。用户表里不直接存角色名称避免角色调整时需要改用户表。菜单表里存的不只是前端路由数据还包括按钮权限标识这样动态路由和前端的按钮显隐就能共用一套权限数据源。设备表设备编号、设备名称、设备型号、设备分类、存放实验室、责任人、购置日期、资产原值、供应商、状态计算标志位。状态标志位是最新一次的状态缓存真正的状态还是通过业务单据实时计算之所以保留缓存字段是为了列表页展示的时候不用每次扫描所有单据列表查得动详情页再做实时状态校验。预约单表预约单号、设备ID、申请人ID、使用开始时间、使用结束时间、用途说明、关联实验项目ID、审批人ID、审批状态、审批意见、创建时间。预约单是整个流程的核心载体审批状态字段用枚举值管理0待审批、1通过、2驳回、3已取消。领用/归还单表领用单号、设备ID、领用人ID、领用时间、预计归还时间、实际归还时间、归还时状态描述、是否损坏。这里要注意要加一个归还状态字段因为设备归还时可能存在损毁需要进入维修流程而非正常入库。耗材批次表批次ID、耗材ID、入库单号、批次数量、已扣减数量、剩余数量、生产日期、有效期至、供应商、采购单价。实验项目表项目ID、项目名称、所属课程、指导教师、参与学生列表、项目周期、使用的设备清单。设备清单建议用单独的关联表去存不要用逗号拼接字符串虽然查询麻烦一点但要做设备使用率分析的时候单靠逗号拼接字段做关联查询写出来的SQL一定很痛苦。2.2 后端接口设计的几个关键边界关于Spring Boot接口设计尤其是在前后端分离的项目里最容易出问题的不是接口逻辑本身而是接口粒度和状态码约定。我先说状态码。很多项目用HTTP状态码表达业务结果比如200成功、500失败然后前端axios统一拦截处理。但业务层面的失败比如库存不足预约时间冲突如果用500来表达会给前端日志排查带来很多噪音。我的做法是HTTP状态码只负责表达请求是否到达服务器并正常处理200代表请求被处理了哪怕业务结果是失败。每个响应体统一返回一个结构{ code: 200, message: 操作成功, data: {} }code为200表示业务成功其他code值为业务错误码比如10001库存不足、10002预约冲突、10003无权限操作。前端axios在响应拦截器里统一判断code非200直接ElMessage弹出message。这种约定在联调阶段能省下大量沟通成本。接口粒度这块我踩过一个坑一开始把预约审批做成一个接口参数里带一个通过/驳回标志位后来发现逻辑越加越复杂审批通过要占用设备时间、扣减预约单状态、生成使用记录审批驳回只需要更新状态两个分支的逻辑差异太大硬放一个接口里导致方法越来越长。后期拆成了两个接口approve通过和reject驳回代码清晰很多。2.3 文件上传与数据导出的实现细节实验室管理系统里有两类文件操作必不可少实验报告附件上传、设备/耗材清单导出Excel。文件上传我用的是本地磁盘存储没有引入OSS或者MinIO原因是部署环境在校园内网外网对象存储方案反而绕路。配置一个上传目录存储路径按日期分目录/upload/2025/06/01/uuid_originalname.xlsx数据库里只存相对路径不存绝对路径这样应用迁移到别的服务器只需要改配置文件的upload.base-dir即可。前端Vue用el-upload组件的action属性直接指向后端的upload接口后端接口里做类型和大小校验限制后缀名和20MB大小上限。Excel导出我用的EasyExcel这个库在OOM控制上比Apache POI原生要好。导出时用ExcelProperty注解标注实体字段一行一行写列表页把查询参数传到后端后端从数据库查全量数据并流式写出。3. Vue前端从搭建到路由权限控制动态路由与按钮级权限的落地方式前端的技术栈是Vue3 Element Plus Pinia Vue Router Axios。为什么选Element Plus实验室管理系统是典型的后台管理界面需要大量的表格、表单、弹窗、步骤条组件Element Plus在组件丰富度上是最成熟的选择。3.1 项目初始化和环境配置的坑Vue项目初始化这块我用的是Vite而不是Vue CLI。Vite的启动速度和热更新体验比webpack时代的Vue CLI好太多现在Vue3官方生态也已经全面转向Vite。初始化命令是npm create vitelatest lab-admin -- --template vue创建完项目之后依次安装依赖包npm install element-plus npm install element-plus/icons-vue npm install pinia npm install vue-router4 npm install axios环境配置这里我要重点提一个细节本地开发的代理配置。前后端分离开发时前端跑在5173端口后端跑在8080端口前端直接请求后端接口必然跨域。跨域有两种解决办法一种是在后端代码里加跨域配置另一种是在前端Vite的proxy配置里做代理。我强烈建议用前端代理方式因为这样后端不需要为每个环境写跨域配置部署上线之后前端请求的是同源地址天然不需要跨域。vite.config.js配置export default defineConfig({ plugins: [vue()], server: { host: 0.0.0.0, port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })开发接口统一以/api作为前缀后端controller的RequestMapping里都带上/api代理就把/api开头的请求转发到8080。这个前缀还能带出一个好处后续在Nginx做反向代理时可以精确按路径前缀转发不会和其他服务混淆。3.2 动态路由的实现原理与代码结构动态路由是后台管理项目中权限控制的前端核心。原理不复杂用户登录成功后后端返回该用户的角色信息和菜单列表前端根据菜单列表动态生成路由并注册到Vue Router中而不是在router/index.js里写死所有路由。我的菜单表里设计了component字段存的是前端组件的相对路径字符串比如lab/device/DeviceList.vue。前端拿到菜单列表后用import.meta.glob动态导入组件const modules import.meta.glob(../views/**/*.vue) export function buildRoutes(menus) { const routes [] menus.forEach(menu { const route { path: menu.path, name: menu.name, component: modules[../views/${menu.component}.vue], meta: { title: menu.title, icon: menu.icon } } if (menu.children menu.children.length 0) { route.children buildRoutes(menu.children) } routes.push(route) }) return routes }这里有一点需要注意import.meta.glob默认是懒加载动态导入返回的是一个函数用的时候调用一下才会返回组件Promise。Vue Router的component字段既可以直接传组件对象也可以传一个返回Promise的函数所以上面的写法是可行的。路由守卫方面我加了全局前置守卫在to.meta中判断是否需要登录未登录跳转到登录页。如果已登录还要判断store里是否有当前用户的路由菜单没有则动态添加并replace到目标路由。这里要特别提醒刚接触Vue Router的开发者addRoute添加完路由后页面不能直接用router.push跳转需要调用router.replace重新导航一次否则刷新页面会出现空白。3.3 按钮级权限指令的封装菜单权限只是控制用户能看到哪些页面同一个页面上不同角色能操作的按钮是不一样的。比如设备列表页实验室管理员能看到新增设备编辑删除按钮普通教师就只能看到预约查看详情。按钮权限的落地方式是封装一个自定义指令v-permission。登录时后端返回的角色信息中带上权限标识列表存入Pinia。指令绑定元素时从store中取出当前用户的权限标识列表判断元素绑定的标识是否在其中不包含则直接调用el.parentNode.removeChild(el)移除元素。const permissionDirective { mounted(el, binding) { const { value } binding if (!value) return const userStore useUserStore() const permissions userStore.permissions || [] const all [*:*:*] const hasPermission permissions.some(p all.includes(p) || p value) if (!hasPermission) { el.parentNode el.parentNode.removeChild(el) } } } export default permissionDirective后端在菜单表里给每个按钮配了一个权限标识比如lab:device:add表示可以新增设备。接口鉴权时Spring Security的PreAuthorize注解里用的标识和前端按钮指令用的标识保持一致。这样前后端的权限控制共用同一套权限码不会出现前端隐藏了按钮、后端接口还能直接调用的问题双端都有校验安全上才算闭环。4. 核心业务流实测预约审批、领用归还、耗材库存预警的完整链路前面的模块设计和技术选型都聊完了这一节我们实打实梳理一遍三条核心业务流。这三条链路的完整性直接决定系统投入使用后管理员愿不愿意天天用。4.1 设备预约审批时间冲突检测的写法学生要预约设备流程是选择设备 - 选择时间 - 填写用途 - 提交申请 - 教师或管理员审批 - 审批通过后到预约时间直接扫码使用。这里最核心的后端逻辑是时间冲突检测。同一台设备在同一时间段不能被两个预约单占用。冲突检测的SQL条件是这样的SELECT COUNT(*) FROM reserve_order WHERE device_id #{deviceId} AND approve_status IN (0, 1) AND ( (start_time #{endTime} AND end_time #{startTime}) )这个条件覆盖了所有重叠的情况新的预约开始时间落在已有预约时间段内、新的预约结束时间落在已有预约时间段内、新的预约完全包含已有预约时间段。后端查到count大于0就返回业务错误码10002该设备在此时间段已被预约。需要留意的是审批状态必须是待审批或已通过才算占用时间已驳回和已取消的单子不参与冲突计算。同时系统要支持管理员手动关闭某台设备的预约功能设备表中加一个reserve_enabled字段预约提交时先检查这个字段关闭状态直接提示该设备暂停预约。4.2 领用到归还设备状态的全链路闭环预约通过之后学生到实验室现场管理员需要在系统中做出库确认生成领用单。如果学生预约了但是人不来管理员过时未确认预约单自动取消释放时间占用。这个自动取消我最初想用消息队列延迟消息来做但因为系统体量小用Spring的Scheduled每分钟扫一次待审批且起始时间已过5分钟的预约单批量置为已取消状态完全够用且省事。设备归还时管理员选择对应领用单填写归还状态。这里有一个隐藏的分支逻辑如果归还时设备正常领用单闭环设备状态恢复可用如果归还时设备有异常比如探头损坏领用单虽然闭环了但系统会自动生成一条维修工单设备状态变为维修中在维修工单闭环之前设备不可被预约。这个领用单闭环但设备状态不直接恢复的设计是防止设备带病上架的关键。我见过很多台账型系统只记录借了和还了还了之后就认为设备可用结果下一个学生预约后发现设备是坏的体验极差。4.3 耗材入库到预警批次扣减与补货提醒耗材采购到货后管理员做入库单选择耗材、填写批次数量、单价、有效期。入库操作会生成批次记录同时更新耗材主表的库存总量。出库操作选择批次录入出库数量系统校验不能超过该批次的剩余数量。整个流程执行过程中所有涉及数量变动的操作都放在同一个事务里Spring的事务注解加到Service方法上如果出库单保存失败批次库存的扣减自动回滚。这个事务边界一定要把控好入库单保存、库存量更新、批次记录插入这三步操作必须在同一个事务方法里完成。库存预警的规则是耗材主表的当前可用总量低于安全库存阈值自动生成一条预警记录管理员登录后在首页的待办卡片上能看到XX试剂库存不足当前剩余xx安全库存xx建议采购。补货采购完成后入库单审核通过预警状态自动置为已处理。要做到这个入库后自动关预警入库接口里加一步逻辑查询该耗材是否有未处理的预警记录有且当前库存已高于阈值则更新预警状态。这个小逻辑虽然代码就几行但体现的是业务闭环思维。5. 联调与部署实战跨域、打包、Nginx反向代理的完整方案前后端开发完成后最折腾人的是联调和部署阶段。这个阶段踩的坑比写业务代码踩的坑要多一倍而且很多是环境层面的不花时间根本绕不过去。5.1 联调阶段的常见问题与排查思路我先说一个遇到过的最隐蔽的问题前端请求正常发出后端日志里也显示接口被调用但前端的response返回的是404。排查了很久才发现是接口路径大小写不一致。Spring Boot的Controller路径是/devices前端Axios请求的是/api/devices代理配置的rewrite规则没写对导致/api/devices被转发到后端但是因为路径不匹配。Vite代理如果没有配置rewrite/api/devices转发到后端还是/api/devices而后端接口路径是/devices如果全局配置了server.servlet.context-path/api那后端恰好能匹配如果没有配置context-path就会404。这个问题的最佳实践是要么后端统一加context-path/api要么前端代理里加rewrite把/api前缀剥掉。两选一但必须全局统一。我最终选择后端不加context-path全部在Vite代理的rewrite里处理proxy: { /api: { target: http://localhost:8080, changeOrigin: true, rewrite: (path) path.replace(/^\/api/, ) } }生产环境同样在Nginx里做rewrite保持与开发环境一致减少环境差异带来的心智负担。联调阶段另一个高频报错是CORS。如果用了前端代理开发环境不存在跨域问题。但如果在某些场景下需要后端直接支持跨域比如第三方系统直接调用接口那就在后端配置一个CorsFilter允许的域名写在配置文件里不要直接allowAllOrigins。5.2 前端构建产物如何扔进Spring Boot实验室管理系统的部署环境通常是学校或企业的一台内网服务器资源有限最简单的部署方式是把前端打包后的dist目录和Spring Boot打成的一个jar包配合Nginx做反代。前端打包命令npm run build打包完成后dist目录里就是一堆静态文件。把这些静态文件拷贝到Nginx的html目录配置Nginx监听80端口。后端jar包单独部署在8080端口。Nginx配置里静态请求直接返回dist文件/api开头的请求转发给8080server { listen 80; server_name lab.example.edu.cn; location / { root /usr/local/lab/frontend/dist; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这里有两个关键点。第一个是try_files配置Vue是单页应用刷新页面时如果路由是/aboutNginx在dist目录下找不到about物理文件需要try_files指令把所有路径都回退到index.html由前端路由接管。没有这个配置刷新非首页路由就会404这是部署时最高频的错误。第二点是proxy_pass末尾的斜杠。当proxy_pass是http://127.0.0.1:8080/这种带路径的写法时Nginx会用location中匹配到的部分替换为斜杠后的路径。假设请求是/api/auth/loginlocation /api/将/api前缀剥掉转发的目标路径是http://127.0.0.1:8080/auth/login。如果后端Controller里没有/api前缀这种写法正好匹配。前后端联调确认好的接口前缀规则在Nginx这里要再次确保一致。后端启动方式也很简单Spring Boot打成了可执行jar包nohup java -Xms512m -Xmx1024m -jar lab-server.jar --spring.profiles.activeprod lab.log 21 实际运行中512MB到1GB的堆内存对实验室管理系统的业务量来说足够。系统里如果做Excel大批量导出堆内存可能会吃紧Xmx给到1GB比较保险。5.3 数据库初始化与定时任务的生产配置数据库初始化我建议用Spring Boot自带的schema.sql和data.sql配合spring.sql.init.modealways在所有环境下一致执行保证开发、测试、生产环境的表结构一致。如果表结构有增量变更再额外写alter脚本按版本号执行避免一个文件从头跑到尾在已有数据的表上重复执行报错。定时任务这块用Scheduled注解实现的两个任务超时预约自动取消、耗材库存每日预警在生产环境需要注意定时任务默认是一个JVM进程内的调度如果后面做了多实例部署同一个任务会在多个实例上重复执行。实验室项目单实例部署没问题但如果后期扩展建议加上分布式锁或者把定时任务独立成一个服务。我记得这块时特意在任务代码里加了一个开关配置方便随时停掉某个任务。Scheduled注解使用需要基于时间字符串的cron表达式我举一个例子方便新手参考// 每分钟执行一次检查超过开始时间5分钟仍未确认到场的预约单 Scheduled(cron 0 * * * * ?) public void cancelTimeoutOrders() { // 查询超时预约单并批量取消 } // 每天凌晨1点执行耗材库存预警扫描 Scheduled(cron 0 0 1 * * ?) public void inventoryWarningScan() { // 扫描库存低于安全阈值的耗材生成预警记录 }6. 学生选课场景下的扩展从单台设备预约到实验项目维度管理前面讲的所有功能都是围绕物的管理但实验室管理系统的使用者在真实场景中很多操作是围绕课程和实验项目展开的。当系统跑起来之后我发现如果只做设备和耗材的管理老师的使用意愿并没有那么强因为老师打开的动机不只是查设备而是管理自己带的学生和实验课。所以我在二期迭代中增加了实验项目模块这一块也确实是最受老师欢迎的功能。整个设计是老师创建实验项目 - 添加参与学生 - 为项目批量申请设备 - 学生确认自己的实验安排 - 实验完成后填写实验数据 - 老师查看完成情况。这里和预约功能的联动是最大的增量。设备预约单上增加了一个projectId字段预约通过后这条记录不仅关联到学生本人还关联到老师创建的实验项目中。老师在项目详情页可以直接看到这个项目申请了哪些设备、每个设备的使用时间、哪些学生参与了操作。批量申请设备这个需求是老师提出来的——我下周的实验课要用15台示波器和15套面包板一台一台预约太蠢了。于是做了批量接口老师在页面上勾选多台设备、填写统一的时间段后端循环校验每台设备的时间冲突一次性生成多条预约单。实验数据填报这块最直接的需求是学生上传实验报告附件和记录关键实验参数老师能在后台逐条查看和评分。这就把平台从设备管理系统进一步延伸到了教学辅助平台整个系统在实验室日常使用中的粘性一下就上来了。7. 踩坑回忆录前后端分离项目里那些文档查不到的疑难杂症做到最后我把自己在实际开发中遇到最典型的五个问题和最终解决方案列出来这些内容在官方文档里往往一笔带过但实际排查起来非常折磨人。7.1 FileUpload文件大小超限导致的奇怪报错Spring Boot默认的单个文件上传上限是1MB如果前端上传超过1MB的文件后端的MultipartFile解析会直接抛异常。但诡异的是这个异常在不同的Spring Boot版本中表现不一样有的版本会返回500有的版本会返回一个不太友好的错误JSON。前端Axios收到非200响应走统一错误拦截器弹出报错但报错信息里只有服务器错误四个字根本看不出是文件太大。解决方法是三处统一配置后端application.yml里设置spring.servlet.multipart.max-file-size和max-request-size为20MBNginx的client_max_body_size也设置成对应的大小前端el-upload组件的before-upload钩子里做前置校验。不三层都设置就会出现本地开发正常、上线后传大附件报413的诡异问题。7.2 表单提交时日期时间字段少了8小时这是前后端分离项目里最经典的坑。Vue端Element Plus的DatePicker组件默认返回的Date对象带有浏览器的本地时区东八区信息。后端Jackson反序列化时如果配置的日期格式是yyyy-MM-dd HH:mm:ss并且没有指定时区就会按服务器默认时区去解析。而实际上前端传输的JSON字符串里如果带上了T和时区偏移量后端解析就容易出现时间错乱。我的统一方案是前端提交前用dayjs把日期格式化为字符串yyyy-MM-dd HH:mm:ss不传Date对象给后端。后端Jackson全局配置spring.jackson.date-formatyyyy-MM-dd HH:mm:ss spring.jackson.time-zoneGMT8同时数据库连接串里也加serverTimezoneAsia/Shanghai三层时区统一日期就再也不会出错了。排查时间问题时的思路就是先看前端传过去的JSON字符串是什么格式再看后端接收到的Java对象是什么值最后看数据库里存的值是什么三步逐步定位而不是盲目在代码里加8小时或减8小时。7.3 MyBatis-Plus分页插件失效问题MyBatis-Plus的分页查询需要配置一个PaginationInnerInterceptor拦截器。如果用的是老版本或者配置漏了分页插件调用selectPage时不会真正执行分页SQL而是查全表后把所有数据返回再由MyBatis-Plus内存里进行分页。数据量小的时候看起来没问题数据量一上去接口就会慢慢变慢。检查方法很简单打开MyBatis日志看看打印的SQL语句里是否带有LIMIT关键字没有LIMIT就是分页插件没生效。配置方式Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }还要注意如果自己手写了大量关联查询的SQL比如设备列表带了预约状态子查询这类SQL里如果有自定义的count查询注意分页插件自动生成的count语句可能会因为JOIN语法问题报错这时需要手动在Mapper里写count方法并加上Count注解指定。7.4 前端打包后路由模式的问题Vue Router默认是hash模式URL里带#号。如果改成history模式URL更美观但生产环境必须配合Nginx的try_files配置否则刷新子页面就是404。上面部署章节已经提过try_files。如果你用hash模式开发在部署到Nginx时可以少配置try_files但history模式下缺了这个配置一定出问题。我的建议是开发环境怎么舒服怎么来生产环境如果Nginx配置跟不上就先用hash模式上线稳定后再切history。为了追求URL美观而上线后疯狂报404得不偿失。7.5 JWT过期后前端页面陷入无限重定向用户登录后拿到JWT前端每个请求都带Authorization头。如果用户长时间停留在页面不操作JWT过期后用户再次点操作按钮后端返回401axios拦截器收到401就跳转到登录页。这里如果处理不当会陷入跳登录页 - 登录页发现本地有token - 又跳回首页 - 首页请求又401的循环。正确做法是在axios响应拦截器里判断401后先清除本地存储的token和用户信息然后强制跳转到登录页并使用window.location.reload确保应用状态完全重置。不要用router.push跳转因为SPA状态下Pinia和Vue Router的内存状态可能还残留着旧用户的信息。拦截器核心代码service.interceptors.response.use( response { return response.data }, error { if (error.response error.response.status 401) { userStore.logout() router.replace(/login) window.location.reload() } return Promise.reject(error) } )实际跑下来这套系统从需求梳理到完整上线大约用了三个月的时间。说实话技术本身没有多难Spring Boot和Vue的组合在这几年里已经被无数项目验证过了难的事情是如何把实验室管理的业务流程抽成清晰的功能模块如何保证每个单据状态流转不丢数据如何在权限控制和数据安全上做出合理的权衡。如果你也准备做类似的系统我建议在动手写代码之前花至少一周时间泡在实验室里看管理员、老师和学生的真实操作把所有线下单据的流转路径画一遍再开始建表写接口。系统好不好用往往不取决于代码写得漂不漂亮而取决于你对业务流理解得到不到位。
返回列表