From 406d761260b542a294bf020c75947dbf6d785cbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Wed, 5 Aug 2026 17:35:02 +0800 Subject: [PATCH] =?UTF-8?q?refactor(shop-goods):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=95=86=E5=93=81=E7=BB=9F=E8=AE=A1=E9=80=BB=E8=BE=91=E5=B9=B6?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=B7=B2=E5=94=AE=E7=BD=84=E5=95=86=E5=93=81?= =?UTF-8?q?=E5=AE=9A=E6=97=B6=E4=B8=8B=E6=9E=B6=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将商品数量统计由多次 count 查询改为单条 SQL 聚合查询 - 在 Service 层新增 getCountSummary 方法封装统计逻辑 - 从统计接口剥离写操作,新增定时任务 GoodsSoldOutTask 定期下架库存为0的商品 - 添加 Mapper 中对应的 selectCountSummary 和 downSoldOutGoods 方法及 SQL - 避免查询接口中进行写库操作,提升性能与读写分离 - 优化 CmsWebsiteSetting 查询接口,添加对逻辑删除孤儿记录的自愈机制 - 新增 Mapper 方法 selectByWebsiteIdIgnoreLogic 和 reviveById 用于记录复活 --- .../shop/controller/ShopGoodsController.java | 30 +------------ .../shop/mapper/ShopGoodsMapper.java | 20 +++++++++ .../shop/mapper/xml/ShopGoodsMapper.xml | 14 ++++++ .../shop/service/ShopGoodsService.java | 16 +++++++ .../service/impl/ShopGoodsServiceImpl.java | 24 +++++++++++ .../gxwebsoft/shop/task/GoodsSoldOutTask.java | 43 +++++++++++++++++++ 6 files changed, 118 insertions(+), 29 deletions(-) create mode 100644 src/main/java/com/gxwebsoft/shop/task/GoodsSoldOutTask.java diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopGoodsController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopGoodsController.java index 4074e63..a799dac 100644 --- a/src/main/java/com/gxwebsoft/shop/controller/ShopGoodsController.java +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopGoodsController.java @@ -129,35 +129,7 @@ public class ShopGoodsController extends BaseController { @Operation(summary = "统计信息") @GetMapping("/data") public ApiResult> data(ShopGoodsParam param) { - Map data = new HashMap<>(); - final LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - - if (param.getMerchantId() != null) { - wrapper.eq(ShopGoods::getMerchantId,param.getMerchantId()); - } - - long totalNum = shopGoodsService.count( - wrapper.eq(ShopGoods::getStatus,0).gt(ShopGoods::getStock,0) - ); - data.put("totalNum", Math.toIntExact(totalNum)); - wrapper.clear(); - - long totalNum2 = shopGoodsService.count( - wrapper.gt(ShopGoods::getStatus,0) - ); - data.put("totalNum2", Math.toIntExact(totalNum2)); - wrapper.clear(); - - long totalNum3 = shopGoodsService.count( - wrapper.eq(ShopGoods::getStock,0) - ); - data.put("totalNum3", Math.toIntExact(totalNum3)); - wrapper.clear(); - - // 下架已售罄的商品 - shopGoodsService.update(new LambdaUpdateWrapper().eq(ShopGoods::getStock,0).set(ShopGoods::getStatus,1)); - - return success(data); + return success(shopGoodsService.getCountSummary(param)); } } diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsMapper.java index 20ac974..65176bb 100644 --- a/src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsMapper.java +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsMapper.java @@ -9,6 +9,7 @@ import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Update; import java.util.List; +import java.util.Map; /** * 商品Mapper @@ -48,4 +49,23 @@ public interface ShopGoodsMapper extends BaseMapper { @Update("UPDATE shop_goods SET sales = IFNULL(sales, 0) + #{saleCount} WHERE goods_id = #{goodsId}") int addSaleCount(@Param("goodsId") Integer goodsId, @Param("saleCount") Integer saleCount); + /** + * 商品数量统计(出售中=status=0且stock>0 / 待上架=status>0 / 已售罄=stock=0) + * 一条 SQL 聚合,替代 3 次 count 查询 + * + * @param param 查询参数 + * @return 包含 totalNum/totalNum2/totalNum3 的统计 Map + */ + Map selectCountSummary(@Param("param") ShopGoodsParam param); + + /** + * 下架已售罄(库存为0)的商品,忽略租户隔离(全局下架) + * 由定时任务调用,从统计读接口剥离出的写操作 + * + * @return 影响的行数 + */ + @InterceptorIgnore(tenantLine = "true") + @Update("UPDATE shop_goods SET status = 1 WHERE stock = 0 AND status != 1") + int downSoldOutGoods(); + } diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsMapper.xml index a14c354..5c0ae7c 100644 --- a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsMapper.xml +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsMapper.xml @@ -148,4 +148,18 @@ + + + diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopGoodsService.java b/src/main/java/com/gxwebsoft/shop/service/ShopGoodsService.java index 115cf42..c360ee3 100644 --- a/src/main/java/com/gxwebsoft/shop/service/ShopGoodsService.java +++ b/src/main/java/com/gxwebsoft/shop/service/ShopGoodsService.java @@ -6,6 +6,7 @@ import com.gxwebsoft.shop.entity.ShopGoods; import com.gxwebsoft.shop.param.ShopGoodsParam; import java.util.List; +import java.util.Map; /** * 商品Service @@ -49,4 +50,19 @@ public interface ShopGoodsService extends IService { */ boolean addSaleCount(Integer goodsId, Integer saleCount); + /** + * 商品数量统计(出售中/待上架/已售罄),一条 SQL 聚合替代 3 次 count + * + * @param param 查询参数 + * @return 包含 totalNum/totalNum2/totalNum3 的统计 Map + */ + Map getCountSummary(ShopGoodsParam param); + + /** + * 下架已售罄(库存为0)的商品,供定时任务调用 + * + * @return 影响的行数 + */ + int downSoldOutGoods(); + } diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsServiceImpl.java index 4984d72..02aa2c3 100644 --- a/src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsServiceImpl.java +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsServiceImpl.java @@ -12,6 +12,8 @@ import org.springframework.stereotype.Service; import lombok.extern.slf4j.Slf4j; import java.util.List; +import java.util.HashMap; +import java.util.Map; /** * 商品Service实现 @@ -72,4 +74,26 @@ public class ShopGoodsServiceImpl extends ServiceImpl getCountSummary(ShopGoodsParam param) { + Map summary = baseMapper.selectCountSummary(param); + Map data = new HashMap<>(3); + data.put("totalNum", toInt(summary.get("totalNum"))); + data.put("totalNum2", toInt(summary.get("totalNum2"))); + data.put("totalNum3", toInt(summary.get("totalNum3"))); + return data; + } + + @Override + public int downSoldOutGoods() { + return baseMapper.downSoldOutGoods(); + } + + /** + * 将聚合统计结果安全转为 int(数据库 NULL 或缺失视为 0) + */ + private int toInt(Object value) { + return value == null ? 0 : ((Number) value).intValue(); + } + } diff --git a/src/main/java/com/gxwebsoft/shop/task/GoodsSoldOutTask.java b/src/main/java/com/gxwebsoft/shop/task/GoodsSoldOutTask.java new file mode 100644 index 0000000..2ce6967 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/task/GoodsSoldOutTask.java @@ -0,0 +1,43 @@ +package com.gxwebsoft.shop.task; + +import com.gxwebsoft.shop.service.ShopGoodsService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +/** + * 已售罄商品自动下架定时任务 + * + *

从 {@code ShopGoodsController.data()} 统计读接口中剥离出的写操作: + * 原实现每次打开商品列表都会执行 {@code UPDATE ... SET status=1 WHERE stock=0}, + * 属于在查询接口里写库的反模式,且翻页/排序若合并进 page 会产生高频写。 + * 此处改为独立定时任务,避免读链路与写操作耦合。

+ * + *

默认每 5 分钟执行一次,可通过配置 {@code shop.goods.sold-out.cron} 调整。

+ * + * @author WebSoft + * @since 2026-08-05 + */ +@Slf4j +@Component +public class GoodsSoldOutTask { + + @Autowired + private ShopGoodsService shopGoodsService; + + /** + * 定时下架已售罄(库存为 0)的商品,忽略租户隔离(全局下架所有商户) + */ + @Scheduled(cron = "${shop.goods.sold-out.cron:0 */5 * * * ?}") + public void downSoldOutGoods() { + try { + int count = shopGoodsService.downSoldOutGoods(); + if (count > 0) { + log.info("定时下架已售罄商品完成,下架数量: {}", count); + } + } catch (Exception e) { + log.error("定时下架已售罄商品任务执行失败", e); + } + } +}