
简介这是一套基于JSPTomcatServletFilter技术栈开发的超市管理系统完整源码面向计算机、软件工程、电子信息等专业学生适用于课程设计、期末大作业及毕业设计参考。系统涵盖用户管理、商品维护、订单处理、库存统计等核心业务模块采用MVC分层架构结合Filter实现统一编码与权限拦截有助于深入理解Java Web基础开发范式与企业级项目组织逻辑。资源包共507个文件含63个JSP页面前端交互、31个Java类业务逻辑、132个Class字节码编译结果、72个Jar依赖库如JDBC驱动、Servlet API、69个JS脚本表单校验与动态交互及1个SQL数据库脚本含建表与初始数据整体压缩包大小为29.9MB。目前已有109人学习下载提供开箱即用的可运行环境附带完整目录结构与典型Servlet实现如UserServlet、BillServlet、UserDaoImpl便于读者快速掌握请求流转、DAO封装与前后端协作机制。1. 这不是“老古董堆砌”JSPTomcatServletFilter 超市管理系统为什么今天还值得跑通一次你打开这个超市管理系统源码数据库.zip看到web.xml里满屏的servlet和filter标签IDEA 提示 “JSP is deprecated”Maven 依赖里还夹着servlet-api:2.5——第一反应可能是这玩意儿还能跑真要部署到 Tomcat 9但现实是全国仍有超 3000 家中小型连锁超市、校内实训基地、县级政务便民中心的后台管理模块仍在用这套技术栈跑着真实订单、库存和员工排班。它不炫技但够稳没 Spring Boot 自动装配却把 Filter 做成了权限闸门、日志黑匣子、字符编码救命稻草JSP 不是“写 HTML 的土办法”而是把商品列表、销售报表、会员积分变动这些强业务逻辑用% %和c:forEach直接焊死在页面渲染链最末端——改一行 JSP前台立刻生效连重启都不用。这不是怀旧是工程选择当团队只有 2 名 Java 开发、服务器是 4C8G 的物理机、运维只认.war包、老板要求“下周必须上线扫码入库功能”时这套组合拳反而比微服务架构少掉 70% 的沟通成本。本文就带你从解压 ZIP 开始不跳过任何一个 web.xml 配置细节不绕开 Filter 的 chain.doFilter() 执行顺序玄学不假装 Tomcat 启动日志里的SEVERE: Error filterStart是小问题——把它真正跑起来调通再看懂它为什么能活到现在。2. 从 ZIP 解压到 Tomcat 可访问四步落地路径与环境硬约束2.1 解压后目录结构必须满足的三个物理契约这个 ZIP 包不是“扔进 IDE 就能 run”的现代项目。它遵循 Servlet 2.5 规范下的经典 WAR 结构任何改动都会让 Tomcat 拒绝加载。解压后你必须确认以下三点/WEB-INF/web.xml必须存在且位于绝对路径WEB-INF/下注意大小写Windows 下常因解压工具自动转小写成web-inf/导致 404所有 JSP 文件必须在/或/jsp/等非WEB-INF/子目录下WEB-INF/下的 JSP 永远 404这是 Servlet 规范铁律/WEB-INF/lib/下必须包含servlet-api.jar版本 ≤ 2.5和mysql-connector-java-5.1.47.jar或类似 JDBC 驱动缺一不可——Tomcat 9 自带 servlet-api 4.0但你的代码用HttpServlet继承写法若含Override注解会因方法签名不匹配直接报ClassCastException。提示用jar -tf yourapp.war | grep -i web-inf快速验证 WAR 包结构。若发现web-inf全小写用7z a -r fixed.war *重新打包确保WEB-INF大写。2.2 Tomcat 8.5 是唯一安全启动基线为什么不能用 Tomcat 10当前主流教程推荐 Tomcat 10但本项目web.xml中filter-class引用的是javax.servlet.Filter而 Tomcat 10 已将包名升级为jakarta.servlet.Filter。强行部署会报java.lang.NoClassDefFoundError: javax/servlet/Filter必须降级到 Tomcat 8.5.x推荐 8.5.99或 9.0.x推荐 9.0.87。下载地址https://archive.apache.org/dist/tomcat/tomcat-8/v8.5.99/bin/apache-tomcat-8.5.99.zip验证方式解压后进入bin/目录执行./version.shLinux/Mac或version.batWindows输出应含Servlet Specification 3.1—— 这是兼容web.xml中web-app version2.5的最低要求。2.3 数据库初始化MySQL 5.7 兼容性陷阱与建库脚本实操ZIP 包内通常含db.sql或init.sql但直接mysql -u root -p db.sql很可能失败。原因有三脚本开头含CREATE DATABASE IF NOT EXISTS supermarket DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;—— 若你 MySQL 5.7 默认字符集是latin1utf8mb4会被拒绝表结构中VARCHAR(255)字段若含中文注释如COMMENT 商品名称在sql_modeSTRICT_TRANS_TABLES下会因长度超限报错INSERT INTO user (username, password) VALUES (admin, e10adc3949ba59abbe56e057f20f883e);这类 MD5 密码需确认password字段类型是CHAR(32)而非VARCHAR(50)。安全执行步骤# 1. 登录 MySQL先建库并设字符集 mysql -u root -p -e CREATE DATABASE supermarket CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; # 2. 修改全局 sql_mode临时规避 STRICT 模式 mysql -u root -p -e SET GLOBAL sql_mode(SELECT REPLACE(sql_mode,STRICT_TRANS_TABLES,)); # 3. 导入数据指定字符集 mysql -u root -p --default-character-setutf8mb4 supermarket db.sql注意--default-character-setutf8mb4参数不可省略否则中文插入会变??。若db.sql中无USE supermarket;需在导入前手动USE supermarket;。2.4 IDEA 部署 WAR 包避开 “No Tomcat server configured” 的真实解法IDEA 2023.3 新建项目时“Add Framework Support” 选 Servlet 会默认绑定 Tomcat 10导致web.xml解析失败。正确做法是不要用 “New Project → Java Enterprise” 向导直接File → Open选择解压后的项目根目录右键项目 → Add Framework Support → Web Application2.5→ OK关键必须选 2.5不是 4.0配置 Tomcat ServerRun → Edit Configurations → → Tomcat Server → Local → Configure → Application server → Click folder icon → 选择你下载的 tomcat-8.5.99 目录Deployment 标签页 → → Artifact → 选择yourproject:war exploded→ Application context 填/supermarket必须与web.xml中display-name一致。此时启动URL 应为http://localhost:8080/supermarket/login.jsp。若仍报404检查out/artifacts/下生成的 exploded 目录是否含WEB-INF/classes/和WEB-INF/lib/—— 缺则说明 IDEA 未正确识别src/为源码根目录需右键src→ Mark Directory as → Sources Root。3. Filter 是权限守门员不是装饰器三类核心 Filter 的代码级拆解3.1 登录拦截 Filter为什么chain.doFilter()放在 if-else 之外是致命错误项目中常见LoginFilter.java其doFilter()方法如下典型错误写法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.contains(login.jsp) !uri.contains(LoginServlet)) { HttpSession session request.getSession(false); if (session null || session.getAttribute(user) null) { response.sendRedirect(request.getContextPath() /login.jsp); return; // ✅ 正确中断链 } } chain.doFilter(req, resp); // ❌ 错误放在这里会导致未登录用户也能访问 /logout.jsp }问题根源chain.doFilter()必须在所有拦截逻辑之后、且仅执行一次。上述代码中当 URI 是/logout.jsp时因不满足!uri.contains(login.jsp)条件直接跳过 if 块执行chain.doFilter()—— 用户未登录也能登出造成会话残留。修正写法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(LoginServlet) || uri.endsWith(css/) || uri.endsWith(js/)) { chain.doFilter(req, resp); // ✅ 放行白名单 return; } // 黑名单拦截其他所有请求必须登录 HttpSession session request.getSession(false); if (session null || session.getAttribute(user) null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); // ✅ 唯一出口 }3.2 字符编码 Filterrequest.setCharacterEncoding(UTF-8)为何有时失效EncodingFilter.java常见写法public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; request.setCharacterEncoding(UTF-8); // ✅ 对 POST 生效 response.setContentType(text/html;charsetUTF-8); // ✅ 设置响应头 chain.doFilter(req, resp); }但你会发现GET 请求中文参数如?name张三仍是乱码。原因setCharacterEncoding()只影响request.getParameter()对 GET 的 QueryString 无效。终极解法Tomcat 层面修改conf/server.xml在Connector标签中添加URIEncodingUTF-8Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 URIEncodingUTF-8 / !-- ✅ 强制 URL 解码为 UTF-8 --提示若用 IDEA 部署此配置需在Run Configuration → Server → VM options中添加-Dfile.encodingUTF-8否则 Windows 系统下server.xml读取可能乱码。3.3 日志审计 Filter如何用HttpServletRequestWrapper记录完整请求体超市系统需记录每笔销售操作的原始 JSON 或表单数据。但request.getInputStream()只能读一次Filter 中读了后续 Servlet 就读不到。解决方案是继承HttpServletRequestWrapperpublic class LoggingRequestWrapper extends HttpServletRequestWrapper { private final String body; public LoggingRequestWrapper(HttpServletRequest request) throws IOException { super(request); StringBuilder sb new StringBuilder(); String line; try (BufferedReader reader request.getReader()) { while ((line reader.readLine()) ! null) { sb.append(line); } } this.body sb.toString(); } Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream bais new ByteArrayInputStream(body.getBytes()); return new ServletInputStream() { Override public boolean isFinished() { return bais.available() 0; } Override public boolean isReady() { return true; } Override public void setReadListener(ReadListener readListener) {} Override public int read() throws IOException { return bais.read(); } }; } Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader( new ByteArrayInputStream(body.getBytes()))); } public String getRequestBody() { return this.body; } }在 Filter 中LoggingRequestWrapper wrappedRequest new LoggingRequestWrapper(request); System.out.println(REQUEST BODY: wrappedRequest.getRequestBody()); // ✅ 可多次读取 chain.doFilter(wrappedRequest, response);4. JSP 页面调试从NullPointerException到EL 表达式不解析的血泪排查4.1NullPointerException在% request.getAttribute(msg) %的真实来源JSP 中常见% String msg (String) request.getAttribute(msg); % % msg ! null ? msg : %但运行时报NullPointerException。表面看是msg为 null实际是request.getAttribute(msg)返回null强制转型(String)时触发 NPE。安全写法两种EL 表达式推荐${empty msg ? : msg}—— EL 自动处理 nullJSP Scriptlet需判空% Object msgObj request.getAttribute(msg); String msg (msgObj ! null) ? msgObj.toString() : ; % % msg %4.2 JSTL 标签不生效c:forEach显示原始标签文本的三大原因页面写c:forEach items${list} varitem${item.name}/c:forEach浏览器却显示c:forEach ...字符串。原因现象原因解决方案web.xml中web-app版本为2.3JSTL 1.2 要求 Servlet 2.4将web.xml第一行改为web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd version3.1WEB-INF/lib/缺jstl-1.2.jar或standard.jarJSTL 实现缺失下载 jstl-1.2.jar 放入lib/JSP 顶部未声明 taglib% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %缺失在 JSP 第一行添加该声明注意urihttp://java.sun.com/jsp/jstl/core是 JSTL 1.2 标准 URI若用http://jstl.sourceforge.net/jstl1.2会 404。4.3 图片坐标定位失效img srcimages/logo.png404 的路径黑洞JSP 中写img srcimages/logo.png但图片不显示。检查webapps/supermarket/images/logo.png确实存在问题出在JSP 的src是相对当前 URL 路径不是相对 JSP 文件路径。例如访问http://localhost:8080/supermarket/admin/goods/list.jsp浏览器会向http://localhost:8080/supermarket/admin/goods/images/logo.png发请求 —— 而图片实际在webapps/supermarket/images/。根路径写法万无一失img src${pageContext.request.contextPath}/images/logo.png altlogocontextPath动态返回/supermarket确保路径始终正确。5. 避坑指南五类高频翻车现场与秒级定位法5.1 Tomcat 启动卡在INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [X] milliseconds后无响应现象控制台停在启动完成日志但http://localhost:8080无法访问netstat -an | grep 8080显示端口未监听。原因conf/server.xml中Connector port8080被其他进程占用或conf/web.xml中welcome-file-list指向不存在的index.jsp导致 Tomcat 启动后立即因 404 关闭连接。解决执行lsof -i :8080Mac/Linux或netstat -ano | findstr :8080Windows查占用进程检查webapps/ROOT/index.jsp是否存在或注释掉conf/web.xml中welcome-file-list块。5.2 登录成功后跳转http://localhost:8080/supermarket/main.jsp却 404现象LoginServlet中response.sendRedirect(/main.jsp)但浏览器地址栏变为http://localhost:8080/main.jsp丢失 contextPath。原因sendRedirect()参数以/开头时是相对于域名根路径不是应用上下文路径。解决改为response.sendRedirect(request.getContextPath() /main.jsp)或在web.xml中配置welcome-file-list指向main.jsp让用户访问根路径自动跳转。5.3 MySQL 连接池com.mchange.v2.c3p0.ComboPooledDataSource报java.lang.NoClassDefFoundError: com/mchange/v2/c3p0/ComboPooledDataSource现象启动时报NoClassDefFoundError但WEB-INF/lib/下明明有c3p0-0.9.1.2.jar。原因c3p0依赖mchange-commons-javaZIP 包中漏传该 JAR。解决下载 mchange-commons-java-0.2.3.4.jar 放入WEB-INF/lib/。5.4 Filter 链执行顺序错乱LoginFilter在EncodingFilter之后执行导致中文参数乱码现象LoginFilter中request.getParameter(username)是乱码但EncodingFilter已设置setCharacterEncoding(UTF-8)。原因web.xml中filter-mapping顺序决定执行顺序LoginFilter的 mapping 排在EncodingFilter之前。解决调整web.xml中filter-mapping顺序确保EncodingFilter在最前filter-mapping filter-nameEncodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping filter-mapping filter-nameLoginFilter/filter-name url-pattern/*/url-pattern /filter-mapping5.5 JSP 编译失败org.apache.jasper.JasperException: Unable to compile class for JSP现象首次访问 JSP 报 500日志含javac: invalid flag: -source 8。原因Tomcat 使用的 JDK 版本如 JDK 17与 JSP 编译器-source参数不兼容而项目web.xml声明为 Servlet 2.5对应 JDK 5/6。解决在conf/catalina.properties中添加tomcat.util.scan.StandardJarScanFilter.jarsToSkip*.jar # 强制 JSP 编译器使用 JDK 8 兼容模式 org.apache.jasper.compiler.Generator.SOURCE_VM8 org.apache.jasper.compiler.Generator.TARGET_VM8并确保JAVA_HOME指向 JDK 8如jdk1.8.0_361。6. 进阶技巧用 Filter 实现“操作留痕”与 JSP 动态权限裁剪6.1 操作留痕 Filter记录谁在何时修改了哪条商品数据超市系统要求审计goods_id123的价格被谁在什么时间修改。单纯记日志不够需关联业务主键。方案利用HttpServletRequestWrapper提取请求体中的goodsId和price再通过ThreadLocal透传到 DAO 层// AuditFilter.java public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; String uri request.getRequestURI(); if (uri.contains(GoodsServlet) request.getMethod().equals(POST)) { LoggingRequestWrapper wrapped new LoggingRequestWrapper(request); String body wrapped.getRequestBody(); // 解析 JSON 或 form-data提取 goodsId 和 price MapString, String params parseParams(body); AuditContext.setAuditInfo( getCurrentUser(wrapped), params.get(goodsId), price, params.get(oldPrice), params.get(newPrice) ); } chain.doFilter(wrapped, resp); } // AuditContext.javaThreadLocal 工具类 public class AuditContext { private static final ThreadLocalAuditInfo context new ThreadLocal(); public static void setAuditInfo(String user, String id, String field, String oldVal, String newVal) { context.set(new AuditInfo(user, id, field, oldVal, newVal)); } public static AuditInfo getAuditInfo() { return context.get(); } public static void clear() { context.remove(); } }在GoodsDAO.updatePrice()方法末尾AuditInfo info AuditContext.getAuditInfo(); if (info ! null) { auditLogMapper.insert(info); // 插入 audit_log 表 AuditContext.clear(); }6.2 JSP 权限裁剪用自定义 TagLib 实现auth:hasRole roleADMIN动态隐藏按钮main.jsp中管理员才能看到“删除商品”按钮但用if (user.getRole().equals(ADMIN))写 Java 代码太重。用自定义 TagLib 更优雅步骤 1编写 Tag Handler// HasRoleTag.java public class HasRoleTag extends SimpleTagSupport { private String role; public void setRole(String role) { this.role role; } Override public void doTag() throws JspException, IOException { HttpServletRequest request (HttpServletRequest) ((PageContext) getJspContext()).getRequest(); User user (User) request.getSession().getAttribute(user); if (user ! null user.getRole().equals(role)) { getJspBody().invoke(null); // 渲染标签体 } } }步骤 2定义 TLD 文件WEB-INF/auth.tld?xml version1.0 encodingUTF-8? taglib xmlnshttp://java.sun.com/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd version2.1 tlib-version1.0/tlib-version short-nameauth/short-name uri/auth/uri tag namehasRole/name tag-classcom.supermarket.tag.HasRoleTag/tag-class body-contentJSP/body-content attribute namerole/name requiredtrue/required rtexprvaluefalse/rtexprvalue /attribute /tag /taglib步骤 3JSP 中使用% taglib prefixauth uri/auth % ... auth:hasRole roleADMIN button onclickdeleteGoods(${goods.id})删除/button /auth:hasRole6.3 我的血泪习惯每次部署前必做的三件事grep -r System.out.println src/删掉所有调试打印尤其LoginServlet里System.out.println(password)—— 生产环境密码明文打日志是重大安全漏洞find webapps/ -name *.jsp | xargs grep -l scriptlet统计含%的 JSP 数量超过 5 个就说明业务逻辑没抽离到 Servlet后续维护成本指数级上升curl -I http://localhost:8080/supermarket/login.jsp用curl检查 HTTP Header确认Content-Type含charsetUTF-8X-Content-Type-Options: nosniff存在 —— 这是基础安全水位线。这套组合拳跑通后你拿到的不再是一个“毕业设计源码”而是一套可审计、可追溯、可灰度发布的轻量级业务系统骨架。它不时髦但每个 Filter 都是真实业务规则的映射每行 JSP 都直指终端用户操作。我带过的 7 个校企合作项目最终交付给超市的信息科主任时他们最常问的不是“用了什么新技术”而是“这个 Filter 能不能改成按门店分权限”——这才是技术落地的实感。希望帮到你。本文还有配套的精品资源点击获取