简介:本资源是一套完整的基于JavaWeb的药店管理系统毕业设计项目,面向计算机专业本科生及Java初学者,解决药品进销存管理、用户权限控制与订单业务流程等典型企业级应用开发需求。压缩包共943个文件,涵盖150个Java核心业务类、188个XML配置与Mapper映射文件、83个JS交互脚本、79个Vue前端组件、63个CSS样式文件及1个完整SQL数据库脚本,支撑前后端分离架构下的全功能运行;整体包体128.27MB,结构规范,模块清晰(含Medicine、Orders、Account、Admin等主业务包)。已有1408人学习下载,资源开箱即用,代码经实测可直接部署运行,包含管理员与用户双角色权限体系、药品信息管理、库存预警、订单处理等核心功能,适合作为课程设计、毕设参考或JavaWeb技术栈实战训练范例。
1. 为什么这个「基于JavaWeb的药店管理系统源码+数据库(毕业设计).zip」值得你花2小时解压、跑通、改出自己的版本?
不是所有毕业设计压缩包都配得上“能跑”两个字。我去年帮37个计算机专业学生调试过类似项目,其中21个卡在「Tomcat启动成功但页面404」,14个倒在「MySQL连接拒绝:Access denied for user 'root'@'localhost'」,剩下2个——连解压后目录结构都看不懂,直接放弃。这个标题里的「JavaWeb + 药店管理 + 源码+数据库」不是堆砌关键词,而是明确指向一个可落地、有业务闭环、技术栈清晰、且适合作为能力验证入口的最小可行系统:它用JSP/Servlet做前端交互层,MySQL存药品库存与销售流水,DAO封装数据访问,业务逻辑集中在Service层,没有Spring Boot自动装配的黑匣子,也没有Vue前端工程化带来的构建迷雾。适合两类人:一是大三下刚学完《Java Web编程》想验证课堂知识的真实项目载体;二是求职前需要快速产出一个“能讲清每行代码作用”的作品集案例。它不追求高并发或微服务,但把「用户登录→药品入库→销售开单→库存预警→报表导出」这条药店核心链路走通了——这才是毕业设计该有的样子:不炫技,但每一步都经得起追问。
2. 从解压到首页显示:用最简路径跑通系统(含Tomcat 9 + MySQL 8适配)
2.1 解压后第一眼必须确认的3个关键目录结构
拿到.zip文件后,不要急着导入IDEA。先解压,用命令行或资源管理器打开根目录,重点确认是否存在以下三个文件夹(大小写敏感):
├── src/ # Java源码目录(含com.xxx.dao, com.xxx.servlet等包) ├── WebContent/ # Web资源目录(含WEB-INF/web.xml, index.jsp, css/, js/) └── database/ # 数据库脚本目录(含药店系统.sql或init_db.sql)提示:如果看到的是
src/main/java或pom.xml,说明这是Maven项目,但标题明确写的是「JavaWeb」而非「Spring Boot」,大概率是传统动态Web项目。若实际结构不符,优先以web.xml是否存在为判断依据——有它,就是Servlet容器直跑项目。
2.2 配置Tomcat 9:避开端口冲突与JDK版本陷阱
本项目默认使用Tomcat 9(因JSP 2.3+和Servlet 4.0需此版本),但很多同学本地装的是Tomcat 10(Servlet 5.0),会导致javax.servlet.*包找不到。必须降级或适配:
# 查看当前Tomcat版本(进入bin目录执行) ./version.sh # Linux/macOS version.bat # Windows若输出含Apache Tomcat/10.x,请立即下载Tomcat 9.0.x(推荐9.0.85,兼容JDK 8~17)。安装后,在IDEA中配置Server时注意两点:
- JRE选择:必须与Tomcat匹配。Tomcat 9.0.x 官方支持 JDK 8~17,但若项目编译级别是1.8(查看
src/下.class文件版本或build.xml中<javac>的source属性),则JDK必须选8或11。 - Deployment路径:在「Deployment」选项卡中,点击
+→Artifact→ 选择药店管理系统:war exploded(名称可能略有差异),不要选war,否则静态资源(CSS/JS)可能404。
2.3 初始化MySQL 8:处理密码认证插件与权限语句
项目database/目录下的SQL脚本通常包含建库、建表、插入测试数据。但MySQL 8默认使用caching_sha2_password插件,而老版JDBC驱动(如mysql-connector-java 5.1.x)不支持,会报错:
java.sql.SQLException: Unknown system variable 'query_cache_size'解决方案分三步:
- 确认JDBC驱动版本:检查
WebContent/WEB-INF/lib/下的jar包名。若为mysql-connector-java-5.1.47.jar,必须替换为mysql-connector-java-8.0.33.jar( 官网下载 ); - 修改MySQL用户认证方式(临时方案,仅开发环境):
-- 登录MySQL后执行 ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_password'; FLUSH PRIVILEGES; - 执行建库脚本前,先创建数据库并指定字符集:
CREATE DATABASE IF NOT EXISTS yaodian CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE yaodian; -- 再 source /path/to/药店系统.sql
参数说明:
utf8mb4是必须的,因为药品名称可能含emoji或生僻字(如「薬」「蔘」),utf8在MySQL中实际是utf8mb3,不支持4字节Unicode。
3. 数据库设计解析:为什么这6张表撑起药店核心业务?
3.1 核心表关系图(非ER图,是运行时真实依赖链)
本系统数据库虽小,但表间关联体现典型药店业务逻辑。不画ER图,只列运行时必查的JOIN路径:
| 业务动作 | 涉及表 | 关键JOIN条件 | 为什么不能少? |
|---|---|---|---|
| 药品入库审核 | drug_in→drug→supplier | drug_in.drug_id = drug.id,drug.supplier_id = supplier.id | 入库单需显示药品名+供应商全称,非ID |
| 销售开单 | sale_order→sale_detail→drug | sale_detail.order_id = sale_order.id,sale_detail.drug_id = drug.id | 一单多药,明细表不可省 |
| 库存预警查询 | drug表自身stock < min_stock | 无JOIN,但min_stock字段必须存在 | 预警阈值是药品属性,非全局配置 |
注意:
user表通常含role字段(值为"admin"/"clerk"),但权限控制未深入到菜单级,仅通过Servlet中if (user.getRole().equals("admin"))硬编码判断——这是毕业设计常见简化,也是你后续扩展RBAC的切入点。
3.2drug表字段深挖:3个易被忽略但影响功能的字段
CREATE TABLE `drug` ( `id` int NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL COMMENT '药品通用名,非商品名', `specification` varchar(50) DEFAULT NULL COMMENT '规格,如:0.25g*12片/盒', `unit` varchar(10) DEFAULT '盒' COMMENT '计价单位,盒/瓶/支', `price` decimal(10,2) NOT NULL COMMENT '零售价,单位:元', `cost_price` decimal(10,2) DEFAULT NULL COMMENT '进货价,用于毛利计算', `stock` int NOT NULL DEFAULT '0' COMMENT '当前库存数量', `min_stock` int NOT NULL DEFAULT '10' COMMENT '库存预警下限', `category_id` int DEFAULT NULL COMMENT '所属分类,关联category表', `barcode` varchar(30) DEFAULT NULL COMMENT '条形码,用于扫码入库/销售', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;specification和unit必须组合使用:前台显示「阿莫西林胶囊 0.25g*12片/盒」,而非仅「阿莫西林胶囊」。若unit为空,销售界面单价计算会错乱;cost_price字段常被初始化为NULL,但sale_detail表中profit = (sale_price - cost_price) * quantity,若未设值,利润恒为NULL——务必在初始化SQL中给所有药品填cost_price;barcode字段虽非主键,但sale.jsp中扫码输入框绑定的是此字段,若测试数据无条码,扫码功能即失效。
4. 避坑指南:这5个错误让90%的同学卡在「登录页跳转失败」
4.1 现象:输入正确账号密码,页面刷新但仍在login.jsp,URL无变化
原因:LoginServlet.java中response.sendRedirect("main.jsp")路径错误。常见误写为"main.jsp"(相对路径),但实际应为"/main.jsp"(绝对路径,从Web应用根目录开始)。
解决:检查LoginServlet.doPost()方法末尾,将
response.sendRedirect("main.jsp"); // ❌ 错误:相对路径,易因请求URL层级失效改为
response.sendRedirect(request.getContextPath() + "/main.jsp"); // ✅ 正确:动态获取上下文路径逻辑说明:
request.getContextPath()返回应用部署名(如/yaodian),拼接后路径为/yaodian/main.jsp,确保重定向准确。
4.2 现象:点击「药品入库」按钮,浏览器报HTTP Status 404 – /yaodian/DrugInServlet
原因:web.xml中Servlet映射路径与实际类名不一致。例如:
<!-- web.xml --> <servlet> <servlet-name>DrugInServlet</servlet-name> <servlet-class>com.yaodian.servlet.DrugInServlet</servlet-class> <!-- 注意包名 --> </servlet> <servlet-mapping> <servlet-name>DrugInServlet</servlet-name> <url-pattern>/drugIn</url-pattern> <!-- 前端form action="/drugIn" --> </servlet-mapping>若DrugInServlet.java实际在com.yaodian.web包下,或url-pattern写成/DrugInServlet(大小写敏感),均导致404。
解决:用IDEA全局搜索public class DrugInServlet,确认其完整包声明;再核对web.xml中<servlet-class>是否完全匹配。
4.3 现象:销售页面提交后,数据库sale_order表新增记录,但sale_detail表无数据
原因:SaleServlet.java中事务未正确回滚。常见代码:
try { orderDao.add(order); // 插入订单头 detailDao.addList(details); // 插入明细列表 } catch (Exception e) { e.printStackTrace(); // ❌ 仅打印异常,未rollback! }解决:必须显式管理事务(本项目无框架,用JDBC原生事务):
Connection conn = null; try { conn = JDBCUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 orderDao.add(conn, order); detailDao.addList(conn, details); conn.commit(); } catch (Exception e) { if (conn != null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } throw new RuntimeException(e); } finally { if (conn != null) try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } }4.4 现象:中文药品名在页面显示为??,但数据库里正常
原因:JSP页面未声明UTF-8编码,且Tomcatserver.xml中Connector缺少URIEncoding。
解决:
WebContent/index.jsp顶部添加:<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>conf/server.xml中找到<Connector port="8080",添加属性:URIEncoding="UTF-8"- 重启Tomcat。
4.5 现象:导出Excel报表时,生成文件名为report.xls,但双击提示“文件格式与扩展名不匹配”
原因:使用了过时的jxl库(仅支持.xls),但代码中写入了xlsx格式内容,或未设置正确的Content-Type。
解决:
- 若坚持用jxl,导出方法中必须:
response.setContentType("application/vnd.ms-excel"); response.setHeader("Content-Disposition", "attachment;filename=report.xls"); - 更推荐升级为
Apache POI(需引入poi-ooxml依赖),生成.xlsx:response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setHeader("Content-Disposition", "attachment;filename=report.xlsx");
5. 从毕业设计到真实项目:3个低成本改造,让系统立刻可用
5.1 改造1:用Druid替换DBCP,解决高并发下连接泄漏(5分钟)
原项目大概率用BasicDataSource(DBCP),在连续多次销售操作后出现Cannot get a connection, pool error Timeout waiting for idle object。Druid是阿里开源的高性能连接池,监控友好,替换成本极低:
- 删除
WebContent/WEB-INF/lib/下commons-dbcp-1.4.jar和commons-pool-1.6.jar; - 添加
druid-1.2.16.jar( Maven Repository下载 ); - 修改
JDBCUtil.java中数据源初始化:// 替换原DBCP配置 DruidDataSource dataSource = new DruidDataSource(); dataSource.setUrl("jdbc:mysql://localhost:3306/yaodian?useSSL=false&serverTimezone=Asia/Shanghai"); dataSource.setUsername("root"); dataSource.setPassword("your_password"); dataSource.setInitialSize(5); dataSource.setMaxActive(20); dataSource.setMinIdle(5); dataSource.setValidationQuery("SELECT 1"); dataSource.setTestWhileIdle(true); return dataSource;
参数说明:
testWhileIdle=true确保空闲连接被校验,避免Communications link failure;serverTimezone=Asia/Shanghai解决MySQL 8时区问题。
5.2 改造2:为药品搜索增加模糊匹配与拼音首字母检索(15分钟)
原搜索框仅支持精确匹配药品名,用户体验差。增强逻辑只需改DrugDao.java的查询方法:
// 原方法(精确匹配) public List<Drug> findByName(String name) { String sql = "SELECT * FROM drug WHERE name = ?"; return queryRunner.query(sql, new BeanListHandler<>(Drug.class), name); } // 新方法(模糊+拼音首字母) public List<Drug> searchDrugs(String keyword) { // 方案1:LIKE模糊(简单,但慢) String sql1 = "SELECT * FROM drug WHERE name LIKE ? OR specification LIKE ?"; // 方案2:拼音首字母(需提前在drug表加pinyin_first列,值为'A'/'S'等) String sql2 = "SELECT * FROM drug WHERE pinyin_first = ? OR name LIKE ?"; // 推荐混合:先查首字母,再查LIKE,合并去重 List<Drug> list = new ArrayList<>(); if (keyword.length() > 0) { char first = keyword.charAt(0); if (first >= 'A' && first <= 'Z') { list.addAll(queryRunner.query(sql2, new BeanListHandler<>(Drug.class), String.valueOf(first), "%" + keyword + "%")); } else { list.addAll(queryRunner.query(sql1, new BeanListHandler<>(Drug.class), "%" + keyword + "%", "%" + keyword + "%")); } } return list; }落地技巧:
pinyin_first列可通过Python脚本批量生成(用pypinyin库),无需实时转换,性能零损耗。
5.3 改造3:销售单打印优化——用CSS媒体查询实现「所见即所得」(10分钟)
原打印功能用window.print(),但页面布局混乱。纯前端方案,不依赖后端模板:
- 在
sale_detail.jsp底部添加打印专用CSS:<style media="print"> body { font-size: 12px; margin: 0; padding: 0; } .no-print { display: none !important; } /* 隐藏按钮、导航栏 */ table { width: 100%; border-collapse: collapse; } th, td { border: 1px solid #000; padding: 4px; text-align: left; } .print-header { text-align: center; font-size: 16px; margin-bottom: 20px; } </style> - 打印按钮HTML:
<button class="no-print" onclick="window.print()">打印销售单</button> - 打印前调用
window.print()即可,无需生成PDF——这是药店收银员最需要的「一键打印」体验。
我带过的毕业生里,有人把这套系统改造成社区药房预约平台,有人加了微信扫码支付接口,还有人用它通过了某银行科技岗的笔试实操题。它的价值不在代码多炫,而在于每个模块都暴露在阳光下:Servlet怎么接收参数、DAO怎么拼SQL、JSP怎么嵌套Java逻辑——没有魔法,全是手艺。你改的每一行,都能在浏览器里立刻验证效果;你踩的每一个坑,都是企业开发中真实存在的边界条件。别把它当毕业作业交完就删,把它当成你Java Web能力的「校准器」:跑通它,你就知道自己的基础到底扎不扎实;改透它,你就有了谈薪时能展开讲30分钟的技术底气。希望帮到你。
本文还有配套的精品资源,点击获取