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