ARTICLE DETAIL

资讯详情

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

Spring Boot 乡镇卫生所医用物资进销存系统设计:批次建模与效期预警实践

Spring Boot 乡镇卫生所医用物资进销存系统设计:批次建模与效期预警实践 前阵子帮一个乡镇卫生所改造库存管理流程我到现在还记得库管员那张办公桌——三本厚册子一本入库、一本出库、一本报废登记全部手工记账每月底对账要对到天黑。最让他们发怵的不是数量对不上而是一批医用耗材躺在角落里到了效期才发现已经不能用了。这种场景在基层网点和中小医疗机构里其实非常普遍医用物资的种类不算特别多但管理颗粒度一点都不低批号、效期、注册证号、急救物资保管这些事绕不开。我当时就决定用 Spring Boot 做一套轻量的医用物资进销存系统前后端最终打成一个 jar扔到一台普通电脑或小服务器上就能跑。整个过程做下来踩了不少坑也积累了一些对这个垂直场景的判断。这篇就完整分享一下乡镇卫生所医用物资进销存系统的设计与实现思路包含需求定位、技术选型、库存建模、出入库流程、效期预警、权限和部署以及我最想说的那些容易翻车的细节。正在做 Spring Boot 毕设的同学或者准备给基层做信息化改造的人都可以直接参考这套方案。1. 为什么乡镇卫生所的物资账比普通仓库难管得多很多人觉得进销存系统到处都是做个 CRUD 套个模板就行。但医用物资和普通商品有个根本差异普通仓库管的是“数量对不对”医疗场景还要管“效期到没到”“批号追不追得到”“采购资质全不全”。乡镇卫生所又是医疗场景里比较特殊的一种我把它拆成三层来看。1.1 医用物资管理的三个特殊基因第一品种杂且每一类都有“身份信息”。从一次性注射器、纱布、棉签、口罩、手套这类低值耗材到缝合线、留置针、骨科耗材这类相对高值的材料再到急救药品、消毒液它们的档案字段完全不一样。有的要登记注册证号有的要记录生产批号和有效期有的还要在意冷链和保存条件。如果数据库里只存“名称 库存数量”后面批号追溯和效期管控根本没法定。第二效期是硬约束过期不是“商品贬值”而是“安全性事故”。普通超市卖一罐过期罐头顶多下架医用物资过期如果还用直接是人命关天的事。这意味着系统里每个批次都要单独记录生产日期和有效期不仅出库要按效期优先出还要在过期前提前预警让库管员有时间处理。第三乡镇卫生所通常没有专职信息岗。库管员可能同时还管收费、管医保结算工作人员对电脑操作的熟悉程度参差不齐。系统如果做得太重、菜单太绕、动不动报错最后一定会被弃用打回手工记账。所以设计目标里“简单稳定”排在“功能丰富”前面。1.2 手工账和 Excel 管理撑到极限的四个表现我调研时问过几家乡镇卫生所去翻了一下他们之前的账本和 Excel 表格问题高度集中在四件事上。效期失控手工登记时批号、效期经常漏填只能在纸面上记一个“2024年买的一批纱布”具体几月到期完全查不到。即便是 Excel 表很多人也只会登记总数不会按批次去拆。账实不符领用的时候不登记、借调给隔壁卫生所不写单月底一盘点账上 100 卷纱布柜子里只有 60 卷谁都说不清楚中间去哪了。应急物资断档急救药品和抢救耗材平时用得少经常等到真要用的时候才发现过期了或库存不足。这种物资恰恰是最不该断的。审计说不清医用耗材采购、入库、领用、报损必须留痕。手工本子虽然勉强能看但纸质单据和记账之间经常对不上遇到检查或者内部审计就很被动。这些问题不是 Excel 加几列就能解决的。关键是“批次”和“效期”这两个维度必须成为系统的一等公民所有业务都围绕它们流转。这也是我后来整个数据库建模的核心出发点。1.3 这套系统要解决到什么程度基于上面的痛点我把项目目标收敛成一句话让一个不懂技术的库管员每天只需要录入“进了什么、出了什么”系统自动维护批次库存、效期预警、低库存提醒和全部台账记录。对应的功能模块我列下来是这样的基础数据物资档案、供应商管理、科室管理、物资分类。入库管理采购入库单、入库退货单、入库历史。出库管理科室领用单、报损单、借用归还单。库存管理实时总库存、批次库存、近效期预警、低库存预警、过期锁定。盘点管理盘点单生成、盘点差异处理、盘盈盘亏自动出入库。报表统计入库统计、出库统计、库存周转、科室领用排行。系统管理用户、角色、菜单、日志管理。这套模块清单对 Spring Boot 毕设来说也非常友好既有 CRUD、又有业务规则、还有定时任务和报表技术点和业务点都够说。接下来聊聊技术选型这部分我想先给你泼一盆冷水。2. 技术选型我为什么用单体的 Spring Boot 而不是微服务干这件事每年都能看到不少毕设和项目把 Spring Cloud 那一套塞进一个十几张表的管理系统里服务拆了七八个每个服务就两三个接口最后部署还要写半天 Docker 编排。对这种体量的系统来说属于过度设计。2.1 先算算这个系统的真实规模乡镇卫生所的典型规模大概是这样一到三个服务网点几十个科室和卫生室可管理的物资 SKU 在几百到一千出头同时在线操作的用户可能就三五个到十来个人。这种访问压力下一台 2 核 4G 内存的服务器甚至一台普通的台式电脑就能稳稳扛住。并发量不会超过几十数据量一年也就几万条出入库记录。用微服务去拆分这种规模的系统除了招人围观解决不了任何真实问题。Spring Boot 单体应用把业务模块分好包代码一样清晰部署还特别简单——一个 jar 启动就完了。对乡镇卫生所这种没有专职运维的场景“出问题一个人能搞定”就是最大的优点。2.2 我最终采用的技术栈和选择理由我在这套系统里用的是前后端分离方案最终打成单 jar 部署具体选型如下表。层次选型关键理由后端框架Spring Boot 2.7.18稳定、生态兼容最好不盲目追新JDKJava 8乡镇所和老服务器兼容性最好毕设环境也常见ORMMyBatis-Plus 3.5.3.1单表 CRUD 省事分页和条件构造器好用数据库MySQL 8.0免费、生态成熟初始化脚本好维护缓存Caffeine本地缓存数据量不大没必要上 Redis认证授权Sa-Token 1.37 JWT 模式比手写 Spring Security 配置简单太多前端Vue 3 Element Plus ECharts表单、表格、报表可视化都有现成组件文件上传本地目录存储 预览Excel 导入用不做对象存储部署单 jar 内置 Tomcat前端 dist 打入 static一个进程全部搞定这里最想强调的是版本匹配问题。二〇二三年以后 Spring Boot 3.x 已经很普及JDK 17 也成了默认选项但如果你的项目和毕设用的是 MyBatis-Plus 老版本、Druid 连接池换到 Spring Boot 3 经常会遇到 javax 和 jakarta 包名冲突、自动配置不生效、拦截器失效这些问题。对这套业务来说技术新旧的收益微乎其微踩兼容性坑的代价却很大。所以我最后坚定选了 Spring Boot 2.7.18 JDK 8这是所有组件兼容性最稳的组合。2.3 前端两种路线对比一套是前后端分离一套是服务端渲染如果你做的是毕设我建议你认真想想前端方案因为答辩时老师主要看的是“完整性和合理性”。方案一Vue 3 做前端开发时用 vite 代理到后端 8080 端口写完npm run build把生成的dist目录拷进src/main/resources/static。这样最终部署就是一个 springboot jar不用额外起 Nginx也不用管跨域。这也是热搜词里“vue打包放进springboot中”那个问题的标准答案后面部署章节我会把具体步骤和坑都写出来。方案二后端用 Thymeleaf 模板加 Bootstrap 或 Layui后端小姐姐直接返回页面。这个方案开发速度快但页面交互和报表展示的美观度要差不少。如果只是为了快速跑通业务流程可以选但想拿它当完整作品展示我还是推荐方案一。2.4 别被“Spring Boot 整合 Flink / ActiveMQ”这类关键词带偏我在检索资料时发现很多人搜“springboot 整合 flink”“springboot 整合 activemq”我不建议在这个项目里碰这些东西。乡镇卫生所没有大流量日志流要处理也没有复杂的异步消息需要解耦。硬上 Flink 和 MQ除了把部署复杂度拉高业务价值基本为零。如果后面想把预警通知做成异步用 Spring 自带的Async事件监听就够了如果后续要多卫生所接入优先考虑在数据库模型里加org_id做多租户而不是引入服务拆分。技术选型永远跟着业务规模走这个判断比任何技术本身都重要。3. 库存建模物资表、批次表、库存表怎么拆才不出烂账这块是整个系统能不能真正能用的命门。我先给你看一个反例这是很多进销存系统做烂的原因。3.1 为什么不能只用一张“物资-数量”表假设现在有一张material表字段是id, name, category, total_qty。纱布一共 5000 卷就记一个总数 5000。问题来了这 5000 卷是分三批进的一批生产日期是今年 1 月、效期到明年 1 月另一批是今年 9 月进的、效期到后年 9 月。你光看总数根本不知道哪批快过期。等到医生领用时想先用效期短的系统也没法区分只能靠库管员去货架前翻。更麻烦的是追溯。某一批纱布被消费者投诉或者质检不过关要召回你只知道“进了 5000 卷、卖了 3000 卷”但不知道这 3000 卷分别出给哪个科室、哪一天出的。没有批次概念召回和追溯就是空中楼阁。所以医用物资进销存的第一步就是把“批次”立起来。3.2 核心表结构物资档案、批次库存、总库存三层我最终的表结构围绕三层展开。第一层是物资档案表material存的是“这件物资是什么”不关心有多少。关键字段包括CREATE TABLE material ( id bigint NOT NULL AUTO_INCREMENT, material_code varchar(50) NOT NULL COMMENT 物资编码, material_name varchar(100) NOT NULL COMMENT 物资名称, category_id bigint DEFAULT NULL COMMENT 分类ID, specification varchar(100) DEFAULT NULL COMMENT 规格型号, unit varchar(20) NOT NULL COMMENT 单位, manufacturer varchar(100) DEFAULT NULL COMMENT 生产厂家, registration_no varchar(50) DEFAULT NULL COMMENT 注册证号/备案号, min_stock int DEFAULT 0 COMMENT 最低库存预警线, expiry_warn_days int DEFAULT 90 COMMENT 近效期预警天数, status tinyint DEFAULT 1 COMMENT 1正常 0停用, create_time datetime DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_material_code (material_code) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;第二层是批次库存表material_batch_stock这是整套系统的心脏。同一件医用物资进来几批就要拆成几条记录每条记录自带批号、生产日期、有效期和剩余数量。CREATE TABLE material_batch_stock ( id bigint NOT NULL AUTO_INCREMENT, material_id bigint NOT NULL, batch_no varchar(50) NOT NULL COMMENT 生产批号, production_date date DEFAULT NULL COMMENT 生产日期, expiry_date date NOT NULL COMMENT 有效期至, quantity int NOT NULL DEFAULT 0 COMMENT 剩余数量, purchase_price decimal(10,2) DEFAULT NULL COMMENT 入库单价, supplier_id bigint DEFAULT NULL COMMENT 供应商ID, status tinyint DEFAULT 1 COMMENT 1正常 2过期锁定 3已清空, create_time datetime DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_material_batch (material_id, batch_no), KEY idx_expiry (expiry_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;第三层是总库存表material_stock存“这件物资目前一共还剩多少”只做快速查询和预警用不承担批次逻辑。所有数量变化必须同时更新总库存和批次库存。这样拆完物资基础的“身份证信息”在档案表一批具体的“货”在批次表实时总余量在库存表各管一摊。你在页面列表上看到的“纱布总数 3500 卷”来自总库存表点进详情按有效期排序看到的具体批次明细来自批次表把鼠标移到某一批上能查它所有出入库历史靠的是后面的流水表。3.3 批次数量、总库存、流水表三者如何协作我见过一些方案是直接用批次表汇总算总库存不单独建总库存表。这种做法逻辑上没毛病但每次列表页展示库存都要SUM(quantity) GROUP BY material_id几十个物料还好几百上千个物料再加上筛选条件查询会明显变慢而且每次都要算。我更推荐冗余一个总库存表出库入库时在同一事务里同时更新。查询时直查总库存表加索引后秒开。需要注意总库存字段的更新不能靠应用层“先查出来加加减减再写回去”要用原子 SQL后面讲防超卖时还会再提。最后每一笔入库、出库、盘点调整都要往库存流水表stock_record里插一条记录记录变更类型、变更数量、变更后数量、关联单号、操作人。这张流水表是审计和追溯的关键也方便你在出问题时排查到底是哪笔单子把库存搞乱的。3.4 批次查询必须默认按有效期排序因为核心业务是效期管理所以“查询某物资的可用批次”这个操作在任何地方都要默认按expiry_date ASC排序。哪怕你没做 FEFO 出库列表展示也要让效期最近的排最上面让人一眼看到危险。SELECT id, batch_no, expiry_date, quantity FROM material_batch_stock WHERE material_id #{materialId} AND quantity 0 AND status 1 ORDER BY expiry_date ASC;低库存预警的 SQL 也很简单直接关联两张表查低于最低库存线的物资SELECT m.id, m.material_name, s.total_quantity, m.min_stock FROM material m JOIN material_stock s ON s.material_id m.id WHERE s.total_quantity m.min_stock;4. 出入库流程的核心逻辑批次流转、事务边界、防超卖数据库建模定下来以后真正的业务逻辑就在出入库流程里。这部分我建议你先把“单据”这个概念想清楚不要直接在库存表上做加减而是要先生成入库单、出库单再通过单据去影响库存。4.1 入库流程单头、明细、批次三层嵌套入库不是简单“点一下数量 1”而是要留全程凭证。我设计的入库单结构是一张入库单头对应多条入库明细一条明细可能对应一个或多个批次。单头字段入库单号、供应商、入库日期、操作员、单据状态草稿/已入库/已作废、备注。 明细字段物资、入库数量、单价、生产批号、生产日期、有效期。 批次展开如果同一种物资一批有多个批号就在保存时拆成多条批次库存记录。入库保存的核心逻辑是在一个Transactional方法里先插入单头和明细再根据明细的批号批量插入或累加material_batch_stock最后累加material_stock.total_quantity并写库存流水。中间任何一步失败整个事务回滚绝不出现“单子保存了但库存没加”的情况。这里有个实操细节供应商送货单上的批号同一个物资可能重复出现。如果系统里已经存在同批号记录不要新建批次而是在原批次上quantity quantity 采购数量。用代码判断时先SELECT id FROM material_batch_stock WHERE material_id? AND batch_no?查一下有就更新没有就插入。4.2 出库流程默认按效期优先也就是 FEFO 策略出库是科室领用的核心场景。库管员在页面上选一个科室再选“纱布 500 卷”提交后系统不是简单地找总库存减 500而是自动分解到批次上优先扣除效期最近的批次。这就是 FEFOFirst Expire First Out先到期先出库。这个策略很好理解常规仓储讲究先进先出 FIFO按入库时间先出但医用物资的效期和生产日期往往不对应可能一批先进来的物资效期反而比后进来的更长。按入库时间出库极可能把效期长的先出完把效期短的压在库里直到过期。所以这里必须牺牲“入库时间顺序”严格以expiry_date排序。具体实现的思路是这样Transactional(rollbackFor Exception.class) public void doOutbound(OutboundBill bill) { // 1. 保存出库单头、出库明细先保存状态为待出库 // 2. 逐条处理出库明细 for (OutboundItem item : bill.getItems()) { int remainQty item.getQty(); // 查出所有可用批次按效期升序 ListBatchStock batches batchStockMapper.selectAvailableBatch(item.getMaterialId()); for (BatchStock batch : batches) { if (remainQty 0) break; int deductQty Math.min(remainQty, batch.getQuantity()); // 用行锁/条件更新扣减批次库存 int rows batchStockMapper.deductQtyWithLock(batch.getId(), deductQty); if (rows 0) { // 说明这一批已经被并发掏空重新刷列表继续找下一批 continue; } // 同步扣减总库存写库存流水 stockService.deductTotalStock(item.getMaterialId(), deductQty); stockRecordService.record(item.getMaterialId(), batch.getId(), OUT, deductQty, bill.getBillNo()); remainQty - deductQty; } if (remainQty 0) { throw new RuntimeException(物资[ item.getMaterialName() ]库存不足剩余未出库 remainQty); } } // 3. 更新出库单状态为已出库 }这个代码示例省略了不少细节但核心骨架就是这样遍历批次、逐批扣减、不足就抛异常回滚。跨批次出库后出库明细里可以记录“实际扣减了哪几个批次、各扣多少”这样以后追溯“这 500 卷纱布给哪个科室了”就能直接按批次查到。这也是卫生所审计时最喜欢看到的效果。4.3 防超卖条件更新是底线不能用“先查后改”很多新手写扣库存习惯先查一下剩余数量Java 里判断够不够再 UPDATE。这在单用户测试时没问题一旦两个人同时领用就会出大问题。举个例子纱布总库存还剩 100 卷。A 要领 80B 要领 50。两个请求同时读到剩余 100A 判断够写回去 20B 判断也够写回去 50。最终库存变成 50而实际出了 130 卷库存变成负数账全乱了。解决办法只有一个把“判断 扣减”合并成一条原子 SQL。UPDATE material_batch_stock SET quantity quantity - #{deductQty} WHERE id #{batchId} AND quantity #{deductQty} AND status 1;这条 SQL 的执行是数据库行锁级别的原子操作。如果quantity不够扣影响行数是 0代码就知道这个批次不能用了接着去试下一个批次。总库存表同理UPDATE material_stock SET total_quantity total_quantity - #{deductQty} WHERE material_id #{materialId} AND total_quantity #{deductQty};这两个操作必须放在同一个事务里。我早期版本就是因为只看批次扣减、忘了同步扣总库存导致列表显示总数不变排查了整整一个下午才发现少了第二步。4.4 盘点与报损把“盘盈盘亏”变成标准的出入库单据月底盘点是卫生所避不开的工作。我的实现方式是盘点开始时生成一张盘点单系统自动把当前账存数量冻结到盘点明细里库管员拿着明细表去点数把实盘数量填回来提交后系统自动计算差异生成盘盈单或盘亏单——盘亏走类似出库流程扣减库存盘盈走类似入库流程增加库存。这里要注意盘点期间如果有人领用物资会导致差异算不准。简单做法是盘点单保存时记录账存快照差异 实盘数量 - 快照数量后续领用不影响这个差异计算但盘点完成后需要重新同步总库存。如果卫生所规模小、操作人不多也可以约定盘点期间暂停领用系统里给一张盘点单加个“锁定”状态被锁定的物资在盘点期间禁止出库逻辑更干净。报损单则单独处理记录物资、批次号、报损数量、报损原因过期/破损/污染/质检不合格、经手人、处理方式销毁/退货并关联到某个批次做扣减。过期的批次必须走报损流程不能静默删掉这样才能在报表里看到“这个卫生所这个季度报损了多少物资、主要是什么原因”。4.5 事务边界的三条纪律出入库逻辑基本成型后我特别总结了三条事务纪律写代码时一直守着。不要把文件上传、消息通知、邮件发送放进大事务里。比如提交入库单后要通知供应商发邮件失败不应该导致入库回滚。做法是先提交事务再用事件监听异步去做通知。Excel 批量导入不要在一个事务里insert几千条。MyBatis-Plus 的saveBatch底层虽然分段批量插入但大事务里还是容易锁表和超时。我的做法是分批读取、分批校验、分批保存每批 200 条出错的记录单独返回给前端。跨方法调用事务时注意this自调用问题Transactional不会生效。要通过注入的代理对象调用或者把事务逻辑拆到一个独立的 Service 类里。这一点很多人笔试都会答但真写代码时经常忘。5. 近效期预警与低库存提醒卫生所的“监工”怎么写进定时任务进销存系统做到这里业务闭环已经完整。但真正让乡镇卫生所觉得“这系统终于救了我”的其实是预警功能——它把原先靠人工翻箱子的活儿变成了每天早上自动检查。5.1 预警规则设计三条规则三个档位我把预警拆成三条规则尽量简单直接不搞太多花哨参数。近效期预警物资有效期减去当天小于等于该物资设定的expiry_warn_days默认给 90 天分类里可以单独覆盖。比如急救药品可能要求 180 天就开始预警一次性手套 30 天预警就够。低库存预警material_stock.total_quantity material.min_stock并且物资状态正常。这个最适合作出采购建议。过期锁定每天扫描批次表把expiry_date CURDATE()且状态为正常的批次自动置为status 2过期锁定这样一来过期的物资在出库里就选不到从系统层面杜绝“误用过期物资”。在页面展示上我习惯做三个红黄绿色块红色是 30 天内过期黄色是 30~90 天内过期绿色是安全。库管员一上班打开首页扫一眼色块就知道今天要先处理哪批货。5.2 定时任务如何实现凌晨扫一遍加锁防止重复跑Spring Boot 做定时任务很简单EnableScheduling打开然后在 Service 方法上加Scheduled(cron 0 0 2 * * ?)表示每天凌晨两点执行一次。核心逻辑有三步Component public class StockWarnTask { Scheduled(cron 0 0 2 * * ?) public void scanExpiryAndStock() { // 1. 近效期批次扫描查询 expiry_date 今天预警天数 的批次 // 2. 低库存扫描查询 total_quantity min_stock 的物资 // 3. 过期批次锁定UPDATE batch SET status2 WHERE expiry_date CURDATE() AND status1 // 4. 生成预警记录写站内消息 } }这里有个多实例部署才会踩到的坑如果你以后用两台服务器跑同一个 jar两台机器会在同一个时间点同时跑定时任务预警消息会重复生成。解决方式有几种简单点只部署一台实例复杂点用 Redis 分布式锁在一个任务开始时setnx一个 key设置了过期时间其他实例发现 key 存在就跳过本次执行。乡镇卫生所的场景一台服务器就够了但代码里加个SchedulerLock或者简单 Redis 锁也不算多大事毕设里写上这个设计点还是个加分项。5.3 预警之后怎么通知站内消息优先邮件和企业微信按需加预警信息生成后必须让库管员能“看见”。我在系统里建了一张warn_record表字段包括物资、批次、预警类型、预警天数、当前数量、预警时间、处理状态、处理人、处理时间。同时往站内消息表插一条记录用户登录后右上角有红点提示。邮件通知不是必须的。如果确实要配Spring Boot 集成spring-boot-starter-mailYAML 里配置 SMTP 服务器地址、账号、密码就行。企业微信机器人则更轻量只要一个 Webhook 地址用RestTemplatePOST 一段 JSON 就能推送消息到群里。这个对乡镇卫生所不一定用得上但医院集团或者连锁网点会喜欢。5.4 避免重复预警同一条记录“未处理”时不重复生成这里有一个非常影响体验的细节如果今天生成了一条“纱布近效期预警”明天扫描时又查出来再生成一条一模一样的站内消息会变成刷屏。我的做法是生成前先查warn_record判断“同一物资、同一批次、同一规则、处理状态为未处理”的记录是否存在存在就跳过不存在才插入新记录。等到库管员点了“确认处理”比如这批纱布已经用完了、或者已经报损出库了这条预警状态变成已处理。下次扫描时如果还有别的批次继续预警就不会再骚扰他。6. 权限、前端打包与部署让系统在乡镇卫生所真正跑起来开发环境跑得好不算本事让一个不懂技术的库管员在一台老电脑上稳定用三个月才算。这里分享几个部署和落地阶段的硬经验。6.1 角色和权限怎么设计才不麻烦不需要很复杂的 RBAC 模型三类角色就够管理员用户管理、系统配置、数据字典、全部菜单可见。库管员出入库、盘点、预警处理、报表查看。普通用户/护士只能申请领用、查看库存和预警不能直接改库存。权限注解挂在 Controller 方法上就行。Sa-Token 的用法比较简洁——登录后拿到 token拦截器里校验登录按钮和菜单按权限码控制。我用的方式是在后端角色表里维护权限码集合前端登录后返回菜单和按钮权限。SaCheckPermission(stock:outbound:add) PostMapping(/outbound) public RVoid createOutbound(RequestBody OutboundBill bill) { outboundService.doOutbound(bill); return R.ok(); }值得提醒的是权限不是给你自己看的是给卫生所的责任划分看的。谁入了库、谁出了库、谁调整了盘点日志表里都要有记录出了问题能找到人。所以我在所有写操作上还加了一个 AOP 日志注解自动记录操作人、IP、时间、请求参数和方法描述统一写入sys_log表。6.2 Vue 项目打包放进 Spring Boot三个关键点前端npm run build之后把dist目录下的文件复制到src/main/resources/static重新打包后端 jar前端页面就能从同一个端口访问。这一步很简单但有三个人人都会踩的坑。第一Vue Router 如果用了createWebHistory模式刷新某个子路由页面时会 404因为后端 Tomcat 不知道这个路径。最简单的解决方式是改用createWebHashHistoryURL 变成/#/stock/list刷新时不会发给后端。如果一定要 history 模式就得在后端加一个转发 Controller把非/api开头的路径统一转发到index.html。第二开发时前端会跨域。在vite.config.js里配置 proxy 转发把/api代理到http://localhost:8080这样调试前端不用每次改后端CrossOrigin。第三打包后要检查前端资源路径是相对路径还是绝对路径。有些项目构建出来的 JS 和 CSS 路径是/assets/xxx.js在根路径部署没问题如果后面要放到子路径下就会白屏。这类系统一般直接部署在根路径问题不大但建议提前确认。6.3 部署时最容易忽略的配置项以下这几个地方是我实际部署时踩过或帮别人排查过的提出来你可以直接抄。server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/hospital_stock?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: xxxxxx servlet: multipart: max-file-size: 50MB max-request-size: 50MB sa-token: token-name: satoken timeout: 86400数据库连接串里serverTimezoneAsia/Shanghai必须加否则 MySQL 驱动会找默认时区可能和你系统时间差 8 个小时导致登录时间、单据时间全部错位。文件上传上限默认 Tomcat 的max-file-size只有 1MB做 Excel 导入时随便一个带图片的表格就超过不调大会直接报 500。改成 50MB 后省很多事。jar 包启动内存一台 2G 内存的服务器启动时提示OutOfMemory或者很卡用java -Xms256m -Xmx512m -jar stock-system.jar限定内存即可。如果用了 Redis 做缓存记得确认卫生所的服务器安装了 Redis如果不想额外装就把代码里的缓存换成 Caffeine 本地缓存少一个外部依赖。7. 我踩过的配置坑和对这套系统的扩展想法最后这部分没有章法就是我在真实开发里磕出来的几个教训加上一点对未来扩展的判断。写出来是希望你不必重走这些弯路。7.1 版本选择真的能卡死人我做这个项目时最早图新鲜用了 Spring Boot 3.2 JDK 21结果 MyBatis-Plus 老版本里的分页插件不兼容Druid 连接池初始化报错Redis 序列化方式也变了。折腾一整天后我把整个项目回退到 Spring Boot 2.7.18 JDK 8所有问题当场消失。如果你的毕设已经用了 Spring Boot 3倒也不用推翻注意把 MyBatis-Plus 升到 3.5.5 以上、JDK 用 17、Druid 用 1.2.20 以上。但我建议大部分读者直接复制我的组合2.7.18 JDK 8少流一滴泪。7.2 时间差 8 小时和日期类型选择第一次联调时发现数据库里存的时间比页面显示时间多了 8 小时。根因就是连接串没加serverTimezoneAsia/Shanghai而 MySQL 驱动默认用服务器 UTC 时区。后来我除了改连接串还把代码里所有的java.util.Date统一换成LocalDateTime并配置了 Jackson 序列化格式避免前端显示“2025-11-12T10:30:00”这种带 T 的字串。7.3 库存变成负数问题出在“先查后改”这应该是我在整个项目里遇到的最严重 bug。A 科室和 B 科室同时领用同一种纱布我在用户少的情况下先查库存判断够不够再执行扣减更新结果两个请求都判断“够”最后库存直接变成负数。修正方式就是前面写的条件更新 SQL一行解决问题。从那以后团队里只要有人写库存更新我都会盯着他用原子操作。7.4 后续扩展多卫生所、对接 HIS、消息异步化这套系统如果要推广到多个卫生所我建议在核心表里增加org_id字段做成逻辑多租户登录时根据用户所属机构自动过滤数据。系统层面不需要太大改动但要注意报表统计时要带上机构维度。如果后续要对接 HIS 系统或者医保平台一般不是通过页面操作而是提供 REST API 接口让 HIS 侧调用入库、出库、库存查询。这个时候你可以在系统里增加一个独立的api_client表维护调用凭据和签名认证避免外部系统直接用用户名密码登录。如果消息通知压力变大再考虑用 MQ 解耦现阶段事件监听完全够用。说实话这种系统做完你会发现真正难的不是 CRUD而是业务规则建模和事务边界控制。只要你把批号和效期这两件事想清楚把库存更新做成原子操作乡镇卫生所医用物资进销存系统的核心价值就已经立住了。剩下的报表、权限和页面优化都是在这个可靠地基上慢慢添砖加瓦的事。
返回列表