news 2026/11/3 22:04:47

SpringBoot宠物医院药品管理系统:批次效期与库存扣减实战解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot宠物医院药品管理系统:批次效期与库存扣减实战解析

SpringBoot + Java的宠物医院药品管理系统,是很多同学做毕业设计时绕不开的“标准题”。我见过不少大三学生拿到类似题目后,第一反应就是上网找现成代码,下载下来跑起来就算完事。可真正到了开题答辩和系统演示的时候,连“库存批次”“先进先出”“效期预警”都解释不清楚。这套系统表面上是药品的增删改查,背后其实是宠物医院日常运营中最敏感的一条链路:药品怎么进来、怎么存放、怎么被开出去、怎么处理过期。这篇文章不会给你一堆下载链接,而是把系统从业务到代码完整拆开讲,适合正在做SpringBoot课题、或者打算用药品管理方向交作业的同学直接参考。

1. 宠物医院药品管理究竟在管什么:先理清业务再谈代码

1.1 药品管理不是简单的增删改查

宠物医院的药品管理跟你想象的小诊所药柜不是一回事。同样是阿莫西林,可能有多个厂家、多个批次、不同有效期;有些是兽用处方药,开药必须关联宠物病历和医生处方;同一只猫的用药量还要按体重计算。如果单纯建一张药品表,把所有信息堆在一起,刚开始看不出问题,等数据量到了几千条,效期预警和批次追溯就完全没法做。所以业务模型是先于代码的。

很多人习惯先翻前端模板再建数据库,这是反的。你应该先回答几个问题:入库单怎么生成?医生开的处方以什么形式保存?药房发药时扣的是哪个批次库存?过期药品是直接删除还是保留记录?这些问题的答案,直接决定你建几张表、每个表留哪些字段。

1.2 三类使用者和四条核心链路

先区分角色,再谈功能。核心角色有三类:

  • 管理员:维护药品基础数据、供应商、入库审核、参数配置。
  • 医生:查看库存、开处方、查看效期预警。
  • 药房人员:发药、退药、报损、出入库登记。

在这套系统里,最核心的业务链路有四条:

  1. 采购入库:药品从供应商到达医院,生成入库单,写入批次库存。
  2. 库存管理:维护批号、效期、库存上下限,触发预警。
  3. 处方发药:医生开处方单,药房按处方扣减对应批次库存。
  4. 过期报损:效期临近或过期药品进入报损流程。

这些链路不用全部一步到位,但数据库设计时必须为它们预留字段。很多同学把系统做成“只记录库存数量”的玩具,根源就是只画了页面,没画业务链路。建议动手前先用Excel模拟一次:一张药品入库单进来之后,会经历哪些状态变化。你会发现同一盒药,从入库到发药,往往要牵动三四张表,这就是药品管理系统的核心价值。

2. 技术栈与项目结构:为什么SpringBoot是毕业设计的最优解

2.1 技术选型的对比思路

如果你是2026年做毕设,我不推荐再写SSM的XML配置式项目。不是说SSM不能做,而是同样的功能,SpringBoot可以少写一半配置文件,留给业务的时间更多。技术栈可以定为SpringBoot 2.7 + MyBatis Plus + MySQL,前端二选一:Vue3 + Element Plus,或者直接用Thymeleaf + Bootstrap。前者页面好看、能讲前后端分离;后者部署简单,适合不熟悉前端的同学。

选SpringBoot的原因很实际:内嵌Tomcat,不用单独配服务器;自动配置机制让数据源、JSON转换、日志这些常见问题都有默认方案;MyBatis Plus又提供代码生成器和条件构造器,能把单表CRUD的时间压缩到很小。毕设周期通常只有几个月,你需要把更多精力放在库存扣减、效期预警这类有区分度的业务上,而不是配置一个拦截器就折腾两天。

2.2 后端工程分包与启动流程

工程结构不用太复杂,按业务分包足够清晰:

com.pet.hospital ├── controller # 接口层 ├── service # 业务逻辑 │ └── impl ├── mapper # 数据访问 ├── entity # 数据库实体 ├── dto # 请求参数 ├── vo # 返回视图对象 └── config # 配置类、拦截器、定时任务

启动流程也很简单:先创建数据库pet_hospital,导入初始化SQL,改一下数据源配置,启动Application主类。需要注意的一个细节是数据库连接串里的时区参数,很多人不写serverTimezone,启动后时间字段会差8小时。下面是一份可直接用的数据源配置:

spring: datasource: url: jdbc:mysql://localhost:3306/pet_hospital?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 mybatis-plus: global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0

如果你的系统规模不大,完全不需要引入微服务或者Redis。毕业设计的重点是把一个场景做透,而不是技术堆砌。

2.3 前端选型的两个方向

很多同学纠结前端到底用Vue还是用模板引擎,这里直接给对比结论:

维度Vue3 + Element PlusThymeleaf + Bootstrap
开发效率前后端分离,联调成本稍高单体应用,一个工程直接跑
页面效果组件丰富,表格表单完善中规中矩,但够用
答辩说服力能讲接口设计、跨域、路由守卫成本低,容易自查
部署复杂度需要打包前端并配置跨域打包进SpringBoot即可

我的建议是:如果时间比较充足,优先Vue3,因为前后端分离的架构本身就是一个讲解亮点;如果时间只剩一两周,Thymeleaf完全够用,把业务讲清楚照样能过。

3. 数据库设计:一张库存表如何承载批次、效期和处方来源

3.1 八张核心表的职责划分

数据库是一套系统的底盘,我建议你至少设计下面这些表:

表名核心作用关键字段提示
user员工账号username、password、status
role角色role_code、role_name
pet_info宠物档案pet_name、owner_name、pet_type
drug_info药品基础信息drug_name、spec、manufacturer、retail_price
drug_batch药品批次库存batch_no、production_date、expire_date、available_qty
stock_in_order入库单主表supplier_id、order_no、total_amount
stock_in_item入库单明细batch_id、qty、purchase_price
prescription处方单主表pet_id、doctor_id、diagnosis、status
prescription_item处方明细drug_id、dosage、qty、batch_id
drug_loss报损记录batch_id、loss_qty、loss_reason

为什么要把入库单拆成主表和明细表?因为一张入库单里往往包含多种药品,每种药品的批次信息独立。如果只用一张表,字段会出现大量冗余,统计供应商供货情况也很痛苦。同理,处方单和处方明细也要拆开,一只猫可能在一张处方里开三四种药,每种药的剂量和数量都需要单独记录。

3.2 药品表与库存批次表的最关键设计

几乎每一个把系统做崩的同学,都会犯同一个错误:在drug_info表里直接放一个stock字段。表面上看很省事,查询库存也方便,但你无法回答三个问题:这批药品什么时候过期?同一药品不同批次的进价分别是多少?如果某一批次出现问题,怎么下架?

正确的做法是把药品基础信息和批次库存拆成两张表,用drug_id关联。drug_info保存通用名、商品名、规格、生产厂商、默认零售价;drug_batch保存每一批次的专属信息,比如:

  • batch_no:批次号,同一批进货同一个批号。
  • production_date:生产日期。
  • expire_date:有效期。
  • available_qty:当前可用数量。
  • purchase_price:入库价。
  • sale_price:零售价。

查询当前库存时,对drug_batch按drug_id做SUM(available_qty)即可。发药时,则必须精确到批次。这套设计的本质是让“库存来源”可追溯,也是答辩时你能讲清楚的关键点。

3.3 处方单和发药记录如何串联

处方单主表记录一次诊疗的结论,处方明细记录具体药品、剂量、数量。药房发药时,再把处方明细与drug_batch关联,扣减对应批次的库存。

这里有个很多人忽略的设计:处方明细里除了drug_id,还要冗余一份drug_name和spec。为什么?药品基础信息可以被管理员修改,比如商品名从A改成B,但已经开出去的处方不能跟着变。医疗记录讲究可追溯,冗余快照虽然违反第三范式,却是业务上的正确选择。同理,发药记录中也应该记录实际发放的批次号batch_no,方便将来对某批次药品做召回。

很多同学会在这里犹豫:要不要单独建一张发药表?我的建议是建。因为一次处方可能被拆成两次发药,或者发药过程中发现某一批次库存不足,需要换批次。如果只在处方明细上加个状态字段,想查“哪批药发给了哪只宠物”会很困难。

4. 核心业务代码:库存扣减如何避免超卖和效期错误

4.1 先进效期先出的核心思路

药品库存管理的核心是FEFO,也就是效期近的先出。人用药房习惯先进先出,宠物医院药品流转速度慢,效期问题更敏感,必须优先考虑有效期。

发药时,先查当前药品所有可用的批次,按有效期升序排列,然后依次扣减。伪代码如下:

public void dispenseDrug(Long drugId, int needQty) { // 查询该药品的有效批次,按有效期升序、入库时间升序 List<DrugBatch> batches = drugBatchMapper.selectValidBatches(drugId); for (DrugBatch batch : batches) { if (needQty <= 0) { break; } int available = batch.getAvailableQty(); if (available <= 0) { continue; } int takeQty = Math.min(available, needQty); drugBatchMapper.reduceStock(batch.getId(), takeQty); needQty -= takeQty; // 写发药明细,记录batchId和batchNo } if (needQty > 0) { throw new RuntimeException("库存不足"); } }

这段代码逻辑不复杂,但有一个隐藏问题:如果一个批次只有5盒,你需要发7盒,系统必须自动拆成“这个批次扣5盒,下一个批次扣2盒”。很多学生写到这里会直接用总库存减,导致明明批次数据存在,却说不清药到底是从哪个批次发的。

4.2 并发场景下的库存更新写法

扣减库存最怕两件事:超卖和重复扣减。用Java代码先查再改,在高并发场景下会出现并发覆盖问题。解决办法很简单,把扣减操作写成一条带条件的UPDATE语句:

@Update("UPDATE drug_batch SET available_qty = available_qty - #{qty} " + "WHERE id = #{batchId} AND available_qty >= #{qty}") int reduceStock(@Param("batchId") Long batchId, @Param("qty") Integer qty);

当返回值是0,说明当前批次库存不足,可以立即停止并回滚事务。这种方式不需要分布式锁,在单库场景下足够可靠。整个发药方法要加@Transactional注解,保证批次扣减和发药记录写入要么全部成功,要么全部失败。

有一点要提醒:不要在循环里逐条调用SELECT再UPDATE,尽量先把符合条件的批次列表一次查出,再在内存中计算每批次应扣数量。数据量大时,逐条查询数据库会拖慢接口响应,而且事务时间越长,锁冲突的概率也越高。

4.3 过期预警定时任务

过期货品如果继续留在库里,容易误发。因此需要一个定时任务,每天扫描近效期批次,生成预警记录。SpringBoot提供的@Scheduled注解已经够用:

@Component public class ExpireRemindTask { @Scheduled(cron = "0 0 2 * * ?") public void remind() { List<DrugBatch> expiringBatches = drugBatchMapper.selectExpiringBatches(30); expiringBatches.forEach(batch -> { // 生成一条预警消息,或者调用消息接口通知药房 }); } }

查出的批次判断条件是available_qty大于0,且expire_date在30天以内。为什么不用quantity?因为quantity可能包含已经出库但还没结算的历史数据,只有可用库存才需要处理。这个细节也会成为你和普通同学拉开差距的地方。

定时任务的cron表达式建议设置在凌晨,避免影响白天正常业务。如果想让预警更及时,还可以在前端登录后显示一个醒目的提示条,点击后跳到近效期列表。

5. 权限与安全:三种角色互不越权的实现细节

5.1 基于RBAC的权限模型

药品管理系统一定要做权限控制,因为医生和药房人员的职责边界很清晰。管理员负责基础数据和入库审核,医生可以开处方,药房人员可以发药和报损,但不应该能修改药品价格。

推荐使用RBAC模型,也就是用户-角色-权限。简单做法是建五张表:user、role、user_role、role_menu、menu。登录后把当前用户的权限码列表放进Session或Redis,接口调用时用拦截器校验。如果用了Spring Security,可以在配置类里开启方法级权限注解@EnableGlobalMethodSecurity,然后直接在Controller或Service方法上加@PreAuthorize,代码会更简洁。

很多同学一张用户表加一个type字段就来控制权限,确实快,但遇到“某个药房人员同时需要查看统计报表”的场景,就要临时改代码。用RBAC模型,只需要给角色分配菜单权限即可,灵活性和答辩说服力都更强。

5.2 登录态与接口鉴权的常见实现

登录接口建议使用JWT,而不是传统Session。不是因为它更安全,而是前后端分离架构下更自然。先校验用户名密码,密码用BCrypt加密存储,不要明文入库。登录成功后生成token,前端放到请求头的Authorization字段。

自定义一个拦截器,做三件事:

public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization"); if (token == null || !JwtUtil.verify(token)) { response.setStatus(401); return false; } // 解析用户信息,放入ThreadLocal UserContext.set(JwtUtil.parseUser(token)); return true; } }

注意JWT并不适合保存敏感数据,里面只放userId和角色编码,过期时间建议设置2到4小时。前端路由守卫也要配合,未登录用户跳到登录页。权限验证不能只靠前端隐藏按钮,因为接口可以直接调用,必须在后端做二次校验。

6. 从开发到上线容易踩的坑:日志、时间、枚举转换

6.1 MyBatis Plus 自动填充与逻辑删除

MyBatis Plus最实用的功能是自动填充。在entity的createTime字段上加@TableField(fill = FieldFill.INSERT),再实现一个MetaObjectHandler,插入和更新时就不用手动set时间了。这能减少很多低级错误,尤其是数据库字段和时间类型不一致导致的插入失败。

逻辑删除也是一个省心配置。设置logic-delete-field: deleted后,所有查询会自动带上WHERE deleted = 0。但有一个坑:如果给drug_name字段建了唯一索引,逻辑删除后会再次插入同名的药品时,会因为唯一索引冲突插入失败。处理办法有两种,要么删除唯一索引,要么把deleted字段改成删除时间,让每个被删除的记录都保留不同的时间戳。这个问题在答辩前务必自测一遍。

6.2 LocalDateTime 序列化与前端显示

如果使用Java 8的LocalDateTime,没有额外配置时Jackson会把时间序列化成数组,前端拿到的是“2026,5,10,14,30”这种格式,非常难处理。建议在application.yml里统一配置:

spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8

同时在实体类的日期字段上加上@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss"),双保险。数据库连接串的serverTimezone也要保持一致,否则你存进去的时间是正确的,读出来却多了8小时。这在统计报表功能中尤其明显。

6.3 库存扣减与报表统计的脏数据问题

写库存扣减时,最容易产生脏数据的场景是异常事务没有回滚。比如先扣减批次库存,再生成发药明细,第二步抛异常且没有事务,库存就平白少了。解决方法是把整个发药流程封装到一个事务方法中,并且只对异常做回滚,不要用try-catch吞掉异常。

另一个常见问题是报表统计时,把逻辑删除的数据也算进去了。统计库存金额时,要明确统计口径:以available_qty为准,乘以入库价。统计销量时,要关联发药记录,而不是用“库存总量变化”去推算。这个口径不统一,做出来的报表数据会对不上账。

7. 如果这是你的毕设:文档、演示和答辩怎么准备

7.1 演示脚本要覆盖一条完整业务闭环

很多同学答辩演示时,只点开几个列表页,说“这里可以增删改查”,讲两分钟就结束。这样很难让评委相信系统真的可用。建议准备一条完整业务闭环的演示脚本:

  1. 用管理员账号登录,新增一个药品基础信息。
  2. 选择该药品,做一次采购入库,打开入库单能看到新批次和数量。
  3. 切换到医生账号,为一只宠物档案开处方,选药时能看到库存余量。
  4. 切换到药房账号,找到该处方,点击发药,确认扣减的是哪个批次。
  5. 回到药品列表,确认当前库存数量已经减少。
  6. 把某个批次的效期手动改成临近30天,等待或触发定时任务,查看预警列表。
  7. 对过期批次做报损,查看报损记录。

这一套流程走下来,不到十分钟,却覆盖了三个角色、四张核心表、两条业务链。比单纯展示表格有说服力得多。

7.2 答辩时如何介绍“技术亮点”

答辩时不用贪多,讲清楚两三个亮点就够了。我的建议顺序是:批次效期设计、防超卖扣减、权限控制。

效期设计可以这样讲:“为了追溯药品来源,我把库存拆成批次表,发药时按效期优先扣减,记录每个批次去往哪只宠物。”这体现出你对业务的理解。

防超卖可以这样讲:“扣库存时用带条件的UPDATE语句,保证并发场景下库存不会变成负数,同时用事务保证发药记录和库存扣减的一致性。”这体现出你的工程设计能力。

权限控制可以这样讲:“系统采用RBAC模型,医生和药房人员的操作权限分开,前端只控制显示,后端通过拦截器对每个接口做鉴权。”这体现出你对安全性的考量。

不要主动提微服务、分布式缓存这些没用到的技术。评委如果追问,坦诚回答“当前场景并发量不高,单机事务足够,过度设计会增加复杂度但不会带来收益”即可。

最后说一个我自己的习惯:做这类系统,不要在没跑通业务链之前就急着追求界面炫酷。先把数据表建好,用一份测试Excel模拟一轮入库和发药,看库存数字能不能对得上;再开始写代码。药品管理系统最关键的不是某个页面好不好看,而是数据在哪个环节都不能丢。如果你也能做到这一点,不管最后功能多还是少,你的项目至少是完整的、能自洽的。

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

从零搭建自然语言优化求解系统:架构设计与语义解析实战

1. 为什么我要从零搭一套自然语言优化求解系统1.1 一个真实的需求场景去年年底&#xff0c;我接手了一个内部工具的需求。业务侧的同事每天要处理大量的排班、装箱、路径规划类的小问题&#xff0c;他们不想学Python&#xff0c;更不想学建模语言&#xff0c;只希望能用大白话把…

作者头像 李华
网站建设 2026/11/2 12:35:27

林业病虫害识别:数据集构建与预处理实战指南

林业病虫害数据集的质量&#xff0c;直接决定识别模型的上限。我第一次做林业病虫害识别项目时&#xff0c;以为“数据预处理”只是把图片缩放一下、统一转个格式&#xff0c;结果模型在验证集上跑出98%的准确率&#xff0c;一到实地应用就彻底翻车——新拍的虫害照片有一半认错…

作者头像 李华
网站建设 2026/11/3 0:31:48

2026新生儿姓名趋势观察:当代父母正在远离哪些“网红名字”?

如果把十年前和今天幼儿园的花名册放在一起&#xff0c;会发现一件很有意思的事情&#xff1a; 曾经无处不在的一批“爆款名字”&#xff0c;正在慢慢退出最新的新生儿热门榜单。 但另一个问题也随之出现&#xff1a;父母真的已经告别“网红名字”了吗&#xff1f;从2025年各地…

作者头像 李华
网站建设 2026/10/29 7:55:52

SP-API收费强制执行下的开发者自查指南:限流与成本优化实战

做亚马逊后台系统开发的朋友&#xff0c;最近多少都感受到了同一股压力&#xff1a;SP-API收费政策强制执行的通知陆续落到了开发者账号后台&#xff0c;原来靠“多发几次请求”就能糊弄过去的数据同步逻辑&#xff0c;现在要么被限流&#xff0c;要么直接被记录成扣费项&#…

作者头像 李华
网站建设 2026/10/29 0:28:50

Cursor高效配置三层次:规则文件、语言服务器与语义锚点

1. 为什么“Cursor配置”不是设置问题&#xff0c;而是工作流重构问题很多人第一次打开Cursor&#xff0c;点开Settings面板&#xff0c;下意识就想把所有开关都调成“最智能”“最自动”“最激进”&#xff0c;结果越配越卡、越配越不准——代码补全开始胡乱插入无关变量&…

作者头像 李华
网站建设 2026/11/1 7:27:50

AI Agent评测闭环实战:从偶尔成功到稳定可用

1. 为什么你的 Agent 总是“偶尔成功”1.1 从一次让人崩溃的演示说起上个月我帮一个朋友看他做的客服 Agent&#xff0c;演示的时候一切正常&#xff0c;用户问退货政策&#xff0c;它准确调用了知识库检索&#xff0c;回答得滴水不漏。结果第二天上线&#xff0c;第一个真实用…

作者头像 李华