
简介这是一套基于Java Web技术栈的医药管理系统源码面向计算机专业学生、课程设计开发者及需要练手SSM/JSP项目的初学者可帮助快速搭建药品进销存管理场景。系统覆盖药品添加与查看、高级查询、库存管理、类别维护与统计、购买药品、销售管理、进货需求管理及系统管理等后台功能业务链路较为完整。压缩包共162个文件约9.34MB以31个jsp页面、30个java源文件、30个class编译文件、26个jar依赖包及12个xml配置为主另含sql建库脚本、properties配置与少量图片资源结构清晰便于二次开发。开发环境为JDK1.8搭配MySQL数据库导入后即可对照源码理解分层设计与数据库交互逻辑。目前已有2058人学习下载适合作为课程设计、毕业设计或Java Web入门实战的参考项目。1. 医药管理系统源码拆包从 class 文件到 MySQL 落库的完整链路拿到一个只有.class没有.java的医药管理系统源码包第一反应往往是「这玩意儿还能改吗」。我拆过不少这种包结论是能跑、能改、能二次开发但前提是你得先搞清楚它的分层结构和数据库依赖。这个包的核心是一套后台管理功能覆盖药品添加、库存查看、类别统计、购买销售、进货需求、系统管理这几块技术栈是 JDK 1.8 MySQL典型的 Java Web 三层架构。适合两类人一是做 Java 课程设计需要现成业务逻辑参考的二是想拿一套完整 CRUD 统计报表练手 MySQL 建表和 DAO 封装的。它不是什么高并发架构但业务闭环是完整的药品从入库到销售到补货需求这条线能跑通这在教学和快速原型场景里比很多半成品强。2. 反编译与工程还原把 class 还原成可维护的 Java 工程2.1 为什么必须先反编译再谈二次开发源码包里只有SellAction.class、MedicineAction.class、UserDao.class这类编译产物没有.java源文件。这意味着你直接改业务逻辑是改不动的——class 文件是 JVM 字节码不是给人读的。常见做法是用 JD-GUI 或 CFR 做反编译把字节码还原成近似源码。注意反编译出来的代码变量名可能是arg0、var1这种方法体里的泛型和 lambda 也可能变形但业务逻辑和 SQL 调用链是完整的足够你理解它怎么运转。我一般会先反编译BaseAction.class和SupperDao.class因为这两个是父类其他 Action 和 DAO 都继承它们。看懂父类里的连接获取、事务提交、异常回滚逻辑后面每个子类的行为就都能对上号。2.2 反编译操作步骤与工具参数# 用 CFR 反编译整个包输出到 src 目录 java -jar cfr.jar medicine-system.zip --outputdir ./src --caseinsensitivefs true # 如果只想看某个类的逻辑单独反编译 java -jar cfr.jar SellAction.class --outputdir ./singleCFR 的--caseinsensitivefs true在 Windows 上必须加否则大小写敏感的文件系统判断会报错。反编译完成后你会得到一堆.java文件但目录结构是扁平的需要按包名手动归位。我通常按action、dao、form、entity四层重建目录这样后面导入 IDE 才不会一团乱。2.3 工程结构还原与依赖补齐反编译出来的代码不能直接编译缺依赖。从类名判断MedicineForm和Medicine是表单和实体UserDao、SupperDao是数据访问层*Action是控制层。这套命名很像早期 Struts2 或 Servlet JDBC 的写法。你需要补的依赖通常包括依赖项用途常见版本servlet-apiAction 层的 HTTP 处理3.1 或 4.0mysql-connector-java数据库驱动5.1.x 或 8.0.xcommons-dbutils 或手写 JDBCDAO 层查询封装1.7jstl页面标签1.2补齐后先别急着跑把SupperDao里的数据库连接参数找出来通常是硬编码的DriverManager.getConnection或者一个简易连接池。这一步的坑在于反编译后的字符串常量可能被混淆URL 里的会变成amp;需要手动改回来。2.4 验证反编译结果是否可用最直接的验证方式是写一个 main 方法调用UserDao的查询方法看能不能连上 MySQL 并返回结果。如果报ClassNotFoundException说明驱动没引入如果报No suitable driver检查 JDBC URL 前缀是不是jdbc:mysql://。这一步过了说明反编译和依赖补齐是成功的可以进入建库阶段。3. MySQL 建库与 DAO 层适配让药品、类别、销售三张表跑起来3.1 从实体类反推表结构Medicine.class和MedicineForm.class是药品相关的实体字段通常包括药品 ID、名称、类别 ID、单价、库存量、生产日期、保质期。CategoryAction对应类别表字段是类别 ID 和类别名称。SellAction涉及销售记录需要销售 ID、药品 ID、销售数量、销售时间、操作员。反推出来的建表语句大致如下CREATE TABLE medicine ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, category_id INT, price DECIMAL(10,2), stock INT DEFAULT 0, produce_date DATE, expire_date DATE, FOREIGN KEY (category_id) REFERENCES category(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE category ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE sell_record ( id INT PRIMARY KEY AUTO_INCREMENT, medicine_id INT, quantity INT, sell_time DATETIME, operator VARCHAR(50), FOREIGN KEY (medicine_id) REFERENCES medicine(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;字符集用utf8mb4而不是utf8因为药品名称里可能出现生僻字或特殊符号utf8在三字节以上字符会截断。外键约束建议加上虽然有些教学项目为了省事不加但加上之后你的类别统计查询才不会出现孤儿记录。3.2 DAO 层查询适配与参数绑定反编译出来的MedicineDao里大概率是拼接 SQL 字符串的写法这种写法在 JDK 1.8 环境下能跑但有两个问题一是注入风险二是参数类型不匹配时直接抛异常。我一般会把它改成PreparedStatement预编译形式// 改造前字符串拼接容易出错 String sql SELECT * FROM medicine WHERE name LIKE % keyword %; // 改造后预编译参数绑定 String sql SELECT * FROM medicine WHERE name LIKE ?; PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, % keyword %); ResultSet rs ps.executeQuery();setString的索引从 1 开始不是 0这个血泪经验每次带新人都要强调一遍。另外LIKE的通配符%要拼在参数值里不能写在 SQL 里否则预编译失效。3.3 类别统计与库存查询的 SQL 实现类别统计是这套系统的核心功能之一本质是GROUP BY加聚合SELECT c.name AS category_name, COUNT(m.id) AS medicine_count, SUM(m.stock) AS total_stock FROM category c LEFT JOIN medicine m ON c.id m.category_id GROUP BY c.id, c.name;用LEFT JOIN而不是INNER JOIN这样没有药品的类别也会显示数量为 0。库存查询则是带条件的SELECT通常按库存量倒序或按类别筛选。高级查询功能一般支持多条件组合比如名称模糊 类别精确 价格区间这种动态 SQL 在 DAO 层要用StringBuilder拼 WHERE 子句但参数仍然走预编译绑定。3.4 购买与销售的事务处理购买药品涉及两个操作插入销售记录、扣减库存。这两个操作必须在一个事务里否则会出现卖了药但库存没减的情况。反编译代码里如果用的是conn.setAutoCommit(false)加commit()/rollback()那逻辑是对的如果没看到事务控制需要手动补上Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 插入销售记录 sellDao.insert(conn, sellRecord); // 扣减库存 medicineDao.reduceStock(conn, medicineId, quantity); conn.commit(); } catch (Exception e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(true); conn.close(); }注意setAutoCommit(true)要在close之前恢复否则连接回到池里会带着未提交状态下一个请求就翻车了。4. 避坑与常见问题排查反编译包最容易卡住的五个地方4.1 反编译后中文乱码现象反编译出来的 Java 文件里药品名称、类别名称等中文字符串显示为乱码或问号。原因class 文件里的字符串常量是 UTF-8 编码但反编译工具默认用系统字符集读取Windows 中文环境是 GBK导致解码错误。解决CFR 加--encoding utf-8参数重新反编译JD-GUI 在设置里把字符集改成 UTF-8。如果已经反编译完用 IDE 的「重新加载文件编码」功能批量转码。4.2 数据库连接报时区错误现象启动后连接 MySQL 报The server time zone value ?D1ú±ê×?ê±?? is unrecognized。原因MySQL 8.0 驱动要求显式指定时区而反编译出来的 JDBC URL 里没有serverTimezone参数。解决在 URL 后面加?serverTimezoneAsia/ShanghaiuseUnicodetruecharacterEncodingutf8。注意在 XML 配置文件里要写成amp;在 Java 代码里直接写。4.3 Action 层 404 但类已编译现象访问药品列表页面返回 404但控制台没有报错class 文件也确实在WEB-INF/classes下。原因反编译后的 Action 类可能丢失了注解或web.xml里的 servlet 映射没配。早期项目用web.xml配置 Action 映射如果反编译时没还原web.xml请求就找不到入口。解决检查WEB-INF/web.xml是否存在如果缺失根据 Action 类名手动补servlet和servlet-mapping。或者改用注解方式重新注册。4.4 库存扣减出现负数现象购买药品后库存变成负数或者并发购买时库存扣减不正确。原因扣减库存的 SQL 是UPDATE medicine SET stock stock - ? WHERE id ?没有加AND stock ?条件也没有在事务里加行锁。解决改成UPDATE medicine SET stock stock - ? WHERE id ? AND stock ?根据返回的受影响行数判断是否扣减成功。并发场景下在SELECT时加FOR UPDATE行锁。4.5 类别统计数字对不上现象类别统计页面显示的药品数量和实际列表数量不一致。原因统计 SQL 用了COUNT(*)但 JOIN 产生了笛卡尔积或者LEFT JOIN后COUNT统计的是主表行数而不是关联表行数。解决用COUNT(m.id)而不是COUNT(*)确保统计的是关联表的实际记录数。如果有多表 JOIN先用子查询聚合再关联。5. 进阶用法把反编译工程改造成可调试的 Maven 项目反编译出来的工程最大的问题是没法断点调试——你看到的代码和实际运行的字节码有差异行号对不上。我的做法是把它重构成 Maven 项目补上pom.xml然后用 IDEA 的「Attach to Process」或者直接以 Debug 模式启动 Tomcat。这样你改完 DAO 层的 SQL能直接在PreparedStatement那行打断点看参数绑定的值对不对。具体步骤先建一个标准 Maven Web 项目骨架把反编译的src按包名放进src/main/javaweb.xml和 JSP 放进src/main/webapp。pom.xml里加上 servlet-api、mysql-connector、jstl 三个依赖scope 该 provided 的写 provided。然后配 Tomcat 运行启动时如果报ClassNotFoundException看缺哪个类就往 pom 里加对应依赖。验证改造是否成功我一般会走一遍完整业务流添加类别 → 添加药品 → 查看库存 → 购买药品 → 查看销售记录 → 查看类别统计。每一步都打断点看 SQL 执行结果。这个流程走通说明工程还原是完整的后面你想加新功能、改统计维度、接前端框架都有了一个可调试的基座。从那以后我每次拿到只有 class 的源码包都强制先反编译、再建库、再跑通一条完整业务流三步缺一不可。希望帮到你。本文还有配套的精品资源点击获取