医药流通、连锁药房、基层医疗机构这些场景里,用 SpringBoot + Vue + MyBatis + MySQL 做管理系统的开发同学非常多,开源社区以“企业级医药管理系统源码【完整版】”为标题的项目也一直很多。今年我陆续帮两三家药房做过源码评估,发现大部分项目跑起来很顺畅,可真要接到实际药房业务里,难点从来不在框架、页面,而在批次、效期、权限、单据状态这些容易被忽略的设计。
这篇文章不打算把代码逐行念一遍,而是从一个“拿到完整源码准备二开”的视角来拆:先讲医药业务对软件有哪些硬约束,再讲这套技术栈为什么值得选,要理解源码得优先看哪几张表,后端哪些接口最容易改坏,前端跑不起来大部分是什么原因,以及从开发样例到上线交付还差哪些事。如果你正在做相关课程设计、毕设,或者刚接触前后端分离项目想看看真实工程长什么样,这篇内容应该都够用。
1. 先搞清楚:标题里的“企业级医药”到底指什么
很多开发者拿到源码第一件事是启动项目,第二件事是打开菜单一个个点。这样其实浪费了源码里最值钱的部分——业务模型。医药管理系统外表看是典型的“进销存”,但它和普通商品进销存有本质差异,把这种差异吃透,后面看代码才不会迷路。
1.1 药品不能简单当成普通商品:批次、效期和资质
普通进销存模型里,一款商品基本就是一条主数据加一个库存数量。医药行业不一样,同样是阿莫西林胶囊,今天入库这批和上个月入库那批可能是不同生产批号,生产日期、有效期、供应商都不同。销售的时候如果只按“药品编码+总数量”扣减,过期药品就无法追踪,召回时也无从下手。
所以我评估一套医药系统是否靠谱,先看它的库存表里有没有“批号”和“效期”的维度。如果数据库只有一个库存数字,没有任何批次概念,那不管界面做得多华丽,本质上就是普通进销存换了身皮肤。
此外,药品档案必须包含批准文号、生产厂家、规格、贮藏条件这类字段。在实际药店操作里,营业员拿药品包装扫一下条码,系统要能精准匹配到药监信息。处方药和非处方药也不能混在一起管理,如果涉及处方药销售,还需要关联处方记录。这些约束在代码里体现为表结构和状态字段,不在页面上的“小图标”里。
1.2 核心业务链路与模块地图
多数医药管理源码的模块结构差异不大,差别在于业务闭环是否完整。我建议你拿到源码后,按下面这条业务链去梳理:
药品主档案建立 → 供应商资质信息维护 → 采购入库生成带批号的有效期库存 → 销售出库时按批号扣减可用库存 → 库存流水记录变化 → 近效期药品预警 → 盘点与报损调整。
这条链走通一遍,项目才算真正“立住了”。按模块拆开会更清晰:
| 模块 | 核心功能 | 关键点 |
|---|---|---|
| 基础资料 | 药品分类、药品档案、供应商、客户 | 药品分类有父子层级,供应商要维护证照信息 |
| 采购管理 | 采购订单、采购入库、采购退货 | 入库时录入批号、生产日期、有效期 |
| 销售管理 | 销售单、销售出库、销售退货 | 处方药需要限制条件,出库尽量按效期排序 |
| 库存管理 | 批次库存、库存流水、盘点、报损 | 流水表不可少,数据有变更必须有据可查 |
| 预警统计 | 效期预警、库存上下限、销售报表 | 大多通过定时任务或查询条件实现 |
| 系统管理 | 用户、角色、菜单、操作日志 | 企业多人使用,权限边界清晰 |
看模块名称没什么稀奇,真正有价值的是每个模块内部的单据状态流转。比如采购单常见状态有草稿、已入库、已退货;销售单有草稿、已出库、已取消。如果源码里把状态都塞进一个普通字段但没有合理流转,二开时会非常痛苦。
1.3 这套源码在企业现场会遇上什么落差
开源项目常见的问题,是自动生成的初始化脚本里带了大量“测试药品”和演示账号。真实药店药品主数据少则几千,多则几万条,字段规范程度、分类梳理、厂家归并都需要专门整理。源码里那几十条演示数据,只能证明功能能跑,不能代表真实数据规模下的查询性能。
但换个角度看,对正在学习的人来说,这反而是优点。数据量小更容易看清表与表之间关系,也方便在本地反复做入库、出库试验。把演示系统当成一个“业务沙盘”来研究,比直接上生产环境要安全得多。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型:为什么这套组合在企业级项目里依然站得住
标题里把 SpringBoot、Vue、MyBatis、MySQL 全部写出来,并不是堆热门词,这套组合放到现在依然是中小型企业项目的“性价比之选”。它不追求炫技,但每个组件都有明确分工,替换成本也低。
2.1 选型的起点:先看 Java 环境和 SpringBoot 版本
很多读者会问,源码下载下来启动不了,第一反应是代码有问题。我遇到的情况大多不是代码问题,而是版本不匹配。SpringBoot 2.7.x 对应 JDK 8,部署轻松;SpringBoot 3.x 要求 JDK 17 起步,依赖坐标也有变化。如果源码用 <parent> 统一管理版本,先把 pom.xml 顶层结构看清楚再启动项目。
以最常见的 JDK 8 环境为例,后端依赖大概是下面这样:
xml复制<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
</dependencies>
如果你打开 pom 发现版本太新或者太老,不要急着全部升级,多数情况下保持源码自带版本最能稳定运行。真实项目里“能用”比“最新”重要得多。
2.2 MySQL 连接串和时区问题:最常卡壳的地方
MySQL 8 和 MySQL 5.7 的连接驱动写法有差异。很多源码配套数据库是 MySQL 8 就会报时区错误,需要在 JDBC 连接串里显式指定 serverTimezone。
yaml复制spring:
datasource:
url: jdbc:mysql://localhost:3306/medicine?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
这里有个容易被忽略的细节:characterEncoding=utf8 虽然古老但稳妥,建议建库时直接使用 utf8mb4 字符集,因为药品名称里常会出现生僻字,utf8 有少量字符存不进去。如果你本地不想安装 MySQL,可以用 Docker 快速起一个测试实例,但别把生产数据也放在容器里。
code复制docker run -d --name mysql8 -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_DATABASE=medicine mysql:8.0
2.3 用 MyBatis 还是 MyBatis-Plus,取决于 SQL 掌控需求
标题写的是 MyBatis,实际源码里可能用纯 MyBatis XML,也可能用 MyBatis-Plus。两者没有绝对优劣。纯 MyBatis 的好处是每个 SQL 都看得见摸得着,适合复杂多表联查和特殊业务;MyBatis-Plus 的开发效率更高,但处理批次库存这种“同一药品多批号”场景,还是要自己写 SQL。
如果项目里用了 MyBatis-Plus,又开启了逻辑删除,你会看到常规查询自动带上 del_flag=0。这个特性一开始很方便,可一旦要做历史报表或者跨表数据核对,系统会强制过滤掉已删除记录,反而需要写特殊 SQL 绕过。我的建议是:单据明细这种关键表不要轻易做逻辑删除,保留物理删除或审计日志会更直观。
3. 数据库表设计:判断系统是否“懂医药”的第一标准
先看建表脚本,往往能看出写这套源码的人有没有
