资讯动态

基于SpringBoot的服装销售平台设计与实现:SKU表结构、下单扣库存与源码避坑指南

发布时间:2026/10/27 6:36:47 来源:尧图企业网站定制
简介这是一套基于SpringBoot的服装销售平台完整项目源码面向计算机专业学生、Java初学者及需要课程设计或毕业设计参考的开发者帮助解决从零搭建电商类Web系统的实际需求。项目采用Java、SpringBoot、Vue、Ajax、Maven、MySQL与MyBatisPlus等技术栈前后端分离涵盖用户信息管理、图片素材管理、视频素材管理等核心模块并附有绪论、相关技术介绍、系统分析、系统设计、数据库设计与系统实现等完整文档章节便于理解整体架构与开发思路。资源包共862个文件包含146个Java源文件、52个Vue组件、153个JavaScript脚本、46个HTML页面、44个CSS样式及大量svg、gif、jpg等图片素材另有xml配置、sql相关与bat启动脚本压缩包约17.98MB目录结构清晰便于按模块查阅与二次开发。目前已有111人学习下载适合需要快速掌握SpringBoot电商项目开发流程、对照源码查漏补缺的读者参考使用。1. 服装销售平台从零到一一套 SpringBoot 源码到底能跑出什么电商系统里服装类目是最难啃的骨头之一。同一个 SPU 下要挂 S/M/L/XL 四个尺码、五六种颜色每个组合还得独立管库存和价格这跟卖一本书、一个手机完全不是一个量级。很多 Java 工程师想拿一套服装销售平台源码来练手或做二次开发结果拉下来发现跑不起来、表结构看不懂、下单逻辑绕成一团。这篇笔记就围绕「基于 SpringBoot 的服装销售平台设计与实现」这条线把技术选型、表结构设计、核心链路代码、部署踩坑一次讲透。适合正在找毕业设计选题的在校生、想补一个完整项目经验的初中级 Java 工程师以及需要快速搭一套服装电商原型的小团队。读完你能自己判断一套源码值不值得改也能从零把最小可运行版本搭出来。2. 技术选型与工程骨架为什么是 SpringBoot MyBatis-Plus 而不是别的2.1 服装电商场景对技术栈的真实要求服装销售平台跟通用商城最大的区别在 SKU 维度爆炸。一件 T 恤4 个尺码 × 5 个颜色 20 个 SKU每个 SKU 有独立库存、独立条码、可能还有独立售价。这意味着数据访问层要频繁做多条件组合查询对 ORM 框架的动态 SQL 能力要求很高。选 SpringBoot 的理由很直接自动配置省掉大量 XML起步依赖把 Web、事务、连接池一次性拉齐内嵌 Tomcat 让部署变成java -jar一条命令。选 MyBatis-Plus 而不是 JPA核心原因是服装平台的查询条件太灵活——后台要按季节、品类、尺码段、价格区间、上架状态任意组合筛选MyBatis-Plus 的QueryWrapper和自定义 XML 能精确控制 SQL不会像 JPA 那样在复杂关联查询时生成一堆看不懂的嵌套语句。数据库用 MySQL 8.x缓存用 Redis 存购物车和热点商品前端可以是 Vue 打包后放进 SpringBoot 的static目录也可以前后端分离部署。如果你的项目里还涉及实时销售大屏SpringBoot 整合 Flink 做流式统计也是一条常见路线但那是进阶需求最小版本不需要。2.2 工程目录结构与依赖配置一个能跑起来的服装销售平台后端目录大致长这样src/main/java/com/example/clothing/ ├── ClothingApplication.java // 启动类 ├── config/ // 配置类 │ ├── MybatisPlusConfig.java // 分页插件、乐观锁插件 │ ├── RedisConfig.java // 序列化配置 │ └── WebMvcConfig.java // 跨域、拦截器注册 ├── controller/ // 接口层 ├── service/ // 业务层 │ └── impl/ ├── mapper/ // 数据访问层 ├── entity/ // 数据库实体 ├── dto/ // 传输对象 ├── vo/ // 视图对象 └── common/ // 统一返回、异常处理pom.xml里必须锁死版本SpringBoot 版本太高会导致 MyBatis-Plus 的某些插件不兼容这是高频翻车点。我一般用 SpringBoot 2.7.x 配 MyBatis-Plus 3.5.x稳定且资料多。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency /dependenciesapplication.yml里几个关键配置不能漏spring: datasource: url: jdbc:mysql://localhost:3306/clothing_mall?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 database: 0 mybatis-plus: mapper-locations: classpath:mapper/*.xml global-config: db-config: logic-delete-field: deleted # 逻辑删除字段 logic-delete-value: 1 logic-not-delete-value: 0逻辑删除字段deleted是电商系统的后悔药——商品下架、订单取消都不做物理删除方便对账和追溯。分页插件必须在MybatisPlusConfig里注册否则Page对象查出来永远是全量数据。Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); // 分页插件指定数据库类型 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 乐观锁插件用于库存扣减 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; } }分页插件不注册后台商品列表一次查出几万条接口直接超时。乐观锁插件不注册Version注解不生效并发扣库存必出超卖。这两个是骨架里最容易被忽略但后果最严重的配置。3. 服装 SKU 表结构设计与核心链路代码3.1 商品-SPU-SKU 三表怎么拆服装平台的数据模型核心就三张表product商品/SPU、product_spec规格项、product_sku具体库存单元。很多人图省事把尺码颜色塞进一个 JSON 字段后期做库存扣减和销量统计时哭都来不及。表名关键字段说明productid, name, category_id, season, main_image, statusSPU 主表存公共信息product_specid, product_id, spec_name, spec_values规格定义如「颜色:红,蓝;尺码:S,M,L」product_skuid, product_id, spec_json, price, stock, versionSKU 表spec_json 存组合值product_sku的spec_json存{颜色:红,尺码:M}这样的结构配合version字段做乐观锁。查询时用 MySQL 的 JSON 函数或直接在应用层解析服装平台 SKU 数量通常几百到几千应用层解析完全够用。3.2 下单扣库存的完整代码链路下单是服装平台最容易出问题的环节。用户选中某个 SKU数量 1点击提交后端要做的事校验库存 → 锁定库存 → 生成订单 → 扣减库存。高并发下必须用乐观锁或 Redis 预扣。Service public class OrderServiceImpl implements OrderService { Autowired private ProductSkuMapper skuMapper; Autowired private OrderMapper orderMapper; Override Transactional(rollbackFor Exception.class) public OrderVO createOrder(Long skuId, Integer quantity, Long userId) { // 1. 查询 SKU带 version 字段 ProductSku sku skuMapper.selectById(skuId); if (sku null || sku.getStock() quantity) { throw new BizException(库存不足); } // 2. 乐观锁扣减UPDATE ... SET stock stock - ? , version version 1 WHERE id ? AND version ? int affected skuMapper.deductStock(skuId, quantity, sku.getVersion()); if (affected 0) { // 版本冲突说明有其他线程同时扣减让用户重试 throw new BizException(当前下单人数过多请重试); } // 3. 生成订单 Order order new Order(); order.setUserId(userId); order.setSkuId(skuId); order.setQuantity(quantity); order.setTotalPrice(sku.getPrice().multiply(new BigDecimal(quantity))); order.setStatus(OrderStatus.PENDING_PAY.getCode()); orderMapper.insert(order); return convertToVO(order); } }对应的 Mapper XMLupdate iddeductStock UPDATE product_sku SET stock stock - #{quantity}, version version 1 WHERE id #{skuId} AND version #{version} AND stock #{quantity} /updateWHERE条件里同时带version和stock quantity是双保险。version防并发覆盖stock quantity防超卖。Transactional保证订单插入和库存扣减在同一事务里任何一步失败全部回滚。注意rollbackFor Exception.class不能省否则受检异常不回滚。3.3 购物车用 Redis 还是 MySQL购物车数据读写频率极高放 MySQL 会给数据库带来不必要的压力。常见做法是用 Redis Hash 存储key 为cart:{userId}field 为skuIdvalue 为数量。Service public class CartService { Autowired private StringRedisTemplate redisTemplate; private static final String CART_KEY cart:%d; public void addToCart(Long userId, Long skuId, Integer quantity) { String key String.format(CART_KEY, userId); // Hash 结构fieldskuId, valuequantity redisTemplate.opsForHash().increment(key, skuId.toString(), quantity); // 设置过期时间避免冷用户购物车永久占用内存 redisTemplate.expire(key, 7, TimeUnit.DAYS); } public MapObject, Object getCart(Long userId) { String key String.format(CART_KEY, userId); return redisTemplate.opsForHash().entries(key); } }increment保证同一 SKU 多次加购数量累加expire设置 7 天过期。下单时从 Redis 读取购物车清空对应 field再走上面的扣库存流程。Redis 挂了购物车会丢对服装平台来说可接受但订单数据必须落 MySQL。4. 避坑与排查服装销售平台源码跑不起来的 5 个高频原因4.1 启动报错 Unknown database 或表不存在现象java.sql.SQLSyntaxErrorException: Table clothing_mall.product doesnt exist。原因源码包里通常只给了.sql文件但没说明要先建库再导入或者 SQL 文件里的库名和application.yml里配的不一致。解决先CREATE DATABASE clothing_mall DEFAULT CHARACTER SET utf8mb4;再USE clothing_mall;然后source xxx.sql;。导入后执行SHOW TABLES;确认表数量。如果 SQL 文件里写死了USE other_db手动改成你的库名。4.2 SpringBoot 版本太高导致 MyBatis-Plus 报错现象启动时java.lang.NoClassDefFoundError: org/mybatis/logging/LoggerFactory或Invalid bound statement (not found)。原因SpringBoot 3.x 默认用 Jakarta EE 命名空间而老版本 MyBatis-Plus 还在用javax。或者 MyBatis-Plus 版本和 SpringBoot 版本不匹配。解决要么把 SpringBoot 降到 2.7.x要么把 MyBatis-Plus 升到 3.5.3 并使用mybatis-plus-spring-boot3-starter。我一般直接锁 2.7.18 3.5.3.1 这套组合省心。4.3 前端 Vue 打包后放进 static 目录刷新 404现象首页能打开点进商品详情再刷新浏览器报 404。原因Vue 的 history 路由模式需要服务端把所有未匹配路径转发到index.htmlSpringBoot 默认不处理。解决加一个配置类把非 API 路径转发到首页。Configuration public class WebMvcConfig implements WebMvcConfigurer { Override public void addViewControllers(ViewControllerRegistry registry) { // 所有非 /api 开头的路径都转发到 index.html registry.addViewController(/{path:[^\\.]*}) .setViewName(forward:/index.html); } }注意/{path:[^\\.]*}这个正则排除了带点的静态资源请求否则 JS/CSS 文件也会被转发。4.4 库存扣减出现负数现象压测或多人同时下单后stock字段变成负数。原因扣减 SQL 只写了SET stock stock - #{quantity}没有WHERE stock #{quantity}条件或者没用乐观锁。解决扣减语句必须同时带version和stock quantity条件检查affected rows是否为 0。为 0 就抛异常让用户重试绝不能默默继续。4.5 图片上传后访问 404现象后台上传商品图片成功但前端img src加载不出来。原因文件存到了服务器本地磁盘但没配置静态资源映射SpringBoot 不知道去哪个目录找。解决在application.yml里配file.upload-path然后加映射。Configuration public class ResourceConfig implements WebMvcConfigurer { Value(${file.upload-path}) private String uploadPath; Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // 把 /images/** 映射到本地上传目录 registry.addResourceHandler(/images/**) .addResourceLocations(file: uploadPath); } }file:前缀不能少否则会被当成 classpath 路径。上传目录末尾要带/这是血泪经验。5. 从能跑到好用接口压测与二次开发切入点5.1 用 JMeter 验证下单接口的并发安全性源码跑起来只是第一步你得知道它在并发下什么表现。用 JMeter 对/api/order/create发 200 个并发请求观察库存是否精确扣减、有没有超卖、响应时间是否可接受。线程组配置线程数 200Ramp-Up 1 秒循环 1 次。HTTP 请求填localhost:8080/api/order/createBody 里带skuId和quantity。跑完后查数据库SELECT stock, version FROM product_sku WHERE id 1; -- 预期stock 初始库存 - 成功订单数version 初始版本 成功订单数 SELECT COUNT(*) FROM orders WHERE sku_id 1; -- 订单数应等于库存扣减量如果stock出现负数说明乐观锁没生效回去检查MybatisPlusConfig里插件是否注册、实体类version字段有没有加Version注解。如果订单数远小于库存扣减量说明事务边界有问题检查Transactional是否加在 public 方法上、是否被同类内部调用绕过代理。5.2 二次开发的三个高价值切入点拿到一套服装销售平台源码别急着改页面。先看三个地方第一SKU 生成逻辑。很多源码的商品发布页是让你手动一个个填 SKU效率极低。你可以改成笛卡尔积自动生成选好颜色和尺码后后端自动组合出所有 SKU 记录运营只需批量填价格和库存。这个改动对实际使用体验提升最大。第二订单状态机。检查订单状态流转是否用了状态机模式。如果只是if-else堆砌加一个「换货」状态就会牵一发动全身。用枚举 状态流转表重构后续加售后流程会轻松很多。第三缓存策略。商品详情页是读多写少的典型场景。如果源码里每次请求都查数据库加一层 Redis 缓存设置 5 分钟过期配合更新时主动删除缓存。注意缓存穿透问题对不存在的商品 ID 也缓存空值过期时间设短一点比如 60 秒。public ProductVO getProductDetail(Long productId) { String cacheKey product:detail: productId; // 先查缓存 String cached redisTemplate.opsForValue().get(cacheKey); if (cached ! null) { return JSON.parseObject(cached, ProductVO.class); } // 查数据库 ProductVO vo loadFromDb(productId); if (vo null) { // 缓存空值防穿透60 秒过期 redisTemplate.opsForValue().set(cacheKey, , 60, TimeUnit.SECONDS); return null; } redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(vo), 5, TimeUnit.MINUTES); return vo; }缓存空值时存空字符串而不是null因为 Redis 存不了null。读取时判断cached ! null且非空字符串才反序列化。5.3 一个容易被忽略的细节逻辑删除与唯一索引冲突服装平台里商品编码、SKU 条码通常要加唯一索引。但如果你用了逻辑删除删除一条记录后再插入相同编码唯一索引会报冲突——因为旧记录还在表里只是deleted 1。解决办法有两个一是唯一索引带上deleted字段变成联合唯一索引UNIQUE KEY (code, deleted)但这样只能删一次二是删除时把编码改掉比如追加时间戳后缀。我一般用第二种在delete方法里把code更新为code _del_ System.currentTimeMillis()再设deleted 1。这样既保留了历史数据又不影响新记录插入。这套方案我前后搭过三次每次都会在库存扣减和缓存一致性上翻车后来养成了习惯任何涉及库存的 SQL 必须带stock quantity任何缓存更新必须先操作数据库再删缓存。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑