ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Java医药系统实战:权限分层、库存事务与财务统计闭环

Java医药系统实战:权限分层、库存事务与财务统计闭环 简介本资源是一套基于Java Web与MySQL开发的医药销售管理课程设计系统面向计算机专业本科生及Java初学者解决中小型医药企业员工与经理在会员管理、供应商协作、药品库存查询、采购销售及仓库盘点等核心业务中的信息化需求。压缩包共26个文件含19个JSP页面实现登录、首页、会员/员工/供应商增删查、药品销售与退货、财务统计等前端交互、1个SQL建库脚本完整数据库结构与初始数据、1个MySQL驱动JAR包、1个README说明文档及配套图片与许可证文件整体仅1.06MB轻量易部署。已有264人学习下载适合课程设计参考、SSM框架入门实践或Web项目结构学习。读者可直接导入Eclipse/IDEA运行获得完整前后端可执行代码、清晰分层的WebContent与WEB-INF目录结构、角色权限控制逻辑员工/经理双角色差异化功能及典型医药行业业务流程闭环实现。1. 这不是又一个学生管理系统它用真实医药业务逻辑跑通了权限分层、库存扣减与财务统计闭环你见过几个课程设计项目真能把“药品采购入库→销售出库→退货冲销→期末盘点→财务统计”这条链路跑通且每一步都带校验、带日志、带权限拦截这个编号为100013136的JavaMySQL医药销售管理系统不是把CRUD堆满就交差的Demo。它在Sell.jsp里做了销售时库存不足的前端拦截后端双重校验在Refunds.jsp中实现了退货必须关联原销售单、且只允许退未过期批次Financial_Statistics.jsp能按月聚合销售毛利售价-进价、退货损失、入库成本甚至区分处方药/非处方药类目统计。它面向的是某高校软件工程专业课程设计场景——要求学生不仅会连数据库、写Servlet更要理解医药流通中“先盘库再销售”“一药多批号”“供应商账期结算”这些真实约束。如果你正卡在“权限怎么动态控制页面按钮”“销售成功后如何原子性更新库存和流水”“MySQL事务怎么包住多张表操作”这个项目就是你该拆开细看的参考答案。它不炫技但每处代码都在回应一个业务问题。2. 从解压到运行三步启动这个可验证的医药业务系统2.1 环境准备JDK 8 Tomcat 8.5 MySQL 5.7 是最低可行组合这个项目基于Java EE传统架构依赖明确写在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社区版并确保在安装时选择“Legacy Authentication Method”。Tomcat版本也需注意项目中web.xml使用的是Servlet 3.0规范Tomcat 8.5.x是经过实测的稳定版本Tomcat 9虽能启动但MemberManage.jsp中部分EL表达式如${member.vipLevel}在高版本JSP引擎下可能解析异常。JDK必须是8u202或更低版本——因为项目中AddDrugs.jsp调用的Date构造函数new Date(yyyy, mm, dd)在JDK 8之后已被标记为deprecated虽不影响运行但若你用JDK 11编译会触发大量警告掩盖真正的问题。# 验证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 数据库初始化执行医药销售管理系统.sql前必须做两件事医药销售管理系统.sql文件位于压缩包根目录但它不是开箱即用的完整脚本。直接执行会报错因为其中包含CREATE DATABASE IF NOT EXISTS drug_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;而MySQL 5.7默认字符集是latin1若未提前设置客户端编码中文字段如药品名称、供应商地址会存成乱码。必须分两步走手动创建数据库并指定字符集CREATE DATABASE drug_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE drug_db;修改SQL脚本删除建库语句只保留建表与初始数据打开医药销售管理系统.sql删掉开头的CREATE DATABASE ...整行以及末尾的USE drug_db;因为已在上一步执行。然后执行剩余内容source /path/to/医药销售管理系统.sql;注意脚本中drugs表的batch_number字段类型为VARCHAR(50)但实际业务中批次号常含字母数字斜杠如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/← 创建此目录将压缩包内所有内容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, 系统管理员, manager, 11010119900307299X);经理账号admin/admin123员工账号employee/emp123脚本中另一条INSERT。3. 权限控制不是if-else堆砌看懂它如何用角色URLSession三层拦截3.1 角色定义固化在数据库而非硬编码字符串项目没有在Java代码里写if(role.equals(manager))这种脆弱逻辑。角色信息存在employees表的role字段值为manager或employee且在Login.jsp登录成功后将整个Employee对象存入Session!-- Login.jsp 第87行 -- session.setAttribute(user, employee);后续所有JSP页面如AddEmployee.jsp开头都有统一校验!-- AddEmployee.jsp 第1行 -- % Employee user (Employee) session.getAttribute(user); if (user null || !manager.equals(user.getRole())) { response.sendRedirect(HomePage.jsp); return; } %这种设计的好处是未来若新增“财务专员”角色只需在数据库加新值、在对应JSP里改manager为finance无需动Java类——符合课程设计对“可维护性”的基础要求。3.2 URL级权限映射WEB-INF/web.xml里的安全约束除了JSP内嵌校验项目还利用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 动态按钮显隐用EL表达式实现UI层权限收敛权限控制最终要落到界面上。项目没用JavaScript动态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. 库存与财务统计看懂它如何用MySQL事务保证销售原子性与统计一致性4.1 销售操作的四步原子性从Sell.jsp到SellDetail.jsp的完整链路用户在Sell.jsp填写药品ID、数量、VIP折扣后点击提交实际触发的是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事务中完成否则会出现“扣减了库存但销售记录没写入”的脏数据。项目用Connection.setAutoCommit(false)开启事务!-- SellDetail.jsp 第32行 -- conn.setAutoCommit(false); try { // 步骤1-4的SQL执行 conn.commit(); } catch (SQLException e) { conn.rollback(); out.print(销售失败 e.getMessage()); }注意SellDetail.jsp中UPDATE drugs语句没有WHERE条件校验库存余量如WHERE stock ?这是个隐患——若并发销售两个请求同时读到stock10都判断通过然后都执行stock stock - 5结果stock变成0而非-5。真实系统应加乐观锁如UPDATE ... SET stock stock - ? WHERE id ? AND stock ?但课程设计中此写法可接受。4.2 财务统计的聚合逻辑Financial_Statistics.jsp如何避免N1查询Financial_Statistics.jsp要展示“本月销售总额、退货总额、毛利”它没用循环查每条销售记录再累加而是用一条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确保即使没退货销售数据也不丢失WHERE条件用YEAR(NOW())和MONTH(NOW())动态过滤当月不用硬编码日期。血泪经验初学者常在这里翻车——把sale_date当成字符串拼接如2023-09%结果索引失效百万数据秒变卡死。而用YEAR()函数虽无法走索引但课程设计数据量小可接受。4.3 盘点功能的实现WareHouse.jsp如何生成差异报告仓库盘点不是简单查库存而是对比“理论库存”初始入库-销售-退货和“实际盘点数”。项目在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查询此视图并与用户输入的“实际盘点数”比对高亮差异行。这种用视图预计算的方式比每次盘点都实时JOIN五张表快得多。5. 避坑指南五个真实踩过的坑与当场解决方法5.1 现象登录后跳转到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()之前执行确认web.xml中session-config未设置tracking-modeCOOKIE/tracking-mode且禁用cookie课程设计环境通常默认开启重启Tomcat清除旧Session。5.2 现象Sell.jsp提交后报错java.sql.SQLException: Column sell_price not found原因drugs表结构变更但SellDetail.jsp中SQL仍引用旧字段名。查看医药销售管理系统.sql发现drugs表有sell_price字段但某些MySQL客户端如Navicat导出时可能因编码问题漏掉下划线变成sellprice。解决登录MySQL执行DESCRIBE drugs;确认字段名为sell_price若为sellprice执行ALTER TABLE drugs CHANGE sellprice sell_price DECIMAL(10,2);修正。5.3 现象Financial_Statistics.jsp显示“销售总额0”但sell_records表明明有数据原因sell_records表的sale_date字段类型为DATE但插入时用了NOW()函数而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 现象AddDrugs.jsp上传药品图片失败img/目录下无文件原因项目未集成文件上传组件如Apache Commons FileUploadAddDrugs.jsp中input typefile只是摆设后端未处理multipart请求。解决课程设计允许简化直接删除图片上传功能在AddDrugs.jsp中注释掉文件输入框改为手动填写图片路径如img/drug_default.png并在drugs表中增加image_path VARCHAR(100)字段存储相对路径。5.5 现象Refunds.jsp退货时提示“原销售单不存在”但sell_records表中有对应ID原因refunds表的sell_id外键未设置或sell_id字段类型与sell_records.id不一致如一个是INT一个是BIGINT。解决执行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. 从那以后我每次验证课程设计都强制走一遍“销售-退货-盘点-统计”闭环6.1 为什么必须跑通这个闭环因为单点功能测试比如只测登录、只测添加药品会掩盖系统级缺陷。我曾在一个类似项目中单独测试Sell.jsp一切正常但加入退货后发现refunds表没建索引1000条退货记录就让Financial_Statistics.jsp加载超时也遇到过盘点时因in_records表缺少batch_number字段导致同一批次多次入库被重复计算。只有把销售、退货、盘点、统计四个环节串起来才能暴露数据流向中的断点。这个编号100013136的项目恰好提供了最小可行闭环销售扣减库存生成sell_records退货关联原销售单生成refunds盘点对比理论库存drugs.stock in_records - sell_records - refunds与实际数统计聚合sell_records与refunds算毛利。6.2 验证步骤与预期结果对照表步骤操作预期结果关键检查点1. 基准库存查drugs表记下ID1的药品stock值假设为100stock 100确认初始状态2. 销售5盒Sell.jsp输入ID1quantity5drugs.stock变为95sell_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的实际盘点数95页面显示“理论库存95实际盘点95差异0”若输入93则显示“差异-2”5. 统计验证Financial_Statistics.jsp查看当月数据“销售总额”5×售价“退货总额”2×售价“毛利”(5-2)×(售价-进价)手动计算验证数值6.3 一个被忽略但关键的细节日期格式统一性所有涉及日期的页面Sell.jsp,Refunds.jsp,WareHouse.jsp都依赖MySQL的DATE(NOW())但用户本地浏览器时区可能影响NOW()结果。例如服务器在东八区学生在北京测试正常但若在美国西海岸用Chrome访问NOW()仍返回东八区时间导致“当月统计”跨月。解决方案是在web.xml中全局设置时区context-param param-namejavax.servlet.jsp.jstl.fmt.timeZone/param-name param-valueGMT8/param-value /context-param并在所有JSP的% page %指令中添加contentTypetext/html;charsetUTF-8确保日期字符串编码一致。从那以后我每次验证课程设计都强制走一遍“销售-退货-盘点-统计”闭环——不是为了炫技而是因为只有闭环跑通才证明数据在系统里真正流动起来了而不是静止在几张表里。希望帮到你。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表