简介这是一份基于JspTomcatServletFilter架构的超市管理系统完整源码与数据库打包面向计算机、数学、电子信息等专业的学生可作为课程设计、期末大作业或毕业设计的参考。项目围绕用户与账单管理等核心功能展开通过Servlet接收请求、Filter处理过滤、JSP渲染界面并配套数据库脚本解压后即可部署运行。压缩包包含507个文件主要由132个class编译类、72个jar依赖库、63个JSP页面、31个Java源文件、15个CSS样式及数据库sql文件组成便于查看逻辑、修改样式和扩展功能。资源包大小29.9MB体积适中。目前已有108人学习下载适合具备一定Java Web基础、能读懂代码并希望实践综合项目的开发者用来学习分层架构、会话控制与数据库交互或在此基础上二次开发。1. 到底该不该接手这个老技术栈的超市管理系统当有人丢给你一个“基于JspTomcatServletFilter的超市管理系统源码数据库.zip”第一反应可能是“都2025年了还玩 JavaWeb 老一套”。但这类项目恰恰是课程设计和中小型内部系统里存活率最高的形态依赖少、启动快、逻辑透明一个 Tomcat 加一个 MySQL 就能跑改起来比动辄几十个 starter 的 Spring Boot 工程要直白得多。它解决的核心诉求很具体商品档案、分类库存、供应商、员工登录、前台收银和订单记录。适合两类人——需要交课设作业的学生和要在一周内交付一个社区小超市管理系统的初级开发者。与其纠结换不换框架不如先把这套组合吃透。2. JspTomcatServletFilter 这套组合为什么课设和中小系统还在用它2.1 一条请求从登录到商品列表经过几个环节老一套的请求链路一句话就能讲完浏览器请求先进 FilterFilter 决定放不放行放行后到达 ServletServlet 拿参数调 DAO 查数据库最后把结果放进 request 域转发给 JSP 渲染。整个链路上每一步都是公开的类和方法没有框架层帮你“魔法般”完成工作所以断点调试非常舒服。我不止一次遇到刚转 Spring Boot 的同事一个过滤器只因为 Bean 装配顺序不对就排查半天在 ServletFilter 里完全不会有这种黑匣子。这个组合另一个被低估的点是 Filter。登录拦截、字符编码、接口访问白名单三种最常见的基础设施都能在 web.xml 或WebFilter注解里统一声明不侵入任何业务代码。偶尔有人问 Filter 和 Spring 拦截器差在哪本质差别很小——Filter 是 Servlet 规范的一部分Tomcat 原生支持不需要引入 Spring 就能用拦截器依赖 SpringMVC 容器老项目里为了一个登录校验去引全家桶得不偿失。值得注意的是 Filter 的执行顺序完全由 web.xml 里filter-mapping的声明顺序决定声明靠前的先执行。字符编码过滤器必须排在权限过滤器之前否则请求参数在被读了一次之后才转码中文乱码就成了定局。这一点在源码包自带多个 Filter 时尤其要留意也是我读任何老项目时最先看 web.xml 的原因。2.2 解压后先别急着导入 IDE把目录结构读一遍拿到压缩包我一般不会先打开 IDEA而是先解压看结构。常见的 JSP 老项目非 Maven目录长这样supermarket/ ├── src/ │ ├── com/supermarket/ │ │ ├── entity/ # 实体类Product、Employee、Order │ │ ├── dao/ # JDBC 数据访问 │ │ ├── servlet/ # 控制器 │ │ └── filter/ # 登录/编码过滤器 │ └── db.properties # 数据库连接参数 ├── web/ │ ├── WEB-INF/ │ │ ├── web.xml # Servlet/Filter 映射声明 │ │ └── jsp/ # 受保护的 JSP 页面 │ ├── static/ # css/js/图片 │ ├── login.jsp │ └── index.jsp └── docs/ └── supermarket.sql # 数据库脚本这个结构本身就暗示了项目的答案web.xml 是路由中心db.properties 是唯一要改的配置文件JSP 放在 WEB-INF 下意味着只能通过 Servlet 转发访问。先花十分钟把 web.xml 里servlet-mapping和 Filter 声明顺序读一遍比直接跑起来再猜 404 要省一个晚上。如果你拿到的包是 Maven 结构则多一个 pom.xml依赖集中声明编译打包一条mvn clean package完成如果不是 Maven则需要在 WEB-INF/lib 下手动放 jar 包。判断方法很简单看根目录有没有 pom.xml。有就按 Maven 工程导入没有就在 IDEA 的 Artifacts 配置里把依赖的 jar 一并打进去这一步做漏部署到独立 Tomcat 就会抛NoClassDefFoundError。2.3 最小启动流程从数据库脚本到看到登录页跑通这类项目的最小流程是五步启动 MySQL导入 SQL 脚本改 db.properties 里的账号密码把工程部署到 Tomcat启动后访问http://localhost:8080/supermarket/。Tomcat 可以下载解压版不需要安装程序前提是环境变量JAVA_HOME指向的 JDK 版本和 Tomcat 匹配。# 以解压版 Tomcat 8.5 MySQL 8 为例 mysql -uroot -p docs/supermarket.sql # 修改 src/db.properties 里的 jdbc.password 后重新编译打包 # 将打包好的 supermarket.war 放入 Tomcat 的 webapps 目录 cp target/supermarket.war $CATALINA_HOME/webapps/ # 启动并观察日志出现 Server startup in ... ms 即成功 $CATALINA_HOME/bin/startup.sh tail -f $CATALINA_HOME/logs/catalina.out这段命令有两个实操点一个是用mysql 脚本在外部导入比在客户端里一句句执行可靠脚本里如果有USE supermarket也不会影响另一个是tail -f catalina.outTomcat 很多启动失败信息只在日志里出现控制台窗口反而被吞掉。在 IDEA 里配置 Tomcat 也同理关键是把 Deployment 里的 Application context 设为/supermarket并勾选 Deploy at server startup 自动部署。IDEA 配 Tomcat 时很多人卡在 Application context 这一步。我一般把它设为/supermarket这样启动后访问的就是http://localhost:8080/supermarket/login.jsp而不是默认的根路径。还要分清 Update resources 和 Update classes and resources改 JSP 用前者热更新改了 Java 类就要用后者重启上下文否则你会怀疑“为什么我改了代码没生效”。3. 数据库设计与初始化五张表把超市进销存串起来3.1 这个业务模型为什么只需要五张表超市管理系统听起来复杂去掉营销和会员以后核心数据只有五类商品、商品分类、供应商、员工、销售订单。商品挂在分类和供应商下面订单拆分出订单明细员工管操作人。复杂连锁超市的 ERP 可能有几十张表但一个课设或社区小超市的源码包五张主表加两张关联表已经是完整闭环。设计时有一条硬规矩金额用 DECIMAL 不用 FLOAT。浮点数在购物车里累加会出现 2.899999 这种玄学结果顾客看到价格不对会直接投诉。库存字段用 INT 并加默认值 0查询时配合stock 0就能做下架判断。下面这张表给了一个可参考的最小字段集表名核心字段约束与说明categoryid, name, remarkname 唯一分类不做层级supplierid, name, contact, phone供应商联系人productid, category_id, supplier_id, name, price, stock, statusprice DECIMAL(10,2)status 控制上下架employeeid, username, password, real_name, roleusername 唯一role 区分管理员/收银员sale_orderid, order_no, employee_id, total_amount, create_timeorder_no 唯一落单时间做索引order_itemid, order_id, product_id, quantity, price订单明细一对多关联 sale_order这种设计的边界也很明确不做多仓库、不打折促销、不记录会员积分。超过这个边界这套源码就要加表而这正是第 6 章要讲的方向。索引方面只对高频查询字段建product.name、sale_order.create_time和order_no。订单表每次收银都要按时间倒序查idx_create_time是最常被用到的索引不是越多越好每多一个索引INSERT 和 UPDATE 就要多维护一棵 B 树小系统里两三个索引已经够用。3.2 建库建表和初始化数据的 SQL 脚本拿到包里的数据库脚本通常是 supermarket.sql如果你打算重建库脚本主体和我下面这段等价。注意建库语句必须带字符集否则中文注释和商品名都会变成问号CREATE DATABASE IF NOT EXISTS supermarket DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE supermarket; CREATE TABLE category ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL UNIQUE, remark VARCHAR(200) ) ENGINEInnoDB; CREATE TABLE supplier ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, contact VARCHAR(20), phone VARCHAR(20) ) ENGINEInnoDB; CREATE TABLE product ( id INT AUTO_INCREMENT PRIMARY KEY, category_id INT NOT NULL, supplier_id INT, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, status TINYINT DEFAULT 1, KEY idx_category (category_id), KEY idx_name (name), CONSTRAINT fk_product_category FOREIGN KEY (category_id) REFERENCES category(id) ) ENGINEInnoDB; CREATE TABLE employee ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(30) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, real_name VARCHAR(30), role VARCHAR(20) DEFAULT CASHIER ) ENGINEInnoDB; CREATE TABLE sale_order ( id INT AUTO_INCREMENT PRIMARY KEY, order_no VARCHAR(20) NOT NULL UNIQUE, employee_id INT NOT NULL, total_amount DECIMAL(10,2) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_create_time (create_time), CONSTRAINT fk_order_employee FOREIGN KEY (employee_id) REFERENCES employee(id) ) ENGINEInnoDB; CREATE TABLE order_item ( id INT AUTO_INCREMENT PRIMARY KEY, order_id INT NOT NULL, product_id INT NOT NULL, quantity INT NOT NULL, price DECIMAL(10,2) NOT NULL, CONSTRAINT fk_item_order FOREIGN KEY (order_id) REFERENCES sale_order(id), CONSTRAINT fk_item_product FOREIGN KEY (product_id) REFERENCES product(id) ) ENGINEInnoDB; INSERT INTO employee (username, password, real_name, role) VALUES (admin, e10adc3949ba59abbe56e057f20f883e, 系统管理员, ADMIN); INSERT INTO category (name, remark) VALUES (饮料, 含碳酸饮料与果汁), (零食, 膨化食品与糖果), (日用品, 清洁与个人护理); INSERT INTO supplier (name, contact, phone) VALUES (恒丰食品, 李经理, 13800000001); INSERT INTO product (category_id, supplier_id, name, price, stock, status) VALUES (1, 1, 可乐 500ml, 3.50, 120, 1), (1, 1, 矿泉水 550ml, 2.00, 200, 1), (2, 1, 薯片 原味, 6.80, 80, 1);几个值得较真的选择所有表都指定 ENGINEInnoDB因为收银台要事务下单MyISAM 不支持行级锁外键约束保留它能阻止删掉正在被订单引用的商品这是老项目里最容易漏的admin 的密码是123456的 MD5 值源码包大多用这种存储方式只适合课设上线前要换成 BCrypt。如果包里的脚本是 utf8 而不是 utf8mb4建议先执行一条ALTER DATABASE supermarket CHARACTER SET utf8mb4否则表情符号和生僻字会在 JSP 页面显示成乱码。插入的示例数据也别急着删商品列表页没有数据时很多新手会误以为自己代码写错了。3.3 连接数据库的三个必调参数脚本导完决定系统能不能连上库的是 db.properties。这个文件通常是整个项目里唯一的“后悔药”改错了启动会报Communications link failure改对了就再也碰不到它。我给的参数模板如下jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/supermarket?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8 jdbc.usernameroot jdbc.password你的实际密码注意serverTimezone不写MySQL 8 的驱动会拿服务器默认时区去解析 DATETIME日期字段全部偏移 8 小时表现为“下单时间对不上”。参数按重要性排useSSLfalse是去掉本地连接的无谓加密握手不加也能连但会有一大段警告日志看着吓人characterEncodingutf8决定中文从 MySQL 到 JSP 全程不乱码少一个都可能出现“???”。驱动类名也要分清MySQL 5 是老驱动com.mysql.jdbc.DriverMySQL 8 是com.mysql.cj.jdbc.Driver两个写反都会启动即抛ClassNotFoundException——这种问题在搜索“tomcat启动出现 java.lang.ClassNotFoundException”时能找到一堆同款帖子。如果连接池里配的是 maxActive20但系统并发只有 5 个收银员可以放心下调到 10。连接数不是越大越好每个连接都是 MySQL 的一个线程配大了反而把数据库拖垮——这是我在一个会员系统上踩过的坑连接池超限最典型的报错是Too many connections。4. 用 FilterServletJsp 打通登录到商品列表核心代码与参数说明4.1 Filter 权限拦截与放行规则登录拦截是这类项目里最重要的 Filter。我见过不少把权限判断写在每个 Servlet 里的写法代码重复不说漏一个接口就裸奔。正确做法是集中在 LoginFilter 里用一条链统一处理package com.supermarket.filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; WebFilter(/*) public class LoginFilter implements Filter { Override 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.contains(/static/) || uri.contains(/error)) { chain.doFilter(req, resp); return; } // 用 getSession(false) 避免为每个请求创建无用的会话 HttpSession session request.getSession(false); if (session ! null session.getAttribute(loginUser) ! null) { chain.doFilter(req, resp); } else { response.sendRedirect(request.getContextPath() /login.jsp); } } }这段代码有三个细节值得说。第一是WebFilter(/*)配合 Servlet 3.0省去在 web.xml 里写映射但如果包里 web.xml 版本低于 3.0注解不会生效要在 web.xml 里补filter和filter-mapping。第二是放行条件的顺序只要请求 URI 以/login.jsp结尾就放行意味着登录页本身不需要会话否则会死循环——被拦截跳转到登录页登录页又要被拦。第三是getSession(false)参数为 false 时没有会话就返回 null不会新建会话这一点在高并发下能省不少内存也是网上常搜的“servlet 登录拦截 session 判断”的标准写法。4.2 Servlet 做商品分页查询的请求入口Filter 只管放行真正的参数解析在 Servlet 里。商品列表页最常见的需求是分页和关键字搜索一个 ProductServlet 就能把这两件事做完package com.supermarket.servlet; import com.supermarket.dao.ProductDao; import com.supermarket.entity.Product; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.List; WebServlet(/product/list) public class ProductServlet extends HttpServlet { private final ProductDao productDao new ProductDao(); Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // page 参数默认为 1直接拼进 SQL 做分页 int page 1; String pageStr request.getParameter(page); if (pageStr ! null !pageStr.isEmpty()) { try { page Integer.parseInt(pageStr); } catch (NumberFormatException e) { page 1; // 用户手输 ?pageabc 时兜底 } } String keyword request.getParameter(keyword); ListProduct list productDao.findByPage(page, 10, keyword); // 把结果放进 request 域转发到 JSP request.setAttribute(list, list); request.setAttribute(currentPage, page); request.getRequestDispatcher(/WEB-INF/jsp/product_list.jsp) .forward(request, response); } Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }两个参数层面的实践Integer.parseInt(pageStr)在用户传?pageabc时会抛 NumberFormatException更稳的写法是 try-catch 后再兜底成 1用户手输 URL 是很常见的findByPage(page, 10, keyword)里的 10 是每页条数老项目喜欢写死在代码里改成从常量类读取会方便后期调。转发到/WEB-INF/jsp/下的页面而不是直接重定向是因为 WEB-INF 对浏览器不可见用户无法绕过 Servlet 直接看 JSP 源码结构这是安全上的一个基础细节。doPost 里直接转调 doGet则表单搜索和链接点击共用同一套逻辑少写一遍重复代码。4.3 JSP 页面用 EL 和 JSTL 渲染列表JSP 最容易被写烂的地方是嵌入大量%Java 代码。维护过一个把 200 行 Java 写进 jsp 的项目之后你会认同“页面里只留标签和 EL”这条铁律。商品列表页面的循环渲染用 JSTL 的c:forEach加 EL 表达式十几行搞定% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html body table border1 tr thID/thth商品名/thth分类/thth价格/thth库存/th /tr c:forEach items${list} varp tr td${p.id}/td td${p.name}/td td${p.categoryId}/td td${p.price}/td td${p.stock}/td /tr /c:forEach /table a href${pageContext.request.contextPath}/product/list?page${currentPage 1}下一页/a /body /html${p.id}这种 EL 写法会自动调用 Product 实体的 getId() 方法并且自带 HTML 转义商品名里出现script也不会被执行这是 JSTL 胜过字符串拼接的关键理由。页脚那个“下一页”链接用${pageContext.request.contextPath}拼应用名避免部署路径改了链接全部失效。如果你下载的包里 JSP 大量使用%%输出我一般会顺手把它们改写成 EL 表达改动量不大安全性提升立竿见影。如果你那个包登录后自带一个员工个人信息展示页面同理把${sessionScope.loginUser.realName}写进 EL而不是%session.getAttribute(...)%可读性完全不在一个层次。5. 部署运行避坑Tomcat 启动、JDK 版本和乱码的五个现场5.1 Tomcat 启动闪退与端口占用现象双击 startup.bat 窗口一闪而过浏览器访问 8080 无响应点关闭按钮直接结束进程整台机器像被“劝退”了一样。原因绝大多数是JAVA_HOME没配或端口被占用。闪退时 Tomcat 会把错误写在logs/catalina.out或logs/localhost.log但窗口关闭得太快看不到内容所以第一动作永远是看日志不是重装 Tomcat。解决Windows 下在命令行里手动执行%CATALINA_HOME%\bin\catalina.bat run错误会留在前台端口占用用netstat -ano | findstr 8080找到 PID 后结束进程或在conf/server.xml里把 Connector port 改成 8081 一劳永逸。记得改完删掉 Tomcat 里work/目录下的缓存否则“改了配置不生效”的现象能骗你半天。5.2 JDK 25 与老版本 Tomcat 不兼容现象Tomcat 直接起不来日志报UnsupportedClassVersionError或ClassCastException下载最新 JDK 反而更严重。热词里常刷到“jdk 25 tomcat 安装配置”很多人以为越新越好。原因Tomcat 9 之前的版本基于旧 Servlet 规范编写用新版 JDK 编译的 class 文件它读不了而新版 JDK 又移除了很多老 Tomcat 依赖的 API。更关键的是 Tomcat 10 之后包名从javax.*迁到了jakarta.*老源码的 import 全部失效不是简单换个版本就能编译的。解决老项目严格按 JDK 8 Tomcat 8.5 或 9 搭配新一点的源码可以 JDK 11 Tomcat 9。版本对应关系我一般按这张表记JDK 版本建议 Tomcat注意点JDK 8Tomcat 8.5 / 9.0老源码最稳javax.* 无痛JDK 11Tomcat 9.0.x兼容大多数老项目JDK 17 及以上Tomcat 10.1代码必须迁移到 jakarta.*排查时在命令行java -version确认版本多个 JDK 共存的机器要把JAVA_HOME指到项目需要的那个IDEA 的 Project SDK 和 Tomcat 运行配置里的 JRE 也分别检查一遍。这三处任何一个不一致都等于白配。5.3 404 与 Servlet 映射路径不一致现象登录成功后跳转商品列表浏览器地址栏 URL 是对的页面却 404控制台没有任何 Java 报错——这是最让人头大的“没报错就是不对”。原因先排除路径问题。Servlet 如果用的是注解WebServlet(/product/list)但 web.xml 里还残留一份servlet-mapping指向旧路径或 JSP 里的表单 action 写成了product/list而少了前导斜杠就会出现这种“看起来没坏但就是找不到”。解决统一映射来源注解和 web.xml 二选一我优先用注解并删掉 web.xml 里的重复声明。表单和链接里的地址一律用${pageContext.request.contextPath}开头例如form action${pageContext.request.contextPath}/product/list这样部署名怎么改都不翻车。排查时先用浏览器开发者工具看 Network 里的请求 URL再对照 Servlet 映射两相对比 99% 能定位。5.4 Tomcat Manager 上传 war 被限制 IP现象在 Tomcat 后台页面点 Deploy 上传 war返回 403 Access Denied页面上还写着默认只允许本地地址。网上搜“tomcat后台页面上传war被限制ip”能搜到一堆相同提问这不是个例。原因Tomcat Manager 应用默认用 RemoteAddrValve 限制源 IP只放行 127.0.0.1。这是保护措施不是 bug远程改配置改错了反而可能把后台彻底锁死连本地都登不进去。解决开发环境直接在webapps/manager/META-INF/context.xml里注释掉Valve classNameorg.apache.catalina.valves.RemoteAddrValve allow127\.\d\.\d\.\d|::1|0:0:0:0:0:0:0:1 /重启 Tomcat 后用http://localhost:8080/manager登录账号密码在conf/tomcat-users.xml里配置 manager-gui 角色。生产环境不建议外露 Manager更稳妥的是直接把 war 放进 webapps 目录让它自动解压部署根本不需要开后台。5.5 中文乱码从页面到数据库三层排查现象商品名称和管理员姓名在 JSP 显示成“???”数据库里存的就是乱码怎么改页面编码都不管用。网上常搜“jsp页面个人信息展示乱码”和“mysql数据库修改结构”本质都是同一件事。原因乱码有三个可能层缺一不可查清请求编码、响应编码、数据库编码。最常见的是连接串没加characterEncodingutf8JSP 页面加了% page contentTypetext/html;charsetUTF-8%而数据库表却是 utf8两边一凑就是乱码。解决按这个顺序排查——先看页面源码是不是 UTF-8再给 Servlet 入口加request.setCharacterEncoding(UTF-8)最后执行SHOW CREATE TABLE product确认表字符集。改表用ALTER TABLE product CONVERT TO CHARACTER SET utf8mb4记住只改连接参数不改表结构等于白忙。请求、响应、数据库三层全部统一到 UTF-8 之后乱码问题基本绝迹。6. 把课设改成能用的系统连接池、库存事务与刷新防重复源码包能跑只是起点离“真能在一个小超市上线”还差三样东西连接池、库存事务、防重复提交。这三样恰好是本项目最薄弱也最值得花时间的进阶点。第一件把DriverManager.getConnection换成连接池。老源码每个 DAO 里都写一次连接获取并发一高就在Too many connections上翻车。常见做法是引入 Druid 或 HikariCP在 db.properties 里配initialSize5、maxActive10DAO 层从一个统一的 DBUtil 类取连接。改动只集中在一个类里业务代码几乎不动收益却立竿见影。第二件下单扣库存必须走事务。结算时先执行带条件的 UPDATE受影响行数为 0 说明库存不足立即回滚并提示顾客否则 total_amount 和明细数量对不上账START TRANSACTION; UPDATE product SET stock stock - 1 WHERE id 1001 AND stock 1; -- 如果受影响行数为 0说明库存不足立即 ROLLBACK INSERT INTO order_item (order_id, product_id, quantity, price) VALUES (20240101001, 1001, 1, 3.50); UPDATE sale_order SET total_amount total_amount 3.50 WHERE id 20240101001; COMMIT;两个收银员同时买最后一件商品时靠这条带条件的 UPDATE 配合 InnoDB 行锁就能挡住超卖不需要业务层加 synchronized。这是并发场景里最容易被忽略的一课也是数据库并发锁机制实实在在的用武之地。第三件是一个很小的 JSP 习惯表单提交后不要在服务端原样转发回页面而是重定向到列表页。否则用户按 F5 刷新浏览器会重复提交同一笔订单系统里出现两条一模一样的销售记录——这就是网上常搜的“jsp页面让加载完后刷新一次”的真实场景。用 Post-Redirect-Get 模式或者下单成功后立刻清空页面里的订单号都能把重复提交挡在门外。我自己每接到一个这样的包都会先按第 2 章的目录结构把骨架读一遍再按第 5 章的日志优先顺序排雷最后确认连接池和事务有没有到位。这套顺序帮我挡掉过好几台“demo 能跑、上线就垮”的翻车机器。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?