ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

JavaWeb医院药品管理系统:从数据库设计到核心业务逻辑实现

JavaWeb医院药品管理系统:从数据库设计到核心业务逻辑实现 简介本资源是一套面向Java初学者与高校实训学生的医院药品管理系统实战项目专为JavaWeb课程设计、期末大作业及毕业设计打造覆盖B/S架构开发全流程助力学习者扎实掌握Servlet、JSP、MySQL数据库交互及DAO分层开发等核心技能。压缩包共422个文件含22个核心Java源码如Drugs.java、Users.java、44个编译后class文件、96个HTML前端页面、54个JS交互脚本、10个CSS样式文件、4个JSP动态页、1个完整SQL建库建表脚本以及Jar依赖与XML配置文件整体10.21MB结构清晰、模块分明。已有494人下载学习所有代码经严格调试可直接导入Tomcat运行配套数据库脚本开箱即用。学习者将获得从需求分析、数据库设计、前后端联调到系统部署的完整闭环实践体验特别适合夯实JavaWeb工程化能力与项目文档撰写规范。1. 项目概述与核心价值最近在整理硬盘翻出来一个压箱底的老项目——一个完整的基于JavaWeb的医院药品管理系统。这个项目当年是我带学生实训时的核心案例包含了从源码、数据库脚本到部署文档的全套材料。今天决定把它彻底拆解一遍把里面的设计思路、技术选型、关键实现以及那些只有踩过坑才知道的“暗坑”都分享出来。无论你是正在做课程设计的学生还是需要快速搭建一个类似管理系统的开发者这个项目都能提供一个非常扎实的、可落地的参考模板。这个系统的核心目标很明确为中小型医院或诊所的药房部门提供一个数字化、流程化的药品管理解决方案。它要解决的就是手工台账效率低、易出错、库存不准、效期难追踪这些老大难问题。整个系统围绕药品的“进、销、存、管”核心流程构建涵盖了基础数据管理、采购入库、处方发药、库存盘点、效期预警、统计报表等完整功能模块。技术栈是经典的JavaWeb“三件套”Servlet/JSP JavaBean JDBC后端是MySQL数据库前端是JSP页面配合一些基础的JavaScript和CSS。虽然现在看技术不算新潮但它的业务逻辑完整、架构清晰是理解MVC模式和Web应用开发流程的绝佳范例。2. 系统整体架构与设计思路拆解2.1 技术栈选型背后的考量为什么选择这套看起来有点“复古”的技术栈这得从实训教学和项目实际需求两个角度来说。首先对于JavaWeb入门者而言Servlet/JSP是理解Web请求/响应模型、会话管理、数据持久化等核心概念的基石。跳过它们直接上Spring Boot很容易变成“配置工程师”而不知其所以然。这个项目采用经典的“Model 1”架构JSP JavaBean并逐步向“Model 2”MVC思想靠拢比如我们会把业务逻辑尽量写在JavaBean或独立的Service类中而不是全部堆在JSP里。这样设计既保证了初学者能直观地看到页面如何与后台交互又潜移默化地引入了分层思想。数据库选择MySQL 5.7原因无他免费、普及率高、生态成熟。对于药品管理这种事务性较强的系统InnoDB存储引擎的事务支持ACID特性是必须的。我们会在建表时明确指定存储引擎并针对核心业务表如库存表、出入库记录表设计合理的事务边界确保数据的一致性比如“扣减库存”和“生成发药记录”必须在同一个事务中完成。前端没有用任何框架就是原生的HTML/CSS/JS加上JSP标签。这固然在交互体验上不如Vue/React但好处是依赖极少部署简单并且能将学习者的注意力集中在后端业务逻辑与数据流转上。我们通过一些简单的JavaScript来实现表单验证、日期选择和异步查询早期的Ajax足以满足管理系统的操作需求。2.2 核心业务模块与数据流设计系统的功能模块划分遵循高内聚、低耦合的原则主要分为以下几个核心部分系统管理模块负责用户药师、药房主任、系统管理员的登录、权限分配和角色管理。权限控制采用基于角色的访问控制RBAC思想不同角色看到的功能菜单和操作按钮不同。药品档案管理模块这是系统的基石。维护药品的“身份证”信息包括药品编码唯一、通用名、商品名、规格、剂型、生产厂家、药品分类如抗生素、心脑血管类、库存单位、处方单位、以及最重要的——药品本位码用于追溯。这里的设计关键点是编码规则的制定和分类体系的建立。库存管理模块核心中的核心。包含库存初始化、采购入库、其他入库如调拨、盘盈、处方发药出库、其他出库报损、调拨、库存盘点、实时库存查询等功能。每一个库存变动都必须有对应的单据记录做到账实相符、有迹可循。药品流通与处方管理模块模拟从医生开具电子处方或录入处方到药房审核、配药、发药的完整流程。这里涉及处方明细与库存的实时核对、药品用法用量的初步审核如剂量是否超限。智能预警与报表模块这是体现系统价值的亮点。包括近效期药品预警比如提前3个月、1个月预警、库存上下限预警库存不足或积压、药品停用/禁用状态管理。报表则提供药品流水账、收发存汇总、供应商采购分析等为管理决策提供数据支持。数据流的设计遵循“单据驱动库存”的原则。任何库存数量的变化都必须由一张具有业务含义的单据如“采购入库单”、“处方发药单”来触发。单据审核后系统自动更新对应药品的库存数量并记录库存明细账。这种设计保证了业务逻辑的严谨性和数据的可审计性。3. 数据库设计与关键表结构解析数据库设计是系统的灵魂设计得好后期开发事半功倍设计得烂则举步维艰。本系统采用符合第三范式3NF的设计在保证数据一致性的前提下也兼顾了查询性能。3.1 核心实体关系模型ER解读系统的核心实体包括用户(User)、药品(Drug)、供应商(Supplier)、库存(Inventory)、入库单(StockIn)、出库单(StockOut)、处方(Prescription)。它们之间的关系如下一个药品可以由多个供应商供应一个供应商可以供应多种药品多对多通过采购关系表连接。一个入库单对应一个供应商包含多个入库明细药品和数量。一个处方对应一个患者或模拟患者包含多个处方明细。库存表是药品在药房存放状态的实时快照其数量由所有入库明细和出库明细汇总计算得出。3.2 关键表结构设计与字段含义这里挑几个最核心的表详细说明1. 药品信息表 (tb_drug)这是最重要的基础表。除了常规的ID、名称、规格字段有几个设计点值得注意drug_code(药品编码)业务主键采用“分类码序列号”的规则生成如“KSS0001”抗生素类第1号便于识别和分类统计。barcode(本位码)存储国家药品本位码用于未来可能的扫码枪快速录入或追溯。unit_stock和unit_prescribe(库存单位 vs. 处方单位)例如库存单位是“盒”处方单位是“粒”。系统中需要维护一个换算关系conversion_factor字段在出入库时进行智能换算。这是业务复杂性的一个体现。alert_upper_limit和alert_lower_limit(库存上下限)用于触发库存预警。status(状态)0-正常1-停用2-禁用。停用药品不再允许采购但可销售库存禁用药品则完全不可见。2. 库存表 (tb_inventory)这是一个典型的“流水账”“余额账”设计中的余额账。它记录的是某个药品在某个仓库如果系统支持多仓库的当前实时数量(current_quantity)、锁定数量(locked_quantity如已开处方未发放)、可用数量(available_quantity)。关键点current_quantity不应该直接手动修改。它必须通过入库明细和出库明细的汇总来维护其准确性。我们通常会提供一个update_inventory的存储过程或Service方法在单据审核时调用原子性地更新库存。3. 入库单主表(tb_stock_in)与明细表(tb_stock_in_detail)这是体现关系型数据库优势的典型例子。主表记录单据整体信息单号(order_no)、供应商(supplier_id)、入库类型采购、退货、盘盈等、总金额、制单人、审核人、审核状态、创建时间。 明细表记录具体药品信息关联主表ID(stock_in_id)、药品ID(drug_id)、批号(batch_no)、生产日期(production_date)、有效期至(expiry_date)、入库数量(quantity)、采购单价(purchase_price)、零售单价(retail_price)。注意批号、生产日期、有效期至这三个字段必须放在明细表而不是药品表。因为同一药品不同批次这些信息都不同。这是设计时最容易犯的错误之一把批次信息挂在药品主数据上会导致无法管理同药不同批次的效期。4. 操作日志表 (tb_operation_log)一个容易被忽略但至关重要的表。记录所有关键操作谁(user_id)、在什么时间(operate_time)、操作了哪个模块(module)、执行了什么动作(action)、操作对象的ID(target_id)、操作前的值(old_value)、操作后的值(new_value)、IP地址(ip)。 这个表对于系统审计、问题追踪、数据恢复有不可替代的作用。建议采用AOP面向切面编程思想将日志记录抽象成公共组件。4. 核心功能模块的代码实现与难点剖析4.1 用户登录、会话管理与权限控制系统采用Filter过滤器实现统一的登录验证和权限检查。定义一个AuthFilter在web.xml中配置其拦截所有请求路径/*但排除登录、注销和静态资源如CSS、JS、图片。// 伪代码示例AuthFilter 核心逻辑 public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; HttpSession session request.getSession(false); // 获取现有session不创建新session String uri request.getRequestURI(); // 1. 判断是否为放行路径登录、静态资源等 if (isExcludedUri(uri)) { chain.doFilter(request, response); return; } // 2. 检查session中是否存在用户对象 if (session null || session.getAttribute(LOGIN_USER) null) { // 未登录重定向到登录页并携带原请求地址以便登录后跳回 response.sendRedirect(request.getContextPath() /login.jsp?redirect URLEncoder.encode(uri, UTF-8)); return; } // 3. (可选) 更细粒度的权限检查 User user (User) session.getAttribute(LOGIN_USER); if (!hasPermission(user, uri)) { // 无权限跳转到错误页面或返回403状态码 request.getRequestDispatcher(/error/403.jsp).forward(request, response); return; } // 4. 已登录且有权限放行 chain.doFilter(request, response); }权限控制的实现我们在用户登录后将其角色(Role)和该角色对应的权限菜单列表(ListMenu)查询出来存入Session。在渲染侧边栏菜单时只显示该用户有权限的菜单项。对于按钮级别的控制则在JSP页面中使用JSTL标签进行判断c:if test${sessionScope.LOGIN_USER.role.code PHARMACIST || sessionScope.LOGIN_USER.role.code ADMIN} button onclickauditPrescription(${prescription.id})审核处方/button /c:if4.2 药品入库与库存更新的原子性操作这是业务逻辑最复杂、对数据一致性要求最高的部分。以“采购入库”为例其核心操作序列必须是原子的插入tb_stock_in主表记录状态为“待审核”。循环插入tb_stock_in_detail明细记录。药房主任审核通过。关键步骤遍历明细为每一种药品、每一个批次更新库存并生成库存流水记录。步骤4必须在一个数据库事务中完成。我们使用JDBC的Connection对象进行手动事务管理// 伪代码示例审核入库单更新库存 public boolean auditStockIn(int stockInId, int auditorId) { Connection conn null; try { conn DataSourceUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 1. 更新入库单状态和审核人 String sqlUpdateOrder UPDATE tb_stock_in SET status已审核, auditor_id?, audit_timeNOW() WHERE id? AND status待审核; // ... 执行更新 // 2. 查询该入库单的所有明细 ListStockInDetail details stockInDetailDao.findByStockInId(stockInId, conn); // 3. 遍历明细更新库存 for (StockInDetail detail : details) { // 3.1 检查库存记录是否存在药品批号唯一确定一条库存记录 Inventory inv inventoryDao.findByDrugIdAndBatchNo(detail.getDrugId(), detail.getBatchNo(), conn); if (inv null) { // 新增库存记录 inv new Inventory(); inv.setDrugId(detail.getDrugId()); inv.setBatchNo(detail.getBatchNo()); inv.setProductionDate(detail.getProductionDate()); inv.setExpiryDate(detail.getExpiryDate()); inv.setCurrentQuantity(detail.getQuantity()); inv.setLockedQuantity(0); inventoryDao.insert(inv, conn); } else { // 更新现有库存数量 String sqlUpdateInv UPDATE tb_inventory SET current_quantity current_quantity ? WHERE id?; // ... 执行更新使用乐观锁版本号控制并发 } // 3.2 插入库存流水记录tb_inventory_flow记录此次变动 InventoryFlow flow new InventoryFlow(); flow.setInventoryId(inv.getId()); flow.setChangeQuantity(detail.getQuantity()); flow.setChangeType(采购入库); flow.setRefOrderId(stockInId); flow.setRefOrderType(STOCK_IN); // ... 设置其他字段 inventoryFlowDao.insert(flow, conn); } conn.commit(); // 提交事务 return true; } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return false; } finally { DataSourceUtil.closeConnection(conn); } }实操心得在高并发场景下虽然医院药房并发通常不高上述更新库存的SQL语句可能存在“丢失更新”问题。更稳妥的做法是使用SELECT ... FOR UPDATE悲观锁或者在库存表增加一个version版本号字段使用乐观锁机制。在实训项目中我们通常采用乐观锁在更新语句的WHERE条件中加入AND version #{oldVersion}并在更新成功后递增版本号。4.3 处方发药与库存扣减的逻辑处方发药的逻辑与入库审核类似但方向相反且多了一个“库存锁定”的中间状态。医生开立处方系统生成tb_prescription记录状态“待审核”。药师审核处方。审核时并不直接扣减库存而是先检查库存是否充足如果充足则锁定这部分库存将tb_inventory的locked_quantity增加。处方状态变为“待发药”。患者缴费后药师执行发药操作。此时才真正扣减库存current_quantity减少locked_quantity也减少并生成出库单(tb_stock_out)和库存流水。处方状态变为“已发药”。这种“两阶段”操作先锁定后扣减是保证在发药前库存不被其他处方占用的关键尤其在处方审核和患者缴费之间存在时间差的情况下。4.4 效期预警与库存预警的定时任务实现预警功能不适合在每次查询库存时实时计算那样性能开销大。通常采用定时任务如每天凌晨执行来扫描数据将预警结果写入一张“预警信息表”(tb_alert_message)然后系统首页或专门页面从这张表读取并展示。我们使用Quartz框架或更简单的ScheduledExecutorService来实现定时任务。任务逻辑如下// 伪代码示例效期预警任务 public void checkExpiryAlert() { // 1. 查询所有库存记录计算距离过期的天数 String sql SELECT i.*, DATEDIFF(i.expiry_date, CURDATE()) as days_to_expire FROM tb_inventory i WHERE i.current_quantity 0; // ... 执行查询 for (Inventory inventory : list) { int days inventory.getDaysToExpire(); if (days 0 days 90) { // 例如90天内过期 // 2. 检查是否已存在未处理的同类预警 boolean exists alertMessageDao.existsUnhandledExpiryAlert(inventory.getId()); if (!exists) { // 3. 插入预警信息 AlertMessage msg new AlertMessage(); msg.setType(EXPIRY); msg.setTitle(药品近效期预警); msg.setContent(String.format(药品[%s]批号[%s]将于%d天后过期当前库存%d%s。, inventory.getDrugName(), inventory.getBatchNo(), days, inventory.getCurrentQuantity(), inventory.getUnit())); msg.setRefId(inventory.getId()); msg.setStatus(UNREAD); alertMessageDao.insert(msg); } } else if (days 0) { // 已过期生成更高级别的预警 // ... } } // 库存上下限预警逻辑类似 String sqlStock SELECT d.name, i.current_quantity, d.alert_lower_limit, d.alert_upper_limit FROM tb_inventory i JOIN tb_drug d ON i.drug_id d.id; // ... 遍历如果 current_quantity alert_lower_limit 或 alert_upper_limit则生成预警 }注意事项定时任务一定要做好日志记录和异常捕获避免因为一次任务失败导致后续任务不再执行。同时预警信息表需要有一个“状态”字段如UNREAD, READ, HANDLED并提供给用户标记已读或已处理的功能避免重复提醒。5. 项目部署、配置与常见问题排查5.1 本地开发环境搭建基于IDEA环境准备确保安装JDK 8或11、Apache Tomcat 9.x、MySQL 5.7。建议使用IDEA作为IDE。导入项目将源码解压在IDEA中选择“Open”定位到项目根目录包含src和web目录的文件夹。IDEA通常能自动识别为Java Web项目。配置数据库在MySQL中创建一个新的数据库例如hospital_drug_db字符集设为utf8mb4。执行项目sql/目录下的数据库脚本文件通常是init_database.sql创建所有表结构和初始化基础数据如管理员账号、药品分类。修改数据库连接配置找到项目中的数据库配置文件通常是src目录下的jdbc.properties或dbconfig.properties修改其中的url、username、password为你本地MySQL的信息。jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/hospital_drug_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.passwordyour_password配置Tomcat在IDEA中点击“Add Configuration”添加一个“Tomcat Server Local”配置。在“Deployment”选项卡中添加项目生成的Artifact通常是项目名:war exploded。注意“Application context”可以设置为/这样访问地址就是http://localhost:8080/。启动与测试启动Tomcat在浏览器访问http://localhost:8080/login.jsp。使用初始化脚本中提供的默认管理员账号如admin/123456登录。5.2 生产环境部署要点数据库优化生产环境务必对核心查询表如tb_inventory,tb_stock_in_detail,tb_stock_out_detail建立合适的索引。例如在tb_inventory表的drug_id和batch_no上建立联合索引在tb_stock_in表的create_time和status上建立索引以加速查询。Tomcat配置调整server.xml中的连接器(Connector)参数如maxThreads最大线程数、acceptCount等待队列长度。调整JVM内存参数在catalina.shLinux或catalina.batWindows中设置JAVA_OPTS例如-Xms512m -Xmx1024m。应用安全修改默认密码和弱口令首次部署后立即修改数据库密码和后台管理员密码。防止SQL注入确保项目代码中全部使用PreparedStatement没有字符串拼接SQL。XSS防护对用户输入如药品名称、供应商名称进行HTML转义后再显示在页面上或者使用JSTL的c:out标签它默认会进行转义。会话超时在web.xml中配置session-configsession-timeout30/session-timeout/session-config控制会话有效期。日志与监控配置log4j或logback将日志输出到文件并合理设置日志级别生产环境用INFO或WARN。定期检查日志文件监控错误信息。5.3 常见问题与排查技巧实录在开发和部署过程中你几乎一定会遇到下面这些问题问题1启动Tomcat时报“ClassNotFoundException: com.mysql.jdbc.Driver”或“No suitable driver found”。排查这是最常见的问题。说明MySQL的JDBC驱动包没有正确放入项目的类路径下。解决下载mysql-connector-java-8.0.x.jar版本需与你的MySQL和JDK匹配。对于传统Web项目将JAR包复制到WEB-INF/lib/目录下。如果使用Maven在pom.xml中添加依赖dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version !-- 使用适合的版本 -- /dependency检查驱动类名MySQL 8.0通常使用com.mysql.cj.jdbc.Driver对应的URL需要添加时区参数serverTimezone。问题2页面显示乱码尤其是中文。排查这是“编码三件套”没有统一导致的。解决数据库确保创建数据库和表时使用utf8mb4字符集和utf8mb4_unicode_ci排序规则。JDBC连接在连接URL中指定字符集如characterEncodingutf8注意这里写utf8JDBC驱动会映射到utf8mb4。JSP页面在页面顶部添加% page contentTypetext/html;charsetUTF-8 languagejava %并且meta charsetUTF-8。Servlet/Filter在doGet/doPost方法最开始处设置请求和响应的编码request.setCharacterEncoding(UTF-8); response.setCharacterEncoding(UTF-8);。Tomcat服务器可选但建议修改Tomcat的conf/server.xml在Connector port8080 ...标签中增加URIEncodingUTF-8属性。问题3进行入库、发药等操作时页面提示成功但数据库数据没有变化。排查99%的原因是数据库事务没有提交。解决检查你的数据库操作代码是否在最后执行了connection.commit()。检查是否在操作中捕获了异常并在catch块中执行了connection.rollback()。如果没有回滚之前的操作可能被部分提交导致数据不一致。一个更隐蔽的原因是你的数据库连接可能被设置为“自动提交”模式connection.setAutoCommit(true)但在执行多条相关SQL语句中间出现了异常。最佳实践是对于业务操作始终手动管理事务开始前setAutoCommit(false)成功后commit()异常时rollback()最后在finally块中恢复自动提交或关闭连接。问题4查询药品列表或报表时速度非常慢。排查数据量稍大后缺乏索引的查询就会成为性能瓶颈。解决使用数据库的EXPLAIN命令分析慢查询SQL。例如在MySQL客户端执行EXPLAIN SELECT * FROM tb_stock_in_detail WHERE drug_id 100 AND create_time 2023-01-01;查看输出结果重点关注type列。如果出现ALL全表扫描就需要考虑加索引。为WHERE条件和ORDER BY子句中频繁使用的字段添加索引。例如为tb_stock_in_detail表的drug_id和create_time字段添加复合索引。避坑技巧索引不是越多越好。索引会降低插入、更新、删除的速度因为需要维护索引树。通常只为高频查询条件和高选择性字段建索引。问题5系统运行一段时间后登录页面验证码不显示。排查验证码通常是动态生成的图片依赖于Session。可能的原因有Session超时或被清除了。生成验证码的Servlet路径没有被正确映射或访问。浏览器缓存了旧的、无效的图片URL。解决检查生成验证码的Servlet是否在web.xml中正确配置或者是否使用了WebServlet注解。在验证码图片的img标签的src属性后添加一个随机参数如时间戳来防止浏览器缓存src/captchaServlet?t%System.currentTimeMillis()%。确保服务器端生成验证码后将验证码字符串正确存入Session如session.setAttribute(captcha, generatedCode)并且在验证登录时从同一个Session中取出比对。这个项目虽然技术栈传统但它所蕴含的业务逻辑、数据库设计思想、事务处理机制和问题排查方法是任何后端开发者都必须掌握的内功。通过亲手部署、运行并尝试修改、扩展这个系统你能获得的收获远比只看源码要多得多。建议你在理解现有代码的基础上尝试自己实现一个功能比如“药品拆零管理”即库存以“盒”为单位但发药可以按“粒”发这能极大地锻炼你的业务抽象和编码能力。本文还有配套的精品资源点击获取
返回列表