☰
基于JSP的服装商城交易管理系统开发与答辩全攻略
2026/9/30 7:28:50 网站建设 项目流程

简介:这份答辩演示文稿聚焦基于JSP的服装商城交易管理系统的设计与实现,适合计算机相关专业学生在毕业设计答辩中作展示,也可供中小服装商家了解线上销售系统的基本构成。PPT内容围绕系统的实际需求展开,先分析选题意义与课题背景,说明网上购物及B2C模式在服装行业中的应用;随后介绍系统的前台选购与后台管理两大核心部分,涵盖管理员对服装、会员、卖家的管理,以及会员购物、订单管理等操作;在技术层面,重点涉及JSP、Servlet、数据库管理和HTML、CSS、JavaScript等前端交互技术,并梳理了从需求分析、系统设计、编码实现到测试优化、上线维护的完整开发流程。压缩包内为单个PPTX演示文件,大小约739KB,共17页,页面结构清晰,便于答辩时按章节讲解。目前已有137人学习,适合需要快速把握JSP商城系统开发脉络和答辩表述思路的读者。

1. 基于jsp的服装商城交易管理系统:答辩PPT背后,评审到底看什么

电脑屏幕上,商品列表、购物车、订单流水一页页翻完,老师抬头问了一句:“你的购物车和订单之间,事务是怎么控制的?”台下直接卡壳。这是很多基于jsp的服装商城交易管理系统答辩现场的真实瞬间。这个标题听起来像一份PPT,实际上它背后是一整套Java Web传统工程的落地问题:JSP页面怎么渲染、Servlet怎么管请求、订单和库存怎么保证不超卖,以及最终怎么把设计过程讲成一场能扛住追问的答辩。本文面向两类人:要交毕设的学生,和一个需要快速接手传统JSP商城项目的开发者。顺着“选型 → 建表 → 写交易闭环 → 避坑 → 自测”的顺序,把这条路完整走一遍。

2. 技术选型与工程结构:JSP商城为什么还能撑起一张订单表

2.1 JSP + Servlet + JavaBean 的职责划分:一个请求是怎么走的

JSP入门的经典问题就是这三角色到底怎么分工。很多同学一上来就把Java代码全塞进JSP页面,答辩时被问“为什么这么做”就答不上来。正确的做法是让JSP只当视图,JavaBean当模型,Servlet当控制器。

一个典型的商品浏览请求是这样走的:浏览器请求/product/list,Servlet接收参数后调用ProductDao查询数据库,得到List ,把结果放进request后转发到productList.jsp,JSP用JSTL标签渲染成HTML返回给浏览器。整个过程里,JSP里看不到一行Class.forName,数据库连接、SQL语句都收在DAO层。

这个结构对交易系统很重要,因为订单、库存、用户这些状态不是孤立的。如果把SQL散落在JSP里,后面加购物车、下单、对账时根本没法维护。更现实的原因是,答辩评审一定会问“为什么不用Spring Boot”,一个能站住的回答是:课程目标要求演示Servlet和JSP生命周期,而且经典三层结构能把请求处理过程讲得更直观。这不是贬低框架,而是说明白选型边界。

还有一点容易被忽略:设计模式java实现层面,至少要在代码里体现DAO模式。DAO不是一个高深东西,就是把每个表的数据访问封装成一个类,ProductDao负责商品表,OrderDao负责订单表。实践时我一般会再给DAO加一个抽象接口,这样Service层依赖接口而不依赖具体JDBC实现,答辩讲到“可替换性”时就有代码依据。

2.2 用 IDEA 新建 JSP 项目并跑通第一个页面:最小配置与打包姿势

新建项目的姿势直接影响后面能不能打包成功。常见做法是用IDEA新建Maven webapp项目,而不是手工建目录。JDK用1.8,Tomcat用8.5,这两个版本配合得最顺。

最小依赖只要三个:Servlet API、JSTL、MySQL驱动。pom.xml关键部分如下:

<dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.49</version> </dependency> </dependencies>

第一处参数是Servlet API的scope=provided,意思是Tomcat自带的Servlet容器类库,编译时需要但打包时没必要塞进WAR。如果这里误用了默认的compile,会把servlet-api也打进去,有时会跟Tomcat自带版本冲突。第二处是MySQL驱动,它必须保持默认的compile范围,否则部署到没装驱动的环境就会报ClassNotFoundException。

接着在web.xml里配置欢迎页和Servlet版本声明:

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>

这里用Servlet 3.1规范,对应Tomcat 8.5,支持@WebServlet注解,写Servlet时不用再在web.xml里逐个登记,省掉不少样板。

配置运行环境时,在IDEA的Tomcat配置里加一个Artifact,Deployment类型选war exploded。Application context填/clothes,这样访问地址是http://localhost:8080/clothes。教材里的例子经常用/根路径,但实际项目部署到Tomcat的webapps目录后都会带一个上下文路径,提前把这个坑躲开,后面样式丢失问题也少了。

2.3 数据库表设计:商品、会员、购物车、订单如何落库

交易系统的核心是订单,不是商品。服装商城业务其实很常规,六张表基本够用:用户表、商品分类表、商品表、购物车项表、订单表和订单明细表。

CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, nickname VARCHAR(50), phone VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE category ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE product ( id INT PRIMARY KEY AUTO_INCREMENT, category_id INT, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, image VARCHAR(255), status TINYINT DEFAULT 1, KEY idx_category (category_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE cart_item ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, product_id INT NOT NULL, num INT NOT NULL DEFAULT 1, UNIQUE KEY uk_user_product (user_id, product_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL, user_id INT NOT NULL, total_amount DECIMAL(10,2) NOT NULL, status TINYINT NOT NULL DEFAULT 0, address VARCHAR(255), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_order_no (order_no) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE order_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, product_id INT NOT NULL, product_name VARCHAR(100), price DECIMAL(10,2) NOT NULL, num INT NOT NULL, KEY idx_order (order_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

先说两个最容易讲不清的点。

订单和订单明细为什么拆两张表?因为一个订单可以包含多件不同款式的服装。orders表保存总额、状态、收货地址,order_item保存每件商品的快照。快照的意思是下单那刻的商品名称和价格要冗余进订单明细,不能靠JOIN product表现查。线下店经常改价、下架,如果订单明细不存快照,历史订单金额会跟着商品表变化,这是对账的大麻烦。

用户表在JavaBean里对应User类,但表名用了user而不是user_info。部分数据库版本里user是保留字,MySQL 8.0之前问题不大,为避免不必要的兼容问题,可以改成sys_user或member。建表DDL顺手把唯一键、索引建好,答辩讲到“根据查询场景设计索引”时就有实物。

购物车表用了唯一键uk_user_product,同一个用户对同一商品只会有一行记录,加入购物车时用ON DUPLICATE KEY UPDATE num = num + 1,而不是盲目插入新行。这个设计是小小的加分项,因为很多同学的控制逻辑写在Java代码里,先查再改,而SQL层面的约束更可靠。

3. 把交易闭环写出来:商品展示、购物车、下单与库存扣减

3.1 商品列表与个人信息展示页面:JSP + JSTL 的最小模板

商品列表页是最常见的JSP个人信息展示页面用法之一。页面本身不写Java代码,只负责循环输出Servlet传过来的productList。

<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <body> <h1>服装商城商品列表</h1> <table> <c:forEach var="p" items="${productList}"> <tr> <td>${p.name}</td> <td>${p.price}</td> <td> <form action="${pageContext.request.contextPath}/cart/add" method="post"> <input type="hidden" name="productId" value="${p.id}"/> <input type="number" name="num" value="1" min="1"/> <button>加入购物车</button> </form> </td> </tr> </c:forEach> </table> </body> </html>

这里的关键是${pageContext.request.contextPath},它会在运行时输出/clothes,确保部署在任意上下文路径下都不丢样式和链接。如果直接写死/cart/add,部署后点击提交会变成http://localhost:8080/cart/add,丢掉/clothes前缀,Tomcat找不到这个Servlet。

ProductServlet里只需要两件事:调用DAO查所有上架商品,然后把List放进request并转发。

@WebServlet("/product/list") public class ProductListServlet extends HttpServlet { private ProductDao productDao = new ProductDao(); protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { List<Product> list = productDao.listOnSale(); request.setAttribute("productList", list); request.getRequestDispatcher("/productList.jsp").forward(request, response); } }

listOnSale()在SQL里加上WHERE status = 1,把下架商品挡在列表之外。这里不要用getSession()保存列表,商品列表是全局数据,不是用户私有数据,放session会白白占内存,也容易让多个请求互相污染。

个人信息展示部分通常放在页面顶部或单独的个人中心页。登录成功后把User对象放进session,JSP用EL判断非空再显示。

<c:if test="${not empty sessionScope.loginUser}"> 欢迎你,${sessionScope.loginUser.nickname} <a href="${pageContext.request.contextPath}/logout">退出</a> </c:if>

not empty既判断了null也判断了空字符串,比${loginUser != null}更省心。这个页面的价值在于串起“登录状态”和“交易身份”,下单时要靠session里的userId识别该扣谁的钱。

3.2 购物车与订单生成的 Servlet 逻辑:参数怎么传、状态怎么记

购物车加入商品是POST请求,因为它在改数据。很多教材把加入购物车写成GET链接,刷新页面就重复加购,这是不严谨的。Servlet的doPost里做三件事:取参数、写表、跳转。

@WebServlet("/cart/add") public class CartAddServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpSession session = request.getSession(); User user = (User) session.getAttribute("loginUser"); if (user == null) { response.sendRedirect(request.getContextPath() + "/login.jsp"); return; } int productId = Integer.parseInt(request.getParameter("productId")); int num = Integer.parseInt(request.getParameter("num")); CartItemDao dao = new CartItemDao(); dao.addOrUpdate(user.getId(), productId, num); response.sendRedirect(request.getContextPath() + "/cart/view"); } }

参数productId和num都来自表单,建议在Servlet里先做范围校验,比如num在1到99之间。否则有人手工POST一个num=99999,库存再大也不够扣。校验不通过就返回错误信息,不要直接抛NumberFormatException。

订单生成是整个交易系统的核心Servlet。它要处理的数据比购物车多得多,因为要把购物车里所有项一起转成订单和订单明细,再清空购物车。下面是一段简化后的核心流程,放在OrderService里:

public String createOrder(int userId, int[] productIds, int[] nums) { Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); String orderNo = "ORD" + System.currentTimeMillis() + ThreadLocalRandom.current().nextInt(1000); OrderDao orderDao = new OrderDao(); orderDao.insert(conn, orderNo, userId, totalAmount); CartItemDao cartDao = new CartItemDao(); for (int i = 0; i < productIds.length; i++) { // 条件扣库存,防止超卖,详见3.3 int rows = productDao.deductStock(conn, productIds[i], nums[i]); if (rows == 0) { throw new RuntimeException("商品库存不足"); } orderItemDao.insert(conn, orderNo, productIds[i], nums[i]); cartDao.deleteByUserAndProduct(conn, userId, productIds[i]); } conn.commit(); return orderNo; } catch (Exception e) { // 任何一步失败都回滚,订单不会留下半截数据 if (conn != null) { try { conn.rollback(); } catch (SQLException ignored) {} } throw new RuntimeException("下单失败:" + e.getMessage(), e); } finally { DBUtil.close(conn); } }

这段代码严格的参数是setAutoCommit(false),它把多个写操作变成一个原子事务。下单包含三个动作:扣库存、插订单明细、清购物车。如果中间断了电或抛异常而没回滚,库存已经扣了但订单没生成,对账时就是一笔糊涂账。

ThreadLocalRandom生成的后缀只有三位随机数,实际上同一毫秒并发时还有碰撞概率。更可靠的做法是用数据库自增主键或雪花算法,但答辩场景用时间戳加随机数也能说清,重点是把“订单号为什么要唯一”讲明白。

3.3 库存扣减与订单状态流转:事务边界放在哪

库存扣减是交易系统里最有含金量的一段代码。新手写法通常是先查库存,判断够不够,再UPDATE,这在单用户下没问题,并发下就翻车。更稳的方式是把判断写进UPDATE语句的条件里。

// 扣减库存:只允许在 stock >= num 时扣减 String sql = "UPDATE product SET stock = stock - ? WHERE id = ? AND stock >= ?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setInt(1, num); ps.setInt(2, productId); ps.setInt(3, num); int rows = ps.executeUpdate(); if (rows == 0) { throw new RuntimeException("库存不足"); }

UPDATE会锁住那一行,第二个请求到达时会等第一个请求提交或回滚后重新判断条件,所以不会出现两个请求都读到stock=1的情况。rows == 0意味着匹配不到满足条件的行,说明库存不够,直接回滚。

如果数据量不大,也可以用SELECT ... FOR UPDATE先锁定商品行再在Java里判断库存,但加锁范围要控制好,只锁product这一行,别锁整个表。锁越大,并发效率越低,这正好是答辩的一个可聊点。

订单状态我习惯定义为四个值:0待付款,1已付款,2已发货,3已完成,4已取消。下单时是0,后台页面提供对应的状态流转按钮。支付环节一般不是毕业设计的重点,常见做法是做一个“模拟支付按钮”,点击后把status从0改成1,并把支付时间记录下来。不要为了显得完整去对接真实支付网关,那要商户号、证书和审核,纯属给自己挖坑。答辩时明确说明“这里是模拟支付,真实支付需要对接第三方网关”,比含糊带过加分。

事务的边界要放在Service层,不是DAO层。每个DAO方法只负责一条SQL,Service里把多个DAO调用包进同一个Connection。如果DAO各自开连接,事务就失效了。这也是我在代码评审里最常看到的问题。

4. 答辩 PPT 与演示细节:评审最爱追问的三个方面要提前准备

4.1 把“设计与实现”拆成一张答辩结构图

答辩PPT的章节不要照抄论文目录,而要用“你做了什么、怎么做的、遇到什么问题”来组织。下面这个表格结构是我带人做类似项目时常用的骨架,可以直接套到服装商城上。

PPT章节建议呈现的内容页面占比
选题背景与目标服装商城线下交易信息不透明、手写订单难统计,做一个在线交易管理系统10%
技术选型与理由JSP + Servlet + JavaBean + MySQL,强调教学要求与Servlet原理展示15%
需求分析用户端五个功能、管理端四个功能,配一张用例图20%
数据库设计六张核心表的关系图,重点讲订单与订单明细拆分20%
系统实现商品列表、购物车、下单流程、库存扣减的截图与核心代码25%
测试与总结自测脚本跑通交易流程,罗列使用的边界情况10%

每一页PPT只讲一件事。常见问题是把几十行代码贴上去,评审根本来不及看,反而追问代码细节。我的习惯是贴核心SQL和关键方法签名,页面截图配一句说明它完成了什么。更重要的是,PPT里要有“问题与解决”的一页,把库存超卖、乱码这些踩坑写进去,这比满屏截图更能证明你真的做过。

4.2 评审最爱追问的三个实现细节

第一个问题铁定是“为什么用JSP不用Spring Boot”。直接答“课程要求”太空洞,要补充两层意思:一是JSP是Java Web的基础,能展示请求从浏览器到Servlet再到视图的完整生命周期;二是Spring Boot默认的模板方案Thymeleaf不在本课程的考察范围,选JSP是为了继承Servlet知识。承认技术选型的局限性不是扣分项,讲不清局限性才是。

第二个问题是“订单和库存怎么保证一致性”。这个问题的标准答案是数据库事务。要能画一条线:开启事务 → 条件更新库存 → 插入订单明细 → 提交事务。如果任何一步失败就回滚。能顺手补一句“实际生产环境会用消息队列做最终一致性,但单机交易系统用数据库事务足够”,会明显加分。

第三个问题是“如果两个人同时买同一个商品怎么办”。这对应5.5里的条件UPDATE。回答时不要只说“加了锁”,要说得具体:UPDATE语句的WHERE条件里带上stock >= num,数据库行锁保证同一行只有一个事务能更新成功,更新影响行数为0就抛出异常回滚。这是设计模式java实现里典型的“事务加条件更新”组合,比口头说同步关键字可靠得多。synchronized只对单进程有效,两台Tomcat一起跑就失效,数据库锁才是兜底。

4.3 打包与部署演示:传统 JSP 项目打包 WAR 并在 Tomcat 上跑通

演示环境里最怕“IDEA能跑,装了Tomcat的电脑却不行”。传统JSP项目打包war的正确姿势是用Maven命令直接在项目根目录执行:

mvn clean package ls target/clothes.war cp target/clothes.war /path/to/tomcat/webapps/ /path/to/tomcat/bin/startup.sh

WAR包放在Tomcat的webapps目录后,Tomcat启动时会自动解压成clothes目录。浏览器访问http://localhost:8080/clothes/index.jsp,注意路径大小写一定要和WAR文件名一致。

演示机如果没装Maven,可以提前在IDEA里Build Artifacts生成WAR,效果一样。关键是要确认WAR里的WEB-INF/lib包含了mysql-connector-java和jstl两个JAR,用jar tf clothes.war | grep lib看一眼就能确认。

关于“nginx支持jsp吗”这个问题,答案是Nginx本身不解析JSP,它只会处理静态文件和转发HTTP请求。JSP页面的翻译、编译、执行都在Tomcat容器里完成。常见的部署图是Nginx在前端监听80端口,将.jsp请求转给Tomcat的8080端口,但答辩演示不需要搭Nginx,直接访问Tomcat就够,把环境复杂度降到最低。

凡是演示,都要提前确认三件事:MySQL服务已启动,数据库和账号密码匹配,Tomcat没有占用8080端口。我就见过答辩当天MySQL没启动,页面一片报错,最后只能靠嘴讲实验结果的场面。

5. JSP 商城开发避坑指南:五个最容易翻车的地方

5.1 页面中文乱码

现象:JSP页面在浏览器里中文显示正常,但写入MySQL后变成?,或者读取出来页面显示乱码。还有前端页面本身正常,点击查询后中文参数到后台变成乱码。

原因:至少三个地方不一致。Tomcat默认的请求编码不是UTF-8,HTML表单提交的中文参数到Servlet里按ISO-8859-1解析,自然乱码;MySQL连接URL没有指定字符编码;数据库表的字符集不是utf8mb4。

解决:三处统一。建表用utf8mb4;JDBC连接URL加参数?useUnicode=true&characterEncoding=utf8;更重要的是在项目里加一个编码过滤器:

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

这个过滤器拦截所有请求,在Servlet读取参数前就把编码设成UTF-8,中文问题基本绝迹。注意顺序要在所有业务Servlet之前,@WebFilter("/*")的匹配范围保证了这点。

5.2 明明导入了 MySQL 驱动,运行却报 ClassNotFoundException

现象:在IDEA里点运行,商品列表正常。打成WAR包放到另一台机器的Tomcat后,访问页面报ClassNotFoundException: com.mysql.jdbc.Driver。

原因:IDEA运行时额外加载了项目里的lib目录,但这不代表WAR包里有驱动。打包时没有把mysql-connector-java.jar放进WEB-INF/lib,Tomcat在类路径里找不到驱动器。

解决:先用命令确认WAR包内容:

jar tf clothes.war | findstr /i "mysql" # Windows jar tf clothes.war | grep -i mysql # Linux/macOS

如果输出为空,就去检查Maven依赖。mysql-connector-java的scope必须是compile(默认),不能是provided。如果是手工导入jar包的方式开发,要确认jar真的被IDEA打包进了Artifacts的WEB-INF/lib,而不是只出现在compile classpath里。

5.3 本地能访问,部署后 404 且样式全丢

现象:开发时直接访问JSP页面没问题,部署到Tomcat的webapps目录后,访问http://localhost:8080/clothes/index.jsp能打开,但点商品详情或提交表单就404,CSS、图片更是完全丢失。

原因:contextPath没加。项目部署路径是/clothes,但JSP里的链接写了action="cart/add",浏览器会基于当前URL去解析,结果请求发到了/cart/add而不是/clothes/cart/add。CSS同理,href="css/style.css"被解析成/css/style.css。

解决:所有前端资源地址、表单action、Servlet重定向都使用${pageContext.request.contextPath}前缀:

<link href="${pageContext.request.contextPath}/css/style.css" rel="stylesheet"> <form action="${pageContext.request.contextPath}/cart/add" method="post">

Servlet里sendRedirect也要带request.getContextPath()。这个坑在第一次部署时几乎必踩,属于血泪经验,提前统一就能省掉一整晚的调试时间。

5.4 刷新页面导致订单重复生成

现象:下单成功后按F5刷新,订单中心出现两条一模一样的订单,而且库存也被扣了两次。双击提交按钮也会触发同样问题。

原因:下单是POST请求,浏览器刷新时会尝试重新提交上一次的POST数据。如果Servlet没有做防重处理,同一个请求就会被执行两次。

解决:下单成功后不要转发到页面,而是重定向到成功页。重定向会让浏览器发起一次新的GET请求,刷新GET请求不会重复下单。这也是Post/Redirect/Get模式的核心思想:

response.sendRedirect(request.getContextPath() + "/orderSuccess.jsp?orderNo=" + orderNo);

同时前端下单按钮在点击后立刻置灰。更硬的一层防护是在订单表给order_no加唯一索引,订单号生成时保证全局唯一,重复插入会被数据库拒掉。做这三层里的两样,演示时按F8连刷都不翻车。

5.5 高并发下单把库存扣成负数

现象:商品只剩1件,两个人同时下单,结果两个订单都成功,数据库里库存变成-1。

原因:代码先SELECT查库存,判断大于0后UPDATE减一。两个请求同时读到stock=1,都认为还有货,先后执行UPDATE,最后成了-1。

解决:换成三条件UPDATE,把判断和扣减合并成一条SQL:

UPDATE product SET stock = stock - ? WHERE id = ? AND stock >= ?;

执行后检查影响行数,等于1说明扣减成功,等于0说明库存不足。这条SQL在InnoDB里会锁住product表对应行,第二个事务会等第一个提交后再执行,自然串行化,库存不可能变成负数。要记得订单表也用InnoDB,MyISAM不支持行锁和事务,这是前提。

6. 答辩前最后的自测脚本:一把梭验证整个交易流程

6.1 用命令行模拟用户从登录到下单

演示前手点页面太慢,还容易漏步骤。更好用的是写一个脚本,用curl按真实业务顺序请求一遍,把整个交易链路跑通。

curl -c cookies.txt -d "username=demo&password=123456" \ http://localhost:8080/clothes/user/login curl -b cookies.txt -d "productId=1&num=1" \ http://localhost:8080/clothes/cart/add curl -b cookies.txt -d "address=测试地址&productId=1&num=1" \ http://localhost:8080/clothes/order/create

第一条命令的-c cookies.txt把登录后的Session ID存进本地文件,后续两条命令用-b cookies.txt带着身份访问,模拟同一个浏览器会话。如果订单创建接口返回成功,说明从登录鉴权到购物车加购再到订单落库的链路是通的。脚本跑完后再用浏览器打开订单中心,核对刚才生成的订单号,能搜到就万无一失。

6.2 用 SQL 核对订单数和库存数据

页面通不代表数据对,还要到MySQL里做两层核对。第一层看库存有没有负数,第二层看订单合计金额是不是和orders.total_amount一致。

SELECT id, name, stock FROM product WHERE stock < 0; SELECT o.id, o.status, SUM(oi.num * oi.price) AS computed_amount, o.total_amount FROM orders o JOIN order_item oi ON oi.order_id = o.id GROUP BY o.id HAVING computed_amount <> o.total_amount;

第一条SQL查出任何负数都说明库存扣减逻辑有漏洞。第二条SQL用订单明细的价格乘数量重新计算一遍,和订单主表的总额对比,不一致就说明下单时计算有问题。这两条是答辩前最便宜也最有效的后悔药。

我过去习惯把演示顺序背熟,结果一次手滑刷新,订单列表凭空多出两条,当场只能硬着头皮解释。后来我把自测命令写成一个bat脚本,每次答辩前先跑一遍再上台,问题基本都暴露在演示之前。答辩PPT只是外壳,能把下单流程、库存扣减和异常处理讲成自己的,才是这个方向最扎实的回报。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询