简介:这是一套基于Java Web技术栈的医药管理系统后台源码,面向计算机专业学生、课程设计开发者及需要练手SSM/JSP项目的初学者,可帮助快速搭建药品进销存管理场景。系统围绕药品、类别、库存与销售展开,实现了添加与查看药品、高级查询、库存查看、类别添加与统计、购买药品、销售管理、进货需求管理及系统管理等模块,业务链路较为完整。压缩包共162个文件,约9.34MB,以31个jsp页面、30个java源文件、30个class编译文件、26个jar依赖包及12个xml配置为主,另含sql建库脚本、properties配置与少量图片资源,结构清晰便于二次开发。开发环境为JDK1.8搭配MySQL数据库,导入后即可对照源码理解分层设计与数据库交互逻辑。目前已有2058人学习下载,适合作为课程设计、毕业设计或Java Web入门实战的参考案例。
1. 医药管理系统源码.zip:一套能跑起来的进销存骨架长什么样
很多做企业信息化的朋友拿到「医药管理系统源码.zip」的第一反应是解压看目录,然后被几十个文件夹和上百个 PHP 文件劝退。我最初接触这类系统是在给一家区域连锁药房做二次开发时,当时的需求很明确:药品批号要能追溯、近效期要能预警、GSP 规范里的首营企业和首营品种要能留痕。市面上的成品 SaaS 按门店收年费,数据还不在自己手里,于是转向源码自建这条路。
医药管理系统源码本质上是一套面向药店、医药批发或门诊药房的进销存系统,核心模块通常包括药品档案、采购入库、销售出库、库存盘点、批号效期管理和基础报表。它和普通商品进销存的区别在于:药品有批号、有效期、批准文号、生产厂家这些强监管字段,库存不能只记数量,还要记批次。这套源码适合有 PHP 或 Java 基础、想自己掌控数据和二次开发节奏的团队,也适合作为课程设计或毕业设计的参考工程。下面我按实际落地顺序,把选型、部署、核心表结构和踩坑点拆开讲。
2. 拿到源码先别急着跑:技术栈盘点和环境选型
2.1 从目录结构判断这套源码的技术栈
解压后先看根目录,不要急着找 index.php。我一般按这个顺序扫一遍:有没有composer.json或pom.xml,有没有application或app目录,有没有sql文件夹,有没有README或install目录。这四个位置基本能确定技术栈和部署方式。
常见的医药管理系统源码分两类。一类是 ThinkPHP 或 Laravel 写的 PHP 项目,目录里会有application、public、think或artisan;另一类是 SpringBoot 写的 Java 项目,会有src/main/java、pom.xml、application.yml。热搜词里「php源码」和「java课程设计案例源码」出现频率很高,说明这两类都是主流。判断清楚再选环境,别用 PHP 的环境去跑 Java 包。
# 查看根目录关键文件,快速判断技术栈 ls -la # 有 composer.json -> PHP 项目 # 有 pom.xml 或 build.gradle -> Java 项目 # 有 package.json 且无后端框架 -> 可能只是前端模板 find . -maxdepth 2 -name "*.sql" -o -name "application.yml" -o -name "config.php"这段命令的作用是快速定位配置文件和数据库脚本。-maxdepth 2限制搜索深度,避免在大目录里卡住。如果找到.sql文件,说明作者提供了建表脚本,这是最省事的情况;如果只有.sql的碎片或没有,就要从实体类反推表结构,工作量会大很多。
2.2 PHP 和 Java 两条路的环境准备
如果确认是 PHP 项目,推荐 PHP 7.4 到 8.1 之间,MySQL 5.7 或 8.0。PHP 8.2 以上有些老源码会因为动态属性废弃而报错,这是血泪经验。用宝塔或 phpstudy 搭环境最快,但生产环境我建议手动配 Nginx + PHP-FPM,因为医药系统涉及数据合规,面板工具的默认配置往往开了不必要的端口。
Java 项目则看pom.xml里的spring-boot-starter-parent版本。如果是 2.x,JDK 用 8 或 11;如果是 3.x,JDK 必须 17 以上。数据库连接池常见 Druid 或 HikariCP,配置文件里搜url:就能找到数据库地址。下面是一个典型的 PHP 项目数据库配置片段,改完就能连库。
// config/database.php 常见结构 return [ 'type' => 'mysql', 'hostname' => '127.0.0.1', 'database' => 'medicine_db', // 数据库名,按 sql 脚本里的 CREATE DATABASE 填 'username' => 'med_user', // 不要用 root,单独建账号 'password' => 'YourStrongPwd', // 生产环境必须强密码 'hostport' => '3306', 'charset' => 'utf8mb4', // 药品名有生僻字,必须 utf8mb4 'prefix' => 'med_', // 表前缀,和 sql 脚本保持一致 ];参数说明:charset必须是utf8mb4,因为药品通用名里有「䓬」「啶」这类字,utf8存进去会变问号。prefix要和导入 SQL 时的表前缀一致,不一致会报「表不存在」。username单独建账号并只授权该库,避免源码里有后门时被拖全库。
2.3 导入数据库和首次访问的完整流程
建库、导表、改配置、配伪静态,这四步顺序不能乱。先建一个空库,字符集选utf8mb4_general_ci,然后用命令行导入 SQL,不要用 phpMyAdmin 导大文件,容易超时截断。
# 1. 建库 mysql -u root -p -e "CREATE DATABASE medicine_db DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci;" # 2. 导入表结构和初始数据 mysql -u root -p medicine_db < install/medicine_db.sql # 3. 检查表数量,正常医药系统在 30 到 60 张表之间 mysql -u root -p medicine_db -e "SHOW TABLES;" | wc -l导入后如果表数量明显偏少,比如只有十几张,说明 SQL 脚本不完整,可能作者只导了部分表。这时候要去install目录或doc目录找有没有分卷的 SQL 文件。首次访问一般是http://你的域名/install或http://你的域名/public/index.php,具体看框架。ThinkPHP 的入口在public下,Nginx 要配伪静态,否则除首页外全是 404。
提示:部署完成后第一件事是改默认管理员密码,第二件事是删掉
install目录。我见过太多源码装完不删安装目录,被人重新跑一遍安装流程直接接管后台。
3. 药品、批号、库存三张核心表怎么设计才算能用
3.1 药品档案表:批准文号和通用名的字段取舍
医药管理系统的数据地基是药品档案表。普通进销存一张goods表就够了,但医药系统必须拆出「药品通用名」「商品名」「批准文号」「生产企业」「剂型」「规格」这些字段。我见过一些源码把通用名和商品名混在一个字段里,结果做 GSP 报表时根本拆不出来。
CREATE TABLE `med_drug` ( `id` int NOT NULL AUTO_INCREMENT, `drug_code` varchar(32) NOT NULL COMMENT '内部编码', `generic_name` varchar(128) NOT NULL COMMENT '通用名,如阿莫西林胶囊', `trade_name` varchar(128) DEFAULT NULL COMMENT '商品名', `approval_number` varchar(64) NOT NULL COMMENT '批准文号,国药准字Hxxxx', `manufacturer` varchar(128) NOT NULL COMMENT '生产企业', `dosage_form` varchar(32) DEFAULT NULL COMMENT '剂型', `spec` varchar(64) DEFAULT NULL COMMENT '规格', `is_prescription` tinyint(1) DEFAULT '0' COMMENT '是否处方药', `storage_condition` varchar(64) DEFAULT NULL COMMENT '储存条件', PRIMARY KEY (`id`), UNIQUE KEY `uk_approval` (`approval_number`), KEY `idx_generic` (`generic_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;approval_number加唯一索引,因为一个批准文号对应一个药品,重复录入会导致批号追溯混乱。generic_name加普通索引,因为销售开单时按通用名搜索是最高频操作。is_prescription字段决定销售时是否强制登记处方信息,这是医药系统区别于普通商城的硬性逻辑。
3.2 批号库存表:为什么不能只存一个数量
普通库存表存「商品 ID + 数量」,医药系统必须存「药品 ID + 批号 + 生产日期 + 有效期 + 数量」。同一盒阿莫西林,批号 A 和批号 B 的效期不同,出库时要按先进先出或近效期先出。如果源码里库存表没有批号字段,这套系统基本不能用于真实药房。
CREATE TABLE `med_stock_batch` ( `id` bigint NOT NULL AUTO_INCREMENT, `drug_id` int NOT NULL, `batch_no` varchar(64) NOT NULL COMMENT '生产批号', `production_date` date DEFAULT NULL, `expiry_date` date NOT NULL COMMENT '有效期至', `quantity` int NOT NULL DEFAULT '0', `warehouse_id` int NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_drug_batch_wh` (`drug_id`,`batch_no`,`warehouse_id`), KEY `idx_expiry` (`expiry_date`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;uk_drug_batch_wh保证同一药品同一批号在同一仓库只有一条记录,入库时用ON DUPLICATE KEY UPDATE quantity = quantity + ?累加。idx_expiry是近效期预警的查询索引,没有这个索引,效期报表在几万条库存下会慢到不可用。
3.3 入库出库单:主表和明细表的事务边界
采购入库和销售出库都是「一张主单 + 多条明细」的结构。主表存供应商、单据号、总金额、状态;明细表存药品、批号、数量、单价。关键点是:出库时扣减库存和写明细必须在同一个事务里,否则会出现单据写了但库存没扣,或者库存扣了单据没写。
// 出库事务示例,ThinkPHP 风格 Db::startTrans(); try { $orderId = Db::name('sale_order')->insertGetId($orderData); foreach ($items as $item) { // 扣减对应批号库存,quantity >= 出库量才允许 $affected = Db::name('stock_batch') ->where('drug_id', $item['drug_id']) ->where('batch_no', $item['batch_no']) ->where('quantity', '>=', $item['qty']) ->dec('quantity', $item['qty']) ->update(); if (!$affected) { throw new Exception('批号库存不足:' . $item['batch_no']); } Db::name('sale_order_item')->insert($item); } Db::commit(); } catch (Exception $e) { Db::rollback(); return json(['code' => 0, 'msg' => $e->getMessage()]); }where('quantity', '>=', $item['qty'])这个条件很关键,它把库存检查放在 UPDATE 语句里,利用数据库行锁避免并发超卖。如果先 SELECT 查库存再 UPDATE,两个收银台同时出库同一批号就会扣成负数。dec是 ThinkPHP 的原子递减方法,等价于quantity = quantity - N。
注意:事务里不要做远程调用或文件写入,否则事务持有时间过长,高并发下会锁等待。打印小票、发短信这类操作放到事务提交之后。
4. 从源码到能用的系统:部署、初始化和权限配置
4.1 后台菜单和权限节点怎么对应
医药管理系统的后台通常有管理员、店长、收银员、库管这几个角色。源码里的权限控制一般基于「节点」实现,每个控制器方法对应一个权限节点,角色勾选节点后才有权访问。部署后第一件事是进「权限管理」把菜单和节点对齐,否则会出现菜单看得见但点进去 403 的情况。
| 角色 | 药品档案 | 采购入库 | 销售出库 | 库存盘点 | 报表 |
|---|---|---|---|---|---|
| 管理员 | 增删改查 | 增删改查 | 增删改查 | 增删改查 | 全部 |
| 店长 | 查改 | 增查 | 增查 | 增查 | 本店 |
| 收银员 | 只读 | 无 | 增查 | 无 | 无 |
| 库管 | 查改 | 增删改查 | 只读 | 增删改查 | 库存类 |
这张表是我给连锁药房配权限时的实际方案。收银员不给采购权限,库管不给销售改价权限,这是内控基本要求。源码如果只有「超级管理员」一个角色,需要自己在角色表里加记录,并在节点表里补全控制器方法名。
4.2 初始化数据:仓库、供应商、药品分类
系统装好后是空库,直接开单会报错。按这个顺序初始化:先建仓库(至少一个「主仓库」),再建药品分类(处方药、非处方药、中药饮片、医疗器械),再建供应商和客户,最后录药品档案。药品档案可以手工录,也可以用 Excel 导入,源码一般带导入模板。
# 检查初始化数据是否完整 mysql -u med_user -p medicine_db -e " SELECT '仓库' AS type, COUNT(*) AS cnt FROM med_warehouse UNION ALL SELECT '分类', COUNT(*) FROM med_category UNION ALL SELECT '供应商', COUNT(*) FROM med_supplier UNION ALL SELECT '药品', COUNT(*) FROM med_drug;"如果仓库数量为 0,先去后台「仓库管理」加一条。如果药品为 0 但分类有数据,说明导入模板的列名和表字段没对上,检查 Excel 表头是否包含「通用名」「批准文号」「生产企业」这三列,缺一列都会导入失败。
4.3 近效期预警和批号追溯的验证方法
部署完不算完,要验证两个医药系统的核心能力。第一,近效期预警:录一个有效期在 90 天内的批号,看首页或报表是否出现预警。第二,批号追溯:随便选一个批号,看能不能查到它的入库单、出库单、当前库存和流向客户。
-- 批号全链路追溯查询 SELECT '入库' AS flow, i.order_no, i.create_time, i.quantity FROM med_purchase_item i WHERE i.batch_no = '20240501A' UNION ALL SELECT '出库', o.order_no, o.create_time, o.quantity FROM med_sale_item o WHERE o.batch_no = '20240501A';这条 SQL 把某批号的入库和出库记录合并展示。如果查出来只有入库没有出库,但库存显示为 0,说明源码的库存扣减逻辑有 bug,可能出库时没写明细或者写错了批号。这种情况在二次开发过的源码里很常见,必须逐单核对。
5. 二次开发避坑:那些让我加班到凌晨的坑
5.1 现象:入库后库存不增加,但单据显示成功
原因:库存表用了drug_id + warehouse_id做唯一键,没有把batch_no纳入唯一键,导致同一药品不同批号互相覆盖。解决:检查med_stock_batch的唯一索引,必须是drug_id + batch_no + warehouse_id三字段联合。如果源码建表时漏了批号,需要改表结构并重算历史库存。
5.2 现象:销售开单搜索药品,输入通用名搜不到
原因:药品档案的generic_name字段没有建索引,或者搜索用的是LIKE '%关键词%'导致全表扫描,数据量一大就超时。解决:先加索引,再把搜索逻辑改成前缀匹配LIKE '关键词%',或者接入全文索引。如果源码用的是FIND_IN_SET存多关键词,建议改成独立搜索表。
5.3 现象:并发出库时库存扣成负数
原因:出库逻辑先SELECT查库存,再UPDATE扣减,两个操作之间没有锁。解决:把库存检查合并进UPDATE的WHERE条件,如第 3 章示例;或者用SELECT ... FOR UPDATE锁行。前者性能更好,后者适合扣减逻辑复杂的场景。
5.4 现象:PHP 8 下后台白屏,日志报动态属性废弃
原因:老源码用了$this->xxx = $yyy动态赋值,PHP 8.2 起标记为废弃,部分框架会抛异常。解决:降级到 PHP 7.4 或 8.1,或者在模型基类加#[\AllowDynamicProperties]注解。生产环境不建议为了跑老源码而长期停留在低版本,最好逐步重构。
5.5 现象:导入 Excel 药品档案,中文全部乱码
原因:Excel 默认编码是 GBK,而数据库是 utf8mb4,导入时没有转码。解决:导入前用iconv('GBK', 'UTF-8', $value)转码,或者要求模板保存为 CSV UTF-8 格式。我一般直接在导入类里统一转码,避免用户操作差异。
6. 让这套源码真正值钱的三个进阶动作
第一个动作是给批号库存加「锁定库存」字段。销售开单时先锁库存,付款后再实扣,取消订单则释放。这样能解决「下单未付款期间库存被其他单占用」的问题。实现上在med_stock_batch加locked_quantity,可用库存等于quantity - locked_quantity,出库时校验可用库存。
第二个动作是把近效期预警做成定时任务,而不是每次打开首页才查。用 crontab 每天凌晨跑一次,把 90 天内到期的批号写进预警表,并推送给库管。这样即使没人登录后台,预警也不会漏。
# 每天凌晨 2 点执行效期扫描 0 2 * * * /usr/bin/php /www/medicine/think expiry:scan >> /var/log/expiry.log 2>&1第三个动作是给关键操作加审计日志。药品档案修改、库存手动调整、价格改动这三类操作必须记录操作人、时间、修改前后值。GSP 检查时这是必查项,临时补日志根本来不及。源码如果没有审计表,自己加一张med_audit_log,在模型事件里统一写入。
我自己的习惯是:拿到任何一套医药管理系统源码,先跑通「采购入库 → 销售出库 → 批号追溯」这条最小闭环,再谈二次开发。闭环跑不通,后面加再多功能都是空中楼阁。希望帮到你。
本文还有配套的精品资源,点击获取