news 2026/10/28 13:20:21

JSP+Servlet+MySQL购物网站部署指南:从环境配置到避坑实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JSP+Servlet+MySQL购物网站部署指南:从环境配置到避坑实践

简介:基于JSP+Servlet+MySQL开发的蛋糕甜品店购物网站源码项目,面向JavaWeb课程设计、毕业设计或入门学习者。系统包含管理员与用户两套角色:管理员可登录后台完成订单管理、客户管理、类目管理等操作;用户则支持首页浏览、商品分类、热销与新品查看、注册登录、个人中心、加购、提交订单及模拟支付等流程。项目采用HTML、CSS、JavaScript、JSP、Servlet、DAO分层设计,由MySQL持久化数据:前端负责展示、Servlet控制请求转发、DAO封装数据库访问,并附带使用文档、数据库脚本及默认账号密码(管理员admin/admin,用户user1/123456)。压缩包共221个文件,大小约7.23MB,主要类型涵盖Java源文件、编译后的class文件、JSP页面、SQL脚本、JAR依赖包、JS/CSS及图片资源,结构与JavaWeb典型分层一致,便于按模块定位源码。目前已有186人学习浏览,可作为理解Servlet请求处理、DAO数据交互与JSP渲染的实践参考,也适合直接运行后二次开发。

1. 开箱一个课设级 JSP 购物网站:这个 zip 值不值得费劲跑起来

手里拿到一个「基于jsp+servlet+mysql蛋糕甜品店购物网站源码+使用文档+数据库(高分项目).zip」这样的压缩包,最要紧的不是急着解压,而是先搞清楚你面对的是什么东西。这是一个典型的 Java Web 课程设计项目:JSP 负责展示页面,Servlet 负责接收浏览器请求和跳转,MySQL 负责把商品、订单、用户数据落盘。附带的使用文档和数据库文件,意味着你不需要从零搭架子,只要环境正确,理论上导入数据库、部署到 Tomcat 就能跑。它适合正在做课程设计、数据库实践,或者想找个经典 Web 项目源码来练手的开发者。但它不是生产级电商系统,别拿它直接做线上售卖。下面我会按真实部署顺序,把环境、数据库、代码、排错、二次改造这一路讲透。

2. 环境准备与技术选型:JDK、Tomcat、MySQL 的版本搭配为什么卡在这里

很多同学拿到源码后第一件事是装最新版 JDK 和 MySQL,结果往往在启动 Tomcat 那一刻就翻车。这套老牌技术栈对环境版本非常敏感,不是越新越好。先把三层结构和版本搭配这块地基打牢,后面才不用反复折腾环境。

2.1 三层结构怎么拆:JSP 管展示、Servlet 管控制、MySQL 管存储

在 JSP+Servlet+MySQL 这套组合里,我建议你先建立一张请求流转图。浏览器发起路径比如/product/list,Tomcat 根据 web.xml 或注解找到对应的 Servlet。Servlet 做两件事:从 request 里拿参数,调用 DAO 层去查 MySQL;然后把结果放到 request 属性里,forward 给 JSP 渲染。JSP 最终生成 HTML 返回浏览器。这一步里,Servlet 是控制层,JSP 是视图层,DAO 是数据访问层,三层各自只干一件事。

打开源码里的 web.xml,你会看到大量这种映射:

<servlet> <servlet-name>ProductServlet</servlet-name> <servlet-class>com.shop.web.ProductServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>ProductServlet</servlet-name> <url-pattern>/product/list</url-pattern> </servlet-mapping>

这段配置的意思是:浏览器访问/product/list时,Tomcat 去创建并调用com.shop.web.ProductServlet这个类的实例。url-pattern是浏览器地址栏里看到的那段路径,servlet-class是编译后的完整类名,二者只要错一个,页面就会变成 404。你拿到源码后先搜索<servlet-mapping>,基本上就能看出这个网站有哪些功能模块:登录、注册、商品列表、商品详情、购物车、订单,多半都有对应的 Servlet。

为什么课程设计偏爱这套结构而不是直接上 Spring Boot?因为 JSP+Servlet 能更直白地展示 HTTP 请求、Servlet 生命周期、Session、JDBC 这些 Java Web 基础概念。答辩时被问到“请求怎么从页面到数据库再回来”,你可以顺着 web.xml 一路讲出来。Spring Boot 帮你把底层都藏了,反而讲不清楚。我一般会先看源码的包结构,如果没有servlet/dao/entity这种分包,而是所有类都堆在默认包,那项目可维护性会比较差,读代码时要多留个心眼。

这里也要提醒一句:不要在 JSP 页面里直接写 JDBC 查询。虽然小项目这么干也能跑,但后续你改表结构、加权限、换数据库,都会变成灾难。正确的做法是 JSP 只负责用 EL 和 JSTL 把后台传来的数据渲染出来,所有数据库操作都收拢到 DAO 层。

2.2 检查环境的三条命令与版本搭配注意

老项目最常见的稳定组合是 JDK 1.8、Tomcat 8.5 或 9.0、MySQL 5.7。如果你用 JDK 17 配合 Tomcat 10,会遇到一个很容易忽略的问题:Tomcat 10 把包名从javax.servlet改成了jakarta.servlet,这套源码里的 import 几乎全部会报错。所以我建议你严格按照项目使用文档里的版本来,如果文档没写,优先 JDK 8 + Tomcat 9 + MySQL 5.7 这个组合。

先打开命令行,确认环境变量没配错:

java -version javac -version mysql --version

java -version只代表 JRE 存在,javac -version才能证明 JDK 装好了。如果 javac 报“不是内部或外部命令”,说明 JAVA_HOME 没配或者 PATH 里没加%JAVA_HOME%\bin。mysql --version是客户端版本,它和 MySQL 服务端版本可能不同,但只要命令能执行,说明客户端能用。

如果你还没装 MySQL,先跟着一份 mysql 安装配置教程装完并记住 root 密码,后面导入数据库要用它。装 5.7 还是 8.0 都可以,但有个关键点:MySQL 8.0 默认认证插件是caching_sha2_password,老源码里的mysql-connector-java 5.x驱动会连不上,解决办法是换成 8.x 驱动包,或者把用户改为mysql_native_password认证。这个坑在本地课设环境里特别常见,我至少帮三个人改过这一处。

Tomcat 的检查方式是直接双击bin/startup.bat,然后访问 http://localhost:8080 ,看到 Tomcat 首页就算过了第一关。如果端口被占,会弹出一个黑色的命令行窗口闪退,先别急着重启,后面避坑章节会专门讲怎么处理端口问题。环境阶段最浪费时间的就是“明明代码没问题,但 Tomcat 起不来”,所以启动 Tomcat 前,把 JDK 版本、MySQL 版本、驱动 jar 这三件事一次性确认好,能省掉大量来回试错的时间。

3. 数据库设计与导入:把 SQL 文件变成能下单的表结构

数据库是这套项目的心脏。课程设计的购物网站,不管页面多华丽,最后分数都落在表结构设计和数据一致性上。我们先把表结构讲明白,再手把手把 SQL 导进去。

3.1 甜品店购物网站的典型表设计:为什么必须把用户、商品、订单拆开

购物网站如果把用户地址、商品信息、订单内容全部塞进一张表,会产生大量重复数据,改一个商品名就要更新几十行。所以常见做法是拆成多张表,每张表只存一类实体。这个项目里至少应该有这六张核心表:

表名作用关键字段
user用户表id, username, password, phone
category商品分类表id, name
product商品表id, category_id, name, price, stock, sales
cart购物车表id, user_id, product_id, quantity
orders订单主表id, order_no, user_id, total_price, status
order_item订单明细表id, order_id, product_id, quantity, price

订单主表和订单明细表为什么要分开?因为一个订单可能包含多个蛋糕,订单主表只记录总价和状态,明细表记录每一件商品的单价和数量。查询“这个订单里有哪些商品”时,通过order_id关联明细表就可以了。用户表和订单表按user_id关联,购物车表也是按user_id和product_id关联,这样数据没有冗余,改商品价格时只需要改 product 表一处。

建表时要注意两个细节:库存和销量这类数值字段,用INT NOT NULL DEFAULT 0,防止插入数据时因为漏填变成 NULL,这正好就是“mysql 设置默认值为 0”的典型场景;订单状态用 TINYINT 存 0 和 1 比存字符串更省空间。下面是一个简化的建表 SQL,你可以在 MySQL 里照着建一遍:

CREATE DATABASE IF NOT EXISTS cake_shop DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE cake_shop; CREATE TABLE user ( id INT NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password VARCHAR(255) NOT NULL, phone VARCHAR(20) DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE product ( id INT NOT NULL AUTO_INCREMENT, category_id INT DEFAULT NULL, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, sales INT NOT NULL DEFAULT 0, image VARCHAR(255) DEFAULT NULL, PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE orders ( id INT NOT NULL AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL, user_id INT NOT NULL, total_price DECIMAL(10,2) NOT NULL, status TINYINT NOT NULL DEFAULT 0, create_time DATETIME NOT NULL, PRIMARY KEY (id), KEY idx_user_id (user_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE order_item ( id INT NOT NULL AUTO_INCREMENT, order_id INT NOT NULL, product_id INT NOT NULL, quantity INT NOT NULL, price DECIMAL(10,2) NOT NULL, PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

建表全部使用 InnoDB 引擎,原因很直接:下单扣库存、插入订单、插入明细,这三步必须保证要么全部成功、要么全部回滚,这正是 mysql 事务处理的应用场景。MyISAM 不支持事务,万一写到一半失败,会出现订单有了但明细没有,或者库存扣了订单没生成的数据不一致问题。user表还加了唯一索引uk_username,防止同一个人注册两次。

这段 SQL 里DEFAULT CHARSET=utf8mb4很关键。如果你建的是 utf8,中文表情和部分生僻字会存不了,页面展示倒是没太大影响,但数据入库时会报错。DECIMAL(10,2)用来存价格,不要用 FLOAT 或 DOUBLE,因为浮点数计算金额会产生精度误差,算总价时出现 0.1 的问题很难排查。

3.2 导入数据库并验证:命令行 source 与两个关键查询

拿到项目包后,在sql目录或database目录下找到.sql文件,命名一般是cake_shop.sql。打开这个文件,先看第一行有没有CREATE DATABASE,如果有直接执行,如果没有就先手工CREATE DATABASE。导入最稳的方式不是用可视化工具点按钮,而是命令行 source,因为可视化工具偶尔会被编码问题干扰。

在项目根目录打开命令行,执行:

mysql -uroot -p source D:/cake_shop/cake_shop.sql;

source后面跟 SQL 文件的绝对路径。Windows 下路径分隔符用反斜杠需要转义,所以建议统一用正斜杠。导入过程中如果出现ERROR而不是Query OK,先看是不是文件里的CREATE DATABASE和现有库重名,或者表之间的外键顺序问题。多数课程设计项目的 SQL 文件是直接导出的,不会带外键,按顺序执行就行。

导入完成后,立刻做三件事验证数据是否正确:

USE cake_shop; SHOW TABLES; SELECT name, price, stock FROM product ORDER BY price DESC LIMIT 10; SELECT COUNT(*) FROM orders;

SHOW TABLES用来确认所有表都建出来了。ORDER BY price DESC就是 mysql 排序的标准写法,甜品店项目通常会在首页按价格倒序或销量倒序展示商品,这个查询是后面商品列表功能的直接来源。如果 product 表是空的,说明源码自带的数据库文件里没有商品数据,你需要去后台或直接手动插入几条样例数据,否则首页会一空白。

手动插入样例数据的写法:

INSERT INTO product (category_id, name, price, stock, sales, image) VALUES (1, '草莓奶油蛋糕', 128.00, 20, 0, 'images/cake1.jpg'), (1, '芒果千层', 98.00, 30, 0, 'images/cake2.jpg');

这里的image字段只存相对路径images/cake1.jpg,不存图片二进制,也不存 base64。原因很简单:图片文件放 web 项目的 images 目录里,通过 URL 访问更高效,数据库只保存索引。课堂上提到的“jsp 图片如何对坐标定位”这类问题,九成都是图片路径没写对,先把image字段和目录结构对应上,再谈样式定位。

导入数据库还有一个容易被忽略的常识:如果你改过 MySQL root 密码,那么源码里的 db 配置文件也必须同步改,否则连接数据库时会被拒绝。我自己就试过先改密码再跑项目,结果页面一直报 500,花十分钟才想起来配置文件还是旧密码。

4. 部署与核心代码走读:从 web.xml 到商品列表跑通一次完整请求

数据库就绪后,下一步是把源码放进 Tomcat 跑起来。这一章不只是讲怎么点按钮,我们会把 web.xml、数据库连接配置、Servlet、JSP 这条链路串起来,让你改代码时知道该去哪个文件里找。

4.1 源码目录结构:第一次打开时先找这四样东西

典型的 JSP 动态 Web 项目目录结构大致如下:

cake_shop ├── src │ ├── com.shop.util │ │ ├── DBUtil.java │ │ └── StringUtil.java │ ├── com.shop.dao │ │ ├── UserDao.java │ │ └── ProductDao.java │ ├── com.shop.servlet │ │ ├── LoginServlet.java │ │ ├── RegisterServlet.java │ │ └── ProductServlet.java │ └── com.shop.entity │ ├── User.java │ └── Product.java ├── WebContent │ ├── WEB-INF │ │ ├── web.xml │ │ └── lib │ │ └── mysql-connector-java-x.x.x.jar │ ├── css │ ├── js │ └── jsp │ ├── login.jsp │ ├── register.jsp │ ├── index.jsp │ └── product_list.jsp └── sql └── cake_shop.sql

如果你用的是 Eclipse,直接 File -> Import -> Existing Projects into Workspace 选择解压目录;如果你用 IDEA,选择 Import Project 后要指定 Tomcat 为应用服务器,并让 IDEA 自动生成 Artifacts。第一次导入源码时,先找四样东西:web.xml、DBUtil.java、mysql-connector-java驱动 jar、sql目录。这四样决定了项目能不能启动、连哪个库、有没有数据可看。

很多源码包里的驱动 jar 版本很老,比如mysql-connector-java-5.1.47.jar。如果你的 MySQL 是 8.0,我建议直接去 mysql 官网下对应版本的驱动替换掉。否则启动时驱动类能加载,但连接协议握手时就会抛Public Key Retrieval is not allowed,这是因为 MySQL 8 缓存了服务器公钥,老驱动不认识新的认证方式。

4.2 数据库连接配置:db.properties 与 JDBC 工具类参数说明

大多数课程设计项目会把数据库连接信息写在一个.properties文件里,放在 src 根目录,这样不用改 Java 代码就能换数据库。打开它你会看到类似这样的内容:

jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/cake_shop?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=123456

这里有一点要特别注意:驱动类com.mysql.jdbc.Driver是 MySQL 5.x 驱动包里的类名,MySQL 8.x 驱动包里改成了com.mysql.cj.jdbc.Driver。如果你替换了 8.x 驱动却没改这里,启动时它会提示ClassNotFoundException。URL 里serverTimezone=Asia/Shanghai在 MySQL 8 里是必须的,不写的话连接时报时区错误;useSSL=false是为了关掉本地连接时的不必要加密握手,可以省下不少失败概率。

接下来看连接数据库的工具类。源码里可能叫DBUtil.java,也可能是DbUtil.java,内容大同小异:

import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ResourceBundle; public class DBUtil { private static String url; private static String username; private static String password; static { ResourceBundle rb = ResourceBundle.getBundle("db"); String driver = rb.getString("jdbc.driver"); url = rb.getString("jdbc.url"); username = rb.getString("jdbc.username"); password = rb.getString("jdbc.password"); try { Class.forName(driver); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, username, password); } public static void close(Connection conn, Statement stmt, ResultSet rs) { try { if (rs != null) rs.close(); } catch (SQLException ignored) {} try { if (stmt != null) stmt.close(); } catch (SQLException ignored) {} try { if (conn != null) conn.close(); } catch (SQLException ignored) {} } }

ResourceBundle.getBundle("db")读取的是 classpath 根目录下的db.properties,所以配置文件不能放到WEB-INF下面,否则工具类找不到。Class.forName(driver)是加载驱动,这一步在 JDBC 4.0 以后可以省略,但老项目里保留着也完全没问题。每次查询完,关闭资源的顺序是先关 ResultSet,再关 Statement,最后关 Connection,顺序反了容易出现连接没释放干净的问题。

这里没有用连接池,因为课程设计项目并发量很低,DriverManager 每次现场建连接已经够用。如果追求更好表现,可以换成 Druid 或 HikariCP,但那会牵扯到更多依赖,不建议在复现阶段引入,先把项目跑起来再谈优化。

4.3 一次商品列表请求的完整链路:从 Servlet 到 JSP 渲染

商品列表是购物网站的核心页面。假设访问路径是/product/list,Servlet 里大概会是这样:

@WebServlet("/product/list") public class ProductServlet extends HttpServlet { private ProductDao productDao = new ProductDao(); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String keyword = request.getParameter("keyword"); String sort = request.getParameter("sort"); List<Product> list = productDao.findList(keyword, sort); request.setAttribute("list", list); request.getRequestDispatcher("/jsp/product_list.jsp").forward(request, response); } }

这段代码的逻辑是:从 URL 参数里拿到keyword和sort,调用 DAO 层查数据库,把结果 List 放进 request,然后 forward 到 JSP 页面。注意forward和redirect不一样,forward 是服务端内部跳转,浏览器地址栏不会变,所以刷新页面时会把同样的请求再发一次,这也是后面避坑章节要讲的重点。

DAO 层查询商品列表的典型写法:

public List<Product> findList(String keyword, String sort) { StringBuilder sql = new StringBuilder("SELECT * FROM product WHERE 1=1"); if (keyword != null && !keyword.trim().isEmpty()) { sql.append(" AND name LIKE ?"); } sql.append(" ORDER BY ").append(safeSort(sort)); // 使用 PreparedStatement 执行 sql,并处理参数 }

WHERE 1=1是拼接 SQL 时很实用的技巧,后面无论加不加 AND 条件都不用担心语法错误。sort排序字段不能直接拼接到 SQL 里,因为用户可能在 URL 里传name; DROP TABLE product之类的字符串,正确做法是白名单校验,只允许price或sales这两个值,这就是“jsp 个人信息展示页面”里同样要注意的安全边界。

最后是 JSP 渲染页面:

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <body> <c:forEach items="${list}" var="p"> <div class="cake-card"> <img src="${pageContext.request.contextPath}/${p.image}" alt="${p.name}"> <h3>${p.name}</h3> <span>&yen;${p.price}</span> <span>库存:${p.stock}</span> <a href="${pageContext.request.contextPath}/cart/add?productId=${p.id}">加入购物车</a> </div> </c:forEach> </body> </html>

所有图片和链接都加上了${pageContext.request.contextPath},这是 JSP 里获取项目上下文路径的标准写法。不加它的话,项目如果部署在http://localhost:8080/cake_shop下,浏览器会错误地到http://localhost:8080/images/cake1.jpg去找图,结果 404。这个坑在课设答辩现场出现率极高,一定要记住:资源路径永远用 contextPath 拼一次。

5. 部署与运行避坑:5 个让新手原地崩溃的常见问题排查

环境、数据库、代码都过了一遍,但真正让项目跑不起来的大多是几个固定问题。这一章是我见过最多的场景,每条都会按现象、原因、解决来讲,你可以把这一章直接当排查手册用。

5.1 数据库连接报错 Communications link failure

现象:Tomcat 启动正常,但一点页面就报Communications link failure或者Access denied for user 'root'@'localhost',有时候数据库驱动的异常栈会指向DBUtil.getConnection()。

原因:这个报错分两类。一类是 MySQL 服务根本没启动,连接被操作系统拒绝;另一类是用户名密码不对,认证失败。很多人以为 MySQL 装了就永远在跑,其实 Windows 上 MySQL 服务可能没有设置成自动启动,重启电脑后就处于停止状态。

解决:先在命令行确认服务状态:

net start mysql mysql -uroot -p

第一条命令让 Windows 服务管理器启动 MySQL,第二条命令手动验证账号密码。如果命令行能登录,说明问题不在 MySQL 而在连接配置文件;如果命令行都登录失败,说明密码记错了,需要用 MySQL 的初始化参数重置密码。另外,检查 db.properties 里用户名和密码前后有没有多余空格,很多复制粘贴会把空格一起带进去。

5.2 中文乱码:页面、请求、数据库三层编码不一致

现象:首页能打开,但商品名变成一串问号,登录注册之后用户名在页面上是乱码,数据库里也是乱码。最典型的场景是 jsp 个人信息展示页面,明明代码里写的是中文,浏览器显示出来却是???或汉å—。

原因:JSP 文件本身的保存编码、Tomcat 请求编码、数据库连接字符集三层不一致。比如 JSP 用pageEncoding="UTF-8",但数据库连接 URL 里没加characterEncoding=utf8;或者 MySQL 表是用 utf8 建的,但页面提交的表单是 GBK。

解决:三层全部固定为 UTF-8。JSP 页面头部写pageEncoding="UTF-8",数据库连接 URL 加characterEncoding=utf8,表结构用utf8mb4。同时给项目加一个统一的编码过滤器:

@WebFilter("/*") public class EncodingFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); chain.doFilter(request, response); } }

过滤器路径/*表示拦截所有请求,这样不管 GET 还是 POST 提交的参数,都会按 UTF-8 解码。注意过滤器要放在任何一个 Servlet 和 JSP 访问之前,只要它在 web.xml 里注册或使用注解,Tomcat 就会在请求进入容器时先执行它。改完编码后,重启 Tomcat,并把数据库里已经乱码的数据删掉重新插入,不要只改一半。

5.3 404 与 500:Servlet 映射和 class 位置到底谁说了算

现象:访问http://localhost:8080/cake_shop/product/list返回 404,或者在服务器日志里看到ClassNotFoundException: com.shop.web.ProductServlet,再或者访问 JSP 直接 500。

原因:代码没编译进部署目录。Eclipse 里源码会自动编译到WebContent/WEB-INF/classes,但 IDEA 导入后如果没有正确配置 Artifacts,编译结果可能根本没有被放进 war 包。另一个常见原因是@WebServlet注解无效,因为 web.xml 里写了metadata-complete="true",告诉 Tomcat 不要扫描注解。

解决:先停掉 Tomcat,执行 Project -> Clean 和 Build -> Rebuild Project,然后看部署目录下WEB-INF/classes/com/shop/web/ProductServlet.class是否存在。如果 web.xml 根标签里有metadata-complete="true",把它删掉或改成false,否则注解派生的 Servlet 映射全部不生效。使用注解时,@WebServlet("/product/list")的路径必须以/开头,少写一个斜杠也会 404。

5.4 表单重复提交:刷新一次订单就多一单

现象:下单页面填完信息点击提交,生成订单成功。用户手一抖按了 F5,浏览器弹出“重新发送表单”的确认框,点确认后又生成一单,数据库里出现两条一模一样的订单。还有人在商品列表页反复刷新,前端会重复执行加入购物车的操作。

原因:Servlet 在处理完 POST 请求后使用了forward跳转到成功页面。forward 是内部转发,浏览器地址栏还停留在原来的下单地址,刷新时自然把刚才的 POST 请求原样重放了一遍。网上有人说“jsp 页面让加载完后刷新一次”能解决,但那只是让页面活着再跳一次,治标不治本。

解决:下单成功之后不要 forward,要用 302 重定向,也就是执行一次新的 GET 请求:

response.sendRedirect(request.getContextPath() + "/order/success?orderNo=" + orderNo);

sendRedirect会让浏览器重新请求一个全新的地址,地址栏变成/order/success,这时候再刷新,重复执行的是查询订单成功页的 GET 请求,而不是下单 POST 请求。对于“加入购物车”这种操作,还可以在表单里塞一个一次性随机 token,提交时和 Session 里的值做比对,用过就作废,这样连重复点击都不会影响。

5.5 Tomcat 端口被占用与内存不足

现象:启动 Tomcat 时控制台报Port 8080 required by Tomcat v9.0 Server is already in use,或者 Eclipse/IDEA 控制台里的 Tomcat 闪退,日志没有明显异常。

原因:8080 端口被另一个进程占用了,可能是之前启动过没关干净的 Tomcat 实例,也可能装了其他软件占用。闪退则可能是 JVM 内存参数太小,老项目启动时类加载多,默认 256MB 不够用。

解决:先找到占用端口的进程:

netstat -ano | findstr :8080 taskkill /pid 进程号 /f

netstat输出最后一列是 PID,把 PID 填到taskkill里强制结束。如果你不想杀进程,也可以改 Tomcat 的端口,打开conf/server.xml,把 Connector 标签的port="8080"改成8081,但前端所有请求地址也要跟着变。内存不足时在 Tomcat 的启动脚本catalina.bat里加一行set CATALINA_OPTS=-Xms256m -Xmx512m,给 JVM 指定最小和最大堆内存,闪退问题通常就解决了。

6. 让这个课设项目更像作品:验收清单与两个零成本小改造

项目跑起来只是及格,想拿高分或者在大作业里更有底气,还需要做两件事:按用户真实动线走一遍验证,再把安全细节补上。

6.1 用一遍用户旅程做最终验收

不要只打开首页看两眼就关掉,我一般会模拟一个完整流程:注册新用户、退出、重新登录、浏览商品、按价格排序、加入购物车、修改数量、清空购物车、选择商品下单、查看订单列表、确认支付。每一步都记录实际结果和预期是否一致。重点看几个容易出错的地方:注册时用户名重复有没有提示;加入购物车后刷新页面会不会重复加;下单成功后再按后退按钮会不会回到表单页;订单状态从 0 到 1 时库存是否同步减少。这套流程走完,答辩时任何操作相关的问题都不会难住你。

6.2 两个改一行就能生效的细节:密码加盐与 PreparedStatement

答案是登录注册模块和相关 DAO 方法。第一处是密码存储,老源码常常明文存 password,答辩时老师一眼就能挑出毛病。简单做法是取一段随机字符串做盐,再把盐和密码拼接后做 MD5:

MessageDigest md = MessageDigest.getInstance("MD5"); String salt = UUID.randomUUID().toString().substring(0, 8); byte[] result = md.digest((salt + password).getBytes("UTF-8"));

把salt和result的十六进制字符串同时存到 user 表,登录时取出盐重新计算比对。这种方式不引入额外依赖,足够给课程设计加分。第二处是所有 SQL 查询,检查是不是用了字符串拼接。把"SELECT * FROM user WHERE username='" + username + "'"改成 PreparedStatement 的?占位符,就能彻底消灭 SQL 注入隐患。我早期做课设时图省事,所有 SQL 都用字符串拼接,答辩时被老师一句“你怎么防注入”问住,后来养成的习惯是:连接参数、资源关闭、编码这三样先写好,再去碰业务逻辑。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/28 13:19:30

Git LFS:从大文件管理到团队协作的最佳实践

1. 为什么Git LFS从"救命工具"变成了"工程必需品"三年前我在一个游戏项目里第一次接触Git LFS&#xff08;Large File Storage&#xff09;&#xff0c;当时美术团队误将2GB的PSD源文件直接提交到了Git仓库&#xff0c;导致整个团队克隆代码库时需要下载数…

作者头像 李华
网站建设 2026/10/28 13:18:55

ChatGPT原理科普:从语言模型、预训练到RLHF对齐与幻觉边界

简介&#xff1a;《ChatGPT大模型技术发展与应用》是一份面向自然语言处理学习者、研究者和开发者的技术综述PDF&#xff0c;适合希望系统梳理大模型技术脉络、理解ChatGPT演进逻辑与应用边界的初中级读者。资源包共1个PDF文件&#xff0c;大小约2.15MB&#xff0c;已有526人学…

作者头像 李华
网站建设 2026/10/28 13:16:24

PES6存档编辑器实战:球员修改、转会与联赛DIY避坑指南

上个月有个朋友把压箱底的移动硬盘翻出来&#xff0c;里面躺着一份他十几年前调过的存档&#xff0c;他想把当年那支自建球队重新做一遍&#xff0c;结果打开编辑器发现球队名全是乱码&#xff0c;几个核心球员的脸糊成一片黑。他问我&#xff1a;不就是改几个数字吗&#xff0…

作者头像 李华
网站建设 2026/10/28 13:16:14

计算机体系结构量化分析:成本、功耗与访存计算复习

简介&#xff1a;面向学习计算机体系结构、准备课程作业或考研复习的读者&#xff0c;这份《计算机体系结构量化分析第六版》课后答案聚焦第一章习题与案例&#xff0c;可用来核对芯片制造成本、良品率、多核缺陷率、功耗与Amdahl定律等计算过程。整包为1个PDF文件&#xff0c;…

作者头像 李华
网站建设 2026/10/28 13:14:46

ODI 12cR1企业版安装:Infrastructure、配域与RCU验证

简介&#xff1a;面向Oracle Data Integrator 12cR1学习者的自编安装文档&#xff0c;重点梳理企业级JEE部署与独立安装的路径差异&#xff0c;说明Fusion Middleware Infrastructure前置依赖何时必需、何时可跳过&#xff0c;并引导完成ODI主体安装及ODI Studio初步配置。文档…

作者头像 李华