ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

药店管理系统源码拆包:Servlet+JSP+MySQL 老架构实战与避坑

药店管理系统源码拆包:Servlet+JSP+MySQL 老架构实战与避坑 简介这是一套基于JavaWeb技术栈的药店管理系统完整源码与数据库资源采用servlet与jsp实现业务逻辑MySQL负责数据存储适合JavaWeb初学者、课程设计或毕业设计开发者参考学习。系统覆盖客户信息管理、药品类别管理、药品信息管理、药品出入库记录管理及用户信息管理五大模块支持药品查询、新增、修改、删除、出库入库以及密码修改与安全退出等操作功能链路较为完整。压缩包共112个文件约2.19MB包含32个java源文件与32个class编译文件、7个jsp页面、8个css样式、6个js脚本及6个jar依赖另有sql数据库脚本与Eclipse项目配置文件便于直接导入运行。目前已有574人学习下载可帮助读者快速理解servlet与jsp协作方式、DAO分层结构及数据库表设计是掌握JavaWeb项目开发流程的实用参考案例。1. 药店管理系统源码拆包ServletJSPMySQL 这套老架构还能不能跑起来前阵子帮一个做医药零售的朋友看库存对账的问题他店里用的还是一套十多年前的进销存我顺手翻了下他服务器上的项目结构发现跟今天要拆的这套药店管理系统几乎是一个模子刻出来的CustomerDao.class、ProductDao.class、WareHouseDao.class、UserDao.class一堆编译好的 class 文件加上RecordListServlet.class、ProductSaveServlet.class、LoginServlet.class这些 Servlet数据库脚本Se122121rvlet.sql一跑整个系统就能转起来。这套基于 JavaWeb Servlet JSP MySQL 的药店管理系统覆盖客户信息、药品类别、药品信息、出入库记录、用户管理五个模块适合拿来做课程设计、毕设或者给小型药店做一套能落地的内部工具。它不新但胜在结构清晰、依赖少Tomcat 一挂就能跑对想搞懂传统 JavaWeb 项目完整链路的人来说比一上来啃 Spring Boot 更容易看清请求是怎么从浏览器走到数据库的。2. 环境搭建与数据库初始化从 Se122121rvlet.sql 到 Tomcat 跑通第一个 Servlet这套项目最容易被低估的一步就是环境。很多人拿到源码直接往 IDE 里一导结果控制台一片红最后卡在数据库连不上或者 JSP 404。下面按我实际复现的顺序拆一遍每一步都落到具体命令和参数上。2.1 JDK、Tomcat、MySQL 的版本对齐这套代码用的是javax.servlet包不是jakarta.servlet所以 Tomcat 必须选 9.x 及以下版本Tomcat 10 之后包名改成jakarta.*Servlet 直接编译不过。JDK 用 8 或 11 都行我一般用 JDK 8因为老项目的web.xml和 JSP 编译在 8 上最稳。MySQL 用 5.7 或 8.0 都可以但要注意 8.0 的驱动类名和连接串参数跟 5.7 有区别后面连接配置会单独说。组件推荐版本原因JDK1.8老项目兼容性最好JSP 编译无警告Tomcat9.0.x仍是 javax.servlet 包Servlet 可直接编译MySQL5.7 / 8.0脚本语法通用8.0 需注意驱动和时区IDEEclipse / IDEA项目自带 .classpath 和 .projectEclipse 导入更顺Eclipse 导入时直接选Existing Projects into Workspace因为项目根目录带了.project和.classpath它会自动识别成 Java Web 项目。用 IDEA 的话得手动配 Facet把WebContent指成 Web 资源目录src指成源码目录这一步配错就是 404 的根源。2.2 导入 Se122121rvlet.sql 并核对库名数据库脚本Se122121rvlet.sql里包含建库、建表和初始数据。我一般先用命令行导入避免 Navicat 图形化工具在字符集上做小动作# 登录 MySQL注意 8.0 默认端口 3306 mysql -u root -p # 创建库并指定字符集防止中文药品名乱码 CREATE DATABASE pharmacy DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; # 退出后用 source 导入脚本路径换成你本地的实际路径 USE pharmacy; SOURCE D:/project/Se122121rvlet.sql;导入完执行SHOW TABLES;正常应该看到客户表、药品表、类别表、出入库记录表、用户表这几张。如果脚本里写死了库名比如USE pharmacy;那你自己建的库名要跟它一致否则后面 JDBC 连接串里的库名对不上报Unknown database。2.3 JDBC 连接配置与驱动放置连接数据库的配置一般写在某个工具类里比如DBUtil或BaseDao里面是DriverManager.getConnection。MySQL 8.0 和 5.7 的写法差异就在这两行// MySQL 5.7 写法 Class.forName(com.mysql.jdbc.Driver); String url jdbc:mysql://localhost:3306/pharmacy?useUnicodetruecharacterEncodingutf8; // MySQL 8.0 写法多了 cj 和时区参数 Class.forName(com.mysql.cj.jdbc.Driver); String url jdbc:mysql://localhost:3306/pharmacy?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse;驱动 jar 包要放到WebContent/WEB-INF/lib下不是放到 Tomcat 的 lib 里。放 Tomcat lib 里本地能跑换台机器就找不到驱动这是打包部署时最常见的翻车点。serverTimezone不写8.0 会报时区错误useSSLfalse不写控制台会刷一堆 SSL 警告虽然不影响功能但看着烦。2.4 部署到 Tomcat 并验证登录链路把项目部署到 Tomcat 后访问http://localhost:8080/项目名/默认会跳到登录页。登录请求走的是LoginServlet它查用户表比对账号密码成功就写 session 再跳主页。验证链路是否通就看三件事登录页能不能打开JSP 编译正常、输错密码有没有提示Servlet 逻辑正常、登录后药品列表能不能加载DAO 查库正常。三步都过环境就算立住了。3. 五大模块的 Servlet 与 DAO 分层请求怎么从 JSP 走到 MySQL这套项目的分层是典型的JSP 页面 → Servlet 控制 → DAO 数据访问 → MySQL没有 Service 层逻辑直接写在 Servlet 里。结构简单但改起来要小心因为业务判断和数据库操作混在一起。下面按模块拆关键实现。3.1 客户与药品类别管理CustomerDao 与 ProductDao 的增删改查客户管理和类别管理是最标准的 CRUD套路完全一样。以客户新增为例JSP 表单提交到CustomerSaveServletServlet 取参数、调CustomerDao的 insert 方法、再重定向回列表页。// CustomerSaveServlet 核心逻辑 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); // 必须放在取参数之前否则中文乱码 String name request.getParameter(name); String phone request.getParameter(phone); Customer customer new Customer(); customer.setName(name); customer.setPhone(phone); CustomerDao dao new CustomerDao(); dao.insert(customer); // 内部就是 PreparedStatement 执行 insert response.sendRedirect(customerList); // 重定向到列表 Servlet避免刷新重复提交 }这里有两个参数层面的细节。第一setCharacterEncoding必须在getParameter之前调用顺序反了中文就是问号。第二新增完用sendRedirect而不是forward重定向会发起新请求刷新页面不会重复插入这是防重复提交最省事的做法。类别管理同理ProductDao里除了药品本身还管类别表的增删改删类别前要先检查该类别下有没有药品有的话不能删否则药品会变成孤儿数据。3.2 药品出入库WareHouseDao 与库存字段的联动出入库是这套系统里唯一带业务联动的模块。WareHouseSaveServlet处理入库和出库WareHouseDao负责写记录表同时要更新药品表里的库存数量。入库就是库存加出库就是库存减减到负数要拦住。// WareHouseDao 出库时的库存校验与更新 public boolean outStock(int productId, int quantity) { String checkSql SELECT stock FROM product WHERE id ?; String updateSql UPDATE product SET stock stock - ? WHERE id ? AND stock ?; try (Connection conn DBUtil.getConnection(); PreparedStatement check conn.prepareStatement(checkSql)) { check.setInt(1, productId); ResultSet rs check.executeQuery(); if (rs.next() rs.getInt(stock) quantity) { PreparedStatement update conn.prepareStatement(updateSql); update.setInt(1, quantity); update.setInt(2, productId); update.setInt(3, quantity); // 条件里再卡一次防并发超卖 return update.executeUpdate() 0; } return false; // 库存不足拒绝出库 } catch (SQLException e) { e.printStackTrace(); return false; } }关键在UPDATE ... WHERE stock ?这个条件。如果只在 Java 里判断库存够不够再执行 update两个请求同时进来就可能把库存扣成负数。把判断塞进 SQL 的 where 条件里靠数据库的行锁保证原子性这是没有事务框架时最实用的兜底。RecordDao则负责把每次出入库写进记录表字段一般有药品 id、类型入/出、数量、操作时间、操作人。3.3 用户管理与密码修改UserDao 与 session 权限控制用户模块管的是系统内部账号UserDao提供新增、删除、按账号查询、改密码几个方法。登录成功后一般把用户对象塞进 session// LoginServlet 登录成功后的 session 写入 HttpSession session request.getSession(); session.setAttribute(user, user); response.sendRedirect(productList);然后在需要权限的 Servlet 或 JSP 里判断session.getAttribute(user)是否为空为空就跳回登录页。改密码的逻辑是先查旧密码对不对对的话再 update 新密码。这里常见的坑是密码明文存储脚本里初始数据就是明文自己用没问题真要上线至少加个 MD5虽然 MD5 也不安全但比明文强。3.4 出入库记录列表RecordListServlet 的查询与分页RecordListServlet负责把记录表查出来展示。数据量小的时候直接SELECT *全查数据量大了要加分页。分页的核心是LIMIT两个参数偏移量和条数。// 简单分页查询page 从 1 开始 int page Integer.parseInt(request.getParameter(page) null ? 1 : request.getParameter(page)); int pageSize 10; int offset (page - 1) * pageSize; String sql SELECT r.*, p.name AS productName FROM record r LEFT JOIN product p ON r.product_id p.id ORDER BY r.create_time DESC LIMIT ?, ?;LEFT JOIN是为了把药品名带出来记录表里只存 id 的话列表页显示不友好。ORDER BY create_time DESC让最新的记录排前面符合实际操作习惯。分页参数从请求里取没传就默认第一页这是最省事的容错写法。4. 避坑与排查这套老项目最容易翻车的五个地方这套代码本身不复杂但架不住环境和使用习惯上的坑多。下面五条是我自己踩过或者帮别人排查过的按现象、原因、解决三段写照着对基本能定位大部分问题。4.1 中文药品名入库变问号现象新增药品时名字是中文存进数据库变成???或者页面显示乱码。原因有三层JSP 页面pageEncoding没设 UTF-8、Servlet 没调setCharacterEncoding、数据库或表的字符集是 latin1。解决JSP 顶部加% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %Servlet 取参数前调request.setCharacterEncoding(UTF-8)建库时用utf8mb4三处对齐就不会乱。4.2 Tomcat 10 启动报 ClassNotFoundException现象换到 Tomcat 10 后启动就报javax.servlet.http.HttpServlet找不到。原因Tomcat 10 把 Servlet API 的包名从javax.servlet改成了jakarta.servlet老代码编译时引的还是 javax。解决要么换回 Tomcat 9要么全局把javax.servlet替换成jakarta.servlet并重新编译后者改动量大不推荐。4.3 数据库连不上报 Access denied 或 Unknown database现象启动后访问页面报 500堆栈里是Access denied for user或Unknown database pharmacy。原因连接串里的用户名密码跟实际不符或者库名跟脚本里写的不一致。解决先用命令行mysql -u root -p确认能登录再SHOW DATABASES;看库名把连接串里的库名、用户名、密码逐个核对。8.0 还要确认serverTimezone参数有没有加。4.4 出库后库存变成负数现象连续出库同一药品库存扣到负数还在扣。原因库存判断只在 Java 层做没在 SQL 的 where 条件里卡并发或者快速点击时判断失效。解决把stock ?写进 update 的 where 条件靠数据库行锁兜底同时前端出库按钮点击后置灰减少重复提交。4.5 部署到服务器后 JSP 404 或静态资源加载失败现象本地跑得好好的传到服务器上页面 404或者 CSS、JS 加载不出来。原因项目名变了导致相对路径失效或者WebContent目录没被正确打包进 war。解决JSP 里的链接尽量用${pageContext.request.contextPath}拼绝对路径打包时确认 war 里WEB-INF/classes和WEB-INF/lib都在静态资源放在WebContent根下而不是WEB-INF里。5. 二次开发与验证把明文密码换成 MD5 并加一层登录拦截这套源码拿来直接用没问题但要交作业或者真给店里用有两处值得动手改密码存储和权限拦截。改完顺便验证一下整套链路是不是真的通了。5.1 密码 MD5 改造与登录验证先加一个工具方法把明文转成 MD5 十六进制串// MD5Util登录和改密码都用它 public static String md5(String input) { try { MessageDigest md MessageDigest.getInstance(MD5); byte[] digest md.digest(input.getBytes(UTF-8)); StringBuilder sb new StringBuilder(); for (byte b : digest) { sb.append(String.format(%02x, b)); // 转成两位十六进制 } return sb.toString(); } catch (Exception e) { throw new RuntimeException(e); } }改造分三步注册或新增用户时存库前调MD5Util.md5(password)登录时把用户输入的密码同样 MD5 后再跟库里比对改密码时旧密码校验和新密码入库都走 MD5。注意数据库里已有的明文密码要先批量刷成 MD5否则老账号登不进去。String.format(%02x, b)里的%02x保证每个字节补足两位不然会出现长度不对的哈希。5.2 用 Filter 做统一登录拦截与其在每个 Servlet 里判断 session不如加一个 Filter 统一拦// LoginFilter拦截所有请求放行登录页和登录接口 public class LoginFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; String uri request.getRequestURI(); // 登录页、登录接口、静态资源放行 if (uri.endsWith(login.jsp) || uri.endsWith(login) || uri.contains(/css/) || uri.contains(/js/)) { chain.doFilter(req, resp); return; } if (request.getSession().getAttribute(user) null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } }Filter 在web.xml里配filter和filter-mappingurl-pattern写/*拦全部。放行规则里登录接口的路径要跟LoginServlet的映射一致写错了就是死循环跳登录页。加完 Filter 后直接访问药品列表 URL应该被弹回登录页登录后再访问就正常这就验证了拦截生效。5.3 验证清单与我的习惯改完跑一遍这个清单全过就说明这套系统从环境到业务都立住了验证项操作预期结果登录拦截未登录直接访问药品列表跳回登录页密码加密新增用户后查库密码字段是 32 位 MD5中文存储新增中文药品名库里和页面都正常显示库存联动出库数量大于库存提示库存不足库存不变记录写入入库一次后查记录表多一条对应记录我自己的习惯是每次拿到这种老 JavaWeb 项目先不急着看业务代码而是把环境跑通、登录链路走一遍再动业务。因为这类项目的坑八成在环境和配置上业务逻辑反而写得直来直去。从那以后我每次部署前都强制走一遍上面的验证清单省得改完一个模块把另一个模块带崩。希望这套拆解能帮你少走点弯路源码和数据库脚本都在包里照着跑一遍就有数了。本文还有配套的精品资源点击获取
返回列表