简介:这是一套基于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-api | Action 层的 HTTP 处理 | 3.1 或 4.0 |
| mysql-connector-java | 数据库驱动 | 5.1.x 或 8.0.x |
| commons-dbutils 或手写 JDBC | DAO 层查询封装 | 1.7 |
| jstl | 页面标签 | 1.2 |
补齐后先别急着跑,把SupperDao里的数据库连接参数找出来,通常是硬编码的DriverManager.getConnection或者一个简易连接池。这一步的坑在于:反编译后的字符串常量可能被混淆,URL 里的&会变成&,需要手动改回来。
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) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE category ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 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) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;字符集用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 后面加?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8。注意&在 XML 配置文件里要写成&,在 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/java,web.xml和 JSP 放进src/main/webapp。pom.xml里加上 servlet-api、mysql-connector、jstl 三个依赖,scope 该 provided 的写 provided。然后配 Tomcat 运行,启动时如果报ClassNotFoundException,看缺哪个类就往 pom 里加对应依赖。
验证改造是否成功,我一般会走一遍完整业务流:添加类别 → 添加药品 → 查看库存 → 购买药品 → 查看销售记录 → 查看类别统计。每一步都打断点看 SQL 执行结果。这个流程走通,说明工程还原是完整的,后面你想加新功能、改统计维度、接前端框架,都有了一个可调试的基座。
从那以后我每次拿到只有 class 的源码包,都强制先反编译、再建库、再跑通一条完整业务流,三步缺一不可。希望帮到你。
本文还有配套的精品资源,点击获取