这套Spring Boot药品库存管理系统源码,我拿到手之后完整跑了一遍,又对着表结构和业务代码捋了好几天。说实话,这类"药房管理系统"在很多课设和毕设里都能见到,但能兼顾业务完整度、代码清晰度和可二次开发空间的并不多。01739这个编号对应的版本,我实测下来属于"麻雀虽小五脏俱全"的类型:有登录鉴权、有药品分类管理、有入库出库流程、有库存预警,数据库表设计也基本贴合实际药房业务。
无论你是准备拿它做毕业设计,还是刚学完Java Web想找个完整项目练手,又或者真的想给小型诊所/校医院搞一套库存管理工具,这套代码都值得花点时间读透。下面我直接从业务拆解、技术架构、数据库设计、核心代码实现这几个维度展开,最后再附上我实际跑项目时踩过的坑和排查思路。
1. 项目整体解析:一个药品库存管理系统到底要做什么
1.1 从药房一线业务看懂系统需求
很多人拿到这类项目第一反应是"不就是增删改查吗",这么想会错过很多东西。医院药品库存管理系统的难点从来不在CRUD本身,而在业务状态的流转和数据一致性。药房里的实际场景是这样的:
- 药库管理员需要维护药品基础信息,包括通用名、商品名、规格、生产厂家、批准文号、有效期。
- 药品入库时,不仅要登记数量,还要记录供应商、生产批号、生产日期和有效期,因为药品是严格按批号管理的,同一个药品不同批次不能混放。
- 临床科室领药或者门诊发药时,库存要实时扣减,同时产生出库记录,方便后期对账。
- 有些药品有库存上下限,低于下限要提醒采购,高于上限要防止积压过期。
- 近效期药品要有预警机制,比如距离失效还有90天、30天要分别提示。
如果一套系统能把这些点都覆盖到,那它在业务逻辑上就是合格的。我核对了源码里的功能清单,它确实覆盖了这些环节,包括药品分类、药品信息管理、供应商管理、入库管理、出库管理、库存查询、预警管理、操作日志,以及基于管理员/操作员两种角色的权限控制。
1.2 管理员的日常工作流:系统如何匹配线下流程
线下药房作业通常是"接收采购计划→验收入库→上架→发药/领药→盘点→报损"。这套系统的菜单设计基本复原了这条链路。管理员登录之后,常见的操作路径是:
- 先维护"药品分类",比如抗生素类、心脑血管类、消化系统类。
- 再维护"药品信息",把具体药品挂到分类下,设置库存上下限。
- 入库操作时选择药品、填写批号和生产日期/有效期,入库单保存后自动增加可用库存。
- 科室领药或患者取药时做"出库登记",保存后自动扣减库存。
- 通过"库存查询"查看实时库存和预警状态,对近效期和低库存药品做处理。
这套流程和真实药房业务是吻合的。我在读源码时特别关注了一个点:入库和出库是否走"单据+明细"两层的设计。如果系统设计得很粗糙,往往会直接在药品表上修改库存数量,连流水都不留。这套系统没有这么做,它有清晰的入库单、入库明细、出库单、出库明细结构,虽然代码量增加了,但数据的可追溯性完全不同。这一点是我认为整套源码里最有价值的部分。
2. 技术栈与架构拆解:为什么用Spring Boot + MyBatis这套组合
2.1 技术选型背后的逻辑
这套系统主框架是Spring Boot 2.x + MyBatis/MyBatis-Plus + MySQL + Thymeleaf(若前端使用了分离模板)或Layui/AdminLTE这类后端模板框架(不同版本可能不同)。从学习和二次开发的角度来说,这个选型非常合理。
先看Spring Boot。它的核心优势是"约定优于配置"。在传统SSH或者SSM时代,光配置数据源、事务管理器、扫描注解就要写一堆XML,而Spring Boot把自动配置做到了极致,引入一个spring-boot-starter-web就能跑起Web项目。对这个项目来说,Spring Boot提供的内嵌Tomcat让部署变得非常轻松,打包成JAR直接丢服务器就行,不再需要单独装Tomcat再扔War包。
再看MyBatis/MyBatis-Plus。药品库存管理这类系统SQL逻辑复杂,尤其是多表关联查询、库存汇总统计、动态条件筛选(按药品名称、分类、供应商、有效期区间查询),MyBatis的XML里写SQL非常灵活。同时MyBatis-Plus提供的BaseMapper内置方法,让简单的单表CRUD不用手写SQL,代码量能减少三分之一左右。这套源码里的Service层大量继承ServiceImpl,用的就是MyBatis-Plus这一套。
提示:如果看到项目里大量使用
LambdaQueryWrapper这类写法,说明集成的是MyBatis-Plus而不是原生MyBatis。你二开写查询条件时可以少写很多XML。
2.2 项目结构规划与分层设计
项目的包结构直接决定代码能不能快速读懂。这套源码的包结构是标准的MVC分层,我拆开看了一遍,总体是这样:
com.xxx.hospital ├── controller(控制层) ├── service(业务接口) ├── service.impl(业务实现) ├── mapper(数据访问层接口) ├── entity(实体类) ├── vo(视图对象,部分模块有) ├── common(通用返回结果、异常处理、常量) └── config(配置类,如拦截器、跨域)这种分包方式的好处是,一个请求的完整路径非常清晰:Controller接收参数→调用Service处理业务→Service调用Mapper操作数据库→返回统一结果给前端。如果后期要加功能,照着现有的分层加一套Controller+Service+Mapper就行了,不需要动其他模块。
源码里的Controller普遍只做参数接收和结果封装,业务判断都扔在Service层,这是正确的做法。有些初学者写项目喜欢把业务逻辑堆在Controller里,看起来能跑,但一行代码上千的Controller会非常恐怖。我建议读这套源码时重点看Service层,那才是整个系统的灵魂。
3. 数据库设计:药品库存管理的核心是数据模型
3.1 核心数据表拆解
数据库表设计决定了这套代码的天花板。我花了一个下午把建表语句全部过了一遍,核心表大致如下:
| 表名 | 作用 | 关键字段 |
|---|---|---|
| sys_user | 系统用户表 | username、password、role、is_deleted |
| drug_category | 药品分类表 | category_name、status |
| drug_info | 药品信息表 | drug_code、drug_name、specification、category_id、stock_lower_limit、stock_upper_limit、manufacturer |
| drug_stock | 药品库存表 | drug_id、batch_no、quantity、production_date、expiry_date |
| supplier | 供应商表 | supplier_name、contact_person、phone |
| inbound_order | 入库单主表 | order_no、supplier_id、inbound_time、operator |
| inbound_order_item | 入库单明细表 | order_id、drug_id、batch_no、quantity、unit_price |
| outbound_order | 出库单主表 | order_no、department或patient、outbound_time、operator |
| outbound_order_item | 出库单明细表 | order_id、drug_id、batch_no、quantity |
| stock_warning_log | 预警记录表 | drug_id、warning_type、warning_content、handle_status |
这里最关键的设计是drug_stock库存表和单据明细表分开。库存表里每条记录对应"某个药品的某个批次",字段包含批号和有效期。这样做的好处有三个:
- 药品可以按批次追踪,哪个批次先到期先出库(FEFO,先效期先出)。
- 出库时能精确定位到批次,避免不同批次混合导致账目混乱。
- 有效期预警可以按批次维度去统计,比如查"90天内过期的批次有哪些"。
很多半成品管理系统只有一张drug_info,在上面直接加stock字段,这样入库、出库、批次、效期管理全部无从谈起。这套源码没有走那种偷懒路线,这一点值得给个好评。
3.2 库存扣减与流水记录的设计要点
库存管理最怕什么?最怕库存数据"凭空变少"或者"变成负数"。这套系统的处理方式是:库存数量的增减只允许通过入库单、出库单、盘点单触发,任何特殊修改都要留下记录。
在数据库层面,设计了一个核心原则:先写单据主表和明细表,再更新库存表,两个操作必须放在同一个事务里。如果事务控制不好,就会出现单据保存成功了、库存没变,或者库存扣了、单据没记录的情况。我读代码时特别留意了这一点,Service层的入库、出库方法都能看到@Transactional注解,说明作者考虑到了事务一致性。
除此之外,drug_info表里的stock_lower_limit和stock_upper_limit不是摆设,它们在每次库存变动后会参与判断。一旦库存数量低于下限,系统要自动插入预警记录;高于上限时也会给出提示。这就是业务规则的数据库落地。
4. 核心功能实现细节:从登录到入库出库的完整链路
4.1 登录与会话拦截的实现方式
登录模块看起来简单,但安全细节很容易忽略。这套系统使用Session机制保存登录状态,用户在登录成功后将用户对象存到Session中。通过拦截器统一校验未登录访问,未登录请求会被重定向到登录页或者返回未授权提示。
实际代码里一个比较有意思的设计是,系统区分了"管理员"和"操作员"两种角色。管理员拥有全部权限,可以配置药品信息、查看所有数据、管理系统用户;操作员主要是执行录入操作,比如登记入库单、出库单,但无法删除基础数据和查看部分敏感信息。这种RBAC(基于角色的访问控制)模型虽然实现上比较简单,但思路是对的,后期如果想扩展更多角色,只要增加角色枚举和权限判断即可。
4.2 药品入库与库存更新逻辑
入库是整个系统的起点,没有入库,后面所有业务都是空谈。入库的代码逻辑主流程如下:
- 前端提交入库单数据,包含供应商信息、入库日期、明细列表(药品、批号、数量、价格等)。
- Controller接收到请求后,调用Service层入库方法。
- Service方法内先创建入库单主表记录,生成单号(通常使用时间戳加随机数),状态设为"已入库"。
- 遍历明细列表,逐条写入入库单明细表。
- 针对每条明细,检查库存表中是否已存在"相同药品+相同批号+相同有效期"的记录:如果存在就直接累加数量;如果不存在则新增一条批次库存记录。
- 更新
drug_info表中的总库存数量映射(如果另有冗余字段)。 - 入库完成后,根据最新库存判断是否需要生成预警。
第五步非常关键,这就是前面提到的批次库存逻辑。同一个药品批号不同会被拆成两条库存记录,后续出库也是按批次独立扣减。如果代码里没有这一步,而是直接给药品总库存加数量,那这个系统就撑不起药品批号管理体系。
4.3 出库(领用/处方发药)与库存扣减
出库逻辑和入库是对称的,但有一些特殊处理点。出库时业务人员需要选择药品和数量,系统要自动计算可用库存是否充足;如果库存不足,必须拒绝本次出库操作并给出提示。
重点来了:出库时如果指定了批次,系统按指定批次扣减;如果没有指定批次,代码默认按"有效期最近的批次优先扣减",这就是药房实际管理中的"近效期先出"原则。避免药品过期浪费是药房管理的核心目标之一,能把这条规则写进代码说明作者有实际业务经验。
出库单保存完成后,库存扣减的SQL类似这样:
UPDATE drug_stock SET quantity = quantity - #{quantity}, update_time = NOW() WHERE drug_id = #{drugId} AND batch_no = #{batchNo} AND quantity >= #{quantity}注意这个SQL里带了quantity >= #{quantity}条件。这个写法好处是防止高并发下超卖。如果同一时间有两个请求同时扣同一个批次,数据库的行锁会保证只有后执行的事务能继续判断,第二个请求如果扣减后库存为负,执行结果为0,不影响下一条记录。Service层再根据更新的行数判断是否扣减成功,不成功就抛出业务异常。
4.4 库存预警与效期提醒怎么落地
预警模块是把这套系统的价值提升一个档次的模块。我看了代码里预警相关的逻辑,主要有两类:
第一类是"低库存预警",也就是药品实时库存低于设置的stock_lower_limit。这个判断做在库存变更事务里,入库或出库后,拿最新库存跟上下限做对比,需要提示就生成预警记录。同时系统支持在列表页高亮显示,或者通过一个专门的预警页面展示。
第二类是"近效期预警",核心是查drug_stock表里的expiry_date字段。SQL逻辑上会计算过期剩余天数:如果剩余天数小于等于90天则标记为"临期";小于等于30天标记为"紧急"。对于已经过期的批次,状态置为"已过期",还要生成报损建议。这个功能关系到用药安全,如果系统里没有它,那它就是一个普通的进销存软件,而不是医院药品管理系统。
5. 一次完整的业务流程演示:入库到预警的链路走查
5.1 前置环境准备与项目启动
拿到源码后,第一步不是着急看代码,而是把环境跑通。我实测的环境组合是这样的:
- JDK 1.8(Spring Boot 2.x对JDK8支持最好,如果你用的是JDK17,建议把Spring Boot版本升上去或者换JDK8)。
- MySQL 5.7(如果MySQL 8.x也兼容,但要稍微留意驱动包版本)。
- Maven 3.6+(依赖下载全靠它)。
- 开发工具用IDEA,打开项目后等待Maven导入完成。
数据库导入步骤很常规:在MySQL中创建数据库(比如hospital_drug_db),设置字符集为utf8mb4,然后执行项目下自带的sql文件夹中的建库建表脚本。多数项目会提供init.sql或schema.sql,里面会连测试数据一起给到,直接导入就能用。
修改配置文件application.yml里的数据源username、password后,直接运行启动类。Spring Boot启动会在控制台打印出端口号(默认8080),浏览器访问登录页即可。
注意:导入SQL时如果报错,最常见的两个原因:一个是字符集不对导致中文乱码,另一个是MySQL版本差异导致某些字段类型不兼容。读取SQL文件时用文本编辑器打开,另存为UTF-8,再导入就正常了。
5.2 核心流程数据流转演示
我用源码自带的测试数据跑了一条完整链路,这里把数据流拆给大家看。
以某抗生素药品为例:
- 药品基础信息:在
drug_info表里有一条记录,drug_name为"头孢呋辛酯片",规格为"0.25g*12片",库存下限设为100盒,上限设为1000盒。 - 入库操作:入库单录入供应商,明细中填写批号"B20250601",生产日期2025-06-01,有效期至2027-06-01,数量300盒,入库完成后
drug_stock表新增一条记录,数量为300。 - 几天后临床科室领药50盒,选择出库并选择库存中的这个批次,保存后
drug_stock表的该批次数值从300变成250。 - 之后如果又入库了另一个批次"B20250901",库存表就会存在两条记录,分别是不同批次的库存。后续出库时会自动选择有效期更近的那一批。
- 如果某天发现该药品总库存低于100盒的下限,预警表会自动生成一条低库存预警记录。如果某个批次距离有效期不足90天,预警表会自动生成一条效期预警记录。
这一套流程走下来,账目清清楚楚,批次可追踪,预警可处理,业务闭环是完整的。对于想拿这套系统当毕业设计的人来说,把这条链路讲明白,答辩时能加分不少。
6. 源码阅读与二次开发建议
6.1 一周快速掌握源码结构的建议
系统源码的代码量不算大,但要精读一遍也需要合理安排顺序。我自己建议的阅读路线是:
第一遍,先看数据库表设计。表之间的关系看明白了,系统功能也就理解了一半。
第二遍,从Controller开始,挑一个最完整的业务模块(比如入库模块),跟着请求路径把Controller、Service、ServiceImpl、Mapper一层层读完。到了SQ阶段重点关注带条件动态拼接的SQL写法,尤其<if>标签和<where>标签的用法。
第三遍,看公共模块,包括统一返回结果类Result、异常处理器、全局拦截器、分页配置。很多项目脚手架能力藏在这些公共类里。
如果之前没有接触过MyBatis-Plus,这套代码里大量使用LambdaQueryWrapper做条件查询,这是MyBatis-Plus的核心特性。我建议边读代码边配合官方文档看,搞清楚eq、like、between、orderByDesc这几个方法,日常查询基本就够用了。
6.2 从毕业设计到生产系统:二开方向
技术成长的关键在于为项目扩展新特性。这套系统虽然功能完整,但距离真正生产级医院药品系统还有距离,这恰好是二次开发的练手机会。我推荐几个改造方向:
一是引入药品批次追溯和图片存储。当前系统已经支持批次管理,可以增加"Excel导入导出"功能,药品目录和入库单据通过Excel批量操作,提升效率;也可以接入对象存储保存药品图片、说明书。
二是引入定时任务做自动预警。当前预警触发依赖库存变动时刻,可以改造为通过Spring定时任务每天扫描一次所有药品批次的效期情况,生成日报并推送通知。
三是引入统计分析模块。通过ECharts展示药品出入库趋势、库存周转率、科室领药排行、近效期药品统计面板。管理系统没有可视化报表,说服力弱了一大截。
四是建一个简单的操作日志切面。用Spring AOP切Controller记录用户操作行为,这在真实系统中几乎是标准配置。
这些方向在家里做练习就能完成,无论选哪个都能学到新东西。
7. 常见问题与排查实录
7.1 启动阶段问题
问题1:项目启动时报数据库连接失败
检查application.yml的数据源配置,重点看端口(3306)、数据库名和密码。如果是MySQL 8.x工具连接却用MySQL 5.x的驱动,或者反过来,都要检查pom里的驱动依赖。
问题2:启动成功但页面404
区分两种情况:如果是直接访问根路径404,去查Controller里的@RequestMapping路径和前端页面的访问路径是否一致;如果是页面能找到但静态资源(JS/CSS)加载不出来,多半是静态资源目录配置错误,或者Thymeleaf模板放错了目录,Spring Boot默认模板位置是templates。
问题3:中文乱码
数据库连接URL中增加参数characterEncoding=utf8&useSSL=false,同时确保数据库表本身的排序规则collation是utf8mb4_general_ci。如果还是不生效,检查IDEA的文件编码设置,把项目编码和文件编码都切到UTF-8。
7.2 业务运行阶段问题
问题1:入库保存成功后,库存数量没有增加
这种情况第一反应不是代码逻辑,而是事务有没有生效。在Service方法上检查是否有@Transactional注解,以及有没有在类级别有出现"自调用"把事务绕过的情况。如果方法A调用同类方法B,B上的事务注解默认不会生效,需要走注入的Bean调用。这是Spring事务里最容易踩的坑。
问题2:出库数量可以超过库存
一般代码里会做库存检查,但有两个漏洞点:一是检查库存和扣减库存之间没有加事务锁,并发情况下会超卖;二是扣减前查到的库存数据是脏数据,导致判断失效。最稳妥的方案是使用前面提到过的带quantity >= #{quantity}条件的原子化UPDATE语句,根据更新行数判断是否成功。
问题3:预警记录被重复生成
同一批次药品持续低库存时,每次出入库都触发预警,会刷出一堆重复记录。解决办法有两种:一是生成预警前检查是否已有"未处理"的同类型预警记录,存在则不重复生成;二是库存预警改成每日定时检查,而不是每次变更后实时判断。前者改起来快,后者更贴近生产系统的做法。
问题4:日期字段在页面上显示格式不对
前端传字符串日期,后端接收到以后要用@DateTimeFormat(pattern = "yyyy-MM-dd")注解或配置全局日期转换器。如果数据库字段是date类型,实体类是LocalDate,两者对应关系也要注意。很多老项目默认用java.util.Date,新写的代码用LocalDateTime,处理不好就会抛转换异常。
7.3 我踩过的一个隐蔽坑:MyBatis-Plus分页失效
这套系统如果用了分页,需要注意MyBatis-Plus的分页插件需要在配置类中显式添加PaginationInnerInterceptor,而不是引入依赖后自动生效。如果没有注册这个插件,Page对象返回的总记录数会一直是0,但数据列表却能正常显示,看起来像"假分页"。排查方法很简单:打印一下返回的Page.getTotal(),如果为0,就说明拦截器没注册。
提示:在使用
selectPage时,调用顺序也有讲究。不要先手动list再手动分页,那样数据量一大就会内存溢出。正确做法是把查询条件封装进LambdaQueryWrapper,直接传给selectPage方法,让分页在SQL层面完成。
写在最后的一点个人体会
这套Spring Boot药品库存管理系统,我前后读了三遍,每次都有收获。第一遍是梳理业务,第二遍跟着代码画调用链,第三遍是尝试加功能。给我印象最深的是它的单据+明细+批次库存的三层设计,很多网上下载的"管理系统"都做不到这么规整。
如果你刚接触Spring Boot项目,把它当作一个"解剖样本"来学非常合适。先跑起来,再断点调试走一遍入库出库流程,最后照着二开方向自己加一个小功能模块。等你能独立说出每一张表是干什么的、每一层代码为什么这么写,你对Java Web开发的理解会上一个台阶。
源码的获取方式通常就在项目文档或者博主提供的说明里,注意选择带数据库脚本和SQL文件的版本。跑通以后,我建议你先打开数据库看一眼那几张核心表里的初始数据,再结合界面操作几次,这样对应起来学特别快。
如果你们用这套代码做课程设计或者毕业设计,建议一定要把"批次管理"和"预警机制"这两个点讲透,这在答辩时是很大的加分项。毕竟业务系统的价值不在于代码写得多炫,而在于能真正解决现实问题,这套项目恰好把这个问题梳理得很清楚。