藥管理系統(tǒng)源碼實戰(zhàn):從部署到二次開發(fā)的進(jìn)銷存核心設(shè)計)
簡介這是一套基于Java Web技術(shù)棧的醫(yī)藥管理系統(tǒng)后臺源碼面向計算機專業(yè)學(xué)生、課程設(shè)計開發(fā)者及需要練手SSM/JSP項目的初學(xué)者可幫助快速搭建藥品進(jìn)銷存管理場景。系統(tǒng)圍繞藥品、類別、庫存與銷售展開實現(xiàn)了添加與查看藥品、高級查詢、庫存查看、類別添加與統(tǒng)計、購買藥品、銷售管理、進(jìn)貨需求管理及系統(tǒng)管理等模塊業(yè)務(wù)鏈路較為完整。壓縮包共162個文件約9.34MB以31個jsp頁面、30個java源文件、30個class編譯文件、26個jar依賴包及12個xml配置為主另含sql建庫腳本、properties配置與少量圖片資源結(jié)構(gòu)清晰便于二次開發(fā)。開發(fā)環(huán)境為JDK1.8搭配MySQL數(shù)據(jù)庫導(dǎo)入后即可對照源碼理解分層設(shè)計與數(shù)據(jù)庫交互邏輯。目前已有2058人學(xué)習(xí)下載適合作為課程設(shè)計、畢業(yè)設(shè)計或Java Web入門實戰(zhàn)的參考案例。1. 醫(yī)藥管理系統(tǒng)源碼.zip一套能跑起來的進(jìn)銷存骨架長什么樣很多做企業(yè)信息化的朋友拿到「醫(yī)藥管理系統(tǒng)源碼.zip」的第一反應(yīng)是解壓看目錄然后被幾十個文件夾和上百個 PHP 文件勸退。我最初接觸這類系統(tǒng)是在給一家區(qū)域連鎖藥房做二次開發(fā)時當(dāng)時的需求很明確藥品批號要能追溯、近效期要能預(yù)警、GSP 規(guī)范里的首營企業(yè)和首營品種要能留痕。市面上的成品 SaaS 按門店收年費數(shù)據(jù)還不在自己手里于是轉(zhuǎn)向源碼自建這條路。醫(yī)藥管理系統(tǒng)源碼本質(zhì)上是一套面向藥店、醫(yī)藥批發(fā)或門診藥房的進(jìn)銷存系統(tǒng)核心模塊通常包括藥品檔案、采購入庫、銷售出庫、庫存盤點、批號效期管理和基礎(chǔ)報表。它和普通商品進(jìn)銷存的區(qū)別在于藥品有批號、有效期、批準(zhǔn)文號、生產(chǎn)廠家這些強監(jiān)管字段庫存不能只記數(shù)量還要記批次。這套源碼適合有 PHP 或 Java 基礎(chǔ)、想自己掌控數(shù)據(jù)和二次開發(fā)節(jié)奏的團(tuán)隊也適合作為課程設(shè)計或畢業(yè)設(shè)計的參考工程。下面我按實際落地順序把選型、部署、核心表結(jié)構(gòu)和踩坑點拆開講。2. 拿到源碼先別急著跑技術(shù)棧盤點和環(huán)境選型2.1 從目錄結(jié)構(gòu)判斷這套源碼的技術(shù)棧解壓后先看根目錄不要急著找 index.php。我一般按這個順序掃一遍有沒有composer.json或pom.xml有沒有application或app目錄有沒有sql文件夾有沒有README或install目錄。這四個位置基本能確定技術(shù)棧和部署方式。常見的醫(yī)藥管理系統(tǒng)源碼分兩類。一類是 ThinkPHP 或 Laravel 寫的 PHP 項目目錄里會有application、public、think或artisan另一類是 SpringBoot 寫的 Java 項目會有src/main/java、pom.xml、application.yml。熱搜詞里「php源碼」和「java課程設(shè)計案例源碼」出現(xiàn)頻率很高說明這兩類都是主流。判斷清楚再選環(huán)境別用 PHP 的環(huán)境去跑 Java 包。# 查看根目錄關(guān)鍵文件快速判斷技術(shù)棧 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這段命令的作用是快速定位配置文件和數(shù)據(jù)庫腳本。-maxdepth 2限制搜索深度避免在大目錄里卡住。如果找到.sql文件說明作者提供了建表腳本這是最省事的情況如果只有.sql的碎片或沒有就要從實體類反推表結(jié)構(gòu)工作量會大很多。2.2 PHP 和 Java 兩條路的環(huán)境準(zhǔn)備如果確認(rèn)是 PHP 項目推薦 PHP 7.4 到 8.1 之間MySQL 5.7 或 8.0。PHP 8.2 以上有些老源碼會因為動態(tài)屬性廢棄而報錯這是血淚經(jīng)驗。用寶塔或 phpstudy 搭環(huán)境最快但生產(chǎn)環(huán)境我建議手動配 Nginx PHP-FPM因為醫(yī)藥系統(tǒng)涉及數(shù)據(jù)合規(guī)面板工具的默認(rèn)配置往往開了不必要的端口。Java 項目則看pom.xml里的spring-boot-starter-parent版本。如果是 2.xJDK 用 8 或 11如果是 3.xJDK 必須 17 以上。數(shù)據(jù)庫連接池常見 Druid 或 HikariCP配置文件里搜url:就能找到數(shù)據(jù)庫地址。下面是一個典型的 PHP 項目數(shù)據(jù)庫配置片段改完就能連庫。// config/database.php 常見結(jié)構(gòu) return [ type mysql, hostname 127.0.0.1, database medicine_db, // 數(shù)據(jù)庫名按 sql 腳本里的 CREATE DATABASE 填 username med_user, // 不要用 root單獨建賬號 password YourStrongPwd, // 生產(chǎn)環(huán)境必須強密碼 hostport 3306, charset utf8mb4, // 藥品名有生僻字必須 utf8mb4 prefix med_, // 表前綴和 sql 腳本保持一致 ];參數(shù)說明charset必須是utf8mb4因為藥品通用名里有「?」「啶」這類字utf8存進(jìn)去會變問號。prefix要和導(dǎo)入 SQL 時的表前綴一致不一致會報「表不存在」。username單獨建賬號并只授權(quán)該庫避免源碼里有后門時被拖全庫。2.3 導(dǎo)入數(shù)據(jù)庫和首次訪問的完整流程建庫、導(dǎo)表、改配置、配偽靜態(tài)這四步順序不能亂。先建一個空庫字符集選utf8mb4_general_ci然后用命令行導(dǎo)入 SQL不要用 phpMyAdmin 導(dǎo)大文件容易超時截斷。# 1. 建庫 mysql -u root -p -e CREATE DATABASE medicine_db DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci; # 2. 導(dǎo)入表結(jié)構(gòu)和初始數(shù)據(jù) mysql -u root -p medicine_db install/medicine_db.sql # 3. 檢查表數(shù)量正常醫(yī)藥系統(tǒng)在 30 到 60 張表之間 mysql -u root -p medicine_db -e SHOW TABLES; | wc -l導(dǎo)入后如果表數(shù)量明顯偏少比如只有十幾張說明 SQL 腳本不完整可能作者只導(dǎo)了部分表。這時候要去install目錄或doc目錄找有沒有分卷的 SQL 文件。首次訪問一般是http://你的域名/install或http://你的域名/public/index.php具體看框架。ThinkPHP 的入口在public下Nginx 要配偽靜態(tài)否則除首頁外全是 404。提示部署完成后第一件事是改默認(rèn)管理員密碼第二件事是刪掉install目錄。我見過太多源碼裝完不刪安裝目錄被人重新跑一遍安裝流程直接接管后臺。3. 藥品、批號、庫存三張核心表怎么設(shè)計才算能用3.1 藥品檔案表批準(zhǔn)文號和通用名的字段取舍醫(yī)藥管理系統(tǒng)的數(shù)據(jù)地基是藥品檔案表。普通進(jìn)銷存一張goods表就夠了但醫(yī)藥系統(tǒng)必須拆出「藥品通用名」「商品名」「批準(zhǔn)文號」「生產(chǎn)企業(yè)」「劑型」「規(guī)格」這些字段。我見過一些源碼把通用名和商品名混在一個字段里結(jié)果做 GSP 報表時根本拆不出來。CREATE TABLE med_drug ( id int NOT NULL AUTO_INCREMENT, drug_code varchar(32) NOT NULL COMMENT 內(nèi)部編碼, generic_name varchar(128) NOT NULL COMMENT 通用名如阿莫西林膠囊, trade_name varchar(128) DEFAULT NULL COMMENT 商品名, approval_number varchar(64) NOT NULL COMMENT 批準(zhǔn)文號國藥準(zhǔn)字Hxxxx, manufacturer varchar(128) NOT NULL COMMENT 生產(chǎn)企業(yè), dosage_form varchar(32) DEFAULT NULL COMMENT 劑型, spec varchar(64) DEFAULT NULL COMMENT 規(guī)格, 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加唯一索引因為一個批準(zhǔn)文號對應(yīng)一個藥品重復(fù)錄入會導(dǎo)致批號追溯混亂。generic_name加普通索引因為銷售開單時按通用名搜索是最高頻操作。is_prescription字段決定銷售時是否強制登記處方信息這是醫(yī)藥系統(tǒng)區(qū)別于普通商城的硬性邏輯。3.2 批號庫存表為什么不能只存一個數(shù)量普通庫存表存「商品 ID 數(shù)量」醫(yī)藥系統(tǒng)必須存「藥品 ID 批號 生產(chǎn)日期 有效期 數(shù)量」。同一盒阿莫西林批號 A 和批號 B 的效期不同出庫時要按先進(jìn)先出或近效期先出。如果源碼里庫存表沒有批號字段這套系統(tǒng)基本不能用于真實藥房。CREATE TABLE med_stock_batch ( id bigint NOT NULL AUTO_INCREMENT, drug_id int NOT NULL, batch_no varchar(64) NOT NULL COMMENT 生產(chǎn)批號, 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是近效期預(yù)警的查詢索引沒有這個索引效期報表在幾萬條庫存下會慢到不可用。3.3 入庫出庫單主表和明細(xì)表的事務(wù)邊界采購入庫和銷售出庫都是「一張主單 多條明細(xì)」的結(jié)構(gòu)。主表存供應(yīng)商、單據(jù)號、總金額、狀態(tài)明細(xì)表存藥品、批號、數(shù)量、單價。關(guān)鍵點是出庫時扣減庫存和寫明細(xì)必須在同一個事務(wù)里否則會出現(xiàn)單據(jù)寫了但庫存沒扣或者庫存扣了單據(jù)沒寫。// 出庫事務(wù)示例ThinkPHP 風(fēng)格 Db::startTrans(); try { $orderId Db::name(sale_order)-insertGetId($orderData); foreach ($items as $item) { // 扣減對應(yīng)批號庫存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])這個條件很關(guān)鍵它把庫存檢查放在 UPDATE 語句里利用數(shù)據(jù)庫行鎖避免并發(fā)超賣。如果先 SELECT 查庫存再 UPDATE兩個收銀臺同時出庫同一批號就會扣成負(fù)數(shù)。dec是 ThinkPHP 的原子遞減方法等價于quantity quantity - N。注意事務(wù)里不要做遠(yuǎn)程調(diào)用或文件寫入否則事務(wù)持有時間過長高并發(fā)下會鎖等待。打印小票、發(fā)短信這類操作放到事務(wù)提交之后。4. 從源碼到能用的系統(tǒng)部署、初始化和權(quán)限配置4.1 后臺菜單和權(quán)限節(jié)點怎么對應(yīng)醫(yī)藥管理系統(tǒng)的后臺通常有管理員、店長、收銀員、庫管這幾個角色。源碼里的權(quán)限控制一般基于「節(jié)點」實現(xiàn)每個控制器方法對應(yīng)一個權(quán)限節(jié)點角色勾選節(jié)點后才有權(quán)訪問。部署后第一件事是進(jìn)「權(quán)限管理」把菜單和節(jié)點對齊否則會出現(xiàn)菜單看得見但點進(jìn)去 403 的情況。角色藥品檔案采購入庫銷售出庫庫存盤點報表管理員增刪改查增刪改查增刪改查增刪改查全部店長查改增查增查增查本店收銀員只讀無增查無無庫管查改增刪改查只讀增刪改查庫存類這張表是我給連鎖藥房配權(quán)限時的實際方案。收銀員不給采購權(quán)限庫管不給銷售改價權(quán)限這是內(nèi)控基本要求。源碼如果只有「超級管理員」一個角色需要自己在角色表里加記錄并在節(jié)點表里補全控制器方法名。4.2 初始化數(shù)據(jù)倉庫、供應(yīng)商、藥品分類系統(tǒng)裝好后是空庫直接開單會報錯。按這個順序初始化先建倉庫至少一個「主倉庫」再建藥品分類處方藥、非處方藥、中藥飲片、醫(yī)療器械再建供應(yīng)商和客戶最后錄藥品檔案。藥品檔案可以手工錄也可以用 Excel 導(dǎo)入源碼一般帶導(dǎo)入模板。# 檢查初始化數(shù)據(jù)是否完整 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 供應(yīng)商, COUNT(*) FROM med_supplier UNION ALL SELECT 藥品, COUNT(*) FROM med_drug;如果倉庫數(shù)量為 0先去后臺「倉庫管理」加一條。如果藥品為 0 但分類有數(shù)據(jù)說明導(dǎo)入模板的列名和表字段沒對上檢查 Excel 表頭是否包含「通用名」「批準(zhǔn)文號」「生產(chǎn)企業(yè)」這三列缺一列都會導(dǎo)入失敗。4.3 近效期預(yù)警和批號追溯的驗證方法部署完不算完要驗證兩個醫(yī)藥系統(tǒng)的核心能力。第一近效期預(yù)警錄一個有效期在 90 天內(nèi)的批號看首頁或報表是否出現(xiàn)預(yù)警。第二批號追溯隨便選一個批號看能不能查到它的入庫單、出庫單、當(dāng)前庫存和流向客戶。-- 批號全鏈路追溯查詢 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可能出庫時沒寫明細(xì)或者寫錯了批號。這種情況在二次開發(fā)過的源碼里很常見必須逐單核對。5. 二次開發(fā)避坑那些讓我加班到凌晨的坑5.1 現(xiàn)象入庫后庫存不增加但單據(jù)顯示成功原因庫存表用了drug_id warehouse_id做唯一鍵沒有把batch_no納入唯一鍵導(dǎo)致同一藥品不同批號互相覆蓋。解決檢查med_stock_batch的唯一索引必須是drug_id batch_no warehouse_id三字段聯(lián)合。如果源碼建表時漏了批號需要改表結(jié)構(gòu)并重算歷史庫存。5.2 現(xiàn)象銷售開單搜索藥品輸入通用名搜不到原因藥品檔案的generic_name字段沒有建索引或者搜索用的是LIKE %關(guān)鍵詞%導(dǎo)致全表掃描數(shù)據(jù)量一大就超時。解決先加索引再把搜索邏輯改成前綴匹配LIKE 關(guān)鍵詞%或者接入全文索引。如果源碼用的是FIND_IN_SET存多關(guān)鍵詞建議改成獨立搜索表。5.3 現(xiàn)象并發(fā)出庫時庫存扣成負(fù)數(shù)原因出庫邏輯先SELECT查庫存再UPDATE扣減兩個操作之間沒有鎖。解決把庫存檢查合并進(jìn)UPDATE的WHERE條件如第 3 章示例或者用SELECT ... FOR UPDATE鎖行。前者性能更好后者適合扣減邏輯復(fù)雜的場景。5.4 現(xiàn)象PHP 8 下后臺白屏日志報動態(tài)屬性廢棄原因老源碼用了$this-xxx $yyy動態(tài)賦值PHP 8.2 起標(biāo)記為廢棄部分框架會拋異常。解決降級到 PHP 7.4 或 8.1或者在模型基類加#[\AllowDynamicProperties]注解。生產(chǎn)環(huán)境不建議為了跑老源碼而長期停留在低版本最好逐步重構(gòu)。5.5 現(xiàn)象導(dǎo)入 Excel 藥品檔案中文全部亂碼原因Excel 默認(rèn)編碼是 GBK而數(shù)據(jù)庫是 utf8mb4導(dǎo)入時沒有轉(zhuǎn)碼。解決導(dǎo)入前用iconv(GBK, UTF-8, $value)轉(zhuǎn)碼或者要求模板保存為 CSV UTF-8 格式。我一般直接在導(dǎo)入類里統(tǒng)一轉(zhuǎn)碼避免用戶操作差異。6. 讓這套源碼真正值錢的三個進(jìn)階動作第一個動作是給批號庫存加「鎖定庫存」字段。銷售開單時先鎖庫存付款后再實扣取消訂單則釋放。這樣能解決「下單未付款期間庫存被其他單占用」的問題。實現(xiàn)上在med_stock_batch加locked_quantity可用庫存等于quantity - locked_quantity出庫時校驗可用庫存。第二個動作是把近效期預(yù)警做成定時任務(wù)而不是每次打開首頁才查。用 crontab 每天凌晨跑一次把 90 天內(nèi)到期的批號寫進(jìn)預(yù)警表并推送給庫管。這樣即使沒人登錄后臺預(yù)警也不會漏。# 每天凌晨 2 點執(zhí)行效期掃描 0 2 * * * /usr/bin/php /www/medicine/think expiry:scan /var/log/expiry.log 21第三個動作是給關(guān)鍵操作加審計日志。藥品檔案修改、庫存手動調(diào)整、價格改動這三類操作必須記錄操作人、時間、修改前后值。GSP 檢查時這是必查項臨時補日志根本來不及。源碼如果沒有審計表自己加一張med_audit_log在模型事件里統(tǒng)一寫入。我自己的習(xí)慣是拿到任何一套醫(yī)藥管理系統(tǒng)源碼先跑通「采購入庫 → 銷售出庫 → 批號追溯」這條最小閉環(huán)再談二次開發(fā)。閉環(huán)跑不通后面加再多功能都是空中樓閣。希望幫到你。本文還有配套的精品資源點擊獲取