Преглед изворни кода

Merge remote-tracking branch 'origin/feature/mall_product' into feature/mall_product

# Conflicts:
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderMapper.java
owen пре 1 година
родитељ
комит
60ea719b46
35 измењених фајлова са 334 додато и 304 уклоњено
  1. 2 41
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java
  2. 8 9
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApi.java
  3. 0 65
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/dto/SeckillActivityProductRespDTO.java
  4. 27 0
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/dto/SeckillValidateJoinRespDTO.java
  5. 4 0
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java
  6. 3 6
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApiImpl.java
  7. 70 21
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/seckill/AppSeckillActivityController.java
  8. 3 2
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/seckill/AppSeckillConfigController.java
  9. 0 2
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/seckill/vo/activity/AppSeckillActivityDetailRespVO.java
  10. 23 22
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/seckill/seckillactivity/SeckillActivityConvert.java
  11. 2 12
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/seckill/SeckillActivityDO.java
  12. 1 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java
  13. 3 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java
  14. 12 8
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityService.java
  15. 56 27
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityServiceImpl.java
  16. 4 4
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillConfigService.java
  17. 7 4
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillConfigServiceImpl.java
  18. 1 0
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApi.java
  19. 1 0
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java
  20. 2 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementReqVO.java
  21. 4 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementRespVO.java
  22. 2 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java
  23. 7 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java
  24. 15 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderItemMapper.java
  25. 6 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderMapper.java
  26. 9 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryService.java
  27. 13 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryServiceImpl.java
  28. 1 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBargainHandler.java
  29. 1 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCombinationHandler.java
  30. 0 34
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeOrderDefaultHandler.java
  31. 4 4
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeOrderHandler.java
  32. 1 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeSeckillHandler.java
  33. 1 18
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java
  34. 0 3
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java
  35. 41 15
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeSeckillActivityPriceCalculator.java

+ 2 - 41
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java

@@ -79,7 +79,8 @@ public class LocalDateTimeUtils {
             return false;
         }
         LocalDate nowDate = LocalDate.now();
-        return LocalDateTimeUtil.isIn(LocalDateTime.now(), LocalDateTime.of(nowDate, LocalTime.parse(startTime)),
+        return LocalDateTimeUtil.isIn(LocalDateTime.now(),
+                LocalDateTime.of(nowDate, LocalTime.parse(startTime)),
                 LocalDateTime.of(nowDate, LocalTime.parse(endTime)));
     }
 
@@ -98,46 +99,6 @@ public class LocalDateTimeUtils {
                 LocalDateTime.of(nowDate, startTime2), LocalDateTime.of(nowDate, endTime2));
     }
 
-    /**
-     * 构建日期时间 TODO 后面有需要的话再继续扩展
-     *
-     * @author HUIHUI
-     */
-    public static class BuilderDateTime {
-
-        /**
-         * 日期;2023-10-01
-         */
-        private String localDate;
-        /**
-         * 时间;10:01:00
-         */
-        private String localTime;
-
-        public BuilderDateTime() {
-        }
-
-        public BuilderDateTime withDate(String date) {
-            this.localDate = date;
-            return this;
-        }
-
-        public BuilderDateTime withDate(LocalDateTime date) {
-            this.localDate = LocalDateTimeUtil.format(date, "yyyy-MM-dd");
-            return this;
-        }
-
-        public BuilderDateTime withTime(String time) {
-            this.localTime = time;
-            return this;
-        }
-
-        public LocalDateTime build() {
-            return LocalDateTimeUtil.parse(this.localDate + " " + this.localTime, "yyyy-MM-dd HH:mm:ss");
-        }
-
-    }
-
     /**
      * 获取指定日期所在的月份的开始时间
      * 例如:2023-09-30 00:00:00,000

+ 8 - 9
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApi.java

@@ -1,9 +1,6 @@
 package cn.iocoder.yudao.module.promotion.api.seckill;
 
-import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityProductRespDTO;
-
-import java.util.Collection;
-import java.util.List;
+import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillValidateJoinRespDTO;
 
 /**
  * 秒杀活动 API 接口
@@ -22,12 +19,14 @@ public interface SeckillActivityApi {
     void updateSeckillStock(Long id, Long skuId, Integer count);
 
     /**
-     * 获取秒杀活动商品信息
+     * 校验是否参与秒杀商品
+     *
+     * 如果校验失败,则抛出业务异常
      *
-     * @param id     活动编号
-     * @param skuIds sku 编号
-     * @return 秒杀活动商品信息列表
+     * @param activityId 活动编号
+     * @param skuId SKU 编号
+     * @param count 数量
      */
-    List<SeckillActivityProductRespDTO> getSeckillActivityProductList(Long id, Collection<Long> skuIds);
+    SeckillValidateJoinRespDTO validateJoinSeckill(Long activityId, Long skuId, Integer count);
 
 }

+ 0 - 65
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/dto/SeckillActivityProductRespDTO.java

@@ -1,65 +0,0 @@
-package cn.iocoder.yudao.module.promotion.api.seckill.dto;
-
-import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
-import lombok.Data;
-
-import java.time.LocalDateTime;
-import java.util.List;
-
-/**
- * 秒杀活动商品 Response DTO
- *
- * @author HUIHUI
- */
-@Data
-public class SeckillActivityProductRespDTO {
-
-    /**
-     * 秒杀参与商品编号
-     */
-    private Long id;
-    /**
-     * 秒杀活动 id
-     *
-     * 关联 SeckillActivityDO#getId()
-     */
-    private Long activityId;
-    /**
-     * 秒杀时段 id
-     *
-     * 关联 SeckillConfigDO#getId()
-     */
-    private List<Long> configIds;
-    /**
-     * 商品 SPU 编号
-     */
-    private Long spuId;
-    /**
-     * 商品 SKU 编号
-     */
-    private Long skuId;
-    /**
-     * 秒杀金额,单位:分
-     */
-    private Integer seckillPrice;
-    /**
-     * 秒杀库存
-     */
-    private Integer stock;
-
-    /**
-     * 秒杀商品状态
-     *
-     * 枚举 {@link CommonStatusEnum 对应的类}
-     */
-    private Integer activityStatus;
-    /**
-     * 活动开始时间点
-     */
-    private LocalDateTime activityStartTime;
-    /**
-     * 活动结束时间点
-     */
-    private LocalDateTime activityEndTime;
-
-}

+ 27 - 0
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/dto/SeckillValidateJoinRespDTO.java

@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.promotion.api.seckill.dto;
+
+import lombok.Data;
+
+/**
+ * 校验参与秒杀 Response DTO
+ */
+@Data
+public class SeckillValidateJoinRespDTO {
+
+    /**
+     * 秒杀活动名称
+     */
+    private String name;
+    /**
+     * 总限购数量
+     *
+     * 目的:目前只有 trade 有具体下单的数据,需要交给 trade 价格计算使用
+     */
+    private Integer totalLimitCount;
+
+    /**
+     * 秒杀金额
+     */
+    private Integer seckillPrice;
+
+}

+ 4 - 0
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java

@@ -56,6 +56,10 @@ public interface ErrorCodeConstants {
     ErrorCode SECKILL_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END = new ErrorCode(1_013_008_004, "秒杀活动未关闭或未结束,不能删除");
     ErrorCode SECKILL_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_008_005, "秒杀活动已关闭,不能重复关闭");
     ErrorCode SECKILL_ACTIVITY_UPDATE_STOCK_FAIL = new ErrorCode(1_013_008_006, "秒杀失败,原因秒杀库存不足");
+    ErrorCode SECKILL_JOIN_ACTIVITY_TIME_ERROR = new ErrorCode(1_013_008_007, "秒杀失败,原因:不在活动时间范围内");
+    ErrorCode SECKILL_JOIN_ACTIVITY_STATUS_CLOSED = new ErrorCode(1_013_008_008, "秒杀失败,原因:秒杀活动已关闭");
+    ErrorCode SECKILL_JOIN_ACTIVITY_SINGLE_LIMIT_COUNT_EXCEED = new ErrorCode(1_013_008_009, "秒杀失败,原因:单次限购超出");
+    ErrorCode SECKILL_JOIN_ACTIVITY_PRODUCT_NOT_EXISTS = new ErrorCode(1_013_008_010, "秒杀失败,原因:商品不存在");
 
     // ========== 秒杀时段 1-013-009-000 ==========
     ErrorCode SECKILL_CONFIG_NOT_EXISTS = new ErrorCode(1_013_009_000, "秒杀时段不存在");

+ 3 - 6
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApiImpl.java

@@ -1,13 +1,10 @@
 package cn.iocoder.yudao.module.promotion.api.seckill;
 
-import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityProductRespDTO;
-import cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity.SeckillActivityConvert;
+import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillValidateJoinRespDTO;
 import cn.iocoder.yudao.module.promotion.service.seckill.SeckillActivityService;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
-import java.util.Collection;
-import java.util.List;
 
 /**
  * 秒杀活动接口 Api 接口实现类
@@ -26,8 +23,8 @@ public class SeckillActivityApiImpl implements SeckillActivityApi {
     }
 
     @Override
-    public List<SeckillActivityProductRespDTO> getSeckillActivityProductList(Long id, Collection<Long> skuIds) {
-        return SeckillActivityConvert.INSTANCE.convertList4(activityService.getSeckillActivityProductList(id, skuIds));
+    public SeckillValidateJoinRespDTO validateJoinSeckill(Long activityId, Long skuId, Integer count) {
+        return activityService.validateJoinSeckill(activityId, skuId, count);
     }
 
 }

+ 70 - 21
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/seckill/AppSeckillActivityController.java

@@ -5,6 +5,7 @@ import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
 import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
 import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityDetailRespVO;
@@ -13,10 +14,12 @@ import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppS
 import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityRespVO;
 import cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity.SeckillActivityConvert;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO;
-import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillProductDO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillConfigDO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillProductDO;
 import cn.iocoder.yudao.module.promotion.service.seckill.SeckillActivityService;
 import cn.iocoder.yudao.module.promotion.service.seckill.SeckillConfigService;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -28,16 +31,37 @@ import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
 import javax.annotation.Resource;
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.cache.CacheUtils.buildAsyncReloadingCache;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.findFirst;
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.isBetween;
 
 @Tag(name = "用户 App - 秒杀活动")
 @RestController
 @RequestMapping("/promotion/seckill-activity")
 @Validated
 public class AppSeckillActivityController {
+
+    /**
+     * {@link AppSeckillActivityNowRespVO} 缓存,通过它异步刷新 {@link #getNowSeckillActivity()} 所要的首页数据
+     */
+    private final LoadingCache<String, AppSeckillActivityNowRespVO> nowSeckillActivityCache = buildAsyncReloadingCache(Duration.ofSeconds(10L),
+            new CacheLoader<String, AppSeckillActivityNowRespVO>() {
+
+                @Override
+                public AppSeckillActivityNowRespVO load(String key) {
+                     return getNowSeckillActivity0();
+                }
+
+            });
+
     @Resource
     private SeckillActivityService activityService;
     @Resource
@@ -47,21 +71,25 @@ public class AppSeckillActivityController {
     @Resource
     private ProductSpuApi spuApi;
 
-    // TODO 芋艿:需要增加 spring cache
     @GetMapping("/get-now")
     @Operation(summary = "获得当前秒杀活动", description = "获取当前正在进行的活动,提供给首页使用")
     public CommonResult<AppSeckillActivityNowRespVO> getNowSeckillActivity() {
+        return success(nowSeckillActivityCache.getUnchecked(""));
+    }
+
+    private AppSeckillActivityNowRespVO getNowSeckillActivity0() {
         // 1. 获取当前时间处在哪个秒杀阶段
-        SeckillConfigDO configList = configService.getSeckillConfigListByStatusOnCurrentTime(CommonStatusEnum.ENABLE.getStatus());
-        if (configList == null) { // 时段不存在直接返回 null
-            return success(null);
+        SeckillConfigDO config = configService.getCurrentSeckillConfig();
+        if (config == null) { // 时段不存在直接返回 null
+            return new AppSeckillActivityNowRespVO();
         }
 
-        // 2. 查询满足当前阶段的活动
-        List<SeckillActivityDO> activityList = activityService.getSeckillActivityListByConfigIdAndStatus(configList.getId(), CommonStatusEnum.ENABLE.getStatus());
-        // 3 获取 spu 信息
+        // 2.1 查询满足当前阶段的活动
+        List<SeckillActivityDO> activityList = activityService.getSeckillActivityListByConfigIdAndStatus(config.getId(), CommonStatusEnum.ENABLE.getStatus());
+        List<SeckillProductDO> productList = activityService.getSeckillProductListByActivityId(convertList(activityList, SeckillActivityDO::getId));
+        // 2.2 获取 spu 信息
         List<ProductSpuRespDTO> spuList = spuApi.getSpuList(convertList(activityList, SeckillActivityDO::getSpuId));
-        return success(SeckillActivityConvert.INSTANCE.convert(configList, activityList, spuList));
+        return SeckillActivityConvert.INSTANCE.convert(config, activityList, productList, spuList);
     }
 
     @GetMapping("/page")
@@ -72,31 +100,52 @@ public class AppSeckillActivityController {
         if (CollUtil.isEmpty(pageResult.getList())) {
             return success(PageResult.empty(pageResult.getTotal()));
         }
+        List<SeckillProductDO> productList = activityService.getSeckillProductListByActivityId(
+                convertList(pageResult.getList(), SeckillActivityDO::getId));
+
         // 2. 拼接数据
         List<ProductSpuRespDTO> spuList = spuApi.getSpuList(convertList(pageResult.getList(), SeckillActivityDO::getSpuId));
-        return success(SeckillActivityConvert.INSTANCE.convertPage(pageResult, spuList));
+        return success(SeckillActivityConvert.INSTANCE.convertPage02(pageResult, productList, spuList));
     }
 
     @GetMapping("/get-detail")
     @Operation(summary = "获得秒杀活动明细")
     @Parameter(name = "id", description = "活动编号", required = true, example = "1024")
     public CommonResult<AppSeckillActivityDetailRespVO> getSeckillActivity(@RequestParam("id") Long id) {
-        // 1. 获取当前时间处在哪个秒杀阶段
-        SeckillConfigDO configList = configService.getSeckillConfigListByStatusOnCurrentTime(CommonStatusEnum.ENABLE.getStatus());
-        if (configList == null) { // 时段不存在直接返回 null
+        // 1. 获取活动
+        SeckillActivityDO activity = activityService.getSeckillActivity(id);
+        if (activity == null
+                || ObjectUtil.equal(activity.getStatus(), CommonStatusEnum.DISABLE.getStatus())) {
             return success(null);
         }
 
-        // 2. 获取活动
-        SeckillActivityDO seckillActivity = activityService.getSeckillActivity(id);
-        if (seckillActivity == null
-                || ObjectUtil.equal(seckillActivity.getStatus(), CommonStatusEnum.DISABLE.getStatus())) {
-            return success(null);
+        // 2. 获取时间段
+        List<SeckillConfigDO> configs = configService.getSeckillConfigListByStatus(CommonStatusEnum.ENABLE.getStatus());
+        configs.removeIf(config -> !CollUtil.contains(activity.getConfigIds(), config.getId()));
+        // 2.1 优先使用当前时间段
+        SeckillConfigDO config = findFirst(configs, config0 -> isBetween(config0.getStartTime(), config0.getEndTime()));
+        // 2.2 如果没有,则获取最后一个,因为倾向优先展示“未开始” > “已结束”
+        if (config == null) {
+            config = CollUtil.getLast(configs);
+        }
+        if (config == null) {
+            return null;
+        }
+        // 3. 计算开始时间、结束时间
+        LocalDate nowDate;
+        // 3.1 如果在活动日期范围内,则以今天为 nowDate
+        if (LocalDateTimeUtils.isBetween(activity.getStartTime(), activity.getEndTime())) {
+            nowDate = LocalDate.now();
+        } else {
+            // 3.2 如果不在活动时间范围内,则直接以活动的 endTime 作为 nowDate,因为还是倾向优先展示“未开始” > “已结束”
+            nowDate = activity.getEndTime().toLocalDate();
         }
+        LocalDateTime startTime = LocalDateTime.of(nowDate, LocalTime.parse(config.getStartTime()));
+        LocalDateTime endTime = LocalDateTime.of(nowDate, LocalTime.parse(config.getEndTime()));
 
-        // 3. 拼接数据
-        List<SeckillProductDO> productList = activityService.getSeckillProductListByActivityId(seckillActivity.getId());
-        return success(SeckillActivityConvert.INSTANCE.convert3(seckillActivity, productList, configList));
+        // 4. 拼接数据
+        List<SeckillProductDO> productList = activityService.getSeckillProductListByActivityId(activity.getId());
+        return success(SeckillActivityConvert.INSTANCE.convert3(activity, productList, startTime, endTime));
     }
 
 }

+ 3 - 2
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/seckill/AppSeckillConfigController.java

@@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.config.AppSeckillConfigRespVO;
 import cn.iocoder.yudao.module.promotion.convert.seckill.seckillconfig.SeckillConfigConvert;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillConfigDO;
 import cn.iocoder.yudao.module.promotion.service.seckill.SeckillConfigService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -28,8 +29,8 @@ public class AppSeckillConfigController {
     @GetMapping("/list")
     @Operation(summary = "获得秒杀时间段列表")
     public CommonResult<List<AppSeckillConfigRespVO>> getSeckillConfigList() {
-        return success(SeckillConfigConvert.INSTANCE.convertList2(
-                configService.getSeckillConfigListByStatus(CommonStatusEnum.ENABLE.getStatus())));
+        List<SeckillConfigDO> list = configService.getSeckillConfigListByStatus(CommonStatusEnum.ENABLE.getStatus());
+        return success(SeckillConfigConvert.INSTANCE.convertList2(list));
     }
 
 }

+ 0 - 2
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/seckill/vo/activity/AppSeckillActivityDetailRespVO.java

@@ -19,8 +19,6 @@ public class AppSeckillActivityDetailRespVO {
     @Schema(description = "活动状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     private Integer status;
 
-    // TODO @芋艿:开始时间、结束时间,要和场次结合起来;就是要算到当前场次,是几点哈;
-
     @Schema(description = "活动开始时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime startTime;
 

+ 23 - 22
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/seckill/seckillactivity/SeckillActivityConvert.java

@@ -3,11 +3,10 @@ package cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
-import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
 import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
 import cn.iocoder.yudao.module.product.enums.DictTypeConstants;
-import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityProductRespDTO;
+import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillValidateJoinRespDTO;
 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityDetailRespVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityRespVO;
@@ -19,17 +18,18 @@ import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppS
 import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityRespVO;
 import cn.iocoder.yudao.module.promotion.convert.seckill.seckillconfig.SeckillConfigConvert;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO;
-import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillProductDO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillConfigDO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillProductDO;
 import org.mapstruct.Mapper;
 import org.mapstruct.Mapping;
 import org.mapstruct.Mappings;
 import org.mapstruct.factory.Mappers;
 
+import java.time.LocalDateTime;
 import java.util.List;
 import java.util.Map;
 
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
 import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
 
 /**
@@ -93,16 +93,19 @@ public interface SeckillActivityConvert {
 
     List<AppSeckillActivityRespVO> convertList3(List<SeckillActivityDO> activityList);
 
-    default AppSeckillActivityNowRespVO convert(SeckillConfigDO filteredConfig, List<SeckillActivityDO> activityList, List<ProductSpuRespDTO> spuList) {
+    default AppSeckillActivityNowRespVO convert(SeckillConfigDO filteredConfig, List<SeckillActivityDO> activityList,
+                                                List<SeckillProductDO> productList, List<ProductSpuRespDTO> spuList) {
         AppSeckillActivityNowRespVO respVO = new AppSeckillActivityNowRespVO();
         respVO.setConfig(SeckillConfigConvert.INSTANCE.convert1(filteredConfig));
         Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId);
+        Map<Long, List<SeckillProductDO>> productMap = convertMultiMap(productList, SeckillProductDO::getActivityId);
         respVO.setActivities(CollectionUtils.convertList(convertList3(activityList), item -> {
-            findAndThen(spuMap, item.getSpuId(), spu -> {
-                item.setPicUrl(spu.getPicUrl())
-                        .setMarketPrice(spu.getMarketPrice())
-                        .setUnitName(DictFrameworkUtils.getDictDataLabel(DictTypeConstants.PRODUCT_UNIT, spu.getUnit()));
-            });
+            // product 信息
+            item.setSeckillPrice(getMinValue(productMap.get(item.getId()), SeckillProductDO::getSeckillPrice));
+            // spu 信息
+            findAndThen(spuMap, item.getSpuId(), spu ->
+                    item.setPicUrl(spu.getPicUrl()).setMarketPrice(spu.getMarketPrice())
+                            .setUnitName(DictFrameworkUtils.getDictDataLabel(DictTypeConstants.PRODUCT_UNIT, spu.getUnit())));
             return item;
         }));
         return respVO;
@@ -110,10 +113,14 @@ public interface SeckillActivityConvert {
 
     PageResult<AppSeckillActivityRespVO> convertPage1(PageResult<SeckillActivityDO> pageResult);
 
-    default PageResult<AppSeckillActivityRespVO> convertPage(PageResult<SeckillActivityDO> pageResult, List<ProductSpuRespDTO> spuList) {
+    default PageResult<AppSeckillActivityRespVO> convertPage02(PageResult<SeckillActivityDO> pageResult, List<SeckillProductDO> productList, List<ProductSpuRespDTO> spuList) {
         PageResult<AppSeckillActivityRespVO> result = convertPage1(pageResult);
         Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId);
+        Map<Long, List<SeckillProductDO>> productMap = convertMultiMap(productList, SeckillProductDO::getActivityId);
         List<AppSeckillActivityRespVO> list = CollectionUtils.convertList(result.getList(), item -> {
+            // product 信息
+            item.setSeckillPrice(getMinValue(productMap.get(item.getId()), SeckillProductDO::getSeckillPrice));
+            // spu 信息
             findAndThen(spuMap, item.getSpuId(), spu -> item.setPicUrl(spu.getPicUrl()).setMarketPrice(spu.getMarketPrice())
                     .setUnitName(DictFrameworkUtils.getDictDataLabel(DictTypeConstants.PRODUCT_UNIT, spu.getUnit())));
             return item;
@@ -126,19 +133,13 @@ public interface SeckillActivityConvert {
 
     List<AppSeckillActivityDetailRespVO.Product> convertList1(List<SeckillProductDO> products);
 
-    default AppSeckillActivityDetailRespVO convert3(SeckillActivityDO seckillActivity, List<SeckillProductDO> products, SeckillConfigDO filteredConfig) {
-        return convert2(seckillActivity)
+    default AppSeckillActivityDetailRespVO convert3(SeckillActivityDO activity, List<SeckillProductDO> products,
+                                                    LocalDateTime startTime, LocalDateTime endTime) {
+        return convert2(activity)
                 .setProducts(convertList1(products))
-                .setStartTime(new LocalDateTimeUtils.BuilderDateTime()
-                        .withDate(seckillActivity.getStartTime())
-                        .withTime(filteredConfig.getStartTime())
-                        .build())// 活动开始日期和时段结合
-                .setEndTime(new LocalDateTimeUtils.BuilderDateTime()
-                        .withDate(seckillActivity.getEndTime())
-                        .withTime(filteredConfig.getEndTime())
-                        .build()); // 活动结束日期和时段结合
+                .setStartTime(startTime).setEndTime(endTime);
     }
 
-    List<SeckillActivityProductRespDTO> convertList4(List<SeckillProductDO> seckillActivityProductList);
+    SeckillValidateJoinRespDTO convert02(SeckillActivityDO activity, SeckillProductDO product);
 
 }

+ 2 - 12
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/seckill/SeckillActivityDO.java

@@ -66,18 +66,7 @@ public class SeckillActivityDO extends BaseDO {
      */
     @TableField(typeHandler = LongListTypeHandler.class)
     private List<Long> configIds;
-    /**
-     * 新增订单数
-     */
-    private Integer orderCount;
-    /**
-     * 付款人数
-     */
-    private Integer userCount;
-    /**
-     * 订单实付金额,单位:分
-     */
-    private Long totalPrice;
+
     /**
      * 总限购数量
      */
@@ -86,6 +75,7 @@ public class SeckillActivityDO extends BaseDO {
      * 单次限够数量
      */
     private Integer singleLimitCount;
+
     /**
      * 秒杀库存(剩余库存秒杀时扣减)
      */

+ 1 - 1
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java

@@ -46,7 +46,7 @@ public interface SeckillActivityMapper extends BaseMapperX<SeckillActivityDO> {
                 .eq(SeckillActivityDO::getId, id)
                 .gt(SeckillActivityDO::getTotalStock, 0)
                 .setSql("stock = stock + " + count)
-                .setSql("totalStock = totalStock - " + count));
+                .setSql("total_stock = total_stock - " + count));
     }
 
     default PageResult<SeckillActivityDO> selectPage(AppSeckillActivityPageReqVO pageReqVO, Integer status) {

+ 3 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java

@@ -102,6 +102,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
         return recordDO;
     }
 
+    // TODO @芋艿:在详细预览下;
     @Override
     public void validateCombinationRecord(Long activityId, Long userId, Long skuId, Integer count) {
         // 1.1 校验拼团活动是否存在
@@ -132,12 +133,14 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
         }
         // 5.1、查询关联的订单是否已经支付
         // 当前 activityId 已经有未支付的订单,不允许在发起新的;要么支付,要么去掉先;
+        // TODO 芋艿:看看是不是可以删除掉;
         Integer orderStatus = tradeOrderApi.getOrderStatus(record.getOrderId());
         if (ObjectUtil.equal(orderStatus, TradeOrderStatusEnum.UNPAID.getStatus())) {
             throw exception(COMBINATION_RECORD_FAILED_ORDER_STATUS_UNPAID);
         }
     }
 
+    // TODO 芋艿:在详细 review 下;
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void createCombinationRecord(CombinationRecordCreateReqDTO reqDTO) {

+ 12 - 8
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityService.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.promotion.service.seckill;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillValidateJoinRespDTO;
 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityUpdateReqVO;
@@ -89,14 +90,6 @@ public interface SeckillActivityService {
      */
     List<SeckillProductDO> getSeckillProductListByActivityId(Collection<Long> activityIds);
 
-    /**
-     * 通过活动时段获取秒杀活动
-     *
-     * @param ids 时段配置编号
-     * @return 秒杀活动列表
-     */
-    List<SeckillActivityDO> getSeckillActivityListByConfigIds(Collection<Long> ids);
-
     /**
      * 通过活动时段编号获取指定 status 的秒杀活动
      *
@@ -123,4 +116,15 @@ public interface SeckillActivityService {
      */
     List<SeckillProductDO> getSeckillActivityProductList(Long id, Collection<Long> skuIds);
 
+    /**
+     * 校验是否参与秒杀商品
+     *
+     * 如果校验失败,则抛出业务异常
+     *
+     * @param activityId 活动编号
+     * @param skuId SKU 编号
+     * @param count 数量
+     */
+    SeckillValidateJoinRespDTO validateJoinSeckill(Long activityId, Long skuId, Integer count);
+
 }

+ 56 - 27
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityServiceImpl.java

@@ -4,10 +4,12 @@ import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
 import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
 import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
+import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillValidateJoinRespDTO;
 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityUpdateReqVO;
@@ -15,10 +17,10 @@ import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product.Sec
 import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityPageReqVO;
 import cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity.SeckillActivityConvert;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillConfigDO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillProductDO;
 import cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity.SeckillActivityMapper;
 import cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity.SeckillProductMapper;
-import cn.iocoder.yudao.module.promotion.util.PromotionUtils;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
@@ -60,17 +62,18 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Long createSeckillActivity(SeckillActivityCreateReqVO createReqVO) {
-        // 校验商品秒杀时段是否冲突
+        // 1.1 校验商品秒杀时段是否冲突
         validateProductConflict(createReqVO.getConfigIds(), createReqVO.getSpuId(), null);
-        // 校验商品是否存在
+        // 1.2 校验商品是否存在
         validateProductExists(createReqVO.getSpuId(), createReqVO.getProducts());
 
-        // 插入秒杀活动
+        // 2.1 插入秒杀活动
         SeckillActivityDO activity = SeckillActivityConvert.INSTANCE.convert(createReqVO)
-                .setStatus(PromotionUtils.calculateActivityStatus(createReqVO.getEndTime()))
-                .setTotalStock(getSumValue(createReqVO.getProducts(), SeckillProductBaseVO::getStock, Integer::sum));
+                .setStatus(CommonStatusEnum.ENABLE.getStatus())
+                .setStock(getSumValue(createReqVO.getProducts(), SeckillProductBaseVO::getStock, Integer::sum));
+        activity.setTotalStock(activity.getStock());
         seckillActivityMapper.insert(activity);
-        // 插入商品
+        // 2.2 插入商品
         List<SeckillProductDO> products = SeckillActivityConvert.INSTANCE.convertList(createReqVO.getProducts(), activity);
         seckillProductMapper.insertBatch(products);
         return activity.getId();
@@ -128,22 +131,24 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void updateSeckillActivity(SeckillActivityUpdateReqVO updateReqVO) {
-        // 校验存在
-        SeckillActivityDO seckillActivity = validateSeckillActivityExists(updateReqVO.getId());
-        if (CommonStatusEnum.DISABLE.getStatus().equals(seckillActivity.getStatus())) {
+        // 1.1 校验存在
+        SeckillActivityDO activity = validateSeckillActivityExists(updateReqVO.getId());
+        if (CommonStatusEnum.DISABLE.getStatus().equals(activity.getStatus())) {
             throw exception(SECKILL_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED);
         }
-        // 校验商品是否冲突
+        // 1.2 校验商品是否冲突
         validateProductConflict(updateReqVO.getConfigIds(), updateReqVO.getSpuId(), updateReqVO.getId());
-        // 校验商品是否存在
+        // 1.3 校验商品是否存在
         validateProductExists(updateReqVO.getSpuId(), updateReqVO.getProducts());
 
-        // 更新活动
+        // 2.1 更新活动
         SeckillActivityDO updateObj = SeckillActivityConvert.INSTANCE.convert(updateReqVO)
-                .setStatus(PromotionUtils.calculateActivityStatus(updateReqVO.getEndTime()))
-                .setTotalStock(getSumValue(updateReqVO.getProducts(), SeckillProductBaseVO::getStock, Integer::sum));
+                .setStock(getSumValue(updateReqVO.getProducts(), SeckillProductBaseVO::getStock, Integer::sum));
+        if (updateObj.getStock() > activity.getTotalStock()) { // 如果更新的库存大于原来的库存,则更新总库存
+            updateObj.setTotalStock(updateObj.getStock());
+        }
         seckillActivityMapper.updateById(updateObj);
-        // 更新商品
+        // 2.2 更新商品
         updateSeckillProduct(updateObj, updateReqVO.getProducts());
     }
 
@@ -151,7 +156,7 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
     @Transactional(rollbackFor = Exception.class)
     public void updateSeckillStock(Long id, Long skuId, Integer count) {
         // 1.1 校验活动库存是否充足
-        SeckillActivityDO seckillActivity = getSeckillActivity(id);
+        SeckillActivityDO seckillActivity = validateSeckillActivityExists(id);
         if (count > seckillActivity.getTotalStock()) {
             throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL);
         }
@@ -243,7 +248,7 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
 
     @Override
     public SeckillActivityDO getSeckillActivity(Long id) {
-        return validateSeckillActivityExists(id);
+        return seckillActivityMapper.selectById(id);
     }
 
     @Override
@@ -261,12 +266,6 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
         return seckillProductMapper.selectListByActivityId(activityIds);
     }
 
-    @Override
-    public List<SeckillActivityDO> getSeckillActivityListByConfigIds(Collection<Long> ids) {
-        return filterList(seckillActivityMapper.selectList(),
-                item -> anyMatch(item.getConfigIds(), ids::contains));
-    }
-
     @Override
     public List<SeckillActivityDO> getSeckillActivityListByConfigIdAndStatus(Long configId, Integer status) {
         return filterList(seckillActivityMapper.selectList(SeckillActivityDO::getStatus, status),
@@ -281,16 +280,46 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
 
     @Override
     public List<SeckillProductDO> getSeckillActivityProductList(Long id, Collection<Long> skuIds) {
-        // 1、校验秒杀活动是否存在
-        validateSeckillActivityExists(id);
+
         // 2、校验活动商品是否存在
         List<SeckillProductDO> productList = filterList(seckillProductMapper.selectListByActivityId(id),
                 item -> skuIds.contains(item.getSkuId()));
         if (CollectionUtil.isEmpty(productList)) {
             throw exception(SKU_NOT_EXISTS);
         }
-
         return productList;
     }
 
+    @Override
+    public SeckillValidateJoinRespDTO validateJoinSeckill(Long activityId, Long skuId, Integer count) {
+        // 1.1 校验秒杀活动是否存在
+        SeckillActivityDO activity = validateSeckillActivityExists(activityId);
+        if (ObjectUtil.notEqual(activity.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {
+            throw exception(SECKILL_JOIN_ACTIVITY_STATUS_CLOSED);
+        }
+        // 1.2 是否在活动时间范围内
+        if (!LocalDateTimeUtils.isBetween(activity.getStartTime(), activity.getEndTime())) {
+            throw exception(SECKILL_JOIN_ACTIVITY_TIME_ERROR);
+        }
+        SeckillConfigDO config = seckillConfigService.getCurrentSeckillConfig();
+        if (config == null || !CollectionUtil.contains(activity.getConfigIds(), config.getId())) {
+            throw exception(SECKILL_JOIN_ACTIVITY_TIME_ERROR);
+        }
+        // 1.3 超过单次购买限制
+        if (count > activity.getSingleLimitCount()) {
+            throw exception(SECKILL_JOIN_ACTIVITY_SINGLE_LIMIT_COUNT_EXCEED);
+        }
+
+        // 2.1 校验秒杀商品是否存在
+        SeckillProductDO product = seckillProductMapper.selectByActivityIdAndSkuId(activityId, skuId);
+        if (product == null) {
+            throw exception(SECKILL_JOIN_ACTIVITY_PRODUCT_NOT_EXISTS);
+        }
+        // 2.2 校验库存是否充足
+        if (count > product.getStock()) {
+            throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL);
+        }
+        return SeckillActivityConvert.INSTANCE.convert02(activity, product);
+    }
+
 }

+ 4 - 4
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillConfigService.java

@@ -54,7 +54,6 @@ public interface SeckillConfigService {
      */
     List<SeckillConfigDO> getSeckillConfigList();
 
-
     /**
      * 校验秒杀时段是否存在
      *
@@ -87,11 +86,12 @@ public interface SeckillConfigService {
     void updateSeckillConfigStatus(Long id, Integer status);
 
     /**
-     * 获取当前日期时间处于的秒杀时段且状态为 status
+     * 获得当前的秒杀时段
+     *
+     * 要求必须处于开启状态、且在当前时间段内
      *
-     * @param status 状态
      * @return 时段
      */
-    SeckillConfigDO getSeckillConfigListByStatusOnCurrentTime(Integer status);
+    SeckillConfigDO getCurrentSeckillConfig();
 
 }

+ 7 - 4
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillConfigServiceImpl.java

@@ -17,6 +17,7 @@ import org.springframework.validation.annotation.Validated;
 import javax.annotation.Resource;
 import java.time.LocalTime;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -70,9 +71,9 @@ public class SeckillConfigServiceImpl implements SeckillConfigService {
     }
 
     @Override
-    public SeckillConfigDO getSeckillConfigListByStatusOnCurrentTime(Integer status) {
-        return findFirst(seckillConfigMapper.selectList(SeckillConfigDO::getStatus, status),
-                config -> isBetween(config.getStartTime(), config.getEndTime()));
+    public SeckillConfigDO getCurrentSeckillConfig() {
+        List<SeckillConfigDO> list = seckillConfigMapper.selectList(SeckillConfigDO::getStatus, CommonStatusEnum.ENABLE.getStatus());
+        return findFirst(list, config -> isBetween(config.getStartTime(), config.getEndTime()));
     }
 
     @Override
@@ -151,7 +152,9 @@ public class SeckillConfigServiceImpl implements SeckillConfigService {
 
     @Override
     public List<SeckillConfigDO> getSeckillConfigListByStatus(Integer status) {
-        return seckillConfigMapper.selectListByStatus(status);
+        List<SeckillConfigDO> list = seckillConfigMapper.selectListByStatus(status);
+        list.sort(Comparator.comparing(SeckillConfigDO::getStartTime));
+        return list;
     }
 
 }

+ 1 - 0
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApi.java

@@ -11,6 +11,7 @@ import java.time.LocalDateTime;
  */
 public interface TradeOrderApi {
 
+    // TODO 芋艿:看看是不是可以删除掉;
     /**
      * 获取订单状态
      *

+ 1 - 0
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java

@@ -55,6 +55,7 @@ public interface ErrorCodeConstants {
     ErrorCode PRICE_CALCULATE_PAY_PRICE_ILLEGAL = new ErrorCode(1_011_003_000, "支付价格计算异常,原因:价格小于等于 0");
     ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND = new ErrorCode(1_011_003_002, "计算快递运费异常,找不到对应的运费模板");
     ErrorCode PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER = new ErrorCode(1_011_003_004, "参与秒杀、拼团、砍价的营销商品,无法使用优惠劵");
+    ErrorCode PRICE_CALCULATE_SECKILL_TOTAL_LIMIT_COUNT = new ErrorCode(1_011_003_005, "参与秒杀的商品,超过了秒杀总限购数量");
 
     // ========== 物流 Express 模块 1-011-004-000 ==========
     ErrorCode EXPRESS_NOT_EXISTS = new ErrorCode(1_011_004_000, "快递公司不存在");

+ 2 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementReqVO.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.trade.controller.app.order.vo;
 
+import cn.hutool.core.util.ObjUtil;
 import cn.iocoder.yudao.framework.common.validation.InEnum;
 import cn.iocoder.yudao.framework.common.validation.Mobile;
 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
@@ -67,7 +68,7 @@ public class AppTradeOrderSettlementReqVO {
     @JsonIgnore
     public boolean isValidActivityItems() {
         // 校验是否是活动订单
-        if (seckillActivityId == null && combinationActivityId == null && combinationHeadId == null) {
+        if (ObjUtil.isAllEmpty(seckillActivityId, combinationActivityId, combinationHeadId)) {
             return true;
         }
         // 校验订单项是否超出

+ 4 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementRespVO.java

@@ -14,7 +14,7 @@ import java.util.List;
 public class AppTradeOrderSettlementRespVO {
 
     @Schema(description = "交易类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") // 对应 TradeOrderTypeEnum 枚举
-    private Integer type = 1; // TODO 芋艿:改成计算
+    private Integer type;
 
     @Schema(description = "购物项数组", requiredMode = Schema.RequiredMode.REQUIRED)
     private List<Item> items;
@@ -75,6 +75,9 @@ public class AppTradeOrderSettlementRespVO {
         @Schema(description = "商品原价(总),单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "500")
         private Integer totalPrice;
 
+        @Schema(description = "订单优惠(总),单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "66")
+        private Integer discountPrice;
+
         @Schema(description = "运费金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "50")
         private Integer deliveryPrice;
 

+ 2 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java

@@ -211,7 +211,8 @@ public interface TradeOrderConvert {
                 .setCouponId(settlementReqVO.getCouponId()).setPointStatus(settlementReqVO.getPointStatus())
                 .setDeliveryType(settlementReqVO.getDeliveryType()).setAddressId(settlementReqVO.getAddressId())
                 .setPickUpStoreId(settlementReqVO.getPickUpStoreId())
-                .setItems(new ArrayList<>(settlementReqVO.getItems().size()));
+                .setItems(new ArrayList<>(settlementReqVO.getItems().size()))
+                .setSeckillActivityId(settlementReqVO.getSeckillActivityId());
         // 商品项的构建
         Map<Long, CartDO> cartMap = convertMap(cartList, CartDO::getId);
         for (AppTradeOrderSettlementReqVO.Item item : settlementReqVO.getItems()) {

+ 7 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java

@@ -288,4 +288,11 @@ public class TradeOrderDO extends BaseDO {
      */
     private Integer vipPrice;
 
+    /**
+     * 秒杀活动编号
+     *
+     * 关联 SeckillActivityDO 的 id 字段
+     */
+    private Long seckillActivityId;
+
 }

+ 15 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderItemMapper.java

@@ -1,13 +1,19 @@
 package cn.iocoder.yudao.module.trade.dal.mysql.order;
 
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.map.MapUtil;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
 
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 @Mapper
 public interface TradeOrderItemMapper extends BaseMapperX<TradeOrderItemDO> {
@@ -38,4 +44,13 @@ public interface TradeOrderItemMapper extends BaseMapperX<TradeOrderItemDO> {
                 .eq(TradeOrderItemDO::getCommentStatus, commentStatus));
     }
 
+    default int selectProductSumByOrderId(@Param("orderIds") Set<Long> orderIds) {
+        // SQL sum 查询
+        List<Map<String, Object>> result = selectMaps(new QueryWrapper<TradeOrderItemDO>()
+                .select("SUM(count) AS sumCount")
+                .in("order_id", orderIds)); // 只计算选中的
+        // 获得数量
+        return CollUtil.getFirst(result) != null ? MapUtil.getInt(result.get(0), "sumCount") : 0;
+    }
+
 }

+ 6 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderMapper.java

@@ -86,6 +86,12 @@ public interface TradeOrderMapper extends BaseMapperX<TradeOrderDO> {
                 .eq(TradeOrderDO::getCommentStatus, commentStatus));
     }
 
+    default List<TradeOrderDO> selectListByUserIdAndSeckillActivityId(Long userId, Long seckillActivityId) {
+        return selectList(new LambdaUpdateWrapper<>(TradeOrderDO.class)
+                .eq(TradeOrderDO::getUserId, userId)
+                .eq(TradeOrderDO::getSeckillActivityId, seckillActivityId));
+    }
+
     default TradeOrderSummaryRespDTO selectSummaryByPayTimeBetween(LocalDateTime beginTime, LocalDateTime endTime) {
         return BeanUtil.copyProperties(CollUtil.get(selectMaps(MPJWrappers.<TradeOrderDO>lambdaJoin()
                         .selectCount(TradeOrderDO::getId, TradeOrderSummaryRespDTO::getOrderPayCount)

+ 9 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryService.java

@@ -84,6 +84,15 @@ public interface TradeOrderQueryService {
      */
     List<ExpressTrackRespDTO> getExpressTrackList(Long id);
 
+    /**
+     * 【会员】在指定秒杀活动下,用户购买的商品数量
+     *
+     * @param userId 用户编号
+     * @param activityId 活动编号
+     * @return 秒杀商品数量
+     */
+    int getSeckillProductCount(Long userId, Long activityId);
+
     // =================== Order Item ===================
 
     /**

+ 13 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryServiceImpl.java

@@ -14,6 +14,7 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper;
 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper;
+import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
 import cn.iocoder.yudao.module.trade.framework.delivery.core.client.ExpressClientFactory;
 import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackQueryReqDTO;
 import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackRespDTO;
@@ -122,6 +123,18 @@ public class TradeOrderQueryServiceImpl implements TradeOrderQueryService {
         return getExpressTrackList(order);
     }
 
+    @Override
+    public int getSeckillProductCount(Long userId, Long activityId) {
+        // 获得订单列表
+        List<TradeOrderDO> orders = tradeOrderMapper.selectListByUserIdAndSeckillActivityId(userId, activityId);
+        orders.removeIf(order -> TradeOrderStatusEnum.isCanceled(order.getStatus())); // 过滤掉【已取消】的订单
+        if (CollUtil.isEmpty(orders)) {
+            return 0;
+        }
+        // 获得订单项列表
+        return tradeOrderItemMapper.selectProductSumByOrderId(convertSet(orders, TradeOrderDO::getId));
+    }
+
     // TODO @puhui999:可以加个 spring 缓存,30 分钟;主要考虑及时性要求不高,但是每次调用需要钱;
     /**
      * 获得订单的物流轨迹

+ 1 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBargainHandler.java

@@ -14,7 +14,7 @@ import javax.annotation.Resource;
  * @author HUIHUI
  */
 @Component
-public class TradeBargainHandler extends TradeOrderDefaultHandler {
+public class TradeBargainHandler implements TradeOrderHandler {
 
     @Resource
     private BargainActivityApi bargainActivityApi;

+ 1 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCombinationHandler.java

@@ -17,7 +17,7 @@ import javax.annotation.Resource;
  * @author HUIHUI
  */
 @Component
-public class TradeCombinationHandler extends TradeOrderDefaultHandler {
+public class TradeCombinationHandler implements TradeOrderHandler {
 
     @Resource
     private CombinationRecordApi combinationRecordApi;

+ 0 - 34
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeOrderDefaultHandler.java

@@ -1,34 +0,0 @@
-package cn.iocoder.yudao.module.trade.service.order.handler;
-
-import cn.iocoder.yudao.module.trade.service.order.bo.TradeAfterOrderCreateReqBO;
-import cn.iocoder.yudao.module.trade.service.order.bo.TradeAfterPayOrderReqBO;
-import cn.iocoder.yudao.module.trade.service.order.bo.TradeBeforeOrderCreateReqBO;
-
-/**
- * 订单活动特殊逻辑处理器 handler 默认抽象实现类
- *
- * @author HUIHUI
- */
-public abstract class TradeOrderDefaultHandler implements TradeOrderHandler {
-
-    @Override
-    public void beforeOrderCreate(TradeBeforeOrderCreateReqBO reqBO) {
-
-    }
-
-    @Override
-    public void afterOrderCreate(TradeAfterOrderCreateReqBO reqBO) {
-
-    }
-
-    @Override
-    public void afterPayOrder(TradeAfterPayOrderReqBO reqBO) {
-
-    }
-
-    @Override
-    public void cancelOrder() {
-
-    }
-
-}

+ 4 - 4
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeOrderHandler.java

@@ -17,25 +17,25 @@ public interface TradeOrderHandler {
      *
      * @param reqBO 请求
      */
-    void beforeOrderCreate(TradeBeforeOrderCreateReqBO reqBO);
+    default void beforeOrderCreate(TradeBeforeOrderCreateReqBO reqBO) {}
 
     /**
      * 订单创建后
      *
      * @param reqBO 请求
      */
-    void afterOrderCreate(TradeAfterOrderCreateReqBO reqBO);
+    default void afterOrderCreate(TradeAfterOrderCreateReqBO reqBO) {}
 
     /**
      * 支付订单后
      *
      * @param reqBO 请求
      */
-    void afterPayOrder(TradeAfterPayOrderReqBO reqBO);
+    default void afterPayOrder(TradeAfterPayOrderReqBO reqBO) {}
 
     /**
      * 订单取消
      */
-    void cancelOrder();
+    default void cancelOrder() {}
 
 }

+ 1 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeSeckillHandler.java

@@ -14,7 +14,7 @@ import javax.annotation.Resource;
  * @author HUIHUI
  */
 @Component
-public class TradeSeckillHandler extends TradeOrderDefaultHandler {
+public class TradeSeckillHandler implements TradeOrderHandler {
 
     @Resource
     private SeckillActivityApi seckillActivityApi;

+ 1 - 18
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java

@@ -98,19 +98,9 @@ public class TradePriceCalculateRespBO {
          * VIP 减免金额,单位:分
          */
         private Integer vipPrice;
-        /**
-         * 秒杀、拼团、砍价活动商品的总金额,单位:分
-         *
-         * 基于 {@link OrderItem#getActivityPrice()} ()} * {@link OrderItem#getCount()} 求和
-         */
-        private Integer activityPrice;
         /**
          * 最终购买金额(总),单位:分
          *
-         * ==========活动情况===========
-         * = {@link #activityPrice}
-         * + {@link #deliveryPrice}
-         * ==========正常情况===========
          * = {@link #totalPrice}
          * - {@link #couponPrice}
          * - {@link #pointPrice}
@@ -186,16 +176,9 @@ public class TradePriceCalculateRespBO {
          * VIP 减免金额,单位:分
          */
         private Integer vipPrice;
-        /**
-         * 秒杀、拼团、砍价活动商品的金额,单位:分
-         */
-        private Integer activityPrice;
         /**
          * 应付金额(总),单位:分
-         * ==========活动情况===========
-         * = {@link #activityPrice} * {@link #count}
-         * + {@link #deliveryPrice}
-         * ==========正常情况===========
+         *
          * = {@link #price} * {@link #count}
          * - {@link #couponPrice}
          * - {@link #pointPrice}

+ 0 - 3
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java

@@ -105,9 +105,6 @@ public class TradePriceCalculatorHelper {
             if (!item.getSelected()) {
                 return;
             }
-            // TODO puhui: 需要在这里计算活动的价格
-            // ========== 一、活动情况 ==========
-            // ========== 二、正常情况 ==========
             price.setTotalPrice(price.getTotalPrice() + item.getPrice() * item.getCount());
             price.setDiscountPrice(price.getDiscountPrice() + item.getDiscountPrice());
             price.setDeliveryPrice(price.getDeliveryPrice() + item.getDeliveryPrice());

+ 41 - 15
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeSeckillActivityPriceCalculator.java

@@ -1,19 +1,22 @@
 package cn.iocoder.yudao.module.trade.service.price.calculator;
 
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.module.promotion.api.seckill.SeckillActivityApi;
-import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityProductRespDTO;
+import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillValidateJoinRespDTO;
+import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
+import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
 import org.springframework.core.annotation.Order;
 import org.springframework.stereotype.Component;
 
 import javax.annotation.Resource;
-import java.util.List;
-import java.util.Map;
 
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_SECKILL_TOTAL_LIMIT_COUNT;
 
+// TODO huihui:单测需要补充
 /**
  * 秒杀活动的 {@link TradePriceCalculator} 实现类
  *
@@ -24,22 +27,45 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
 public class TradeSeckillActivityPriceCalculator implements TradePriceCalculator {
 
     @Resource
-    private SeckillActivityApi activityApi;
+    private SeckillActivityApi seckillActivityApi;
+
+    @Resource
+    private TradeOrderQueryService tradeOrderQueryService;
 
     @Override
     public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
-        // 1判断订单类型和是否具有秒杀活动编号
+        // 1. 判断订单类型和是否具有秒杀活动编号
         if (param.getSeckillActivityId() == null) {
             return;
         }
-        // 2、获取秒杀活动商品信息
-        List<SeckillActivityProductRespDTO> productList = activityApi.getSeckillActivityProductList(param.getSeckillActivityId(), convertSet(param.getItems(),
-                TradePriceCalculateReqBO.Item::getSkuId));
-        Map<Long, SeckillActivityProductRespDTO> productMap = convertMap(productList, SeckillActivityProductRespDTO::getSkuId);
-        result.getItems().forEach(item -> {
-            SeckillActivityProductRespDTO product = productMap.get(item.getSkuId());
-            item.setActivityPrice(product.getSeckillPrice()); // 设置活动金额
-        });
+        Assert.isTrue(param.getItems().size() == 1, "秒杀时,只允许选择一个商品");
+        // 2. 校验是否可以参与秒杀
+        TradePriceCalculateRespBO.OrderItem orderItem = result.getItems().get(0);
+        SeckillValidateJoinRespDTO seckillActivity = validateJoinSeckill(
+                param.getUserId(), param.getSeckillActivityId(),
+                orderItem.getSkuId(), orderItem.getCount());
+
+        // 3.1 记录优惠明细
+        Integer discountPrice = orderItem.getPayPrice() - seckillActivity.getSeckillPrice() * orderItem.getCount();
+        TradePriceCalculatorHelper.addPromotion(result, orderItem,
+                param.getSeckillActivityId(), seckillActivity.getName(), PromotionTypeEnum.SECKILL_ACTIVITY.getType(),
+                StrUtil.format("秒杀活动:省 {} 元", TradePriceCalculatorHelper.formatPrice(discountPrice)),
+                discountPrice);
+        // 3.2 更新 SKU 优惠金额
+        orderItem.setDiscountPrice(orderItem.getDiscountPrice() + discountPrice);
+        TradePriceCalculatorHelper.recountPayPrice(orderItem);
+        TradePriceCalculatorHelper.recountAllPrice(result);
+    }
+
+    private SeckillValidateJoinRespDTO validateJoinSeckill(Long userId, Long activityId, Long skuId, Integer count) {
+        // 1. 校验是否可以参与秒杀
+        SeckillValidateJoinRespDTO seckillActivity = seckillActivityApi.validateJoinSeckill(activityId, skuId, count);
+        // 2. 校验总限购数量,目前只有 trade 有具体下单的数据,需要交给 trade 价格计算使用
+        int seckillProductCount = tradeOrderQueryService.getSeckillProductCount(userId, activityId);
+        if (seckillProductCount + count > seckillActivity.getTotalLimitCount()) {
+            throw exception(PRICE_CALCULATE_SECKILL_TOTAL_LIMIT_COUNT);
+        }
+        return seckillActivity;
     }
 
 }