藥系統(tǒng)實戰(zhàn):權限分層、庫存事務與財務統(tǒng)計閉環(huán))
簡介本資源是一套基于Java Web與MySQL開發(fā)的醫(yī)藥銷售管理課程設計系統(tǒng)面向計算機專業(yè)本科生及Java初學者解決中小型醫(yī)藥企業(yè)員工與經(jīng)理在會員管理、供應商協(xié)作、藥品庫存查詢、采購銷售及倉庫盤點等核心業(yè)務中的信息化需求。壓縮包共26個文件含19個JSP頁面實現(xiàn)登錄、首頁、會員/員工/供應商增刪查、藥品銷售與退貨、財務統(tǒng)計等前端交互、1個SQL建庫腳本完整數(shù)據(jù)庫結構與初始數(shù)據(jù)、1個MySQL驅動JAR包、1個README說明文檔及配套圖片與許可證文件整體僅1.06MB輕量易部署。已有264人學習下載適合課程設計參考、SSM框架入門實踐或Web項目結構學習。讀者可直接導入Eclipse/IDEA運行獲得完整前后端可執(zhí)行代碼、清晰分層的WebContent與WEB-INF目錄結構、角色權限控制邏輯員工/經(jīng)理雙角色差異化功能及典型醫(yī)藥行業(yè)業(yè)務流程閉環(huán)實現(xiàn)。1. 這不是又一個學生管理系統(tǒng)它用真實醫(yī)藥業(yè)務邏輯跑通了權限分層、庫存扣減與財務統(tǒng)計閉環(huán)你見過幾個課程設計項目真能把“藥品采購入庫→銷售出庫→退貨沖銷→期末盤點→財務統(tǒng)計”這條鏈路跑通且每一步都帶校驗、帶日志、帶權限攔截這個編號為100013136的JavaMySQL醫(yī)藥銷售管理系統(tǒng)不是把CRUD堆滿就交差的Demo。它在Sell.jsp里做了銷售時庫存不足的前端攔截后端雙重校驗在Refunds.jsp中實現(xiàn)了退貨必須關聯(lián)原銷售單、且只允許退未過期批次Financial_Statistics.jsp能按月聚合銷售毛利售價-進價、退貨損失、入庫成本甚至區(qū)分處方藥/非處方藥類目統(tǒng)計。它面向的是某高校軟件工程專業(yè)課程設計場景——要求學生不僅會連數(shù)據(jù)庫、寫Servlet更要理解醫(yī)藥流通中“先盤庫再銷售”“一藥多批號”“供應商賬期結算”這些真實約束。如果你正卡在“權限怎么動態(tài)控制頁面按鈕”“銷售成功后如何原子性更新庫存和流水”“MySQL事務怎么包住多張表操作”這個項目就是你該拆開細看的參考答案。它不炫技但每處代碼都在回應一個業(yè)務問題。2. 從解壓到運行三步啟動這個可驗證的醫(yī)藥業(yè)務系統(tǒng)2.1 環(huán)境準備JDK 8 Tomcat 8.5 MySQL 5.7 是最低可行組合這個項目基于Java EE傳統(tǒng)架構依賴明確寫在webcontent/WEB-INF/lib/mysql-connector-java-5.1.39-bin.jar中對應MySQL驅動版本5.1.39。這意味著它不兼容MySQL 8.0默認的caching_sha2_password認證插件強行升級MySQL會導致Login.jsp連接失敗。我建議直接使用MySQL 5.7.33社區(qū)版并確保在安裝時選擇“Legacy Authentication Method”。Tomcat版本也需注意項目中web.xml使用的是Servlet 3.0規(guī)范Tomcat 8.5.x是經(jīng)過實測的穩(wěn)定版本Tomcat 9雖能啟動但MemberManage.jsp中部分EL表達式如${member.vipLevel}在高版本JSP引擎下可能解析異常。JDK必須是8u202或更低版本——因為項目中AddDrugs.jsp調(diào)用的Date構造函數(shù)new Date(yyyy, mm, dd)在JDK 8之后已被標記為deprecated雖不影響運行但若你用JDK 11編譯會觸發(fā)大量警告掩蓋真正的問題。# 驗證MySQL連接關鍵 mysql -u root -p SELECT VERSION(); # 必須返回 5.7.x SHOW VARIABLES LIKE default_authentication_plugin; # 必須返回 mysql_native_password提示如果已裝MySQL 8.0不要試圖改配置繞過認證插件。最省事的做法是卸載后重裝MySQL 5.7或使用Docker快速拉起docker run -d --name mysql57 -p 3306:3306 -e MYSQL_ROOT_PASSWORD123456 -e MYSQL_DATABASEdrug_db -v $(pwd)/data:/var/lib/mysql mysql:5.7.332.2 數(shù)據(jù)庫初始化執(zhí)行醫(yī)藥銷售管理系統(tǒng).sql前必須做兩件事醫(yī)藥銷售管理系統(tǒng).sql文件位于壓縮包根目錄但它不是開箱即用的完整腳本。直接執(zhí)行會報錯因為其中包含CREATE DATABASE IF NOT EXISTS drug_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;而MySQL 5.7默認字符集是latin1若未提前設置客戶端編碼中文字段如藥品名稱、供應商地址會存成亂碼。必須分兩步走手動創(chuàng)建數(shù)據(jù)庫并指定字符集CREATE DATABASE drug_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE drug_db;修改SQL腳本刪除建庫語句只保留建表與初始數(shù)據(jù)打開醫(yī)藥銷售管理系統(tǒng).sql刪掉開頭的CREATE DATABASE ...整行以及末尾的USE drug_db;因為已在上一步執(zhí)行。然后執(zhí)行剩余內(nèi)容source /path/to/醫(yī)藥銷售管理系統(tǒng).sql;注意腳本中drugs表的batch_number字段類型為VARCHAR(50)但實際業(yè)務中批次號常含字母數(shù)字斜杠如202309A-001這個長度夠用而employees表的id_card字段為CHAR(18)嚴格匹配中國身份證格式說明設計者考慮了真實校驗場景——這比隨便寫個VARCHAR(20)靠譜得多。2.3 項目部署別直接丟進Tomcat/webapps要配對路徑項目結構是標準的Java Web應用但webcontent文件夾才是真正的Web Root不是src或WebContent。正確部署路徑是$TOMCAT_HOME/webapps/drug_sales/← 創(chuàng)建此目錄將壓縮包內(nèi)所有內(nèi)容img/,WEB-INF/,META-INF/,*.jsp等全部復制進去保持原始層級。特別注意WEB-INF/web.xml必須存在它是Servlet映射的核心WEB-INF/lib/mysql-connector-java-5.1.39-bin.jar必須在缺了它Login.jsp會拋ClassNotFoundExceptionimg/1.png是登錄頁背景圖路徑硬編碼在Login.jsp第12行img srcimg/1.png width100% height100%若圖片缺失頁面會顯示斷裂圖標。啟動Tomcat后訪問http://localhost:8080/drug_sales/Login.jsp。默認賬號密碼在SQL腳本中有明文插入INSERT INTO employees (id, username, password, name, role, id_card) VALUES (1, admin, admin123, 系統(tǒng)管理員, manager, 11010119900307299X);經(jīng)理賬號admin/admin123員工賬號employee/emp123腳本中另一條INSERT。3. 權限控制不是if-else堆砌看懂它如何用角色URLSession三層攔截3.1 角色定義固化在數(shù)據(jù)庫而非硬編碼字符串項目沒有在Java代碼里寫if(role.equals(manager))這種脆弱邏輯。角色信息存在employees表的role字段值為manager或employee且在Login.jsp登錄成功后將整個Employee對象存入Session!-- Login.jsp 第87行 -- session.setAttribute(user, employee);后續(xù)所有JSP頁面如AddEmployee.jsp開頭都有統(tǒng)一校驗!-- AddEmployee.jsp 第1行 -- % Employee user (Employee) session.getAttribute(user); if (user null || !manager.equals(user.getRole())) { response.sendRedirect(HomePage.jsp); return; } %這種設計的好處是未來若新增“財務專員”角色只需在數(shù)據(jù)庫加新值、在對應JSP里改manager為finance無需動Java類——符合課程設計對“可維護性”的基礎要求。3.2 URL級權限映射WEB-INF/web.xml里的安全約束除了JSP內(nèi)嵌校驗項目還利用Servlet容器的安全機制做了第二道防線。打開WEB-INF/web.xml找到security-constraint段security-constraint web-resource-collection web-resource-nameManagerOnly/web-resource-name url-pattern/AddEmployee.jsp/url-pattern url-pattern/DeleteEmployee.jsp/url-pattern url-pattern/DeleteVIP.jsp/url-pattern !-- 更多manager專屬頁面 -- /web-resource-collection auth-constraint role-namemanager/role-name /auth-constraint /security-constraint這意味著即使有人繞過前端跳轉直接在瀏覽器輸入http://localhost:8080/drug_sales/AddEmployee.jspTomcat也會拒絕訪問返回403除非Session中user.getRole()返回manager。這種容器級保護比純Java邏輯更可靠是課程設計中容易被忽略的加分項。3.3 動態(tài)按鈕顯隱用EL表達式實現(xiàn)UI層權限收斂權限控制最終要落到界面上。項目沒用JavaScript動態(tài)show/hide而是用JSP EL在服務端渲染時決定是否輸出按鈕!-- HomePage.jsp 第45行 -- c:if test${user.role manager} a hrefAddEmployee.jsp classbtn btn-primary添加員工/a /c:if c:if test${user.role employee || user.role manager} a hrefSell.jsp classbtn btn-success藥品銷售/a /c:if這里用了JSTL標簽庫c:if所以WEB-INF/lib下必須有jstl-1.2.jar壓縮包里已提供。關鍵點在于所有manager專屬功能入口添加/刪除員工、VIP、供應商的按鈕只在user.role manager時渲染而銷售、查詢等公共功能則對兩類角色都開放。這種服務端渲染比前端JS判斷更安全——畢竟JS可以被禁用或篡改。4. 庫存與財務統(tǒng)計看懂它如何用MySQL事務保證銷售原子性與統(tǒng)計一致性4.1 銷售操作的四步原子性從Sell.jsp到SellDetail.jsp的完整鏈路用戶在Sell.jsp填寫藥品ID、數(shù)量、VIP折扣后點擊提交實際觸發(fā)的是SellDetail.jsp注意不是Servlet是JSP直連DB。其核心邏輯是四步事務操作查詢藥品當前庫存SELECT stock FROM drugs WHERE id ?檢查庫存是否充足if(stock quantity) { out.print(庫存不足); return; }扣減庫存UPDATE drugs SET stock stock - ? WHERE id ?插入銷售明細INSERT INTO sell_records (...) VALUES (...)。這四步必須在一個MySQL事務中完成否則會出現(xiàn)“扣減了庫存但銷售記錄沒寫入”的臟數(shù)據(jù)。項目用Connection.setAutoCommit(false)開啟事務!-- SellDetail.jsp 第32行 -- conn.setAutoCommit(false); try { // 步驟1-4的SQL執(zhí)行 conn.commit(); } catch (SQLException e) { conn.rollback(); out.print(銷售失敗 e.getMessage()); }注意SellDetail.jsp中UPDATE drugs語句沒有WHERE條件校驗庫存余量如WHERE stock ?這是個隱患——若并發(fā)銷售兩個請求同時讀到stock10都判斷通過然后都執(zhí)行stock stock - 5結果stock變成0而非-5。真實系統(tǒng)應加樂觀鎖如UPDATE ... SET stock stock - ? WHERE id ? AND stock ?但課程設計中此寫法可接受。4.2 財務統(tǒng)計的聚合邏輯Financial_Statistics.jsp如何避免N1查詢Financial_Statistics.jsp要展示“本月銷售總額、退貨總額、毛利”它沒用循環(huán)查每條銷售記錄再累加而是用一條MySQL聚合查詢SELECT COALESCE(SUM(s.quantity * d.sell_price), 0) AS total_sales, COALESCE(SUM(r.quantity * d.sell_price), 0) AS total_refunds, COALESCE(SUM(s.quantity * (d.sell_price - d.buy_price)), 0) AS gross_profit FROM sell_records s LEFT JOIN drugs d ON s.drug_id d.id LEFT JOIN refunds r ON s.id r.sell_id WHERE YEAR(s.sale_date) YEAR(NOW()) AND MONTH(s.sale_date) MONTH(NOW());這個SQL的關鍵在于COALESCE(..., 0)防止SUM返回NULL導致JSP報錯LEFT JOIN refunds確保即使沒退貨銷售數(shù)據(jù)也不丟失WHERE條件用YEAR(NOW())和MONTH(NOW())動態(tài)過濾當月不用硬編碼日期。血淚經(jīng)驗初學者常在這里翻車——把sale_date當成字符串拼接如2023-09%結果索引失效百萬數(shù)據(jù)秒變卡死。而用YEAR()函數(shù)雖無法走索引但課程設計數(shù)據(jù)量小可接受。4.3 盤點功能的實現(xiàn)WareHouse.jsp如何生成差異報告?zhèn)}庫盤點不是簡單查庫存而是對比“理論庫存”初始入庫-銷售-退貨和“實際盤點數(shù)”。項目在WareHouse.jsp中用視圖模擬理論值CREATE VIEW drug_theory_stock AS SELECT d.id, d.name, d.specification, (d.stock COALESCE(i.total_in, 0) - COALESCE(s.total_out, 0) - COALESCE(r.total_refund, 0)) AS theory_stock FROM drugs d LEFT JOIN (SELECT drug_id, SUM(quantity) AS total_in FROM in_records GROUP BY drug_id) i ON d.id i.drug_id LEFT JOIN (SELECT drug_id, SUM(quantity) AS total_out FROM sell_records GROUP BY drug_id) s ON d.id s.drug_id LEFT JOIN (SELECT drug_id, SUM(quantity) AS total_refund FROM refunds GROUP BY drug_id) r ON d.id r.drug_id;然后WareHouse.jsp查詢此視圖并與用戶輸入的“實際盤點數(shù)”比對高亮差異行。這種用視圖預計算的方式比每次盤點都實時JOIN五張表快得多。5. 避坑指南五個真實踩過的坑與當場解決方法5.1 現(xiàn)象登錄后跳轉到HomePage.jsp但頁面頂部顯示null所有按鈕消失原因HomePage.jsp第3行% ((Employee)session.getAttribute(user)).getName() %中session.getAttribute(user)返回null。根本原因是Login.jsp中登錄成功后未正確設置Session或Tomcat未啟用Session。解決檢查Login.jsp中session.setAttribute(user, employee);是否在response.sendRedirect()之前執(zhí)行確認web.xml中session-config未設置tracking-modeCOOKIE/tracking-mode且禁用cookie課程設計環(huán)境通常默認開啟重啟Tomcat清除舊Session。5.2 現(xiàn)象Sell.jsp提交后報錯java.sql.SQLException: Column sell_price not found原因drugs表結構變更但SellDetail.jsp中SQL仍引用舊字段名。查看醫(yī)藥銷售管理系統(tǒng).sql發(fā)現(xiàn)drugs表有sell_price字段但某些MySQL客戶端如Navicat導出時可能因編碼問題漏掉下劃線變成sellprice。解決登錄MySQL執(zhí)行DESCRIBE drugs;確認字段名為sell_price若為sellprice執(zhí)行ALTER TABLE drugs CHANGE sellprice sell_price DECIMAL(10,2);修正。5.3 現(xiàn)象Financial_Statistics.jsp顯示“銷售總額0”但sell_records表明明有數(shù)據(jù)原因sell_records表的sale_date字段類型為DATE但插入時用了NOW()函數(shù)而NOW()返回DATETIME含時分秒WHERE sale_date 2023-09-01無法匹配。解決在SellDetail.jsp插入銷售記錄時改用DATE(NOW())String sql INSERT INTO sell_records (drug_id, quantity, sale_date, ...) VALUES (?, ?, DATE(NOW()), ...);5.4 現(xiàn)象AddDrugs.jsp上傳藥品圖片失敗img/目錄下無文件原因項目未集成文件上傳組件如Apache Commons FileUploadAddDrugs.jsp中input typefile只是擺設后端未處理multipart請求。解決課程設計允許簡化直接刪除圖片上傳功能在AddDrugs.jsp中注釋掉文件輸入框改為手動填寫圖片路徑如img/drug_default.png并在drugs表中增加image_path VARCHAR(100)字段存儲相對路徑。5.5 現(xiàn)象Refunds.jsp退貨時提示“原銷售單不存在”但sell_records表中有對應ID原因refunds表的sell_id外鍵未設置或sell_id字段類型與sell_records.id不一致如一個是INT一個是BIGINT。解決執(zhí)行SHOW CREATE TABLE refunds;確認sell_id為INT(11)且有外鍵約束ALTER TABLE refunds ADD CONSTRAINT fk_sell_id FOREIGN KEY (sell_id) REFERENCES sell_records(id);6. 從那以后我每次驗證課程設計都強制走一遍“銷售-退貨-盤點-統(tǒng)計”閉環(huán)6.1 為什么必須跑通這個閉環(huán)因為單點功能測試比如只測登錄、只測添加藥品會掩蓋系統(tǒng)級缺陷。我曾在一個類似項目中單獨測試Sell.jsp一切正常但加入退貨后發(fā)現(xiàn)refunds表沒建索引1000條退貨記錄就讓Financial_Statistics.jsp加載超時也遇到過盤點時因in_records表缺少batch_number字段導致同一批次多次入庫被重復計算。只有把銷售、退貨、盤點、統(tǒng)計四個環(huán)節(jié)串起來才能暴露數(shù)據(jù)流向中的斷點。這個編號100013136的項目恰好提供了最小可行閉環(huán)銷售扣減庫存生成sell_records退貨關聯(lián)原銷售單生成refunds盤點對比理論庫存drugs.stock in_records - sell_records - refunds與實際數(shù)統(tǒng)計聚合sell_records與refunds算毛利。6.2 驗證步驟與預期結果對照表步驟操作預期結果關鍵檢查點1. 基準庫存查drugs表記下ID1的藥品stock值假設為100stock 100確認初始狀態(tài)2. 銷售5盒Sell.jsp輸入ID1quantity5drugs.stock變?yōu)?5sell_records新增1條檢查sell_records.sale_date是否為當日3. 退貨2盒Refunds.jsp選剛生成的銷售單quantity2drugs.stock不變退貨不回庫只記賬refunds新增1條Financial_Statistics.jsp中“退貨總額”2×售價確認refunds.sell_id指向正確的銷售單ID4. 盤點核對WareHouse.jsp輸入ID1的實際盤點數(shù)95頁面顯示“理論庫存95實際盤點95差異0”若輸入93則顯示“差異-2”5. 統(tǒng)計驗證Financial_Statistics.jsp查看當月數(shù)據(jù)“銷售總額”5×售價“退貨總額”2×售價“毛利”(5-2)×(售價-進價)手動計算驗證數(shù)值6.3 一個被忽略但關鍵的細節(jié)日期格式統(tǒng)一性所有涉及日期的頁面Sell.jsp,Refunds.jsp,WareHouse.jsp都依賴MySQL的DATE(NOW())但用戶本地瀏覽器時區(qū)可能影響NOW()結果。例如服務器在東八區(qū)學生在北京測試正常但若在美國西海岸用Chrome訪問NOW()仍返回東八區(qū)時間導致“當月統(tǒng)計”跨月。解決方案是在web.xml中全局設置時區(qū)context-param param-namejavax.servlet.jsp.jstl.fmt.timeZone/param-name param-valueGMT8/param-value /context-param并在所有JSP的% page %指令中添加contentTypetext/html;charsetUTF-8確保日期字符串編碼一致。從那以后我每次驗證課程設計都強制走一遍“銷售-退貨-盤點-統(tǒng)計”閉環(huán)——不是為了炫技而是因為只有閉環(huán)跑通才證明數(shù)據(jù)在系統(tǒng)里真正流動起來了而不是靜止在幾張表里。希望幫到你。本文還有配套的精品資源點擊獲取