ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Spring Boot+MySQL家具电商系统:从源码到部署的完整避坑指南

Spring Boot+MySQL家具电商系统:从源码到部署的完整避坑指南 简介这是一套基于Spring Boot与MySQL的家具销售电商平台系统设计与实现的毕业设计/课程设计资源适合正在做Java Web项目或电商系统开发的学习者。压缩包内文件总数未单独列出整体约24.89MB包含完整的Java源码目录、设计文档.doc、项目介绍PPT、readme快速上手说明以及开发环境配置清单等文件类型可帮助对照源码理解后端分层结构与前、后台功能逻辑。资源目前已获537人学习下载内容覆盖用户注册登录、商品浏览购物车、订单处理等核心电商模块后台管理部分也可参考。通过阅读设计文档中的系统架构、数据库表结构与接口设计再结合源码中的Controller、Service、Model、Repository分层能快速掌握Spring Boot与MySQL的联合应用学会常规电商业务的落地实现。无论是用于答辩准备还是日常练手都能提供从环境搭建到功能实现的完整参考路径。1. 这套家具电商平台系统真的是“源码到手就能跑”吗先看清它值不值得要一个做家具生意的小商户想把自己店里的沙发、床、书桌放到网上去卖。他不需要秒杀、不需要千人并发只需要把商品挂出去、用户能注册登录、加购物车、下单付款后台能看见订单改状态。市面上绝大多数 Spring Boot 教程拿图书、数码当例子家具销售看着只是换了个类目但家具的多级分类、大件商品的库存逻辑反而比普通小商城更值得认真做一遍。标题里这套基于 Spring Boot MySQL 的家具销售电商平台系统解决的就是这条“线上卖家具”的最小闭环。它适合三类人做毕设和课设的学生需要一个能讲清楚模块和数据库关系的完整 Java Web 项目刚入行的后端开发想看看一个真实商城模块怎么拆、订单和库存怎么配合以及想快速搭一个小型电商后台的从业者。但“源码文档”不等于解压就能跑。我拆过不少类似的交付包真正让你花时间的往往不是业务代码而是 MySQL 版本、驱动参数、端口占用这些环境问题。这篇就把我从建库到自测的完整路径讲清楚照着复制能把坑提前踩平。2. MySQL 与 Spring Boot 的落地配合从建库到第一张订单表电商系统的本质是围绕数据做增删改查而订单、库存这类数据对一致性要求极高。Spring Boot 负责把 HTTP 接口暴露出去MySQL 负责把账本记牢。先决定怎么建库建表再决定接口怎么写顺序不能反。2.1 为什么这套项目选 MySQL 而不是 PostgreSQL 或 NoSQL订单业务对事务的依赖是刚性的一个商品被下单订单表插入一条记录库存减 1这三件事要么全部成功要么全部回滚。MySQL 的 InnoDB 引擎天然支持事务和行级锁这是中小型电商选它的核心理由不是因为它跑得最快而是因为它在本地开发、毕业答辩、课程设计里最容易跑通、最容易找到资料。对比一下就清楚了。如果购物车只用 Redis 存服务一重启数据就没了用户辛辛苦苦挑的三件套家具全丢如果用 MongoDB 存订单事务模型得单独配分布式事务对单人开发来说是平白增加难度。Spring Boot 这边也一样自动装配、starter 依赖、内嵌 Tomcat省掉了传统 SSM 项目里一堆 XML 配置。一个人从零开始写Spring Boot MySQL 是投入产出比最高的组合这也是为什么它成了 Java Web 毕设和课设的默认起点。2.2 建库建表家具类目用一张自关联表就够了家具和数码商品最大的区别在类目结构。数码产品一般就两层手机、电脑下面直接挂商品家具通常有三层客厅下面有沙发沙发下面才到具体的三人位沙发。很多新手会为了层级建三张表完全没有必要。用一张带 parent_id 的自关联表就能解决。启动项目之前先创建数据库和核心表。下面这份 SQL 是这套商城的第一版骨架我按顺序拆给你看。CREATE DATABASE IF NOT EXISTS furniture_mall DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE furniture_mall; DROP TABLE IF EXISTS category; CREATE TABLE category ( id BIGINT PRIMARY KEY AUTO_INCREMENT, parent_id BIGINT DEFAULT 0 COMMENT 父分类id0表示一级分类, name VARCHAR(50) NOT NULL COMMENT 分类名客厅/卧室/书房/餐厅, sort INT DEFAULT 0 COMMENT 排序权重越小越靠前, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB COMMENT家具分类表; DROP TABLE IF EXISTS product; CREATE TABLE product ( id BIGINT PRIMARY KEY AUTO_INCREMENT, category_id BIGINT NOT NULL COMMENT 所属分类id, name VARCHAR(100) NOT NULL COMMENT 商品名如北欧三人布艺沙发, price DECIMAL(10,2) NOT NULL COMMENT 单价单位元, stock INT NOT NULL DEFAULT 0 COMMENT 库存数量, img_url VARCHAR(255) DEFAULT COMMENT 商品图片路径, description TEXT COMMENT 商品详情描述, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_category (category_id) ) ENGINEInnoDB COMMENT家具商品表;这里的几个设计点值得记一下。字符集用 utf8mb4 而不是 utf8因为家具商品描述里很可能出现生僻字和特殊符号utf8 在 MySQL 里存不了四个字节的字符utf8mb4 才是完整的 Unicode 实现。价格字段必须用 DECIMAL(10,2)不能用 FLOAT 或 DOUBLE二进制浮点数在金额计算上会有精度误差0.1 加 0.2 的结果不是 0.3这在订单结算时是事故级的问题。库存用 INT别省空间用 TINYINT一件家具的库存上限远超过 255TINYINT 存不下会直接报错。商品相关表建好后用户、购物车、订单这三张表是电商闭环的另一半。DROP TABLE IF EXISTS user; CREATE TABLE user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(100) NOT NULL COMMENT BCrypt加密后的密文, nickname VARCHAR(50) DEFAULT COMMENT 显示昵称, phone VARCHAR(20) DEFAULT COMMENT 联系电话 ) ENGINEInnoDB COMMENT用户表; DROP TABLE IF EXISTS orders; CREATE TABLE orders ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL COMMENT 下单用户id, order_no VARCHAR(32) NOT NULL UNIQUE COMMENT 订单号全局唯一, total_amount DECIMAL(10,2) NOT NULL COMMENT 订单总金额, status TINYINT DEFAULT 0 COMMENT 0待支付 1已支付 2已发货 3已完成 4已取消, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_user (user_id) ) ENGINEInnoDB COMMENT订单主表; DROP TABLE IF EXISTS order_item; CREATE TABLE order_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT NOT NULL COMMENT 订单主表id, product_id BIGINT NOT NULL COMMENT 商品id, product_name VARCHAR(100) NOT NULL COMMENT 下单时的商品名快照, price DECIMAL(10,2) NOT NULL COMMENT 下单时的单价快照, quantity INT NOT NULL COMMENT 购买数量 ) ENGINEInnoDB COMMENT订单明细表;注意几个容易在答辩时被追问的点。user 是 MySQL 保留字建表时要用反引号包起来。订单明细里必须冗余 product_name 和 price而不是下单时临时去关联 product 表因为商品改价或改名后历史订单不能跟着变这叫快照字段。order_no 要唯一在 Java 代码里生成不能用数据库自增 id 当订单号自增 id 会暴露当天订单量。订单状态用 TINYINT 数字而不是字符串枚举IP 地址过滤器 filter IP 过滤字符串枚举做排序和统计都很别扭。这套表结构没有单独建购物车表演示时可以只设计下单接口如果要做购物车加一张 cart_item 表字段是 user_id、product_id、quantity、checked道理和 order_item 一样。2.3 Spring Boot 连接 MySQLapplication.yml 里最该确认的 5 个参数表建好后第二步就是让 Spring Boot 项目真正连上 MySQL。这一步失败率最高问题几乎都集中在连接串参数上。下面这份是我在多个商城项目上验证过的配置Spring Boot 2.7.x 和 3.x 都可以参考区别只在部分包的命名上。server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/furniture_mall?useUnicodetruecharacterEncodingutf8useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/Shanghai username: root password: 123456 hikari: maximum-pool-size: 10 minimum-idle: 2 connection-timeout: 30000 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted逐个说参数。driver-class-name 用的是 com.mysql.cj.jdbc.Driver这是 MySQL 8.x 驱动 mysql-connector-j 的完整类名MySQL 5.x 那个 com.mysql.jdbc.Driver 在新驱动里已经废弃了好多老项目换 MySQL 8 后卡死在这一行。url 里四个参数缺一不可useSSLfalse 因为本地开发没有配置 SSL 证书开了反而报 SSL 连接错误allowPublicKeyRetrievaltrue 是 MySQL 8 默认认证插件 caching_sha2_password 的要求不开会直接报 Public Key Retrieval is not allowedserverTimezoneAsia/Shanghai 是因为驱动默认拿服务器时区不配对的话时间会差 8 个小时订单时间错乱在演示时特别尴尬。连接池是另一大块。Spring Boot 2.x 之后默认连接池是 HikariCP性能好且配置简单。maximum-pool-size 是连接池上限本地开发 10 就够别学网上教程开到 100连接数不是越大越好超过 MySQL 默认 max_connections 反而会让系统变慢。minimum-idle2 保证热启动时有两条连接待命。mybatis-plus 的 log-impl 配置成 StdOutImpl 后每次执行 SQL 都会在控制台打印完整语句和参数排查问题时能看到真实执行内容项目上线前再关掉。配置写完后验证连接是否成功最直接的方式是启动项目看到控制台出现“Started ...Application”且没有红色报错。如果这时报错优先检查三点MySQL server 是否真的在运行root 密码是否和 application.yml 一致MySQL 8 的认证插件是否为 mysql_native_password。最后一项如果项目用的是 mysql-connector-j 8.x通常不需要改认证插件也能连上只需补上 allowPublicKeyRetrievaltrue。3. 把家具商城拆成模块从用户登录到订单结算的完整链路数据库是地基模块划分是骨架。一个家具销售电商平台从用户视角看流程是注册登录、逛商品、加购物车、结算下单、查看订单。从代码视角看就是用户模块、商品模块、购物车模块、订单模块、分类模块这几个纵向切面。3.1 模块清单与表关系一张图想清楚数据从哪来、到哪去我先给你一张模块对照表写每个模块前都先对着它确认自己要操作的表。模块对应表核心字段典型操作用户模块userusername、password注册、登录、个人信息分类模块categoryparent_id、name树形分类展示商品模块productcategory_id、price、stock列表分页、详情购物车模块cart_itemuser_id、product_id、quantity加购、改数量、勾选订单模块orders order_itemorder_no、status、total_amount下单、支付回调、订单查询模块之间的依赖关系是单向的用户模块在最底层商品模块依赖分类模块购物车模块依赖用户和商品订单模块依赖用户和商品。写代码时顺着这个方向写不会出现循环依赖。很多人把 Controller、Service、Mapper 三个类都堆在一个包下面项目一旦过两千行就乱成一团。我习惯在 com.example.furniture 下面按 controller、service、mapper、entity、common 分包common 里放 Result、BusinessException 这类公共类这样后续加功能不会到处找文件。3.2 商品模块先跑通一个分页接口整条链路就通了商品列表是商城最核心的接口因为它同时用到了四个基础能力Controller 接收参数、Service 业务逻辑、Mapper 数据访问、实体类映射。把这个接口跑通其他模块都是它的变体。下面这段代码是商品分页查询的最简实现。RestController RequestMapping(/api/product) public class ProductController { Autowired private ProductService productService; GetMapping(/list) public Result list(RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer size, RequestParam(required false) Long categoryId) { PageProduct pageParam new Page(page, size); LambdaQueryWrapperProduct wrapper new LambdaQueryWrapper(); wrapper.eq(categoryId ! null, Product::getCategoryId, categoryId); wrapper.orderByDesc(Product::getCreatedAt); return Result.ok(productService.page(pageParam, wrapper)); } }逻辑说明写三点。第一page 和 size 都有默认值前端不传参数时不会报空指针这是接口健壮性的底线。第二categoryId 是可空的用 LambdaQueryWrapper 的 condition 重载categoryId 为 null 时这一条件自动不拼接省去手动 if 拼接 SQL 的繁琐。第三接口返回统一包装 Result 对象而不是裸的 Page前端判断业务状态码比解析 HTTP 状态码更顺手。实体类 Product 上需要加 TableName(product) 注解把驼峰字段映射到下划线列名这一点在配置里开了 map-underscore-to-camel-case 之后对象字段和表列名才能对得上。写完后用浏览器直接访问 http://localhost:8080/api/product/list?page1size5categoryId2 验证返回 JSON能看到只含沙发分类的商品分页数据这条链路就是通的。3.3 下单减库存为什么必须加 Transactional光加注解还不够商品接口跑通后最值得打磨的是下单逻辑。电商系统死在库存上的案例比死在价格上的多得多。下单动作拆开看是三件事校验商品存在且库存够、生成订单号和订单明细、扣减库存。三件事必须全部成功否则数据就脏了。Transactional(rollbackFor Exception.class) public String createOrder(Long userId, ListCartItem cartItems) { // 1. 生成全局唯一订单号 String orderNo UUID.randomUUID().toString().replace(-, ).toUpperCase(); BigDecimal total BigDecimal.ZERO; for (CartItem item : cartItems) { Product product productMapper.selectById(item.getProductId()); if (product null || product.getStock() item.getQuantity()) { throw new BusinessException(库存不足 (product null ? 商品不存在 : product.getName())); } total total.add(product.getPrice() .multiply(BigDecimal.valueOf(item.getQuantity()))); } // 2. 插入订单主表 Orders order new Orders(); order.setUserId(userId); order.setOrderNo(orderNo); order.setTotalAmount(total); order.setStatus(0); orderMapper.insert(order); // 3. 插入订单明细 for (CartItem item : cartItems) { OrderItem orderItem new OrderItem(); orderItem.setOrderId(order.getId()); orderItem.setProductId(item.getProductId()); orderItem.setProductName(productMapper.selectById(item.getProductId()).getName()); orderItem.setPrice(productMapper.selectById(item.getProductId()).getPrice()); orderItem.setQuantity(item.getQuantity()); orderItemMapper.insert(orderItem); } return orderNo; }Transactional(rollbackFor Exception.class) 解决的是原子性问题——任何一个步骤抛异常前面已执行的 insert 全部回滚。这里 rollbackFor 必须写 Exception.class因为默认配置只对 RuntimeException 回滚如果抛的是 SQLException 这类受检异常事务不会回滚订单没插成功但库存扣了这种脏数据很难查。代码里还有个隐藏问题检查库存后再扣减如果只是把库存字段改小一个固定值高并发下会出现超卖。我一般不用 select-by-id 拿内存值再算而是直接写成 update product set stock stock - #{quantity} where id #{id} and stock #{quantity}返回影响行数等于 0 就说明库存已经被抢光立刻抛异常回滚。这个技巧在下一章避坑点里会再展开。4. 复现这套源码最容易翻车的 5 个避坑点从环境到数据到部署标题里写着“源码文档”但源码能跑通和源码能拿来用是两码事。以下这 5 个坑我在带人做项目时几乎全见过有的是环境问题有的是代码写法问题每个都写清楚现象、原因和解决方式。4.1 现象MySQL 8 连接报 Public Key Retrieval is not allowed 或 SSL 连接错误本地 MySQL 8 装好后Spring Boot 启动报com.mysql.cj.exceptions.UnableToConnectException: Public Key Retrieval is not allowed或者控制台刷SSL connection error。原因是 MySQL 8 默认认证插件是 caching_sha2_password驱动用 sha2 密码做身份验证时需要向服务器获取 RSA 公钥而我们的 JDBC 连接串默认不允许取公钥SSL 那条则是本地没有证书连接还强行走 TLS 握手导致失败。解决方式是在 application.yml 的连接串上同时补两个参数useSSLfalseallowPublicKeyRetrievaltrue。如果还报错再检查驱动版本老驱动 com.mysql.jdbc.Driver 在 MySQL 8 上兼容性很差换成 com.mysql.cj.jdbc.Driver。这个坑直接对应热词里反复出现的“mysql ssl连接错误”是新手的第一道拦路虎。4.2 现象Linux 上连 MySQL 报 error 2002 cant connect through socket /tmp/mysql.sock在 Linux 服务器或虚拟机上部署时执行mysql -u root -p登录报ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock。原因是 mysqld 服务没有启动或者虽然启动了但套接字文件不在默认路径。这个报错字面意思是连不上 socket和账号密码无关很多人误以为是密码错白白折腾半天。解决方式先跑systemctl status mysqld确认服务状态如果服务没起来就启动如果服务已经起来了还是报同一个错用 TCP 方式连接绕过 socket 文件mysql -h 127.0.0.1 -P 3306 -u root -p。这让驱动走 TCP 而不是本机 socketSpring Boot 那边本来就用的 TCP 连接串不受影响。另外注意把初始化 SQL 文件传到 MySQL 后用source /path/to/init.sql;执行别用 Navicat 复制粘贴超长脚本容易在中文注释处截断。4.3 现象MyBatis-Plus 启动后报 Invalid bound statement (not found)项目能启动但一调用 Mapper 接口方法就报Invalid bound statement (not found)这个错在标题相关源码包里出现频率极高。原因是 Mapper 接口编译后找不到对应的 SQL通常是两个问题之一Mapper 接口没有在启动类上被扫描到或者 XML 文件名与接口名不一致、namespace 写错。比如接口叫 ProductMapperXML 文件名却写成 productMapper.xmlWindows 上不区分大小写能勉强跑部署到 Linux 上直接爆炸。解决方式启动类加MapperScan(com.example.furniture.mapper)批量扫描接口resources 下的 mapper 目录专门放 XML并检查mapper namespacecom.example.furniture.mapper.ProductMapper是否完全匹配。Spring Boot 3.x 下还要确认 MyBatis-Plus 的 starter 引入了适配新版本的依赖老版本在 jakarta 命名空间下会静默失效。4.4 现象代码里用 Data 注解编译却报找不到 getter 和 setter实体类加了 Lombok 的DataIDEA 里编辑时看不出问题一mvn compile就报类中找不到getProductName()这类符号。原因是 Lombok 是注解处理器需要在编译期工作而 IDEA 和 Maven 都需要明确开启对它支持。常见情况是新拉下来的项目没装 Lombok 插件或 pom.xml 里漏了 annotationProcessorPaths 配置。解决方式IDEA 在 Settings Plugins 里装 Lombok 插件并在 Settings Build Compiler Annotation Processors 勾选 Enable annotation processingpom.xml 里在 maven-compiler-plugin 的 annotationProcessorPaths 中加入 Lombok。坐标用org.projectlombok:lombok具体版本和你的 Spring Boot 版本匹配即可。如果不想依赖 IDE 插件也可以改用 IDE 自带生成 getter/setter但代码会膨胀不少我建议先把 Lombok 配通。4.5 现象启动报 Port 8080 was already in use启动 Spring Boot 时端口被占报Web server failed to start. Port 8080 was already in use.。原因很简单本机有其他 Java 进程占了 8080或者上一个没关掉的调试进程还活着。这个坑在多人协作时特别常见尤其是用了 IDEA 热部署旧的 Spring Boot 进程经常悄悄挂在后台。解决方式最省事的是换端口application.yml 里server.port8081但要同时检查前端访问接口的地址有没有把端口写死另一个方式是找到占用进程Linux 上lsof -i:8080看 PID 再 killWindows 上用netstat -ano | findstr 8080关掉进程。我的习惯是开发阶段直接用 8081 固定下来避免和本机其他 Java 程序抢端口部署时再统一改回 80 或按环境配置。5. 把项目文档和初始化数据做厚让交付和答辩都不慌源码能跑只是第一步那套“源码文档”的交付包里文档和演示数据往往决定项目给人的第一印象是“认真做完”还是“交差”。这里说的做厚不是让文档堆字数而是让对方拿到后按照文档操作30 分钟内跑起来。5.1 初始化数据给家具商品生成一套不穿帮的演示数据初始化 SQL 不只是建表还要把演示数据一并写好。我在好几个项目里见过全表商品库存为 0、价格写成 9999.99 的初始化脚本后台页面打开全是“商品已下架”演示现场非常尴尬。家具商品数据至少要看不出明显的逻辑问题。-- 分类演示数据 INSERT INTO category (id, parent_id, name, sort) VALUES (1, 0, 客厅家具, 1), (2, 1, 沙发, 1), (3, 1, 茶几, 2), (4, 0, 卧室家具, 2), (5, 4, 床, 1); -- 商品演示数据 INSERT INTO product (category_id, name, price, stock, img_url, description) VALUES (2, 北欧三人布艺沙发 浅灰色, 3299.00, 35, /images/sofa_gray.jpg, 可拆洗布艺沙发适合小户型客厅), (2, 头层牛皮电动功能沙发 棕色, 8999.00, 20, /images/sofa_brown.jpg, 电动调节角度真皮表面), (3, 岩板茶几 意式极简 客厅, 1599.00, 50, /images/table_stone.jpg, 岩板台面防刮耐磨), (5, 实木床 1.8米 橡木材质, 4599.00, 15, /images/bed_oak.jpg, 进口橡木环保清漆);演示数据要注意三个点。第一价格符合常识一张沙发卖 189 元会让懂行的评委直接质疑数据设计第二库存不能全为 0也不能全是 9999最好有少量低库存商品方便演示“库存不足”时的报错逻辑第三分类和商品要对应得上客厅下单沙发卧室下单床别出现卧室分类下挂着沙发这种穿帮数据。图片路径用相对路径的话前端能直接展示没有真实图片就留空或放占位图不要填一个打不开的外链地址。5.2 配套文档要覆盖的 4 个部分“源码文档”里的文档不需要写成论文但要覆盖让下一个开发和答辩评委都能快速上手的 4 个部分。我建议目录结构如下表。文档部分写什么常见误区环境要求JDK 版本、MySQL 版本、Maven 版本、IDEA 版本只写“jdk8”不写具体版本区间部署步骤建库、改密码、启动项目、访问地址漏掉数据库密码修改步骤接口清单每个接口的路径、参数、返回示例只写路径不写参数表结构说明每张表字段含义、状态枚举说明只贴建表 SQL 不解释字段环境要求部分明确 JDK 8 或 17、MySQL 8.x 就够了不要写“最新版”因为最新版过半年就变接手人照着最新版装可能遇上兼容问题。部署步骤里第一步永远是执行 SQL 初始化脚本再改 application.yml 里的数据库密码很多新手先改密码后建库启动报数据库不存在又回来问。接口清单不要手打用 IDEA 的 HTTP Request 或 Postman 导出一份即可。表结构说明里订单状态 0 到 4 的意义是答辩时必被问到的内容务必写清。5.3 让交付包可复现工程目录的推荐组织方式拿到一份源码第一眼看目录结构就能判断项目是否专业。一份结构清爽的 Spring Boot 商城项目目录应该长得像下面这样。furniture-mall/ ├── src/main/java/com/example/furniture/ │ ├── common/ # 统一返回、异常处理 │ ├── config/ # MyBatis-Plus、跨域配置 │ ├── controller/ # 接口入口 │ ├── entity/ # 数据库实体 │ ├── mapper/ # MyBatis-Plus Mapper │ ├── service/ # 业务逻辑 │ └── FurnitureApplication.java ├── src/main/resources/ │ ├── mapper/ # XML 自定义 SQL │ └── application.yml ├── sql/ │ └── init.sql # 建库建表 演示数据 ├── docs/ │ └── 项目说明.md └── pom.xml这个目录设计和网上常见的“全部塞进 com.example.demo”有本质区别。sql 目录独立在 resources 外是为了让初始化脚本在部署时可以直接用命令行执行不用从 jar 包里抠出来。docs 目录放说明文档和代码一起入库离职交接时不用口头传文件。common 目录里放 Result 类、BusinessException、全局异常处理器Controller 代码能薄很多。6. 跑通之后怎么自测三件事验证订单与库存的一致性系统跑起来不等于功能对功能对不等于数据不错。我验收这类商城项目时只做三件事每一件都比点击页面看效果更有说服力。第一件事验证库存不超卖。把某件商品库存手动改成 1开两个浏览器无痕窗口同时登录同一账号各自添加这个商品到购物车然后同时下单。下单后查数据库订单表最多只能有 1 条成功订单商品表库存变为 0不能出现库存为 -1 的情况。如果出现负数说明下单逻辑只做了 select 校验库存再 update没有用update product set stock stock - 1 where id ? and stock 1这种原子扣减方式。这一步能把你从表面正常、实际超卖的数据事故里救回来。第二件事验证事务回滚。随意删除订单明细表的 order_item 表里的字段再正常下一次单理论上订单主表不会有收入。如果 orders 表里多出来一条没有任何明细的订单说明 Transactional 没有真正生效或者回滚只覆盖了 RuntimeException。对比一下控制台 SQL 日志就能看出来异常抛出来后是否执行了对应事务的 rollback。第三件事验证接口参数边界。商品列表接口用不同 categoryId 和空参数反复请求看看分类不存在时是返回空列表还是 500。订单创建接口传空购物车列表看后端是返回“购物车不能为空”还是直接 NPE。这些边界测试是答辩里最常见的提问角度一个“参数校验到不到位”能区分出是背代码还是真的会设计。我每次做这类商城项目都把“库存超卖测试”作为验收第一关因为表面功能全通过只有并发和边界条件会把事务和并发控制里的坑炸出来。这套单体架构方案应付毕设、课设和内部小商城足够但要是真搬到公网对外卖货还需要把日志采集、限流和支付回调的幂等处理一并补齐。希望帮到你。本文还有配套的精品资源点击获取
返回列表