
简介这是一套基于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.xJDK 用 8 或 11如果是 3.xJDK 必须 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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;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 21第三个动作是给关键操作加审计日志。药品档案修改、库存手动调整、价格改动这三类操作必须记录操作人、时间、修改前后值。GSP 检查时这是必查项临时补日志根本来不及。源码如果没有审计表自己加一张med_audit_log在模型事件里统一写入。我自己的习惯是拿到任何一套医药管理系统源码先跑通「采购入库 → 销售出库 → 批号追溯」这条最小闭环再谈二次开发。闭环跑不通后面加再多功能都是空中楼阁。希望帮到你。本文还有配套的精品资源点击获取