如果你在高校实验室待过大概率见过这样的场景管理员从抽屉里翻出厚厚一沓纸质台账一页页比对某瓶化学试剂还剩多少、什么时候过期学生填写领取单要跑三个办公室找签字月底盘点把几百种试剂一瓶瓶从柜子里搬出来数。这些看起来是小事可一旦涉及危化品就上升到了安全管理层面。我这次完成的基于SpringBootVue的高校危化试剂仓储管理系统就是冲着这套手工流程去的用Java做后端服务、MySQL存业务数据、MyBatis处理数据库操作、Vue做管理界面把危化品的入库、库存、领用、出库、预警整个链路管起来。这篇文章会把这个项目从需求拆解、数据库设计、后端接口实现、前端页面开发到部署上线的完整思路写出来也会把我实测过程中踩过的坑一并交代。适合正在做毕设、课设或者想拿一个完整前后端分离项目练手的人参考哪怕你之前只是学过框架基础照着这套逻辑也能把项目搭起来。1. 为什么我把“高校危化品仓储管理”当成实战项目来做1.1 高校实验室里的危化品管理痛点远比想象中多我在做这个项目之前专门去了解过几个高校实验室的管理现状。大多数实验室不是没有制度而是制度挂在墙上实操靠人肉。危化品的采购信息、入库信息、存放位置、领用记录往往散落在不同的Excel表甚至纸质本子上。管理员想查某一种试剂什么时候到期的得先翻半天台账想统计这个月哪些实验室领用量最大只能手动汇总更麻烦的是一旦出现账实不符根本说不清楚是入库漏记、出库没销还是中途损耗。这类问题放到普通耗材上可能只是对不上账放到危化品上就是实打实的安全隐患。所以高校普遍要求这类试剂实行闭环管理从采购计划开始到入库验收、库存保管、领用审批、出库登记每一步都要留下可追溯的记录。可手工做闭环管理实在太累这正是软件系统能发挥价值的地方。1.2 这套系统真正要管住的三件事拆解需求的时候我没急着画页面先想清楚了系统到底要管什么。核心就三件事。第一件事是“账实相符”。系统里的库存数量必须能对上仓库里实际摆放的数量这决定了出入库不能只做简单的增删改还要有批次、有流水、有冲销机制任何一次数量变化都要能追溯到单据。第二件事是“过程留痕”。谁在什么时间申请了哪种试剂哪个老师审批的管理员什么时候发的货试剂放到几号柜哪个位置这些信息要完整可查。所以系统的数据模型一定是围绕“流水”设计的而不是围绕“当前库存”设计的。第三件事是“风险预警”。危化品里有很多试剂对存量有上限要求也有不少试剂有保质期和失效期还有的使用频率很低但必须常备。系统需要能提前提示管理人员哪些试剂快低于库存下限了哪些快过期了哪些长期不用需要处理了。把这三点想清楚后面所有表结构和接口设计都有了依据。做管理系统最容易犯的错就是一上来就堆功能结果页面一大堆核心业务链路反而是断的。1.3 什么样的读者适合拿这个项目练手我觉得这个项目最值得学习的地方是它覆盖了一条完整的前后端业务链路但又没有复杂到让新手劝退。如果你刚学完JavaWeb和前端框架想找一个能写进简历、能完整演示的项目它是很合适的。相比纯电商系统危化品仓储的领域模型更清晰业务规则更明确后期答辩或者讲项目的时候很容易把“为什么这么设计”讲透。当然如果你是零基础刚看完语法我建议你先去把SpringBoot的自动配置、MyBatis的Mapper原理、Vue的生命周期这些基础补一补再来啃这个项目。否则遇到报错容易分不清是框架问题还是业务问题。2. 技术选型的分工逻辑SpringBoot、Vue、MyBatis、MySQL各管哪一段2.1 后端为什么锁定SpringBoot现在做Java后端起项目SpringBoot基本是默认选项但我还是想说说它在这个项目里到底带来了什么。最直观的是简化了配置。传统的SSM要写一堆XML配置文件数据源、事务、扫描包都要手动配SpringBoot用自动配置把这层体力活消化掉了我只需要在application.yml里写上数据源连接信息和MyBatis的mapper扫描路径项目就能跑起来。这对于单机部署的管理系统来说省掉的配置成本非常可观。第二点是它内置了Tomcat。开发的时候直接跑main方法不用单独装容器部署的时候打成一个jar包扔到服务器上一条java -jar命令就能启动。对高校实验室这种环境来说服务器资源有限、运维水平参差不齐这种“一个jar包搞定”的部署方式非常友好。还有一点SpringBoot生态对MyBatis的整合做得比较顺。mybatis-spring-boot-starter引入之后SqlSessionFactory自动创建Mapper接口自动扫描开发者只需要关注SQL本身。在快速迭代业务逻辑的阶段这种顺畅度很重要。2.2 MyBatis在库存多条件查询里的价值这个项目的查询场景很典型库存列表要根据化学品名称、CAS号、分类、存放仓库、库存状态等多个条件组合筛选而且这些条件可能任意为空。用JDBC手写的话SQL拼接要写大量if判断用JPA的话复杂查询反而不太直观。MyBatis的XML动态SQL正好卡在这个需求点上。比如库存查询我用一个where标签配合if标签就能把所有可选条件拼成一句安全的SQL。这样做的好处是SQL完全可控遇到性能问题可以直接优化SQL本身而不是去调框架的抽象层。另外一个重要原因是MyBatis对结果集的映射更直白数据库字段是下划线风格Java属性是驼峰风格开启map-underscore-to-camel-case之后查询结果自动对应到实体类上省去了一大堆手动set的代码。2.3 Vue侧负责什么以及前后端分离怎么组织前端我用Vue搭建配合Vue Router做页面路由、Vuex或Pinia存登录状态、Axios请求后端接口。页面分为登录页、工作台、化学品档案管理、库存管理、出入库管理、领用审批、统计报表、系统管理等模块。前后端分离之后开发阶段的协作方式很简单后端只提供JSON接口前端只负责渲染和交互。本地开发时前端用Vite或WebpackDevServer起一个8081端口通过代理把/api前缀的请求转发到后端8080这样就不会有跨域问题。生产环境则是把前端build出来的dist目录交给Nginx托管Nginx再把/api反向代理到后端服务。这套组织方式现在已经是主流但它对这个项目还有一个额外价值危化品管理涉及多个角色比如学生、教师、实验室管理员、系统管理员他们的操作界面差异比较大。前后端分离后前端可以根据角色动态生成菜单和路由后端只需要校验接口权限不用关心页面怎么渲染。3. 数据库设计是这类系统的命根子从台账反推表结构3.1 核心业务表怎么拆分我设计数据库的时候脑子里先放了一本“手工台账”然后想如果要让这套台账电子化、可查询、可统计需要哪些表。最终核心表分成六张。第一张是用户表存登录账号、密码、姓名、角色类型。第二张是化学品档案表记录化学品的名称、别名、CAS号、分子式、危化品分类、储存条件、MSDS附件路径、默认库存上下限。第三张是库存批次表因为同一化学试剂可能分多批入库每一批的生产日期、失效日期、入库日期、当前剩余量很可能不一样不能用一张总库存表糊弄过去。第四张是出入库流水表所有数量变动都记录在这张表里包括入库、出库、盘盈盘亏冲销。第五张是领用申请表记录谁申请、申请什么、申请多少、指导教师是否审批、管理员是否发放。第六张是存放位置表也就是仓库里的柜子、货架、冷藏位等。这里我最想强调的是“库存批次表”存在的必要性。如果只设计一张化学品的总量字段入库时加数量、出库时减数量表面上看很简单但你无法回答“这批试剂还有多少没过期”“是哪批先入库的”这类问题。有了批次表每次入库生成一个批次出库时按批次先进先出扣减才算真正符合危化品仓储的精细化管理要求。3.2 “双人双锁”和台账追溯在数据模型里怎么落地高校危化品仓库通常有“双人双锁”的管理要求意思是一类危险试剂的柜门需要两个保管员同时在场才能打开避免单人接触高风险试剂。这个制度反映到系统里就是出库环节不能只有一个人操作。我的做法是在领用申请和出库记录上增加两个字段保管人确认人和审核人。学生提出领用申请后指导教师先做审批然后管理员在出库登记页面填写实发数量时系统要求记录两位现场人员的工号或姓名相当于把线下“双人双锁”动作数字化。这个设计不仅是形式上满足制度更重要的是让每一次高风险试剂出库都有两个责任人背书。台账追溯则依赖流水表的“不可删除”规则。我在业务代码里做了约定所有出入库记录只能增加冲销记录不能修改或物理删除原始记录。这样做的好处是任何时刻打开系统都能还原出某一种试剂从进场到出场的完整生命周期。数据库层的关联字段也很关键流水表要同时存化学品ID、批次ID、单据号、操作人ID、操作时间这样既能按化学品追溯也能按时间范围筛选。3.3 几个容易设计错的地方我在数据库设计阶段踩过几个坑在这里给后来人提个醒。第一个坑是把“危化品分类”设计成字符串字段直接填“易燃液体”“腐蚀品”这种文字。看起来直观但后面做统计、做权限控制会很痛苦。比如管理员想按类别筛选所有“氧化剂”如果分类字段是自由文本就会遇到大小写不一致、别名混杂的问题。正确做法是单独建一张分类数据字典表化学品表用分类ID关联。第二个坑是库存数量用浮点数。化学品称量经常出现0.35克、1.2毫升这类数据很多人习惯用double存。但浮点数经过多次加减之后会积累误差可能导致库存对不上。应该用DECIMAL类型并且统一单位精度比如全部用“克”“毫升”做基本单位入库时把单位换算清楚再存。第三个坑是忽略“流水号”字段。有人觉得数据库主键自增就够了但实际业务中管理员打印单据、跟线下纸质单核对的时候更习惯看到一条格式明确的单据号比如RK20250601001表示入库、CK20250601001表示出库。建议在表里增加业务单号字段用日期加序列生成方便线上线下对账。3.4 初始化数据字典很重要数据库表建好之后不要急着写接口先把数据字典初始化做掉。我是把化学品分类、计量单位、存放仓库、柜位信息、初始化管理员账号都做成了SQL脚本一次性导入。这一步看起来不起眼但能让后面的开发省很多事因为前端下拉框的数据都来自这些字典表如果每写一个下拉框都去后端单独写一个接口那会非常零散。我还会往化学品档案表里预置一批常见的实验室试剂比如无水乙醇、丙酮、盐酸、氢氧化钠等。这些数据不光方便测试也方便演示系统。答辩或者汇报的时候打开库存页面就能看到完整的示范数据展示效果会好很多。4. 后端核心链路拆解从入库到出库的长链路实现4.1 登录与RBAC权限控制后端第一个模块是登录认证和权限控制。我用SpringSecurity配合JWT实现用户登录成功后后端签发一个token前端把它存在本地每次请求带到Authorization头里后端通过过滤器校验token有效性。权限模型采用的是RBAC也就是用户、角色、权限三层。这个项目里我定义了四种角色学生、指导教师、实验室管理员、系统管理员。他们看到的菜单和能调的接口各不一样比如学生只能提交领用申请、查看自己的申请记录指导教师只能审批自己名下的学生的申请实验室管理员拥有库存管理、出入库登记的权限系统管理员则负责用户维护、数据字典和系统设置。实现上我在每个接口上用PreAuthorize注解标注需要的角色比如出库登记接口限制为管理员角色审批接口限制为教师角色。这样即使前端隐藏了按钮后端也不会被绕过安全性可控。4.2 入库业务批次、数量、存放位置一起锁定入库接口的逻辑比想象中多一点。前端会提交一个入库单内容包括化学品ID、数量、单位、生产日期、失效日期、存放仓库ID、柜位ID、供应商名称、入库经手人。后端接收后在一个事务里完成三件事。第一步往库存批次表插入一条新记录状态为“在库”。第二步在出入库流水表里插入一条入库流水流水类型为“入库”操作人取当前登录用户的ID。第三步更新化学品档案表的库存汇总字段也就是把总可用数量加上本次入库量。这三步必须在同一个数据库事务里完成否则可能出现批次表有了数据但流水缺失或者库存汇总对不上批次明细的情况。我在做扣减库存和增加库存这类操作时还加了一层乐观锁处理。库存表上有一个version字段更新时使用UPDATE stock SET quantity quantity - #{num}, version version 1 WHERE id #{id} AND quantity #{num}这种写法从SQL层面保证不会并发扣成负数。这一点在高校仓库实操场景中尤其重要因为出库可能集中在学期初和学期末多个管理员同时操作很常见。4.3 出库业务申请、审批、核销、扣减四个动作出库链路是这个项目里最核心的业务流程我把整个流程拆成了四个动作申请、审批、核销、扣减。学生或教师发起领用申请时填写要领取的化学品、数量、用途说明系统会校验当前库存是否充足并且拦下那些库存低于预警值的申请提示先联系管理员补货。申请提交后进入待审批状态。指导教师角色在待办列表里看到申请可以同意或者驳回。同意只是流程通行库存此时还没有变化这一点很重要。很多初学者会把审批和出库混成一个接口结果一审批库存就扣了但实际货物还没发出去账实就分叉了。管理员在核销阶段填写实际发放数量。因为实际称量和申请数量一般会有少量出入所以系统允许管理员填写本次实发数。点击确认后后端才真正执行库存扣减并且根据先进先出的原则从最早批次开始扣减数量。同时流水表会生成一条出库记录申请单状态变更为“已出库”。这样拆分下来每一步的职责都很清晰出了问题也容易定位。比如学生说申请了但没拿到货那肯定是卡在审批或核销环节查状态就能知道。4.4 库存预警与有效期提醒的实现方案预警功能是这个项目里比较出彩的部分。我做了三类推送库存低于下限预警、有效期临近预警、呆滞库存提醒。库存预警的逻辑很简单每次出入库操作后对比化学品档案表里设置的库存下限低于下限的化学品ID被标记管理员登录后可以看到一个待办卡片。有效期预警用定时任务来实现我用的Spring自带的Scheduled注解每天凌晨跑一次查询所有批次中失效日期在30天内的记录如果批次还有剩余数量就生成一条预警信息。呆滞库存提醒则统计超过180天没有任何出入库流水的在库批次提醒管理员这些试剂需要检查是否过期或者是否该调剂使用。用定时任务的好处是简单可靠不需要额外引入消息队列。对于高校实验室这种数据量每天扫一次完全够用。需要注意的是预警表的数据要设计成已读状态管理员点击“已读”后不再重复提醒避免每天被同样的消息刷屏。5. 前端Vue侧的实现让老师和管理员愿意天天用5.1 页面结构与路由权限前端页面的规划我按照角色使用频率来排登录后学生第一个看到的是“我的申请”因为他的主要动作就是申请领用教师看到的是“审批待办”实验室管理员看到的是“库存总览”和“出入库登记”。路由权限是用VueRouter的全局前置守卫实现的。用户在登录时后端返回角色信息前端把角色和可访问路由的映射关系维护好。每次跳转前守卫判断目标路由是否需要特定角色如果当前用户角色不匹配直接重定向到403页面。菜单也是根据角色动态渲染的Element Plus的菜单组件配合一个根据角色过滤后的路由配置数组就能做到每个角色只看到自己能用的功能。这里我提一个经验前端路由守卫只是体验层面的控制真正的安全要靠后端接口鉴权。不要因为前端隐藏了按钮就觉得安全了直接调接口一样能绕过。所以我在前后端两边都做了同样的权限控制前端管展示后端管数据。5.2 高危试剂选择的联动表单领用申请表单是这个项目交互上最需要打磨的地方。试剂的种类多、名称相近如果让用户从几百条数据里手动找一个下拉项体验很差。我做了一个联动选择器首先是危化品分类的下拉框用户选了“易燃液体”之后试剂名称的下拉框自动只显示该分类下的化学品。如果用户知道完整名称也可以直接输入关键字远程搜索后端提供一个按名称或CAS号模糊查询的接口前端用el-select的远程搜索方法触发。选中试剂后页面自动带出当前的可用库存总量、存放位置、危险特性标签并显示单位选择。数量输入框上我加了双重的校验前端校验不能超过当前库存后端事务里还会再次判断。表单提交前还会弹出一个确认提示框展示本次领用的试剂名称、危险分类、数量相当于线上版的“操作前确认”让申请人对高危试剂的操作保持敬畏。5.3 统计看板与图表展示统计页面我用ECharts做了几个图表。第一个是每月出入库趋势图横轴是月份纵轴是出入库数量用两条折线对比管理员一眼就能看出来哪些月份是领用高峰方便提前备货。第二个是库存分布饼图按危化品分类统计当前在库试剂的数量占比帮助管理人员了解库存结构是否合理。第三个是高频领用试剂排行榜用柱状图展示相关数据。ECharts在Vue里的用法很简单组件挂载后初始化实例然后setOption传入数据和配置注意在组件销毁时调用dispose方法释放实例避免内存泄漏。图表数据统一由后端统计接口返回聚合结果不要在浏览器端做大量计算因为数据量大了之后前端会卡顿。这块内容不需要做得很复杂但对项目的观感提升明显。之前做管理系统只关心表格能不能展示数据其实一张好的统计图比几页明细表更能说明问题汇报和答辩的时候也更有说服力。5.4 容易被忽视的使用体验细节有几个细节是实际用过之后才补上去的新手开发时特别容易忽略。第一个是列表页的刷新保留条件。管理员在库存页面筛选了分类、输入了名称点进详情再返回筛选条件如果没了会非常恼火。我用Pinia把查询条件暂存起来返回列表时恢复这个小改动极大提升使用体感。第二个是操作反馈要明确。所有提交类操作完成后不仅弹成功提示还要让列表自动刷新。比如出库完成后当前化学品的剩余数量要立刻更新不要让用户手动刷新页面才能看到结果。第三个是危险品要有视觉标识。化学品列表里危化品分类我做了Tag标签易燃易爆类的用比较醒目的颜色普通试剂用中性色。看起来只是样式问题但对仓库管理员来说颜色标签能帮助快速识别高风险物品减少因为看错名称导致的操作失误。6. 联调、打包部署与实测中踩过的坑6.1 本地联调的流程与跨域处理前后端各自开发完之后联调阶段是最容易出问题的。我当时的流程是先确保后端接口用Postman或Swagger全部跑通再去调前端页面。Swagger在这个项目里帮了大忙。SpringBoot集成springfox或springdoc之后启动项目就能看到一个接口文档页面每个接口的请求参数、返回结构一目了然。前端联调的时候对照Swagger不用频繁问后端要文档效率高很多。跨域问题在联调中也出现过。我用的是Vite代理方案在vite.config.js里配置server.proxy把/api开头的请求转发到后端服务的地址这样浏览器看到的请求是同源的不存在跨域。如果你用Nginx部署测试环境也可以把跨域问题交给Nginx处理反向代理天然规避了跨域。6.2 打包成jar后如何配合Nginx上线部署环节是很多学生项目最薄弱的我简单说一下我验证过的方案。后端项目用Maven的package命令打成jar包前提是application.yml里配置了生产环境的数据库地址。服务器上只需要安装JDK和MySQL用nohup java -jar manage-system.jar app.log 21 启动日志输出到文件方便排查问题。前端在项目目录执行npm run build生成dist目录上传到服务器的Nginx目录下。Nginx的配置关键点在location匹配。我把前端页面托管到根路径把/api路径代理到本机的8080端口location /api { proxy_pass http://127.0.0.1:8080; }。这里要注意带不带末尾斜杠会影响转发的URL拼接我当时因为proxy_pass的路径写法和location的路径叠加关系搞混导致接口全部404排查了半天。还有一点前端路由使用history模式时用户刷新某个子页面会报404需要在Nginx配置里加上try_files $uri $uri/ /index.html;让所有找不到的静态资源请求都回退到首页由前端路由接管。6.3 实测中遇到的典型问题与解决记录我把开发过程中印象最深的几个问题列出来按问题、原因、解决思路整理成一张表方便对照排查。问题现象根因分析解决方式前端请求接口报SSL连接错误MySQL 8默认启用SSL连接串缺少配置JDBC连接串加useSSLfalseserverTimezoneAsia/Shanghai时间字段差8小时连接串未指定时区设置serverTimezone为Asia/Shanghai查询结果里下划线字段为nullMyBatis驼峰映射未开启application.yml中配置map-underscore-to-camel-case: true前端刷新页面404history路由模式没有Nginx兜底配置try_files回退到index.html库存扣减出现负数没有并发控制改用带条件的UPDATE语句加version乐观锁导出Excel中文乱码响应头编码设置问题设置Content-Disposition时使用URLEncoder编码文件名这里特别说下时区那个坑。MySQL连接串如果不指定serverTimezone默认可能取到服务器系统时间而JDBC驱动解析的时候又会按JVM默认时区处理两边不一致就会出现数据差了8小时。这个坑在部署到云服务器时更常见因为服务器默认时区往往是UTC本地开发可能没事一部署就出问题。6.4 上线之后我的一些维护建议系统部署完成、能跑通业务之后真正的工作才开始。我看到很多同学做完项目就把源码放一边这其实很可惜。至少有几件事是值得坚持做的。第一件是定期备份数据库。仓库管理系统的数据是资产库存账本丢了对实验室来说是很严重的事故。我在服务器上写了一个简单的crontab脚本每天凌晨用mysqldump导出数据库保留最近30天的备份文件顺便同步一份到远程存储。这个操作成本很低但能救命。第二件是给用户账号做年度盘点。高校人员流动大学生毕业、老师调岗都很快。如果账号不清理权限会越来越泛滥。建议管理员定期把学生账号冻结或删除导师角色也要跟着人事变动调整。第三件是关注预警表的推送效果。预警不是发出来就完事要跟进处置结果。我在系统里加了一个“处置记录”字段管理员可以填写某某批次的临期化学品已经调配到其他实验室使用了或者已进入报废流程。这样预警信息就形成了闭环而不是一条看完就删的通知。做这个项目给我最大的体会是管理系统看起来到处都有但真要把一个领域的业务逻辑理清楚、做成能稳定运行的系统并不像套模板那么简单。尤其是危化品这种涉及安全责任的场景一个字段少设计、一个流程少判断背后都可能对应着真实的账实不符风险。如果你也在做类似的项目建议先下功夫理解业务流程和数据库设计把基础层做好再去填充那些锦上添花的功能。这样不管前端是Vue还是React后端换了什么框架核心思路都能复用。
阅读完成 · 觉得有帮助?