简介:这是一份基于JavaWeb+Servlet+JSP+MySQL的药店管理系统完整源码包,定位为课程设计、毕业设计或Servlet/JSP初学者实践项目,可部署于Tomcat容器运行。系统覆盖客户信息管理、药品类别管理、药品信息查询与出入库、出入库记录展示、用户管理及修改密码、退出登录等功能模块,支撑药店日常运营的常见场景。压缩包共112个文件,大小约2.19MB,内含Java源码、编译后的class文件、JSP页面、数据库SQL脚本,以及CSS/JS前端样式与Eclipse项目配置;其中SQL脚本用于初始化MySQL数据,项目结构清晰便于直接导入IDE。已有574人学习/下载,适合需要参考完整增删改查、会话管理与数据库交互写法的学习者。通过阅读源码,可快速理解Servlet处理请求、JSP展示数据、Dao层操作MySQL的基本流程,也能基于现有模块扩展药品统计、报表等功能,对提升JavaWeb项目实践能力有直接帮助。
1. 药店管理系统:这个 JavaWeb 课设是真的能跑通
手里这份药店管理系统,是典型的 JavaWeb 三层结构项目,Servlet 做控制层、JSP 做页面、MySQL 存数据,跑在 Tomcat 上。它不是那种只有前端页面的半成品,而是把客户、药品类别、药品信息、出入库记录、用户权限这些模块都做全了的完整案例,源码里能看到 LoginServlet、ProductSaveServlet、WareHouseSaveServlet 这些真实的业务处理逻辑,还有一套建表用的数据库脚本。对正在做 JavaWeb 课程设计、毕业设计,或者想找一份能二次开发的参考项目的人来说,这份代码的参考价值在于:它把「登录鉴权 → 数据展示 → 增删改查 → 出入库联动」这条完整链路跑通了,不是教学 Demo 那种点到为止的写法。
我拆这份资源的时候,重点不是看它页面做得有多花哨,而是看它怎么处理药品出库时「库存扣减」和「记录生成」这两件事的先后关系,以及数据库表结构是怎么支撑起多模块联动的。这篇文章会把运行步骤、核心代码逻辑、参数设置和常见的坑都拆开讲,照着走一遍,你就能把这个系统跑起来。
2. 先看清项目骨架:目录结构、技术栈和数据库脚本的对应关系
拿到源码包,先别急着往 Tomcat 里丢。这个项目的目录结构是标准的 Eclipse JavaWeb 工程布局,你要知道每个目录里装的是什么、和数据库里的表是怎么对应的,后面改代码或者排查问题才不抓瞎。
2.1 从 Eclipse 工程目录看整体结构
整个项目的核心目录是src、WebContent和根目录下的数据库脚本文件。src里放的是 Java 源代码,按功能看主要分三块:实体类和 DAO 层负责跟 MySQL 打交道,Servlet 负责接请求和跳页面,还有一些工具类。WebContent下是 JSP 页面、静态资源和WEB-INF/web.xml配置文件。
药店管理系统/ ├── src/ │ ├── com.xxx.dao/ // CustomerDao, ProductDao, WareHouseDao, UserDao, RecordDao │ ├── com.xxx.servlet/ // LoginServlet, ProductSaveServlet, WareHouseSaveServlet 等 │ └── com.xxx.entity/ // 实体类,对应数据库表字段 ├── WebContent/ │ ├── login.jsp // 登录页 │ ├── customer/ // 客户管理相关 JSP │ ├── product/ // 药品信息相关 JSP │ ├── record/ // 出入库记录 JSP │ └── WEB-INF/ │ └── web.xml // Servlet 映射和欢迎页配置 ├── src 同级下的 Se122121rvlet.sql // 建库建表脚本(注意文件名被截断过) ├── .classpath // Eclipse 构建路径 └── .project // Eclipse 项目标识CustomerDao、ProductDao、WareHouseDao、UserDao、RecordDao这五个 DAO 是数据访问的基座,每个 DAO 对应一张或一组表。Servlet 层通过调用 DAO 完成业务,不在 Servlet 里直接拼 SQL 操作数据库,这种分离方式虽然简单,但是分层清晰,很适合用来理解 JavaWeb 工程的请求处理流程。
提示:
Se122121rvlet.sql这个文件名明显被截断过,拿到手后建议先把它重命名成pharmacy.sql之类的正常名字再导入,避免 MySQL 命令行工具因为文件名问题读不到脚本。
2.2 五个 DAO 类分别管什么:表和业务的对应关系
我从源码里把五个 DAO 类的作用理了一下,它们和业务模块的对应关系是这样的:
| DAO 类 | 负责的实体 | 核心方法 | 对应的业务页面 |
|---|---|---|---|
UserDao | 系统用户 | 登录校验findUserByUsernameAndPwd(),新增/删除用户 | 登录、用户管理 |
CustomerDao | 客户信息 | 名单查询list(),增/改/删 | 客户管理列表 |
ProductDao | 药品基本信息 | 分页列表listByPage(),add()/update()/delete(),出库入库更新库存updateStock() | 药品信息管理 |
WareHouseDao | 出入库记录 | 插入记录addRecord(),列出记录list() | 出入库记录 |
RecordDao | 汇总查询 | 按条件查记录 | 记录列表页 |
看到这里你可能会问:出库和入库的动作,为什么是ProductDao和WareHouseDao同时参与?因为出库的本质是「药品表库存减少 + 出入库表新增一条出库记录」这两件事的组合。如果只有ProductDao改库存而WareHouseDao没插入记录,那记录列表就会是空的;反过来如果只写记录不改库存,药品表的数据就永远不变。这套代码里ProductSaveServlet负责协调这两个 DAO 的调用顺序,后面第 4 章会细讲。
2.3 搭建运行环境:JDK、Tomcat、MySQL 的版本搭配
先把环境搭好再导入项目。按照这个项目的年代和技术栈,推荐 JDK 8、Tomcat 8.5 或 9.0、MySQL 5.7 的组合。如果你电脑上装的是 MySQL 8.0,也完全能跑,但要注意mysql-connector-java驱动 JAR 包必须换成 8.x 版本,而且驱动的类名要从com.mysql.jdbc.Driver改成com.mysql.cj.jdbc.Driver,这个坑不换直接用旧驱动跑 MySQL 8 会直接抛异常。
数据库的创建流程是:先在 MySQL 里建一个数据库(比如pharmacy_db),然后把 SQL 脚本导入进去。如果脚本开头有CREATE DATABASE语句,你也可以直接整体导入。
mysql -u root -p < Se122121rvlet.sql导入后建议用use 数据库名;和show tables;确认一下表都建出来了。正常应该有用户表、客户表、药品类别表、药品信息表、出入库记录表这五类核心表。如果发现表数量不对,多半是脚本的字符集或者版本兼容问题,后面第 5 章会展开排查思路。
2.4 在 Eclipse 里导入项目并跑起来的完整步骤
Eclipse 导入这个项目的具体操作:File → Import → General → Existing Projects into Workspace,然后选中项目根目录即可。导入之后要做三件事:把 MySQL 驱动 JAR 包放进WebContent/WEB-INF/lib(如果没有的话),检查 JDK 版本是否匹配,最后确认 Tomcat 运行时已经配置好。
然后是改数据库连接配置。这个项目的数据源信息一般写在 DAO 类顶部的静态变量里,或者在db.properties配置文件里。找到DB_URL、DB_USERNAME、DB_PASSWORD这三个值,改成你自己 MySQL 的账号密码:
private static final String DB_URL = "jdbc:mysql://localhost:3306/pharmacy_db?useUnicode=true&characterEncoding=utf-8&useSSL=false"; private static final String DB_USERNAME = "root"; private static final String DB_PASSWORD = "123456";这里有个细节很容易栽:连接字符串里的useSSL=false和characterEncoding=utf-8这两个参数不要删。前者是避免 MySQL 5.7 默认的 SSL 握手警告导致连接变慢,后者是保证插入中文数据不乱码。如果你用 MySQL 8.0,还得在DB_URL尾部加上serverTimezone=Asia/Shanghai,否则 JDBC 驱动会因为服务器时区不明确直接拒绝连接。
配置完成后,在 Eclipse 里右键项目Run As → Run on Server启动,浏览器访问http://localhost:8080/项目名/,能跳到登录页就算项目跑通了。
3. 登录鉴权和用户管理:从 LoginServlet 看 Session 与请求转发
这个系统的入口是登录页,LoginServlet负责处理登录请求。这块逻辑看上去简单,但它是理解整个系统权限控制的关键,而且新手在这里出的问题特别多,比如登录成功后页面跳不过去、刷新一下又回到登录页,基本都是 Session 和请求转发没搞清楚。
3.1 从 LoginServlet 走一遍登录流程
登录页login.jsp把表单提交到LoginServlet,Servlet 内部调用UserDao查询用户表,判断用户名密码是否匹配。我拆源码时把核心流程整理成下面这个伪代码级别的流程,不是原样抄代码,但逻辑跟源码一致:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username = request.getParameter("username"); String password = request.getParameter("password"); UserDao userDao = new UserDao(); User user = userDao.findUserByUsernameAndPwd(username, password); if (user != null) { HttpSession session = request.getSession(); session.setAttribute("loginUser", user); response.sendRedirect(request.getContextPath() + "/productList.jsp"); } else { request.setAttribute("loginError", "用户名或密码错误"); request.getRequestDispatcher("/login.jsp").forward(request, response); } }先说明这段代码里两个关键参数:username和password是从表单的name属性取出来的,如果 JSP 里<input>标签的 name 和这里不一致,request.getParameter()就会拿到null,那校验必败。loginUser是 Session 的键,后面所有需要判断登录状态的页面都靠这个键。整个逻辑的关键在于——登录成功用的是sendRedirect重定向,失败用的是forward转发。重定向会让浏览器重新发起一次请求,地址栏变成目标页面;转发是服务端内部的跳转,浏览器感知不到。这两个用混了,就会出现「登录成功后手动刷新页面变成 404」的问题。
3.2 Session 控制和退出登录的实现细节
这个系统的用户管理包括新增用户、删除用户和修改密码三个功能。新增用户走UserSaveServlet,删除走UserDeleteServlet,改密码则是单独的一个逻辑——通常是判断旧密码是否匹配,再执行 UPDATE。这里放一段改密码的典型实现,帮助你理解 DAO 和 Servlet 的配合:
// UserDao 中根据用户名修改密码 public boolean updatePassword(String username, String oldPassword, String newPassword) { String sql = "UPDATE tb_user SET password = ? WHERE username = ? AND password = ?"; // 使用 PreparedStatement 执行更新,返回受影响行数 > 0 即成功 return executeUpdate(sql, newPassword, username, oldPassword) > 0; }注意这里用了WHERE username = ? AND password = ?双重条件,这种写法的好处是:如果旧密码输入错误,影响行数就是 0,不用先查一遍用户记录再判断旧密码,一个 SQL 就完成了校验和更新。这是老项目里常见的做法,虽然简单,但逻辑是闭合的。
退出系统的实现一般是一个LogoutServlet,核心动作是session.invalidate()把 Session 销毁,然后重定向到登录页。有些人会误以为把 Session 里的loginUser移除就够了,实际上invalidate()更彻底,它能把这个会话相关的所有属性一次性清掉,防止通过浏览器的「后退」按钮看到登录后才显示的页面内容。
3.3 登录模块的常见误用:拦截器没写在 web.xml
这个项目的页面能在地址栏直接输入 URL 访问,这就暴露了一个隐患——如果 JSP 页面本身没有判断 Session 是否为空的逻辑,用户跳过登录直接访问页面路径也是能打开的。正规做法是在web.xml里配一个Filter,拦截所有.jsp请求做 Session 校验。但这个老项目大概率没做这层防护,它是一个「功能完整但安全基础」的课设型项目,拷贝这份代码用来交作业没问题,如果要上生产环境或者当毕设展示,建议至少补一个登录过滤器,代码很短,但价值很大。
4. 药品管理和出入库联动:ProductDao 与 WareHouseDao 的库存扣减机制
药品信息管理才是这个系统的核心模块,理解这块代码等于理解了整个系统的设计骨架。它包括药品类别管理、药品信息增删改查、库存数量管理和出入库记录四块内容。其中最有讨论价值的是出入库操作,它牵涉到两张表的同时更新,这里的设计决策直接决定了库存数据会不会出差错。
4.1 药品类别的数据模型与管理逻辑
药品类别和药品信息之间是典型的一对多关系——一个类别下挂多个药品。数据库设计上,药品表里有一个category_id外键关联类别表的id。类别管理模块提供的功能是新增类别、修改类别、删除类别、列出所有类别。删除类别时有一个需要注意的地方:如果某个类别下还挂着药品,直接删类别会导致药品表的category_id变成悬空引用,查询药品列表时 join 不到类别名称,页面上就会出现空白。所以严谨的删除逻辑是先查这个类别下有没有药品,有的话提示用户先转移或删除药品。
这种「先查子表再决定是否删除父表」的处理思路,是这个模块里三个控制层类要处理的核心业务规则。如果说新增、修改、删除是基础 CRUD——这类简单操作的模式是统一的,那么「删除前检查有没有关联数据」就属于业务约束,项目里实现可能没那么严密,但你要能看懂这个隐患。
4.2 出库操作:先扣库存,还是先写记录?
先看出库的处理流程。前端页面提交一个出库表单,包含药品 ID、出库数量、备注等字段,请求到达ProductSaveServlet。我拆代码时把关键逻辑整理成下面这个顺序:
// 1. 根据药品 ID 查出当前药品 Product product = productDao.findById(id); // 2. 校验库存是否充足 if (product.getStock() < outCount) { // 返回错误页或提示信息,不执行后续操作 } // 3. 扣减库存:stock = stock - outCount String updateStockSql = "UPDATE tb_product SET stock = stock - ? WHERE id = ? AND stock >= ?"; int rows = productDao.executeUpdate(updateStockSql, outCount, id, outCount); // 4. 只有扣减成功才插入出库记录 if (rows > 0) { wareHouseDao.addRecord(new Record(null, id, "出库", outCount, remark, new Date())); }这段代码最值得学的点是UPDATE ... WHERE id = ? AND stock >= ?这种条件更新的写法。它把「查询校验库存」和「扣减库存」合并成了一个原子操作——如果库存不足,这个 UPDATE 的影响行数会是 0,不会出现并发情况下先查到了库存充足,但实际执行扣减时已经被其他请求抢走库存的情况。虽然这个课设里面大概率没考虑并发场景,但 WHERE 子句带上stock >= ?这个条件是值得保留的好习惯。
出库和入库的区别只是方向相反:出库是stock - ?,入库是stock + ?,记录表里用「出库」「入库」字符串来做标识区分。我在源码里注意到,WareHouseSaveServlet和ProductSaveServlet都涉及出库入库,实际运行时你可能发现两个 Servlet 有功能重叠,这是不同版本迭代留下的痕迹,不影响整体运行,但读代码时要注意别绕晕。
4.3 库存联动在代码里究竟改了几张表
回答一个很多人会纠结的问题:一次出库操作到底执行了几条 SQL?如果上面的逻辑你没看明白,我在这里总结清楚——一次出库 = 一条 UPDATE(改药品表的库存字段) + 一条 INSERT(写入库记录的明细)。两条 SQL 不是在一个数据库事务里执行的,这个设计有一定的风险:如果第一条执行成功、插入记录时报错,库存改了但记录没写成,两边就对不上了。
这个风险在课设演示基本碰不到,因为单用户操作几乎不会有中间那一步报错的情况。但如果你要把它作为期末设计或者项目完善的方向,「用事务包住这两条 SQL」会是答辩时一个很出彩的加分点。下章会提到这个问题的排查方式,你实操时如果遇到「药品库存变了但记录列表里没有数据」的反常现象,根源多半就在这里。
4.4 药品信息的查询、新增、修改、删除的标准模板
药品信息管理的 CRUD 部分是一套标准的 Servlet+JSP+DAO 三层写法。列表页展示的思路是:ProductDao里写一个分页查询方法,接收pageNo和pageSize两个参数,返回当前页的数据列表和总条数,JSP 页面通过 JSTL 或纯 JSP 脚本循环渲染表格。
我建议你重点读一遍ProductDao里分页查询的 SQL 写法。如果是 MySQL 的LIMIT offset, size写法,那么在 SQL Server 或 Oracle 上要改成不同的写法。这套代码绑定 MySQL 语法,所以它不能直接换到别的数据库上,这是选型时就定死的边界。普通新增和修改操作的关键点在于表单参数要与Product实体类字段一一对应,如果 JSP 里输入框的 name 和实体类属性不一致,request.getParameter拿到后组装对象时就会漏字段。这种小问题导致的 bug,不做断点调试很难发现。
5. 避坑排查:Tomcat 部署与 MySQL 连接的五个经典问题
这个项目结构不算复杂,但凡是 Servlet+JSP 的老项目,运行起来总会碰到下面这些经典问题。我把拆代码和实际跑项目时遇到的坑整理成排查手册的形式,每一条都是「现象 → 原因 → 解决」三步说清楚,你遇到类似情况直接对号入座。
5.1 404 报错:访问路径总是差一个上下文名
现象:Tomcat 启动成功,但是访问登录页时页面一直 404,或者 CSS/JS 加载不出来。
原因:Eclipse 部署到 Tomcat 后,项目的访问路径默认是http://localhost:8080/项目名/,不是http://localhost:8080/。如果直接把 JSP 里的链接写成了/login.jsp这种绝对根路径,浏览器会跑去根路径找文件,自然找不到。
解决:JSP 里所有带路径的跳转和静态资源引用,统一在头部加上${pageContext.request.contextPath}拼上下文路径。比如登录表单的 action 写成${pageContext.request.contextPath}/LoginServlet。如果你只是本地演示不想改页面,也可以用另一个办法:右键项目 → Properties → Web Project Settings,把 Context root 改成/,这样就能用根路径访问了,不过要注意这样端口下只能部署一个项目。
5.2 数据库连不上:Access denied for user
现象:启动项目后页面报 500,控制台打印Access denied for user 'root'@'localhost' (using password: YES)。
原因:大概率是你把DB_USERNAME或DB_PASSWORD改成了自己 MySQL 的账号密码,但密码里带了特殊字符,比如@、#、&等。这些字符放在 URL 字符串里如果没做处理,会破坏连接字符串的解析。
解决:密码里有特殊字符时,优先用Properties文件存配置或用DriverManager.getConnection(url, user, password)的方法传参数——注意用户名和密码不要让 JDBC 内部再拼一次 URL。另外一个隐藏问题:如果你用的是别人导出的 SQL 脚本,脚本里可能自带CREATE USER 'root'@'xxx'这种建用户语句,导入后会把你的账号覆盖掉,这个要特别留意。
5.3 中文乱码:插入数据库的数据全是问号或者乱码
现象:页面上正常输入中文,提交后数据库里存的是???,或者在 JSP 页面上显示出来是乱码。
原因:乱码问题的链条非常长,链路中任何一环断了都会出问题。常见的有三种:一是 JSP 文件本身的编码不是 UTF-8,二是数据库表的字符集是latin1,三是 Servlet 接收请求时没有设置request.setCharacterEncoding("UTF-8")。
解决:按顺序排查三步。第一步,确认web.xml里没有配置 CharacterEncodingFilter 的话,就在每个 Servlet 的doPost开头加request.setCharacterEncoding("UTF-8")和response.setContentType("text/html;charset=UTF-8")。第二步,把 MySQL 数据库的默认字符集改成utf8mb4:
ALTER DATABASE pharmacy_db CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; ALTER TABLE tb_product CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;第三步,检查 JDBC 连接串里有没有characterEncoding=utf-8。这三步走完,绝大多数乱码问题都能解决。
5.4 启动秒退:Tomcat 报 ClassNotFoundException 或 NoClassDefFoundError
现象:Tomcat 启动到一半直接失败,控制台报java.lang.ClassNotFoundException: com.mysql.jdbc.Driver。
原因:MySQL 驱动 JAR 包没放对位置。Eclipse 的 JavaWeb 项目里,外部 JAR 必须是放在WebContent/WEB-INF/lib目录下,而不是放在src下或者通过 Build Path 导入就完事了。如果你用的是 Maven 构建的 Tomcat 插件,情况又不一样,但这里不是 Maven 项目,这是传统 Eclipse 工程。
解决:把mysql-connector-java-8.0.x.jar复制到WebContent/WEB-INF/lib目录,然后在项目上右键 Refresh,确保 Deployment Assembly 里包含这个目录。注意驱动类名也要跟着版本走:MySQL 8.x 的驱动类名是com.mysql.cj.jdbc.Driver,MySQL 5.x 才是com.mysql.jdbc.Driver,两个版本驱动类名不能混用。
5.5 时区问题:Server returns invalid timezone
现象:项目能启动,但只要一执行数据库查询,后台就报The server time zone value 'xxx' is unrecognized or represents more than one time zone。
原因:这是 MySQL 8.x 新增的时区校验机制,旧版的 JDBC 驱动不会校验这个,所以 MySQL 5.7 时代不需要设置时区。你本机 MySQL 是 8.0 的话,必须明确指定时区,否则驱动拒绝连接。
解决:在 JDBC URL 上追加serverTimezone=Asia/Shanghai。完整写法参考下面:
private static final String DB_URL = "jdbc:mysql://localhost:3306/pharmacy_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai";注意:如果数据库服务器的系统时区不是中国时区,但你在 URL 里写死了
Asia/Shanghai,查询出来的时间字段可能跟你预期有 1~7 个小时的偏差。这种时间偏差问题在测试时容易被忽略,等真正分析数据时会发现时间轴对不上。
6. 进阶改造:把项目从 Eclipse 迁到 IDEA,并给 DAO 换上 PreparedStatement
如果你不只是想把项目跑起来交差,而是想让它变成一份能写进简历的完整度更高的作品,下面这两个改造方向性价比最高。它们不改变原有的 Servlet+JSP 架构,却在稳定性和工程规范性上有质的提升。
6.1 从 Eclipse 迁移到 IntelliJ IDEA 的关键步骤
现在很多人的开发环境是 IntelliJ IDEA,而不是 Eclipse。IDEA 的 JavaWeb 项目结构和 Eclipse 不同,但迁移这个老项目其实并不难,重点是别直接复制.project和.classpath这类 Eclipse 专属文件。
我的迁移习惯是这样三步走:第一步,在 IDEA 里新建一个空的 JavaWeb 项目,然后把src下的 Java 代码复制过来,把WebContent下的内容复制到web目录;第二步,在 Project Structure 里把mysql-connector-java加到 Libraries,然后配置 Artifacts 的 Lib 目录;第三步,配置 Tomcat 运行环境,Deployment 里把项目部署进去,Application context 填/pharmacy。
真正容易翻车的点在 Artifacts 配置——IDEA 默认不会把WEB-INF/lib下的 JAR 包打包进去,需要你在 Output Layout 里手动把 JAR 包拖进WEB-INF/lib。这一步漏了,就又会看到那个ClassNotFoundException,但这次不是驱动没下载,而是驱动没被部署到运行时环境。
6.2 把 Statement 换成 PreparedStatement:一次替换,终身受益
如果你看过这个项目的 DAO 源码,大概率会看到早期 JavaWeb 教材里经典的Statement.executeQuery(sql)写法,即通过字符串拼接的方式把参数拼进 SQL。这种写法最大的问题不是性能,而是安全——如果从页面上传入的参数包含 SQL 片段,就可能被拼接出非预期的查询语句。
我把常见的写法贴出来对比一下,你自己就能判断为什么推荐升级:
// 不推荐:字符串拼接 SQL,参数面板注入风险 + SQL 每次编译 String sql = "SELECT * FROM tb_product WHERE name = '" + productName + "'"; Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql); // 推荐:PreparedStatement 预编译 + 参数占位 String sql = "SELECT * FROM tb_product WHERE name = ?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, productName); ResultSet rs = ps.executeQuery();PreparedStatement的两个直接好处:第一,参数通过setString传入,MySQL 驱动会自动做转义处理,输入里的特殊字符不会破坏 SQL 结构;第二,SQL 只在第一次执行时编译,循环执行同一条 SQL 时效率比Statement高。把项目里五个 DAO 的 SQL 替换成这个写法,结构性改动会比较大,但替换逻辑是机械性的。
6.3 用 Filter 补一个登录拦截:安全性的最小成本改造
前面第 3 章提到这个系统没有登录拦截,直接输入 URL 可以绕过登录页。想补上这个短板,最经济的办法是写一个LoginFilter,拦截除了LoginServlet和login.jsp之外的所有页面请求:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; HttpSession session = req.getSession(false); if (session == null || session.getAttribute("loginUser") == null) { resp.sendRedirect(req.getContextPath() + "/login.jsp"); return; } chain.doFilter(request, response); }这段代码放在web.xml里配置一个<filter-mapping>,URL 模式写成/*,然后在<filter>里加上<init-param>排除登录相关路径。这个过滤器改造成本很低,但在答辩时你就能理直气壮地说「系统增加了登录状态的统一校验」,而不只是「前端页面隐藏了登录按钮」。从那以后我每次拿到一份老 JavaWeb 源码,都会先做一遍这三件事——确认驱动版本、跑通数据库连接、顺手补一个登录过滤器——再做功能测试,这套流程帮我过滤掉了不少隐藏极深的运行时问题。希望这次的拆解也能让你少走几步弯路。
本文还有配套的精品资源,点击获取