Spring Boot电商后端实战:商品SKU、品类推荐与降价监控系统设计
1. 背景与核心概念在做电商后台系统时我经常遇到一个高频需求商品详情页不仅要展示基本参数还要有品类推荐、降价提醒、多规格 SKU 选择。最近正好梳理了一个以羽绒服商品为例的完整实现思路包含数据库设计、推荐接口、价格监控和库存扣减。文章会从零搭建一个可运行的 Spring Boot Demo不管是学生做课设还是后端同学做电商订单系统都可以参考。先解释几个容易混淆的概念。商品Product与 SKU 的关系商品是业务上的“一件东西”比如“鸭鸭轻壳系列女短款羽绒服”。SKU 是具体可下单的规格组合比如“白色 M 码 130 元”。一个商品可以对应多个 SKU每个 SKU 有自己的价格、库存、编码。详情页上“多色可选”其实就是同一商品下挂了多个不同颜色的 SKU。品类推荐Category Recommend品类推荐是电商系统里最常见的推荐策略用户正在看羽绒服系统就在周边推荐其他羽绒服或同品类商品。实现时不需要上复杂的机器学习模型第一步先做“同品类 排序规则”比如按销量、按价格区间、按上架时间。等数据量大之后再逐步引入协同过滤、向量召回等算法。再降价Price Drop Reminder“再降价”本质上是一个价格变更记录与比较问题。系统需要保存每个 SKU 的历史价格当用户查看商品时把当前价格和历史价格做对比得出“降价 XX 元”或“历史最低价”的结果。更完整的场景还包括用户订阅降价提醒在价格变化时发送通知。起始价Starting Price“130 元起”是商品页经常展示的营销文案。一个商品有多个 SKU不同颜色、尺码价格可能不同前端列表需要展示最低价即所有 SKU 中价格的最小值。这个值可以从 SKU 表中聚合得出。本文的实战目标就是在一个 Spring Boot 项目中把上面三个能力串起来维护商品和 SKU、实现品类推荐接口、记录价格变化并返回“再降价”信息。整体代码量不大但覆盖了电商后端的基本数据模型和常用接口写法。2. 环境准备与项目结构2.1 技术栈与版本说明以 Java 后端为例本文采用的组件如下组件说明JDK建议 JDK 8 或 JDK 11Spring Boot 3.x 需要 JDK 17 以上Spring Boot使用 2.7.x 或 3.x 均可本文示例按 2.7 风格编写MySQL5.7 或 8.0存储商品、SKU、价格历史数据MyBatis-Plus简化 CRUD 和分页查询Lombok减少实体类冗余代码版本需要根据你的项目实际情况调整。如果本地没有 MySQL也可以用 H2 数据库临时运行只需要修改依赖和配置。2.2 项目目录结构为了方便阅读我把代码按包结构拆分如下demo-ecommerce/ ├── pom.xml ├── src/main/java/com/example/ecommerce/ │ ├── EcommerceApplication.java │ ├── controller/ │ │ ├── ProductController.java │ │ └── RecommendController.java │ ├── entity/ │ │ ├── Product.java │ │ ├── Sku.java │ │ ├── PriceHistory.java │ │ └── Category.java │ ├── mapper/ │ │ ├── ProductMapper.java │ │ ├── SkuMapper.java │ │ ├── PriceHistoryMapper.java │ │ └── CategoryMapper.java │ ├── service/ │ │ ├── ProductService.java │ │ ├── RecommendService.java │ │ └── PriceService.java │ └── vo/ │ ├── ProductDetailVO.java │ └── RecommendItemVO.java └── src/main/resources/ ├── application.yml └── schema.sql这个结构是标准的 Controller-Service-Mapper 分层适合中小型项目。生产项目还可以拆分出独立的 price-service、recommend-service 微服务但本文先聚焦单应用实现。2.3 基础依赖在pom.xml中加入依赖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-connector-j/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency如果使用 Spring Boot 3.x请把mybatis-plus-boot-starter替换为mybatis-plus-spring-boot3-starter。3. 数据库设计与核心表结构3.1 品类表 category品类表用于区分羽绒服、外套、卫衣等商品大类也是品类推荐的基本维度。CREATE TABLE category ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL COMMENT 品类名称, sort INT DEFAULT 0 COMMENT 排序权重, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT 商品品类表;3.2 商品表 product商品表保存商品公共信息不保存具体颜色尺码和价格。CREATE TABLE product ( id BIGINT PRIMARY KEY AUTO_INCREMENT, category_id BIGINT NOT NULL COMMENT 品类ID, title VARCHAR(200) NOT NULL COMMENT 商品标题, main_pic VARCHAR(500) COMMENT 主图地址, status TINYINT DEFAULT 1 COMMENT 1上架 0下架, sales INT DEFAULT 0 COMMENT 销量, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) COMMENT 商品表;这里要注意不要把价格字段放在 product 表里因为同一商品不同 SKU 价格不同。商品表的sales可以每次下单后累加用于推荐排序。3.3 SKU 表 skuSKU 表是商品多规格的具体表现。“多色可选”就是同一个product_id下有多个不同color_name的 SKU。CREATE TABLE sku ( id BIGINT PRIMARY KEY AUTO_INCREMENT, product_id BIGINT NOT NULL COMMENT 商品ID, sku_code VARCHAR(64) NOT NULL COMMENT SKU编码, color_name VARCHAR(30) COMMENT 颜色如白色/黑色, size_name VARCHAR(30) COMMENT 尺码如S/M/L, price DECIMAL(10,2) NOT NULL COMMENT 销售价格, stock INT NOT NULL DEFAULT 0 COMMENT 库存, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_product (product_id) ) COMMENT SKU规格表;这里的price字段就是“130 元起”的数据来源。前端展示商品卡片时可以查询MIN(price)。3.4 价格历史表 price_history价格历史表是“再降价”功能的核心。每次 SKU 价格发生变化时需要把旧价格保存为一条历史记录或者定期采集当前价格。CREATE TABLE price_history ( id BIGINT PRIMARY KEY AUTO_INCREMENT, sku_id BIGINT NOT NULL COMMENT SKU ID, product_id BIGINT NOT NULL COMMENT 商品ID, price DECIMAL(10,2) NOT NULL COMMENT 当时价格, record_time DATETIME NOT NULL COMMENT 记录时间, KEY idx_sku_time (sku_id, record_time) ) COMMENT 价格历史表;判断“再降价”时通常比较当前价格与最近一次价格或者与历史最低价比较。如果当前价格低于最近一次记录就可以在商品页展示“降价 XX 元”。4. 实体类与 Mapper 层实现4.1 商品实体类 Product// 文件路径src/main/java/com/example/ecommerce/entity/Product.java Data TableName(product) public class Product { TableId(type IdType.AUTO) private Long id; private Long categoryId; private String title; private String mainPic; private Integer status; private Integer sales; private LocalDateTime createTime; private LocalDateTime updateTime; }使用 MyBatis-Plus 时TableName指定数据库表名TableId声明主键。字段命名遵循驼峰转下划线规则mainPic自动映射到main_pic。4.2 SKU 实体类 Sku// 文件路径src/main/java/com/example/ecommerce/entity/Sku.java Data TableName(sku) public class Sku { TableId(type IdType.AUTO) private Long id; private Long productId; private String skuCode; private String colorName; private String sizeName; private BigDecimal price; private Integer stock; private LocalDateTime createTime; private LocalDateTime updateTime; }4.3 价格历史实体类 PriceHistory// 文件路径src/main/java/com/example/ecommerce/entity/PriceHistory.java Data TableName(price_history) public class PriceHistory { TableId(type IdType.AUTO) private Long id; private Long skuId; private Long productId; private BigDecimal price; private LocalDateTime recordTime; }4.4 Mapper 接口Mapper 接口直接继承BaseMapper即可获得单表 CRUD 能力。// 文件路径src/main/java/com/example/ecommerce/mapper/ProductMapper.java public interface ProductMapper extends BaseMapperProduct { } // 文件路径src/main/java/com/example/ecommerce/mapper/SkuMapper.java public interface SkuMapper extends BaseMapperSku { } // 文件路径src/main/java/com/example/ecommerce/mapper/PriceHistoryMapper.java public interface PriceHistoryMapper extends BaseMapperPriceHistory { }如果需要在 Mapper 里写复杂的多表关联 SQL可以在resources/mapper/目录下新建 XML 文件但本文示例用 Service 层完成逻辑。5. 商品与 SKU 的创建接口在写推荐和降价逻辑之前先保证商品数据能写入。创建商品时需要同时插入商品主数据和 SKU 数据。5.1 Service 层实现// 文件路径src/main/java/com/example/ecommerce/service/ProductService.java Service RequiredArgsConstructor public class ProductService { private final ProductMapper productMapper; private final SkuMapper skuMapper; Transactional(rollbackFor Exception.class) public Long createProduct(Product product, ListSku skuList) { productMapper.insert(product); for (Sku sku : skuList) { sku.setProductId(product.getId()); skuMapper.insert(sku); } return product.getId(); } }这里加上Transactional是为了保证商品主数据和 SKU 数据要么全部写入要么全部回滚。如果第二个 SKU 插入失败第一步插入的商品也会回滚不会出现“只有商品没有 SKU”的脏数据。为什么要在 Service 层做因为 Mapper 层只负责单表操作跨表事务需要 Service 层统一管理。5.2 Controller 层实现// 文件路径src/main/java/com/example/ecommerce/controller/ProductController.java RestController RequestMapping(/api/product) RequiredArgsConstructor public class ProductController { private final ProductService productService; PostMapping(/create) public ResultLong create(RequestBody ProductCreateDTO dto) { Product product new Product(); product.setCategoryId(dto.getCategoryId()); product.setTitle(dto.getTitle()); product.setMainPic(dto.getMainPic()); product.setStatus(1); product.setSales(0); ListSku skuList dto.getSkus().stream().map(s - { Sku sku new Sku(); sku.setSkuCode(s.getSkuCode()); sku.setColorName(s.getColorName()); sku.setSizeName(s.getSizeName()); sku.setPrice(s.getPrice()); sku.setStock(s.getStock()); return sku; }).collect(Collectors.toList()); Long productId productService.createProduct(product, skuList); return Result.success(productId); } }实际业务中商品创建还会涉及图片上传、详情富文本、审核流程。本文为了聚焦核心逻辑省略了这些部分。5.3 查询商品详情与最低价商品详情页需要展示商品信息并计算“130 元起”的起始价。// 文件路径src/main/java/com/example/ecommerce/vo/ProductDetailVO.java Data public class ProductDetailVO { private Long productId; private String title; private String mainPic; private BigDecimal minPrice; private ListSkuVO skus; }Service 中查询商品和 SKU 并计算最低价public ProductDetailVO getProductDetail(Long productId) { Product product productMapper.selectById(productId); ListSku skus skuMapper.selectList( new LambdaQueryWrapperSku().eq(Sku::getProductId, productId) ); ProductDetailVO vo new ProductDetailVO(); vo.setProductId(product.getId()); vo.setTitle(product.getTitle()); vo.setMainPic(product.getMainPic()); vo.setSkus(skus); BigDecimal minPrice skus.stream() .map(Sku::getPrice) .min(BigDecimal::compareTo) .orElse(BigDecimal.ZERO); vo.setMinPrice(minPrice); return vo; }这里使用LambdaQueryWrapper构造查询条件避免硬编码数据库字段名。6. 品类推荐模块实现6.1 推荐策略设计商品详情页底部的“相关推荐”一般会显示同品类商品。基础推荐策略可以拆成三步找到当前商品的categoryId。查询同品类下状态为上架的其他商品。排除当前商品后按销量降序、创建时间降序排列。如果想控制价格区间比如用户看的是 130 元起的羽绒服推荐 100-300 元之间的同类商品可以再加上minPrice条件。但这里有一个细节SKU 价格在 sku 表里按商品聚合后再过滤更合适。为了简单示例中先按同品类和销量排序。6.2 推荐 Service 实现// 文件路径src/main/java/com/example/ecommerce/service/RecommendService.java Service RequiredArgsConstructor public class RecommendService { private final ProductMapper productMapper; private final SkuMapper skuMapper; public ListRecommendItemVO recommendByCategory(Long productId, int limit) { Product current productMapper.selectById(productId); if (current null) { return Collections.emptyList(); } ListProduct productList productMapper.selectList( new LambdaQueryWrapperProduct() .eq(Product::getCategoryId, current.getCategoryId()) .eq(Product::getStatus, 1) .ne(Product::getId, productId) .orderByDesc(Product::getSales) .orderByDesc(Product::getCreateTime) .last(limit limit) ); ListRecommendItemVO result new ArrayList(); for (Product p : productList) { ListSku skus skuMapper.selectList( new LambdaQueryWrapperSku().eq(Sku::getProductId, p.getId()) ); if (skus.isEmpty()) { continue; } BigDecimal minPrice skus.stream() .map(Sku::getPrice) .min(BigDecimal::compareTo) .orElse(BigDecimal.ZERO); RecommendItemVO vo new RecommendItemVO(); vo.setProductId(p.getId()); vo.setTitle(p.getTitle()); vo.setSales(p.getSales()); vo.setMinPrice(minPrice); vo.setColorCount((int) skus.stream().map(Sku::getColorName).distinct().count()); result.add(vo); } return result; } }RecommendItemVO中的colorCount就是“多色可选”的前端展示数据。一个商品有多少种颜色通过 SKU 中colorName去重统计得出。6.3 Controller 接口// 文件路径src/main/java/com/example/ecommerce/controller/RecommendController.java RestController RequestMapping(/api/recommend) RequiredArgsConstructor public class RecommendController { private final RecommendService recommendService; GetMapping(/category) public ResultListRecommendItemVO recommend( RequestParam Long productId, RequestParam(defaultValue 6) int limit) { return Result.success(recommendService.recommendByCategory(productId, limit)); } }这里的limit默认 6前端可以根据页面布局传不同值。7. “再降价”价格监控模块实现7.1 价格变更时写入历史记录价格不是随随便便改的。运营在后台改价时系统要自动保存调价前的价格。设计一个PriceService统一处理。// 文件路径src/main/java/com/example/ecommerce/service/PriceService.java Service RequiredArgsConstructor public class PriceService { private final SkuMapper skuMapper; private final PriceHistoryMapper priceHistoryMapper; Transactional(rollbackFor Exception.class) public void updatePrice(Long skuId, BigDecimal newPrice) { Sku sku skuMapper.selectById(skuId); if (sku null) { throw new RuntimeException(SKU不存在); } BigDecimal oldPrice sku.getPrice(); if (oldPrice.compareTo(newPrice) 0) { return; } // 保存一条历史价格记录 PriceHistory history new PriceHistory(); history.setSkuId(skuId); history.setProductId(sku.getProductId()); history.setPrice(oldPrice); history.setRecordTime(LocalDateTime.now()); priceHistoryMapper.insert(history); // 更新当前价格 sku.setPrice(newPrice); skuMapper.updateById(sku); } }这里遵循“先写历史、再改当前”的顺序保证任何一条价格都能追溯。7.2 查询商品是否有降价商品详情接口中需要返回“是否降价”“降价多少元”。判断逻辑可以这样定义取当前 SKU 价格currentPrice。取最近一条历史价格lastPrice。如果currentPrice lastPrice则说明“再降价”。public PriceDropVO checkPriceDrop(Long skuId) { Sku sku skuMapper.selectById(skuId); PriceHistory lastHistory priceHistoryMapper.selectOne( new LambdaQueryWrapperPriceHistory() .eq(PriceHistory::getSkuId, skuId) .orderByDesc(PriceHistory::getRecordTime) .last(limit 1) ); PriceDropVO vo new PriceDropVO(); vo.setSkuId(skuId); vo.setCurrentPrice(sku.getPrice()); if (lastHistory null) { vo.setDrop(false); vo.setDropAmount(BigDecimal.ZERO); } else { BigDecimal dropAmount lastHistory.getPrice().subtract(sku.getPrice()); if (dropAmount.compareTo(BigDecimal.ZERO) 0) { vo.setDrop(true); vo.setDropAmount(dropAmount); } else { vo.setDrop(false); vo.setDropAmount(BigDecimal.ZERO); } } return vo; }这里只用最近一条历史价格作为参照。更严谨的做法是和“历史最低价”比较避免先涨后降造成误导。7.3 定时扫描降价商品可选延展如果业务需要主动推送“降价通知”可以利用Scheduled定时任务扫描 SKU 表比较当前价格和历史记录生成提醒消息。Component RequiredArgsConstructor public class PriceScanTask { private final SkuMapper skuMapper; Scheduled(cron 0 0 2 * * ?) public void scanPriceDrop() { ListSku skus skuMapper.selectList(null); for (Sku sku : skus) { // 调用 checkPriceDrop 逻辑 // 如果发现有降价写入消息队列或通知表 } } }注意扫描全表在数据量大时效率很低生产环境建议改为“价格变更时实时计算 延迟推送”或者只扫描指定时间段内更新过的 SKU。8. 多色可选与库存扣减实现8.1 查询商品可选规格商品页的“颜色”和“尺码”选择器本质上是查询 SKU 列表并按维度分组。前端需要知道哪些颜色可选。某个颜色下有哪些尺码。每个 SKU 的库存是否充足。可以在SkuVO中直接返回全部 SKU 信息由前端过滤Data public class SkuVO { private Long skuId; private String skuCode; private String colorName; private String sizeName; private BigDecimal price; private Integer stock; }如果要更精确地返回“某颜色下有哪些尺码”Service 可以这样处理public MapString, ListString listSkuOptions(Long productId) { ListSku skus skuMapper.selectList( new LambdaQueryWrapperSku().eq(Sku::getProductId, productId) ); MapString, ListString colorSizeMap new LinkedHashMap(); for (Sku sku : skus) { colorSizeMap.computeIfAbsent(sku.getColorName(), k - new ArrayList()) .add(sku.getSizeName()); } return colorSizeMap; }8.2 库存扣减与防超卖用户下单时需要扣减指定 SKU 的库存。一个容易踩坑的问题是直接使用UPDATE sku SET stock stock - 1 WHERE id ?会比先查再更安全。原因是数据库行锁可以防止并发超卖。public boolean deductStock(Long skuId, int quantity) { int rows skuMapper.deductStock(skuId, quantity); return rows 0; }对应的 Mapper 方法public interface SkuMapper extends BaseMapperSku { Update(UPDATE sku SET stock stock - #{quantity} WHERE id #{skuId} AND stock #{quantity}) int deductStock(Param(skuId) Long skuId, Param(quantity) int quantity); }这里的关键是AND stock quantity如果库存不足影响行数为 0事务回滚不会出现负数库存。8.3 下单接口简单示例PostMapping(/order) Transactional(rollbackFor Exception.class) public ResultLong createOrder(RequestBody OrderCreateDTO dto) { boolean ok skuMapper.deductStock(dto.getSkuId(), dto.getQuantity()); if (!ok) { return Result.error(库存不足); } // 后续创建订单明细、扣减库存、记录销量等 return Result.success(orderId); }实际电商系统还会包含订单状态机、支付回调、售后流程这里只是验证核心库存链路。9. 运行与验证9.1 初始化测试数据启动项目后先用创建接口插入测试商品。假设创建了以下数据商品 A鸭鸭轻壳系列女短款羽绒服品类为羽绒服。SKU1白色 M 码130 元库存 100。SKU2黑色 M 码150 元库存 100。SKU3白色 L 码140 元库存 80。商品 B同品类另一款羽绒服价格 199 元。商品 C同品类童款羽绒服价格 120 元。创建请求示例curl -X POST http://localhost:8080/api/product/create \ -H Content-Type: application/json \ -d { categoryId: 1, title: 鸭鸭轻壳系列女短款羽绒服, mainPic: https://example.com/img/1.jpg, skus: [ {skuCode: A-WHITE-M, colorName: 白色, sizeName: M, price: 130.00, stock: 100}, {skuCode: A-BLACK-M, colorName: 黑色, sizeName: M, price: 150.00, stock: 100}, {skuCode: A-WHITE-L, colorName: 白色, sizeName: L, price: 140.00, stock: 80} ] }9.2 验证推荐接口请求类似curl http://localhost:8080/api/recommend/category?productId1limit6返回结果示例{ code: 0, data: [ { productId: 2, title: 同品类羽绒服B, sales: 320, minPrice: 199.00, colorCount: 3 } ], message: success }9.3 验证降价接口当调用PriceService.updatePrice把 SKU1 从 140 调整为 130 后查询详情或调用降价查询接口可以看到{ skuId: 1, currentPrice: 130.00, drop: true, dropAmount: 10.00 }到这里一个支持品类推荐、降价监控、多规格 SKU 的 Demo 就完整跑通了。10. 常见问题与排查思路问题现象常见原因解决思路接口返回“库存不足”但数据库库存充足并发下使用了先查询再更新的方式改用原子更新UPDATE ... SET stock stock - 1 WHERE stock quantity推荐结果不准确只按销量排序没考虑用户偏好先保证基础召回稳定再引入用户点击行为数据价格历史数据增长过快每次改价都保存一条记录未做归档按时间分区定期归档到历史表或数仓商品详情页最低价不准没考虑 SKU 下架或删除状态统计最低价时过滤有效 SKU定时扫描任务影响数据库性能全表扫描所有 SKU改增量扫描限定更新时间范围或使用消息事件触发多色可选展示异常前端按颜色分组时使用 SKU 顺序不稳定返回数据时使用LinkedHashMap或固定排序字段排查这类问题建议先从 SQL 日志开始看。MyBatis-Plus 开启日志后可以确认每次查询走了哪个索引、有没有全表扫描。mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl11. 最佳实践与工程建议11.1 价格相关操作统一入口价格变更不应该让业务方直接updateSKU 表而是走PriceService.updatePrice()方法。这样可以在入口统一记录历史、发送消息、清理缓存。否则每个业务方改一次价格价格历史就会漏记。11.2 推荐结果加缓存品类推荐接口的 QPS 通常比较高。同品类商品列表短时间内不会频繁变化可以在 Redis 中缓存一段时间。public ListRecommendItemVO recommendByCategoryWithCache(Long productId, int limit) { String cacheKey recommend:category: productId : limit; ListRecommendItemVO cacheResult cacheService.getList(cacheKey); if (cacheResult ! null) { return cacheResult; } ListRecommendItemVO result recommendByCategory(productId, limit); cacheService.setList(cacheKey, result, 300); return result; }缓存失效策略可以简单设为 5 分钟也可以在上架/下架商品时主动删除相关品类缓存。11.3 数据库索引建议SKU 表product_id字段必须建索引商品详情查询才能快。价格历史表sku_id record_time建联合索引支撑“查最近价格”和“查历史最低价”。商品表category_id status sales可以建联合索引推荐接口排序时会用到。11.4 生产环境的安全边界线上环境一定要做接口权限控制商品创建、改价都只能由运营后台调用不能在公网直接暴露。即使做课程设计也应该加一个最简单的 Token 校验避免被人刷接口改库存。11.5 从 Demo 到微服务的演进方向本文的代码放在单个 Spring Boot 应用里适合快速验证。如果业务规模扩大可以按以下方向拆分商品中心负责商品、分类、SKU 数据。价格中心负责价格变更、价格历史、降价提醒。推荐中心负责召回、排序、缓存。拆分时要注意不同服务之间的数据一致性不能依靠强事务要引入消息队列或事件总线。12. 总结本文以羽绒服商品为业务样例完整实现了电商后端几个高频能力商品与 SKU 的数据库设计、品类推荐接口、“再降价”价格监控、多规格选择与库存扣减。代码是一个可以直接启动的 Spring Boot Demo你可以在此基础上继续补充订单、购物车、用户体系等功能。如果你正在做电商相关的课设或毕设建议优先把商品表、SKU 表、价格历史表这三张表设计清楚后面的接口和页面都围绕它们展开。如果已经在企业项目里做类似需求可以参考本文的“价格变更统一入口”和“库存原子更新”这两个点它们能在真实业务中避免很多线上事故。下一步可以继续学习基于 Redis 的推荐缓存与热点商品处理。基于 RocketMQ 或 Kafka 的降价通知异步推送。基于分库分表的价格历史归档方案。基于用户行为数据的协同过滤推荐。动手把代码跑起来再尝试修改推荐排序规则和降价判断逻辑会比只看文章更有收获。