Commit 60ea719b46989511e0e6051d5e9371e11ea0051d

Authored by owen
2 parents 8e905f70 dc134718

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
Showing 35 changed files with 334 additions and 304 deletions
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java
@@ -79,7 +79,8 @@ public class LocalDateTimeUtils { @@ -79,7 +79,8 @@ public class LocalDateTimeUtils {
79 return false; 79 return false;
80 } 80 }
81 LocalDate nowDate = LocalDate.now(); 81 LocalDate nowDate = LocalDate.now();
82 - return LocalDateTimeUtil.isIn(LocalDateTime.now(), LocalDateTime.of(nowDate, LocalTime.parse(startTime)), 82 + return LocalDateTimeUtil.isIn(LocalDateTime.now(),
  83 + LocalDateTime.of(nowDate, LocalTime.parse(startTime)),
83 LocalDateTime.of(nowDate, LocalTime.parse(endTime))); 84 LocalDateTime.of(nowDate, LocalTime.parse(endTime)));
84 } 85 }
85 86
@@ -99,46 +100,6 @@ public class LocalDateTimeUtils { @@ -99,46 +100,6 @@ public class LocalDateTimeUtils {
99 } 100 }
100 101
101 /** 102 /**
102 - * 构建日期时间 TODO 后面有需要的话再继续扩展  
103 - *  
104 - * @author HUIHUI  
105 - */  
106 - public static class BuilderDateTime {  
107 -  
108 - /**  
109 - * 日期;2023-10-01  
110 - */  
111 - private String localDate;  
112 - /**  
113 - * 时间;10:01:00  
114 - */  
115 - private String localTime;  
116 -  
117 - public BuilderDateTime() {  
118 - }  
119 -  
120 - public BuilderDateTime withDate(String date) {  
121 - this.localDate = date;  
122 - return this;  
123 - }  
124 -  
125 - public BuilderDateTime withDate(LocalDateTime date) {  
126 - this.localDate = LocalDateTimeUtil.format(date, "yyyy-MM-dd");  
127 - return this;  
128 - }  
129 -  
130 - public BuilderDateTime withTime(String time) {  
131 - this.localTime = time;  
132 - return this;  
133 - }  
134 -  
135 - public LocalDateTime build() {  
136 - return LocalDateTimeUtil.parse(this.localDate + " " + this.localTime, "yyyy-MM-dd HH:mm:ss");  
137 - }  
138 -  
139 - }  
140 -  
141 - /**  
142 * 获取指定日期所在的月份的开始时间 103 * 获取指定日期所在的月份的开始时间
143 * 例如:2023-09-30 00:00:00,000 104 * 例如:2023-09-30 00:00:00,000
144 * 105 *
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApi.java
1 package cn.iocoder.yudao.module.promotion.api.seckill; 1 package cn.iocoder.yudao.module.promotion.api.seckill;
2 2
3 -import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityProductRespDTO;  
4 -  
5 -import java.util.Collection;  
6 -import java.util.List; 3 +import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillValidateJoinRespDTO;
7 4
8 /** 5 /**
9 * 秒杀活动 API 接口 6 * 秒杀活动 API 接口
@@ -22,12 +19,14 @@ public interface SeckillActivityApi { @@ -22,12 +19,14 @@ public interface SeckillActivityApi {
22 void updateSeckillStock(Long id, Long skuId, Integer count); 19 void updateSeckillStock(Long id, Long skuId, Integer count);
23 20
24 /** 21 /**
25 - * 获取秒杀活动商品信息 22 + * 校验是否参与秒杀商品
  23 + *
  24 + * 如果校验失败,则抛出业务异常
26 * 25 *
27 - * @param id 活动编号  
28 - * @param skuIds sku 编号  
29 - * @return 秒杀活动商品信息列表 26 + * @param activityId 活动编号
  27 + * @param skuId SKU 编号
  28 + * @param count 数量
30 */ 29 */
31 - List<SeckillActivityProductRespDTO> getSeckillActivityProductList(Long id, Collection<Long> skuIds); 30 + SeckillValidateJoinRespDTO validateJoinSeckill(Long activityId, Long skuId, Integer count);
32 31
33 } 32 }
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/dto/SeckillActivityProductRespDTO.java deleted
1 -package cn.iocoder.yudao.module.promotion.api.seckill.dto;  
2 -  
3 -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;  
4 -import lombok.Data;  
5 -  
6 -import java.time.LocalDateTime;  
7 -import java.util.List;  
8 -  
9 -/**  
10 - * 秒杀活动商品 Response DTO  
11 - *  
12 - * @author HUIHUI  
13 - */  
14 -@Data  
15 -public class SeckillActivityProductRespDTO {  
16 -  
17 - /**  
18 - * 秒杀参与商品编号  
19 - */  
20 - private Long id;  
21 - /**  
22 - * 秒杀活动 id  
23 - *  
24 - * 关联 SeckillActivityDO#getId()  
25 - */  
26 - private Long activityId;  
27 - /**  
28 - * 秒杀时段 id  
29 - *  
30 - * 关联 SeckillConfigDO#getId()  
31 - */  
32 - private List<Long> configIds;  
33 - /**  
34 - * 商品 SPU 编号  
35 - */  
36 - private Long spuId;  
37 - /**  
38 - * 商品 SKU 编号  
39 - */  
40 - private Long skuId;  
41 - /**  
42 - * 秒杀金额,单位:分  
43 - */  
44 - private Integer seckillPrice;  
45 - /**  
46 - * 秒杀库存  
47 - */  
48 - private Integer stock;  
49 -  
50 - /**  
51 - * 秒杀商品状态  
52 - *  
53 - * 枚举 {@link CommonStatusEnum 对应的类}  
54 - */  
55 - private Integer activityStatus;  
56 - /**  
57 - * 活动开始时间点  
58 - */  
59 - private LocalDateTime activityStartTime;  
60 - /**  
61 - * 活动结束时间点  
62 - */  
63 - private LocalDateTime activityEndTime;  
64 -  
65 -}  
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/dto/SeckillValidateJoinRespDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.api.seckill.dto;
  2 +
  3 +import lombok.Data;
  4 +
  5 +/**
  6 + * 校验参与秒杀 Response DTO
  7 + */
  8 +@Data
  9 +public class SeckillValidateJoinRespDTO {
  10 +
  11 + /**
  12 + * 秒杀活动名称
  13 + */
  14 + private String name;
  15 + /**
  16 + * 总限购数量
  17 + *
  18 + * 目的:目前只有 trade 有具体下单的数据,需要交给 trade 价格计算使用
  19 + */
  20 + private Integer totalLimitCount;
  21 +
  22 + /**
  23 + * 秒杀金额
  24 + */
  25 + private Integer seckillPrice;
  26 +
  27 +}
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 { @@ -56,6 +56,10 @@ public interface ErrorCodeConstants {
56 ErrorCode SECKILL_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END = new ErrorCode(1_013_008_004, "秒杀活动未关闭或未结束,不能删除"); 56 ErrorCode SECKILL_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END = new ErrorCode(1_013_008_004, "秒杀活动未关闭或未结束,不能删除");
57 ErrorCode SECKILL_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_008_005, "秒杀活动已关闭,不能重复关闭"); 57 ErrorCode SECKILL_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_008_005, "秒杀活动已关闭,不能重复关闭");
58 ErrorCode SECKILL_ACTIVITY_UPDATE_STOCK_FAIL = new ErrorCode(1_013_008_006, "秒杀失败,原因秒杀库存不足"); 58 ErrorCode SECKILL_ACTIVITY_UPDATE_STOCK_FAIL = new ErrorCode(1_013_008_006, "秒杀失败,原因秒杀库存不足");
  59 + ErrorCode SECKILL_JOIN_ACTIVITY_TIME_ERROR = new ErrorCode(1_013_008_007, "秒杀失败,原因:不在活动时间范围内");
  60 + ErrorCode SECKILL_JOIN_ACTIVITY_STATUS_CLOSED = new ErrorCode(1_013_008_008, "秒杀失败,原因:秒杀活动已关闭");
  61 + ErrorCode SECKILL_JOIN_ACTIVITY_SINGLE_LIMIT_COUNT_EXCEED = new ErrorCode(1_013_008_009, "秒杀失败,原因:单次限购超出");
  62 + ErrorCode SECKILL_JOIN_ACTIVITY_PRODUCT_NOT_EXISTS = new ErrorCode(1_013_008_010, "秒杀失败,原因:商品不存在");
59 63
60 // ========== 秒杀时段 1-013-009-000 ========== 64 // ========== 秒杀时段 1-013-009-000 ==========
61 ErrorCode SECKILL_CONFIG_NOT_EXISTS = new ErrorCode(1_013_009_000, "秒杀时段不存在"); 65 ErrorCode SECKILL_CONFIG_NOT_EXISTS = new ErrorCode(1_013_009_000, "秒杀时段不存在");
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApiImpl.java
1 package cn.iocoder.yudao.module.promotion.api.seckill; 1 package cn.iocoder.yudao.module.promotion.api.seckill;
2 2
3 -import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityProductRespDTO;  
4 -import cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity.SeckillActivityConvert; 3 +import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillValidateJoinRespDTO;
5 import cn.iocoder.yudao.module.promotion.service.seckill.SeckillActivityService; 4 import cn.iocoder.yudao.module.promotion.service.seckill.SeckillActivityService;
6 import org.springframework.stereotype.Service; 5 import org.springframework.stereotype.Service;
7 6
8 import javax.annotation.Resource; 7 import javax.annotation.Resource;
9 -import java.util.Collection;  
10 -import java.util.List;  
11 8
12 /** 9 /**
13 * 秒杀活动接口 Api 接口实现类 10 * 秒杀活动接口 Api 接口实现类
@@ -26,8 +23,8 @@ public class SeckillActivityApiImpl implements SeckillActivityApi { @@ -26,8 +23,8 @@ public class SeckillActivityApiImpl implements SeckillActivityApi {
26 } 23 }
27 24
28 @Override 25 @Override
29 - public List<SeckillActivityProductRespDTO> getSeckillActivityProductList(Long id, Collection<Long> skuIds) {  
30 - return SeckillActivityConvert.INSTANCE.convertList4(activityService.getSeckillActivityProductList(id, skuIds)); 26 + public SeckillValidateJoinRespDTO validateJoinSeckill(Long activityId, Long skuId, Integer count) {
  27 + return activityService.validateJoinSeckill(activityId, skuId, count);
31 } 28 }
32 29
33 } 30 }
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; @@ -5,6 +5,7 @@ import cn.hutool.core.util.ObjectUtil;
5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
6 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 6 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
7 import cn.iocoder.yudao.framework.common.pojo.PageResult; 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  8 +import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
8 import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; 9 import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
9 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; 10 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
10 import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityDetailRespVO; 11 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 @@ -13,10 +14,12 @@ import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppS
13 import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityRespVO; 14 import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityRespVO;
14 import cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity.SeckillActivityConvert; 15 import cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity.SeckillActivityConvert;
15 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO; 16 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO;
16 -import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillProductDO;  
17 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillConfigDO; 17 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillConfigDO;
  18 +import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillProductDO;
18 import cn.iocoder.yudao.module.promotion.service.seckill.SeckillActivityService; 19 import cn.iocoder.yudao.module.promotion.service.seckill.SeckillActivityService;
19 import cn.iocoder.yudao.module.promotion.service.seckill.SeckillConfigService; 20 import cn.iocoder.yudao.module.promotion.service.seckill.SeckillConfigService;
  21 +import com.google.common.cache.CacheLoader;
  22 +import com.google.common.cache.LoadingCache;
20 import io.swagger.v3.oas.annotations.Operation; 23 import io.swagger.v3.oas.annotations.Operation;
21 import io.swagger.v3.oas.annotations.Parameter; 24 import io.swagger.v3.oas.annotations.Parameter;
22 import io.swagger.v3.oas.annotations.tags.Tag; 25 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -28,16 +31,37 @@ import org.springframework.web.bind.annotation.RequestParam; @@ -28,16 +31,37 @@ import org.springframework.web.bind.annotation.RequestParam;
28 import org.springframework.web.bind.annotation.RestController; 31 import org.springframework.web.bind.annotation.RestController;
29 32
30 import javax.annotation.Resource; 33 import javax.annotation.Resource;
  34 +import java.time.Duration;
  35 +import java.time.LocalDate;
  36 +import java.time.LocalDateTime;
  37 +import java.time.LocalTime;
31 import java.util.List; 38 import java.util.List;
32 39
33 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 40 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  41 +import static cn.iocoder.yudao.framework.common.util.cache.CacheUtils.buildAsyncReloadingCache;
34 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; 42 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  43 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.findFirst;
  44 +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.isBetween;
35 45
36 @Tag(name = "用户 App - 秒杀活动") 46 @Tag(name = "用户 App - 秒杀活动")
37 @RestController 47 @RestController
38 @RequestMapping("/promotion/seckill-activity") 48 @RequestMapping("/promotion/seckill-activity")
39 @Validated 49 @Validated
40 public class AppSeckillActivityController { 50 public class AppSeckillActivityController {
  51 +
  52 + /**
  53 + * {@link AppSeckillActivityNowRespVO} 缓存,通过它异步刷新 {@link #getNowSeckillActivity()} 所要的首页数据
  54 + */
  55 + private final LoadingCache<String, AppSeckillActivityNowRespVO> nowSeckillActivityCache = buildAsyncReloadingCache(Duration.ofSeconds(10L),
  56 + new CacheLoader<String, AppSeckillActivityNowRespVO>() {
  57 +
  58 + @Override
  59 + public AppSeckillActivityNowRespVO load(String key) {
  60 + return getNowSeckillActivity0();
  61 + }
  62 +
  63 + });
  64 +
41 @Resource 65 @Resource
42 private SeckillActivityService activityService; 66 private SeckillActivityService activityService;
43 @Resource 67 @Resource
@@ -47,21 +71,25 @@ public class AppSeckillActivityController { @@ -47,21 +71,25 @@ public class AppSeckillActivityController {
47 @Resource 71 @Resource
48 private ProductSpuApi spuApi; 72 private ProductSpuApi spuApi;
49 73
50 - // TODO 芋艿:需要增加 spring cache  
51 @GetMapping("/get-now") 74 @GetMapping("/get-now")
52 @Operation(summary = "获得当前秒杀活动", description = "获取当前正在进行的活动,提供给首页使用") 75 @Operation(summary = "获得当前秒杀活动", description = "获取当前正在进行的活动,提供给首页使用")
53 public CommonResult<AppSeckillActivityNowRespVO> getNowSeckillActivity() { 76 public CommonResult<AppSeckillActivityNowRespVO> getNowSeckillActivity() {
  77 + return success(nowSeckillActivityCache.getUnchecked(""));
  78 + }
  79 +
  80 + private AppSeckillActivityNowRespVO getNowSeckillActivity0() {
54 // 1. 获取当前时间处在哪个秒杀阶段 81 // 1. 获取当前时间处在哪个秒杀阶段
55 - SeckillConfigDO configList = configService.getSeckillConfigListByStatusOnCurrentTime(CommonStatusEnum.ENABLE.getStatus());  
56 - if (configList == null) { // 时段不存在直接返回 null  
57 - return success(null); 82 + SeckillConfigDO config = configService.getCurrentSeckillConfig();
  83 + if (config == null) { // 时段不存在直接返回 null
  84 + return new AppSeckillActivityNowRespVO();
58 } 85 }
59 86
60 - // 2. 查询满足当前阶段的活动  
61 - List<SeckillActivityDO> activityList = activityService.getSeckillActivityListByConfigIdAndStatus(configList.getId(), CommonStatusEnum.ENABLE.getStatus());  
62 - // 3 获取 spu 信息 87 + // 2.1 查询满足当前阶段的活动
  88 + List<SeckillActivityDO> activityList = activityService.getSeckillActivityListByConfigIdAndStatus(config.getId(), CommonStatusEnum.ENABLE.getStatus());
  89 + List<SeckillProductDO> productList = activityService.getSeckillProductListByActivityId(convertList(activityList, SeckillActivityDO::getId));
  90 + // 2.2 获取 spu 信息
63 List<ProductSpuRespDTO> spuList = spuApi.getSpuList(convertList(activityList, SeckillActivityDO::getSpuId)); 91 List<ProductSpuRespDTO> spuList = spuApi.getSpuList(convertList(activityList, SeckillActivityDO::getSpuId));
64 - return success(SeckillActivityConvert.INSTANCE.convert(configList, activityList, spuList)); 92 + return SeckillActivityConvert.INSTANCE.convert(config, activityList, productList, spuList);
65 } 93 }
66 94
67 @GetMapping("/page") 95 @GetMapping("/page")
@@ -72,31 +100,52 @@ public class AppSeckillActivityController { @@ -72,31 +100,52 @@ public class AppSeckillActivityController {
72 if (CollUtil.isEmpty(pageResult.getList())) { 100 if (CollUtil.isEmpty(pageResult.getList())) {
73 return success(PageResult.empty(pageResult.getTotal())); 101 return success(PageResult.empty(pageResult.getTotal()));
74 } 102 }
  103 + List<SeckillProductDO> productList = activityService.getSeckillProductListByActivityId(
  104 + convertList(pageResult.getList(), SeckillActivityDO::getId));
  105 +
75 // 2. 拼接数据 106 // 2. 拼接数据
76 List<ProductSpuRespDTO> spuList = spuApi.getSpuList(convertList(pageResult.getList(), SeckillActivityDO::getSpuId)); 107 List<ProductSpuRespDTO> spuList = spuApi.getSpuList(convertList(pageResult.getList(), SeckillActivityDO::getSpuId));
77 - return success(SeckillActivityConvert.INSTANCE.convertPage(pageResult, spuList)); 108 + return success(SeckillActivityConvert.INSTANCE.convertPage02(pageResult, productList, spuList));
78 } 109 }
79 110
80 @GetMapping("/get-detail") 111 @GetMapping("/get-detail")
81 @Operation(summary = "获得秒杀活动明细") 112 @Operation(summary = "获得秒杀活动明细")
82 @Parameter(name = "id", description = "活动编号", required = true, example = "1024") 113 @Parameter(name = "id", description = "活动编号", required = true, example = "1024")
83 public CommonResult<AppSeckillActivityDetailRespVO> getSeckillActivity(@RequestParam("id") Long id) { 114 public CommonResult<AppSeckillActivityDetailRespVO> getSeckillActivity(@RequestParam("id") Long id) {
84 - // 1. 获取当前时间处在哪个秒杀阶段  
85 - SeckillConfigDO configList = configService.getSeckillConfigListByStatusOnCurrentTime(CommonStatusEnum.ENABLE.getStatus());  
86 - if (configList == null) { // 时段不存在直接返回 null 115 + // 1. 获取活动
  116 + SeckillActivityDO activity = activityService.getSeckillActivity(id);
  117 + if (activity == null
  118 + || ObjectUtil.equal(activity.getStatus(), CommonStatusEnum.DISABLE.getStatus())) {
87 return success(null); 119 return success(null);
88 } 120 }
89 121
90 - // 2. 获取活动  
91 - SeckillActivityDO seckillActivity = activityService.getSeckillActivity(id);  
92 - if (seckillActivity == null  
93 - || ObjectUtil.equal(seckillActivity.getStatus(), CommonStatusEnum.DISABLE.getStatus())) {  
94 - return success(null); 122 + // 2. 获取时间段
  123 + List<SeckillConfigDO> configs = configService.getSeckillConfigListByStatus(CommonStatusEnum.ENABLE.getStatus());
  124 + configs.removeIf(config -> !CollUtil.contains(activity.getConfigIds(), config.getId()));
  125 + // 2.1 优先使用当前时间段
  126 + SeckillConfigDO config = findFirst(configs, config0 -> isBetween(config0.getStartTime(), config0.getEndTime()));
  127 + // 2.2 如果没有,则获取最后一个,因为倾向优先展示“未开始” > “已结束”
  128 + if (config == null) {
  129 + config = CollUtil.getLast(configs);
  130 + }
  131 + if (config == null) {
  132 + return null;
  133 + }
  134 + // 3. 计算开始时间、结束时间
  135 + LocalDate nowDate;
  136 + // 3.1 如果在活动日期范围内,则以今天为 nowDate
  137 + if (LocalDateTimeUtils.isBetween(activity.getStartTime(), activity.getEndTime())) {
  138 + nowDate = LocalDate.now();
  139 + } else {
  140 + // 3.2 如果不在活动时间范围内,则直接以活动的 endTime 作为 nowDate,因为还是倾向优先展示“未开始” > “已结束”
  141 + nowDate = activity.getEndTime().toLocalDate();
95 } 142 }
  143 + LocalDateTime startTime = LocalDateTime.of(nowDate, LocalTime.parse(config.getStartTime()));
  144 + LocalDateTime endTime = LocalDateTime.of(nowDate, LocalTime.parse(config.getEndTime()));
96 145
97 - // 3. 拼接数据  
98 - List<SeckillProductDO> productList = activityService.getSeckillProductListByActivityId(seckillActivity.getId());  
99 - return success(SeckillActivityConvert.INSTANCE.convert3(seckillActivity, productList, configList)); 146 + // 4. 拼接数据
  147 + List<SeckillProductDO> productList = activityService.getSeckillProductListByActivityId(activity.getId());
  148 + return success(SeckillActivityConvert.INSTANCE.convert3(activity, productList, startTime, endTime));
100 } 149 }
101 150
102 } 151 }
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; @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.config.AppSeckillConfigRespVO; 5 import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.config.AppSeckillConfigRespVO;
6 import cn.iocoder.yudao.module.promotion.convert.seckill.seckillconfig.SeckillConfigConvert; 6 import cn.iocoder.yudao.module.promotion.convert.seckill.seckillconfig.SeckillConfigConvert;
  7 +import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillConfigDO;
7 import cn.iocoder.yudao.module.promotion.service.seckill.SeckillConfigService; 8 import cn.iocoder.yudao.module.promotion.service.seckill.SeckillConfigService;
8 import io.swagger.v3.oas.annotations.Operation; 9 import io.swagger.v3.oas.annotations.Operation;
9 import io.swagger.v3.oas.annotations.tags.Tag; 10 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -28,8 +29,8 @@ public class AppSeckillConfigController { @@ -28,8 +29,8 @@ public class AppSeckillConfigController {
28 @GetMapping("/list") 29 @GetMapping("/list")
29 @Operation(summary = "获得秒杀时间段列表") 30 @Operation(summary = "获得秒杀时间段列表")
30 public CommonResult<List<AppSeckillConfigRespVO>> getSeckillConfigList() { 31 public CommonResult<List<AppSeckillConfigRespVO>> getSeckillConfigList() {
31 - return success(SeckillConfigConvert.INSTANCE.convertList2(  
32 - configService.getSeckillConfigListByStatus(CommonStatusEnum.ENABLE.getStatus()))); 32 + List<SeckillConfigDO> list = configService.getSeckillConfigListByStatus(CommonStatusEnum.ENABLE.getStatus());
  33 + return success(SeckillConfigConvert.INSTANCE.convertList2(list));
33 } 34 }
34 35
35 } 36 }
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 { @@ -19,8 +19,6 @@ public class AppSeckillActivityDetailRespVO {
19 @Schema(description = "活动状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") 19 @Schema(description = "活动状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
20 private Integer status; 20 private Integer status;
21 21
22 - // TODO @芋艿:开始时间、结束时间,要和场次结合起来;就是要算到当前场次,是几点哈;  
23 -  
24 @Schema(description = "活动开始时间", requiredMode = Schema.RequiredMode.REQUIRED) 22 @Schema(description = "活动开始时间", requiredMode = Schema.RequiredMode.REQUIRED)
25 private LocalDateTime startTime; 23 private LocalDateTime startTime;
26 24
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; @@ -3,11 +3,10 @@ package cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity;
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 4 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
5 import cn.iocoder.yudao.framework.common.util.collection.MapUtils; 5 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
6 -import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;  
7 import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils; 6 import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
8 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; 7 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
9 import cn.iocoder.yudao.module.product.enums.DictTypeConstants; 8 import cn.iocoder.yudao.module.product.enums.DictTypeConstants;
10 -import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityProductRespDTO; 9 +import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillValidateJoinRespDTO;
11 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO; 10 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO;
12 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityDetailRespVO; 11 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityDetailRespVO;
13 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityRespVO; 12 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 @@ -19,17 +18,18 @@ import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppS
19 import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityRespVO; 18 import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityRespVO;
20 import cn.iocoder.yudao.module.promotion.convert.seckill.seckillconfig.SeckillConfigConvert; 19 import cn.iocoder.yudao.module.promotion.convert.seckill.seckillconfig.SeckillConfigConvert;
21 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO; 20 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO;
22 -import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillProductDO;  
23 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillConfigDO; 21 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillConfigDO;
  22 +import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillProductDO;
24 import org.mapstruct.Mapper; 23 import org.mapstruct.Mapper;
25 import org.mapstruct.Mapping; 24 import org.mapstruct.Mapping;
26 import org.mapstruct.Mappings; 25 import org.mapstruct.Mappings;
27 import org.mapstruct.factory.Mappers; 26 import org.mapstruct.factory.Mappers;
28 27
  28 +import java.time.LocalDateTime;
29 import java.util.List; 29 import java.util.List;
30 import java.util.Map; 30 import java.util.Map;
31 31
32 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; 32 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
33 import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; 33 import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
34 34
35 /** 35 /**
@@ -93,16 +93,19 @@ public interface SeckillActivityConvert { @@ -93,16 +93,19 @@ public interface SeckillActivityConvert {
93 93
94 List<AppSeckillActivityRespVO> convertList3(List<SeckillActivityDO> activityList); 94 List<AppSeckillActivityRespVO> convertList3(List<SeckillActivityDO> activityList);
95 95
96 - default AppSeckillActivityNowRespVO convert(SeckillConfigDO filteredConfig, List<SeckillActivityDO> activityList, List<ProductSpuRespDTO> spuList) { 96 + default AppSeckillActivityNowRespVO convert(SeckillConfigDO filteredConfig, List<SeckillActivityDO> activityList,
  97 + List<SeckillProductDO> productList, List<ProductSpuRespDTO> spuList) {
97 AppSeckillActivityNowRespVO respVO = new AppSeckillActivityNowRespVO(); 98 AppSeckillActivityNowRespVO respVO = new AppSeckillActivityNowRespVO();
98 respVO.setConfig(SeckillConfigConvert.INSTANCE.convert1(filteredConfig)); 99 respVO.setConfig(SeckillConfigConvert.INSTANCE.convert1(filteredConfig));
99 Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId); 100 Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId);
  101 + Map<Long, List<SeckillProductDO>> productMap = convertMultiMap(productList, SeckillProductDO::getActivityId);
100 respVO.setActivities(CollectionUtils.convertList(convertList3(activityList), item -> { 102 respVO.setActivities(CollectionUtils.convertList(convertList3(activityList), item -> {
101 - findAndThen(spuMap, item.getSpuId(), spu -> {  
102 - item.setPicUrl(spu.getPicUrl())  
103 - .setMarketPrice(spu.getMarketPrice())  
104 - .setUnitName(DictFrameworkUtils.getDictDataLabel(DictTypeConstants.PRODUCT_UNIT, spu.getUnit()));  
105 - }); 103 + // product 信息
  104 + item.setSeckillPrice(getMinValue(productMap.get(item.getId()), SeckillProductDO::getSeckillPrice));
  105 + // spu 信息
  106 + findAndThen(spuMap, item.getSpuId(), spu ->
  107 + item.setPicUrl(spu.getPicUrl()).setMarketPrice(spu.getMarketPrice())
  108 + .setUnitName(DictFrameworkUtils.getDictDataLabel(DictTypeConstants.PRODUCT_UNIT, spu.getUnit())));
106 return item; 109 return item;
107 })); 110 }));
108 return respVO; 111 return respVO;
@@ -110,10 +113,14 @@ public interface SeckillActivityConvert { @@ -110,10 +113,14 @@ public interface SeckillActivityConvert {
110 113
111 PageResult<AppSeckillActivityRespVO> convertPage1(PageResult<SeckillActivityDO> pageResult); 114 PageResult<AppSeckillActivityRespVO> convertPage1(PageResult<SeckillActivityDO> pageResult);
112 115
113 - default PageResult<AppSeckillActivityRespVO> convertPage(PageResult<SeckillActivityDO> pageResult, List<ProductSpuRespDTO> spuList) { 116 + default PageResult<AppSeckillActivityRespVO> convertPage02(PageResult<SeckillActivityDO> pageResult, List<SeckillProductDO> productList, List<ProductSpuRespDTO> spuList) {
114 PageResult<AppSeckillActivityRespVO> result = convertPage1(pageResult); 117 PageResult<AppSeckillActivityRespVO> result = convertPage1(pageResult);
115 Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId); 118 Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId);
  119 + Map<Long, List<SeckillProductDO>> productMap = convertMultiMap(productList, SeckillProductDO::getActivityId);
116 List<AppSeckillActivityRespVO> list = CollectionUtils.convertList(result.getList(), item -> { 120 List<AppSeckillActivityRespVO> list = CollectionUtils.convertList(result.getList(), item -> {
  121 + // product 信息
  122 + item.setSeckillPrice(getMinValue(productMap.get(item.getId()), SeckillProductDO::getSeckillPrice));
  123 + // spu 信息
117 findAndThen(spuMap, item.getSpuId(), spu -> item.setPicUrl(spu.getPicUrl()).setMarketPrice(spu.getMarketPrice()) 124 findAndThen(spuMap, item.getSpuId(), spu -> item.setPicUrl(spu.getPicUrl()).setMarketPrice(spu.getMarketPrice())
118 .setUnitName(DictFrameworkUtils.getDictDataLabel(DictTypeConstants.PRODUCT_UNIT, spu.getUnit()))); 125 .setUnitName(DictFrameworkUtils.getDictDataLabel(DictTypeConstants.PRODUCT_UNIT, spu.getUnit())));
119 return item; 126 return item;
@@ -126,19 +133,13 @@ public interface SeckillActivityConvert { @@ -126,19 +133,13 @@ public interface SeckillActivityConvert {
126 133
127 List<AppSeckillActivityDetailRespVO.Product> convertList1(List<SeckillProductDO> products); 134 List<AppSeckillActivityDetailRespVO.Product> convertList1(List<SeckillProductDO> products);
128 135
129 - default AppSeckillActivityDetailRespVO convert3(SeckillActivityDO seckillActivity, List<SeckillProductDO> products, SeckillConfigDO filteredConfig) {  
130 - return convert2(seckillActivity) 136 + default AppSeckillActivityDetailRespVO convert3(SeckillActivityDO activity, List<SeckillProductDO> products,
  137 + LocalDateTime startTime, LocalDateTime endTime) {
  138 + return convert2(activity)
131 .setProducts(convertList1(products)) 139 .setProducts(convertList1(products))
132 - .setStartTime(new LocalDateTimeUtils.BuilderDateTime()  
133 - .withDate(seckillActivity.getStartTime())  
134 - .withTime(filteredConfig.getStartTime())  
135 - .build())// 活动开始日期和时段结合  
136 - .setEndTime(new LocalDateTimeUtils.BuilderDateTime()  
137 - .withDate(seckillActivity.getEndTime())  
138 - .withTime(filteredConfig.getEndTime())  
139 - .build()); // 活动结束日期和时段结合 140 + .setStartTime(startTime).setEndTime(endTime);
140 } 141 }
141 142
142 - List<SeckillActivityProductRespDTO> convertList4(List<SeckillProductDO> seckillActivityProductList); 143 + SeckillValidateJoinRespDTO convert02(SeckillActivityDO activity, SeckillProductDO product);
143 144
144 } 145 }
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 { @@ -66,18 +66,7 @@ public class SeckillActivityDO extends BaseDO {
66 */ 66 */
67 @TableField(typeHandler = LongListTypeHandler.class) 67 @TableField(typeHandler = LongListTypeHandler.class)
68 private List<Long> configIds; 68 private List<Long> configIds;
69 - /**  
70 - * 新增订单数  
71 - */  
72 - private Integer orderCount;  
73 - /**  
74 - * 付款人数  
75 - */  
76 - private Integer userCount;  
77 - /**  
78 - * 订单实付金额,单位:分  
79 - */  
80 - private Long totalPrice; 69 +
81 /** 70 /**
82 * 总限购数量 71 * 总限购数量
83 */ 72 */
@@ -86,6 +75,7 @@ public class SeckillActivityDO extends BaseDO { @@ -86,6 +75,7 @@ public class SeckillActivityDO extends BaseDO {
86 * 单次限够数量 75 * 单次限够数量
87 */ 76 */
88 private Integer singleLimitCount; 77 private Integer singleLimitCount;
  78 +
89 /** 79 /**
90 * 秒杀库存(剩余库存秒杀时扣减) 80 * 秒杀库存(剩余库存秒杀时扣减)
91 */ 81 */
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&lt;SeckillActivityDO&gt; { @@ -46,7 +46,7 @@ public interface SeckillActivityMapper extends BaseMapperX&lt;SeckillActivityDO&gt; {
46 .eq(SeckillActivityDO::getId, id) 46 .eq(SeckillActivityDO::getId, id)
47 .gt(SeckillActivityDO::getTotalStock, 0) 47 .gt(SeckillActivityDO::getTotalStock, 0)
48 .setSql("stock = stock + " + count) 48 .setSql("stock = stock + " + count)
49 - .setSql("totalStock = totalStock - " + count)); 49 + .setSql("total_stock = total_stock - " + count));
50 } 50 }
51 51
52 default PageResult<SeckillActivityDO> selectPage(AppSeckillActivityPageReqVO pageReqVO, Integer status) { 52 default PageResult<SeckillActivityDO> selectPage(AppSeckillActivityPageReqVO pageReqVO, Integer status) {
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 { @@ -102,6 +102,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
102 return recordDO; 102 return recordDO;
103 } 103 }
104 104
  105 + // TODO @芋艿:在详细预览下;
105 @Override 106 @Override
106 public void validateCombinationRecord(Long activityId, Long userId, Long skuId, Integer count) { 107 public void validateCombinationRecord(Long activityId, Long userId, Long skuId, Integer count) {
107 // 1.1 校验拼团活动是否存在 108 // 1.1 校验拼团活动是否存在
@@ -132,12 +133,14 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { @@ -132,12 +133,14 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
132 } 133 }
133 // 5.1、查询关联的订单是否已经支付 134 // 5.1、查询关联的订单是否已经支付
134 // 当前 activityId 已经有未支付的订单,不允许在发起新的;要么支付,要么去掉先; 135 // 当前 activityId 已经有未支付的订单,不允许在发起新的;要么支付,要么去掉先;
  136 + // TODO 芋艿:看看是不是可以删除掉;
135 Integer orderStatus = tradeOrderApi.getOrderStatus(record.getOrderId()); 137 Integer orderStatus = tradeOrderApi.getOrderStatus(record.getOrderId());
136 if (ObjectUtil.equal(orderStatus, TradeOrderStatusEnum.UNPAID.getStatus())) { 138 if (ObjectUtil.equal(orderStatus, TradeOrderStatusEnum.UNPAID.getStatus())) {
137 throw exception(COMBINATION_RECORD_FAILED_ORDER_STATUS_UNPAID); 139 throw exception(COMBINATION_RECORD_FAILED_ORDER_STATUS_UNPAID);
138 } 140 }
139 } 141 }
140 142
  143 + // TODO 芋艿:在详细 review 下;
141 @Override 144 @Override
142 @Transactional(rollbackFor = Exception.class) 145 @Transactional(rollbackFor = Exception.class)
143 public void createCombinationRecord(CombinationRecordCreateReqDTO reqDTO) { 146 public void createCombinationRecord(CombinationRecordCreateReqDTO reqDTO) {
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityService.java
1 package cn.iocoder.yudao.module.promotion.service.seckill; 1 package cn.iocoder.yudao.module.promotion.service.seckill;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillValidateJoinRespDTO;
4 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO; 5 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO;
5 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO; 6 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO;
6 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityUpdateReqVO; 7 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityUpdateReqVO;
@@ -90,14 +91,6 @@ public interface SeckillActivityService { @@ -90,14 +91,6 @@ public interface SeckillActivityService {
90 List<SeckillProductDO> getSeckillProductListByActivityId(Collection<Long> activityIds); 91 List<SeckillProductDO> getSeckillProductListByActivityId(Collection<Long> activityIds);
91 92
92 /** 93 /**
93 - * 通过活动时段获取秒杀活动  
94 - *  
95 - * @param ids 时段配置编号  
96 - * @return 秒杀活动列表  
97 - */  
98 - List<SeckillActivityDO> getSeckillActivityListByConfigIds(Collection<Long> ids);  
99 -  
100 - /**  
101 * 通过活动时段编号获取指定 status 的秒杀活动 94 * 通过活动时段编号获取指定 status 的秒杀活动
102 * 95 *
103 * @param configId 时段配置编号 96 * @param configId 时段配置编号
@@ -123,4 +116,15 @@ public interface SeckillActivityService { @@ -123,4 +116,15 @@ public interface SeckillActivityService {
123 */ 116 */
124 List<SeckillProductDO> getSeckillActivityProductList(Long id, Collection<Long> skuIds); 117 List<SeckillProductDO> getSeckillActivityProductList(Long id, Collection<Long> skuIds);
125 118
  119 + /**
  120 + * 校验是否参与秒杀商品
  121 + *
  122 + * 如果校验失败,则抛出业务异常
  123 + *
  124 + * @param activityId 活动编号
  125 + * @param skuId SKU 编号
  126 + * @param count 数量
  127 + */
  128 + SeckillValidateJoinRespDTO validateJoinSeckill(Long activityId, Long skuId, Integer count);
  129 +
126 } 130 }
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; @@ -4,10 +4,12 @@ import cn.hutool.core.collection.CollectionUtil;
4 import cn.hutool.core.util.ObjectUtil; 4 import cn.hutool.core.util.ObjectUtil;
5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
6 import cn.iocoder.yudao.framework.common.pojo.PageResult; 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
7 import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; 8 import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
8 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; 9 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
9 import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; 10 import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
10 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; 11 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
  12 +import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillValidateJoinRespDTO;
11 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO; 13 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO;
12 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO; 14 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO;
13 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityUpdateReqVO; 15 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 @@ -15,10 +17,10 @@ import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product.Sec
15 import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityPageReqVO; 17 import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityPageReqVO;
16 import cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity.SeckillActivityConvert; 18 import cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity.SeckillActivityConvert;
17 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO; 19 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO;
  20 +import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillConfigDO;
18 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillProductDO; 21 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillProductDO;
19 import cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity.SeckillActivityMapper; 22 import cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity.SeckillActivityMapper;
20 import cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity.SeckillProductMapper; 23 import cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity.SeckillProductMapper;
21 -import cn.iocoder.yudao.module.promotion.util.PromotionUtils;  
22 import org.springframework.stereotype.Service; 24 import org.springframework.stereotype.Service;
23 import org.springframework.transaction.annotation.Transactional; 25 import org.springframework.transaction.annotation.Transactional;
24 import org.springframework.validation.annotation.Validated; 26 import org.springframework.validation.annotation.Validated;
@@ -60,17 +62,18 @@ public class SeckillActivityServiceImpl implements SeckillActivityService { @@ -60,17 +62,18 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
60 @Override 62 @Override
61 @Transactional(rollbackFor = Exception.class) 63 @Transactional(rollbackFor = Exception.class)
62 public Long createSeckillActivity(SeckillActivityCreateReqVO createReqVO) { 64 public Long createSeckillActivity(SeckillActivityCreateReqVO createReqVO) {
63 - // 校验商品秒杀时段是否冲突 65 + // 1.1 校验商品秒杀时段是否冲突
64 validateProductConflict(createReqVO.getConfigIds(), createReqVO.getSpuId(), null); 66 validateProductConflict(createReqVO.getConfigIds(), createReqVO.getSpuId(), null);
65 - // 校验商品是否存在 67 + // 1.2 校验商品是否存在
66 validateProductExists(createReqVO.getSpuId(), createReqVO.getProducts()); 68 validateProductExists(createReqVO.getSpuId(), createReqVO.getProducts());
67 69
68 - // 插入秒杀活动 70 + // 2.1 插入秒杀活动
69 SeckillActivityDO activity = SeckillActivityConvert.INSTANCE.convert(createReqVO) 71 SeckillActivityDO activity = SeckillActivityConvert.INSTANCE.convert(createReqVO)
70 - .setStatus(PromotionUtils.calculateActivityStatus(createReqVO.getEndTime()))  
71 - .setTotalStock(getSumValue(createReqVO.getProducts(), SeckillProductBaseVO::getStock, Integer::sum)); 72 + .setStatus(CommonStatusEnum.ENABLE.getStatus())
  73 + .setStock(getSumValue(createReqVO.getProducts(), SeckillProductBaseVO::getStock, Integer::sum));
  74 + activity.setTotalStock(activity.getStock());
72 seckillActivityMapper.insert(activity); 75 seckillActivityMapper.insert(activity);
73 - // 插入商品 76 + // 2.2 插入商品
74 List<SeckillProductDO> products = SeckillActivityConvert.INSTANCE.convertList(createReqVO.getProducts(), activity); 77 List<SeckillProductDO> products = SeckillActivityConvert.INSTANCE.convertList(createReqVO.getProducts(), activity);
75 seckillProductMapper.insertBatch(products); 78 seckillProductMapper.insertBatch(products);
76 return activity.getId(); 79 return activity.getId();
@@ -128,22 +131,24 @@ public class SeckillActivityServiceImpl implements SeckillActivityService { @@ -128,22 +131,24 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
128 @Override 131 @Override
129 @Transactional(rollbackFor = Exception.class) 132 @Transactional(rollbackFor = Exception.class)
130 public void updateSeckillActivity(SeckillActivityUpdateReqVO updateReqVO) { 133 public void updateSeckillActivity(SeckillActivityUpdateReqVO updateReqVO) {
131 - // 校验存在  
132 - SeckillActivityDO seckillActivity = validateSeckillActivityExists(updateReqVO.getId());  
133 - if (CommonStatusEnum.DISABLE.getStatus().equals(seckillActivity.getStatus())) { 134 + // 1.1 校验存在
  135 + SeckillActivityDO activity = validateSeckillActivityExists(updateReqVO.getId());
  136 + if (CommonStatusEnum.DISABLE.getStatus().equals(activity.getStatus())) {
134 throw exception(SECKILL_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED); 137 throw exception(SECKILL_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED);
135 } 138 }
136 - // 校验商品是否冲突 139 + // 1.2 校验商品是否冲突
137 validateProductConflict(updateReqVO.getConfigIds(), updateReqVO.getSpuId(), updateReqVO.getId()); 140 validateProductConflict(updateReqVO.getConfigIds(), updateReqVO.getSpuId(), updateReqVO.getId());
138 - // 校验商品是否存在 141 + // 1.3 校验商品是否存在
139 validateProductExists(updateReqVO.getSpuId(), updateReqVO.getProducts()); 142 validateProductExists(updateReqVO.getSpuId(), updateReqVO.getProducts());
140 143
141 - // 更新活动 144 + // 2.1 更新活动
142 SeckillActivityDO updateObj = SeckillActivityConvert.INSTANCE.convert(updateReqVO) 145 SeckillActivityDO updateObj = SeckillActivityConvert.INSTANCE.convert(updateReqVO)
143 - .setStatus(PromotionUtils.calculateActivityStatus(updateReqVO.getEndTime()))  
144 - .setTotalStock(getSumValue(updateReqVO.getProducts(), SeckillProductBaseVO::getStock, Integer::sum)); 146 + .setStock(getSumValue(updateReqVO.getProducts(), SeckillProductBaseVO::getStock, Integer::sum));
  147 + if (updateObj.getStock() > activity.getTotalStock()) { // 如果更新的库存大于原来的库存,则更新总库存
  148 + updateObj.setTotalStock(updateObj.getStock());
  149 + }
145 seckillActivityMapper.updateById(updateObj); 150 seckillActivityMapper.updateById(updateObj);
146 - // 更新商品 151 + // 2.2 更新商品
147 updateSeckillProduct(updateObj, updateReqVO.getProducts()); 152 updateSeckillProduct(updateObj, updateReqVO.getProducts());
148 } 153 }
149 154
@@ -151,7 +156,7 @@ public class SeckillActivityServiceImpl implements SeckillActivityService { @@ -151,7 +156,7 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
151 @Transactional(rollbackFor = Exception.class) 156 @Transactional(rollbackFor = Exception.class)
152 public void updateSeckillStock(Long id, Long skuId, Integer count) { 157 public void updateSeckillStock(Long id, Long skuId, Integer count) {
153 // 1.1 校验活动库存是否充足 158 // 1.1 校验活动库存是否充足
154 - SeckillActivityDO seckillActivity = getSeckillActivity(id); 159 + SeckillActivityDO seckillActivity = validateSeckillActivityExists(id);
155 if (count > seckillActivity.getTotalStock()) { 160 if (count > seckillActivity.getTotalStock()) {
156 throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL); 161 throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL);
157 } 162 }
@@ -243,7 +248,7 @@ public class SeckillActivityServiceImpl implements SeckillActivityService { @@ -243,7 +248,7 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
243 248
244 @Override 249 @Override
245 public SeckillActivityDO getSeckillActivity(Long id) { 250 public SeckillActivityDO getSeckillActivity(Long id) {
246 - return validateSeckillActivityExists(id); 251 + return seckillActivityMapper.selectById(id);
247 } 252 }
248 253
249 @Override 254 @Override
@@ -262,12 +267,6 @@ public class SeckillActivityServiceImpl implements SeckillActivityService { @@ -262,12 +267,6 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
262 } 267 }
263 268
264 @Override 269 @Override
265 - public List<SeckillActivityDO> getSeckillActivityListByConfigIds(Collection<Long> ids) {  
266 - return filterList(seckillActivityMapper.selectList(),  
267 - item -> anyMatch(item.getConfigIds(), ids::contains));  
268 - }  
269 -  
270 - @Override  
271 public List<SeckillActivityDO> getSeckillActivityListByConfigIdAndStatus(Long configId, Integer status) { 270 public List<SeckillActivityDO> getSeckillActivityListByConfigIdAndStatus(Long configId, Integer status) {
272 return filterList(seckillActivityMapper.selectList(SeckillActivityDO::getStatus, status), 271 return filterList(seckillActivityMapper.selectList(SeckillActivityDO::getStatus, status),
273 item -> anyMatch(item.getConfigIds(), id -> ObjectUtil.equal(id, configId)) // 校验时段 272 item -> anyMatch(item.getConfigIds(), id -> ObjectUtil.equal(id, configId)) // 校验时段
@@ -281,16 +280,46 @@ public class SeckillActivityServiceImpl implements SeckillActivityService { @@ -281,16 +280,46 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
281 280
282 @Override 281 @Override
283 public List<SeckillProductDO> getSeckillActivityProductList(Long id, Collection<Long> skuIds) { 282 public List<SeckillProductDO> getSeckillActivityProductList(Long id, Collection<Long> skuIds) {
284 - // 1、校验秒杀活动是否存在  
285 - validateSeckillActivityExists(id); 283 +
286 // 2、校验活动商品是否存在 284 // 2、校验活动商品是否存在
287 List<SeckillProductDO> productList = filterList(seckillProductMapper.selectListByActivityId(id), 285 List<SeckillProductDO> productList = filterList(seckillProductMapper.selectListByActivityId(id),
288 item -> skuIds.contains(item.getSkuId())); 286 item -> skuIds.contains(item.getSkuId()));
289 if (CollectionUtil.isEmpty(productList)) { 287 if (CollectionUtil.isEmpty(productList)) {
290 throw exception(SKU_NOT_EXISTS); 288 throw exception(SKU_NOT_EXISTS);
291 } 289 }
292 -  
293 return productList; 290 return productList;
294 } 291 }
295 292
  293 + @Override
  294 + public SeckillValidateJoinRespDTO validateJoinSeckill(Long activityId, Long skuId, Integer count) {
  295 + // 1.1 校验秒杀活动是否存在
  296 + SeckillActivityDO activity = validateSeckillActivityExists(activityId);
  297 + if (ObjectUtil.notEqual(activity.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {
  298 + throw exception(SECKILL_JOIN_ACTIVITY_STATUS_CLOSED);
  299 + }
  300 + // 1.2 是否在活动时间范围内
  301 + if (!LocalDateTimeUtils.isBetween(activity.getStartTime(), activity.getEndTime())) {
  302 + throw exception(SECKILL_JOIN_ACTIVITY_TIME_ERROR);
  303 + }
  304 + SeckillConfigDO config = seckillConfigService.getCurrentSeckillConfig();
  305 + if (config == null || !CollectionUtil.contains(activity.getConfigIds(), config.getId())) {
  306 + throw exception(SECKILL_JOIN_ACTIVITY_TIME_ERROR);
  307 + }
  308 + // 1.3 超过单次购买限制
  309 + if (count > activity.getSingleLimitCount()) {
  310 + throw exception(SECKILL_JOIN_ACTIVITY_SINGLE_LIMIT_COUNT_EXCEED);
  311 + }
  312 +
  313 + // 2.1 校验秒杀商品是否存在
  314 + SeckillProductDO product = seckillProductMapper.selectByActivityIdAndSkuId(activityId, skuId);
  315 + if (product == null) {
  316 + throw exception(SECKILL_JOIN_ACTIVITY_PRODUCT_NOT_EXISTS);
  317 + }
  318 + // 2.2 校验库存是否充足
  319 + if (count > product.getStock()) {
  320 + throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL);
  321 + }
  322 + return SeckillActivityConvert.INSTANCE.convert02(activity, product);
  323 + }
  324 +
296 } 325 }
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 { @@ -54,7 +54,6 @@ public interface SeckillConfigService {
54 */ 54 */
55 List<SeckillConfigDO> getSeckillConfigList(); 55 List<SeckillConfigDO> getSeckillConfigList();
56 56
57 -  
58 /** 57 /**
59 * 校验秒杀时段是否存在 58 * 校验秒杀时段是否存在
60 * 59 *
@@ -87,11 +86,12 @@ public interface SeckillConfigService { @@ -87,11 +86,12 @@ public interface SeckillConfigService {
87 void updateSeckillConfigStatus(Long id, Integer status); 86 void updateSeckillConfigStatus(Long id, Integer status);
88 87
89 /** 88 /**
90 - * 获取当前日期时间处于的秒杀时段且状态为 status 89 + * 获得当前的秒杀时段
  90 + *
  91 + * 要求必须处于开启状态、且在当前时间段内
91 * 92 *
92 - * @param status 状态  
93 * @return 时段 93 * @return 时段
94 */ 94 */
95 - SeckillConfigDO getSeckillConfigListByStatusOnCurrentTime(Integer status); 95 + SeckillConfigDO getCurrentSeckillConfig();
96 96
97 } 97 }
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; @@ -17,6 +17,7 @@ import org.springframework.validation.annotation.Validated;
17 import javax.annotation.Resource; 17 import javax.annotation.Resource;
18 import java.time.LocalTime; 18 import java.time.LocalTime;
19 import java.util.Collection; 19 import java.util.Collection;
  20 +import java.util.Comparator;
20 import java.util.List; 21 import java.util.List;
21 22
22 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 23 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -70,9 +71,9 @@ public class SeckillConfigServiceImpl implements SeckillConfigService { @@ -70,9 +71,9 @@ public class SeckillConfigServiceImpl implements SeckillConfigService {
70 } 71 }
71 72
72 @Override 73 @Override
73 - public SeckillConfigDO getSeckillConfigListByStatusOnCurrentTime(Integer status) {  
74 - return findFirst(seckillConfigMapper.selectList(SeckillConfigDO::getStatus, status),  
75 - config -> isBetween(config.getStartTime(), config.getEndTime())); 74 + public SeckillConfigDO getCurrentSeckillConfig() {
  75 + List<SeckillConfigDO> list = seckillConfigMapper.selectList(SeckillConfigDO::getStatus, CommonStatusEnum.ENABLE.getStatus());
  76 + return findFirst(list, config -> isBetween(config.getStartTime(), config.getEndTime()));
76 } 77 }
77 78
78 @Override 79 @Override
@@ -151,7 +152,9 @@ public class SeckillConfigServiceImpl implements SeckillConfigService { @@ -151,7 +152,9 @@ public class SeckillConfigServiceImpl implements SeckillConfigService {
151 152
152 @Override 153 @Override
153 public List<SeckillConfigDO> getSeckillConfigListByStatus(Integer status) { 154 public List<SeckillConfigDO> getSeckillConfigListByStatus(Integer status) {
154 - return seckillConfigMapper.selectListByStatus(status); 155 + List<SeckillConfigDO> list = seckillConfigMapper.selectListByStatus(status);
  156 + list.sort(Comparator.comparing(SeckillConfigDO::getStartTime));
  157 + return list;
155 } 158 }
156 159
157 } 160 }
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; @@ -11,6 +11,7 @@ import java.time.LocalDateTime;
11 */ 11 */
12 public interface TradeOrderApi { 12 public interface TradeOrderApi {
13 13
  14 + // TODO 芋艿:看看是不是可以删除掉;
14 /** 15 /**
15 * 获取订单状态 16 * 获取订单状态
16 * 17 *
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 { @@ -55,6 +55,7 @@ public interface ErrorCodeConstants {
55 ErrorCode PRICE_CALCULATE_PAY_PRICE_ILLEGAL = new ErrorCode(1_011_003_000, "支付价格计算异常,原因:价格小于等于 0"); 55 ErrorCode PRICE_CALCULATE_PAY_PRICE_ILLEGAL = new ErrorCode(1_011_003_000, "支付价格计算异常,原因:价格小于等于 0");
56 ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND = new ErrorCode(1_011_003_002, "计算快递运费异常,找不到对应的运费模板"); 56 ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND = new ErrorCode(1_011_003_002, "计算快递运费异常,找不到对应的运费模板");
57 ErrorCode PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER = new ErrorCode(1_011_003_004, "参与秒杀、拼团、砍价的营销商品,无法使用优惠劵"); 57 ErrorCode PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER = new ErrorCode(1_011_003_004, "参与秒杀、拼团、砍价的营销商品,无法使用优惠劵");
  58 + ErrorCode PRICE_CALCULATE_SECKILL_TOTAL_LIMIT_COUNT = new ErrorCode(1_011_003_005, "参与秒杀的商品,超过了秒杀总限购数量");
58 59
59 // ========== 物流 Express 模块 1-011-004-000 ========== 60 // ========== 物流 Express 模块 1-011-004-000 ==========
60 ErrorCode EXPRESS_NOT_EXISTS = new ErrorCode(1_011_004_000, "快递公司不存在"); 61 ErrorCode EXPRESS_NOT_EXISTS = new ErrorCode(1_011_004_000, "快递公司不存在");
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementReqVO.java
1 package cn.iocoder.yudao.module.trade.controller.app.order.vo; 1 package cn.iocoder.yudao.module.trade.controller.app.order.vo;
2 2
  3 +import cn.hutool.core.util.ObjUtil;
3 import cn.iocoder.yudao.framework.common.validation.InEnum; 4 import cn.iocoder.yudao.framework.common.validation.InEnum;
4 import cn.iocoder.yudao.framework.common.validation.Mobile; 5 import cn.iocoder.yudao.framework.common.validation.Mobile;
5 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum; 6 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
@@ -67,7 +68,7 @@ public class AppTradeOrderSettlementReqVO { @@ -67,7 +68,7 @@ public class AppTradeOrderSettlementReqVO {
67 @JsonIgnore 68 @JsonIgnore
68 public boolean isValidActivityItems() { 69 public boolean isValidActivityItems() {
69 // 校验是否是活动订单 70 // 校验是否是活动订单
70 - if (seckillActivityId == null && combinationActivityId == null && combinationHeadId == null) { 71 + if (ObjUtil.isAllEmpty(seckillActivityId, combinationActivityId, combinationHeadId)) {
71 return true; 72 return true;
72 } 73 }
73 // 校验订单项是否超出 74 // 校验订单项是否超出
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; @@ -14,7 +14,7 @@ import java.util.List;
14 public class AppTradeOrderSettlementRespVO { 14 public class AppTradeOrderSettlementRespVO {
15 15
16 @Schema(description = "交易类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") // 对应 TradeOrderTypeEnum 枚举 16 @Schema(description = "交易类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") // 对应 TradeOrderTypeEnum 枚举
17 - private Integer type = 1; // TODO 芋艿:改成计算 17 + private Integer type;
18 18
19 @Schema(description = "购物项数组", requiredMode = Schema.RequiredMode.REQUIRED) 19 @Schema(description = "购物项数组", requiredMode = Schema.RequiredMode.REQUIRED)
20 private List<Item> items; 20 private List<Item> items;
@@ -75,6 +75,9 @@ public class AppTradeOrderSettlementRespVO { @@ -75,6 +75,9 @@ public class AppTradeOrderSettlementRespVO {
75 @Schema(description = "商品原价(总),单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "500") 75 @Schema(description = "商品原价(总),单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "500")
76 private Integer totalPrice; 76 private Integer totalPrice;
77 77
  78 + @Schema(description = "订单优惠(总),单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "66")
  79 + private Integer discountPrice;
  80 +
78 @Schema(description = "运费金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "50") 81 @Schema(description = "运费金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "50")
79 private Integer deliveryPrice; 82 private Integer deliveryPrice;
80 83
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 { @@ -211,7 +211,8 @@ public interface TradeOrderConvert {
211 .setCouponId(settlementReqVO.getCouponId()).setPointStatus(settlementReqVO.getPointStatus()) 211 .setCouponId(settlementReqVO.getCouponId()).setPointStatus(settlementReqVO.getPointStatus())
212 .setDeliveryType(settlementReqVO.getDeliveryType()).setAddressId(settlementReqVO.getAddressId()) 212 .setDeliveryType(settlementReqVO.getDeliveryType()).setAddressId(settlementReqVO.getAddressId())
213 .setPickUpStoreId(settlementReqVO.getPickUpStoreId()) 213 .setPickUpStoreId(settlementReqVO.getPickUpStoreId())
214 - .setItems(new ArrayList<>(settlementReqVO.getItems().size())); 214 + .setItems(new ArrayList<>(settlementReqVO.getItems().size()))
  215 + .setSeckillActivityId(settlementReqVO.getSeckillActivityId());
215 // 商品项的构建 216 // 商品项的构建
216 Map<Long, CartDO> cartMap = convertMap(cartList, CartDO::getId); 217 Map<Long, CartDO> cartMap = convertMap(cartList, CartDO::getId);
217 for (AppTradeOrderSettlementReqVO.Item item : settlementReqVO.getItems()) { 218 for (AppTradeOrderSettlementReqVO.Item item : settlementReqVO.getItems()) {
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 { @@ -288,4 +288,11 @@ public class TradeOrderDO extends BaseDO {
288 */ 288 */
289 private Integer vipPrice; 289 private Integer vipPrice;
290 290
  291 + /**
  292 + * 秒杀活动编号
  293 + *
  294 + * 关联 SeckillActivityDO 的 id 字段
  295 + */
  296 + private Long seckillActivityId;
  297 +
291 } 298 }
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderItemMapper.java
1 package cn.iocoder.yudao.module.trade.dal.mysql.order; 1 package cn.iocoder.yudao.module.trade.dal.mysql.order;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.map.MapUtil;
3 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
4 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 6 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
5 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; 7 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
  8 +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
6 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; 9 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
7 import org.apache.ibatis.annotations.Mapper; 10 import org.apache.ibatis.annotations.Mapper;
  11 +import org.apache.ibatis.annotations.Param;
8 12
9 import java.util.Collection; 13 import java.util.Collection;
10 import java.util.List; 14 import java.util.List;
  15 +import java.util.Map;
  16 +import java.util.Set;
11 17
12 @Mapper 18 @Mapper
13 public interface TradeOrderItemMapper extends BaseMapperX<TradeOrderItemDO> { 19 public interface TradeOrderItemMapper extends BaseMapperX<TradeOrderItemDO> {
@@ -38,4 +44,13 @@ public interface TradeOrderItemMapper extends BaseMapperX&lt;TradeOrderItemDO&gt; { @@ -38,4 +44,13 @@ public interface TradeOrderItemMapper extends BaseMapperX&lt;TradeOrderItemDO&gt; {
38 .eq(TradeOrderItemDO::getCommentStatus, commentStatus)); 44 .eq(TradeOrderItemDO::getCommentStatus, commentStatus));
39 } 45 }
40 46
  47 + default int selectProductSumByOrderId(@Param("orderIds") Set<Long> orderIds) {
  48 + // SQL sum 查询
  49 + List<Map<String, Object>> result = selectMaps(new QueryWrapper<TradeOrderItemDO>()
  50 + .select("SUM(count) AS sumCount")
  51 + .in("order_id", orderIds)); // 只计算选中的
  52 + // 获得数量
  53 + return CollUtil.getFirst(result) != null ? MapUtil.getInt(result.get(0), "sumCount") : 0;
  54 + }
  55 +
41 } 56 }
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&lt;TradeOrderDO&gt; { @@ -86,6 +86,12 @@ public interface TradeOrderMapper extends BaseMapperX&lt;TradeOrderDO&gt; {
86 .eq(TradeOrderDO::getCommentStatus, commentStatus)); 86 .eq(TradeOrderDO::getCommentStatus, commentStatus));
87 } 87 }
88 88
  89 + default List<TradeOrderDO> selectListByUserIdAndSeckillActivityId(Long userId, Long seckillActivityId) {
  90 + return selectList(new LambdaUpdateWrapper<>(TradeOrderDO.class)
  91 + .eq(TradeOrderDO::getUserId, userId)
  92 + .eq(TradeOrderDO::getSeckillActivityId, seckillActivityId));
  93 + }
  94 +
89 default TradeOrderSummaryRespDTO selectSummaryByPayTimeBetween(LocalDateTime beginTime, LocalDateTime endTime) { 95 default TradeOrderSummaryRespDTO selectSummaryByPayTimeBetween(LocalDateTime beginTime, LocalDateTime endTime) {
90 return BeanUtil.copyProperties(CollUtil.get(selectMaps(MPJWrappers.<TradeOrderDO>lambdaJoin() 96 return BeanUtil.copyProperties(CollUtil.get(selectMaps(MPJWrappers.<TradeOrderDO>lambdaJoin()
91 .selectCount(TradeOrderDO::getId, TradeOrderSummaryRespDTO::getOrderPayCount) 97 .selectCount(TradeOrderDO::getId, TradeOrderSummaryRespDTO::getOrderPayCount)
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 { @@ -84,6 +84,15 @@ public interface TradeOrderQueryService {
84 */ 84 */
85 List<ExpressTrackRespDTO> getExpressTrackList(Long id); 85 List<ExpressTrackRespDTO> getExpressTrackList(Long id);
86 86
  87 + /**
  88 + * 【会员】在指定秒杀活动下,用户购买的商品数量
  89 + *
  90 + * @param userId 用户编号
  91 + * @param activityId 活动编号
  92 + * @return 秒杀商品数量
  93 + */
  94 + int getSeckillProductCount(Long userId, Long activityId);
  95 +
87 // =================== Order Item =================== 96 // =================== Order Item ===================
88 97
89 /** 98 /**
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; @@ -14,6 +14,7 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
14 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; 14 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
15 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper; 15 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper;
16 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper; 16 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper;
  17 +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
17 import cn.iocoder.yudao.module.trade.framework.delivery.core.client.ExpressClientFactory; 18 import cn.iocoder.yudao.module.trade.framework.delivery.core.client.ExpressClientFactory;
18 import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackQueryReqDTO; 19 import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackQueryReqDTO;
19 import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackRespDTO; 20 import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackRespDTO;
@@ -122,6 +123,18 @@ public class TradeOrderQueryServiceImpl implements TradeOrderQueryService { @@ -122,6 +123,18 @@ public class TradeOrderQueryServiceImpl implements TradeOrderQueryService {
122 return getExpressTrackList(order); 123 return getExpressTrackList(order);
123 } 124 }
124 125
  126 + @Override
  127 + public int getSeckillProductCount(Long userId, Long activityId) {
  128 + // 获得订单列表
  129 + List<TradeOrderDO> orders = tradeOrderMapper.selectListByUserIdAndSeckillActivityId(userId, activityId);
  130 + orders.removeIf(order -> TradeOrderStatusEnum.isCanceled(order.getStatus())); // 过滤掉【已取消】的订单
  131 + if (CollUtil.isEmpty(orders)) {
  132 + return 0;
  133 + }
  134 + // 获得订单项列表
  135 + return tradeOrderItemMapper.selectProductSumByOrderId(convertSet(orders, TradeOrderDO::getId));
  136 + }
  137 +
125 // TODO @puhui999:可以加个 spring 缓存,30 分钟;主要考虑及时性要求不高,但是每次调用需要钱; 138 // TODO @puhui999:可以加个 spring 缓存,30 分钟;主要考虑及时性要求不高,但是每次调用需要钱;
126 /** 139 /**
127 * 获得订单的物流轨迹 140 * 获得订单的物流轨迹
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; @@ -14,7 +14,7 @@ import javax.annotation.Resource;
14 * @author HUIHUI 14 * @author HUIHUI
15 */ 15 */
16 @Component 16 @Component
17 -public class TradeBargainHandler extends TradeOrderDefaultHandler { 17 +public class TradeBargainHandler implements TradeOrderHandler {
18 18
19 @Resource 19 @Resource
20 private BargainActivityApi bargainActivityApi; 20 private BargainActivityApi bargainActivityApi;
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; @@ -17,7 +17,7 @@ import javax.annotation.Resource;
17 * @author HUIHUI 17 * @author HUIHUI
18 */ 18 */
19 @Component 19 @Component
20 -public class TradeCombinationHandler extends TradeOrderDefaultHandler { 20 +public class TradeCombinationHandler implements TradeOrderHandler {
21 21
22 @Resource 22 @Resource
23 private CombinationRecordApi combinationRecordApi; 23 private CombinationRecordApi combinationRecordApi;
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeOrderDefaultHandler.java deleted
1 -package cn.iocoder.yudao.module.trade.service.order.handler;  
2 -  
3 -import cn.iocoder.yudao.module.trade.service.order.bo.TradeAfterOrderCreateReqBO;  
4 -import cn.iocoder.yudao.module.trade.service.order.bo.TradeAfterPayOrderReqBO;  
5 -import cn.iocoder.yudao.module.trade.service.order.bo.TradeBeforeOrderCreateReqBO;  
6 -  
7 -/**  
8 - * 订单活动特殊逻辑处理器 handler 默认抽象实现类  
9 - *  
10 - * @author HUIHUI  
11 - */  
12 -public abstract class TradeOrderDefaultHandler implements TradeOrderHandler {  
13 -  
14 - @Override  
15 - public void beforeOrderCreate(TradeBeforeOrderCreateReqBO reqBO) {  
16 -  
17 - }  
18 -  
19 - @Override  
20 - public void afterOrderCreate(TradeAfterOrderCreateReqBO reqBO) {  
21 -  
22 - }  
23 -  
24 - @Override  
25 - public void afterPayOrder(TradeAfterPayOrderReqBO reqBO) {  
26 -  
27 - }  
28 -  
29 - @Override  
30 - public void cancelOrder() {  
31 -  
32 - }  
33 -  
34 -}  
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 { @@ -17,25 +17,25 @@ public interface TradeOrderHandler {
17 * 17 *
18 * @param reqBO 请求 18 * @param reqBO 请求
19 */ 19 */
20 - void beforeOrderCreate(TradeBeforeOrderCreateReqBO reqBO); 20 + default void beforeOrderCreate(TradeBeforeOrderCreateReqBO reqBO) {}
21 21
22 /** 22 /**
23 * 订单创建后 23 * 订单创建后
24 * 24 *
25 * @param reqBO 请求 25 * @param reqBO 请求
26 */ 26 */
27 - void afterOrderCreate(TradeAfterOrderCreateReqBO reqBO); 27 + default void afterOrderCreate(TradeAfterOrderCreateReqBO reqBO) {}
28 28
29 /** 29 /**
30 * 支付订单后 30 * 支付订单后
31 * 31 *
32 * @param reqBO 请求 32 * @param reqBO 请求
33 */ 33 */
34 - void afterPayOrder(TradeAfterPayOrderReqBO reqBO); 34 + default void afterPayOrder(TradeAfterPayOrderReqBO reqBO) {}
35 35
36 /** 36 /**
37 * 订单取消 37 * 订单取消
38 */ 38 */
39 - void cancelOrder(); 39 + default void cancelOrder() {}
40 40
41 } 41 }
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; @@ -14,7 +14,7 @@ import javax.annotation.Resource;
14 * @author HUIHUI 14 * @author HUIHUI
15 */ 15 */
16 @Component 16 @Component
17 -public class TradeSeckillHandler extends TradeOrderDefaultHandler { 17 +public class TradeSeckillHandler implements TradeOrderHandler {
18 18
19 @Resource 19 @Resource
20 private SeckillActivityApi seckillActivityApi; 20 private SeckillActivityApi seckillActivityApi;
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java
@@ -99,18 +99,8 @@ public class TradePriceCalculateRespBO { @@ -99,18 +99,8 @@ public class TradePriceCalculateRespBO {
99 */ 99 */
100 private Integer vipPrice; 100 private Integer vipPrice;
101 /** 101 /**
102 - * 秒杀、拼团、砍价活动商品的总金额,单位:分  
103 - *  
104 - * 基于 {@link OrderItem#getActivityPrice()} ()} * {@link OrderItem#getCount()} 求和  
105 - */  
106 - private Integer activityPrice;  
107 - /**  
108 * 最终购买金额(总),单位:分 102 * 最终购买金额(总),单位:分
109 * 103 *
110 - * ==========活动情况===========  
111 - * = {@link #activityPrice}  
112 - * + {@link #deliveryPrice}  
113 - * ==========正常情况===========  
114 * = {@link #totalPrice} 104 * = {@link #totalPrice}
115 * - {@link #couponPrice} 105 * - {@link #couponPrice}
116 * - {@link #pointPrice} 106 * - {@link #pointPrice}
@@ -187,15 +177,8 @@ public class TradePriceCalculateRespBO { @@ -187,15 +177,8 @@ public class TradePriceCalculateRespBO {
187 */ 177 */
188 private Integer vipPrice; 178 private Integer vipPrice;
189 /** 179 /**
190 - * 秒杀、拼团、砍价活动商品的金额,单位:分  
191 - */  
192 - private Integer activityPrice;  
193 - /**  
194 * 应付金额(总),单位:分 180 * 应付金额(总),单位:分
195 - * ==========活动情况===========  
196 - * = {@link #activityPrice} * {@link #count}  
197 - * + {@link #deliveryPrice}  
198 - * ==========正常情况=========== 181 + *
199 * = {@link #price} * {@link #count} 182 * = {@link #price} * {@link #count}
200 * - {@link #couponPrice} 183 * - {@link #couponPrice}
201 * - {@link #pointPrice} 184 * - {@link #pointPrice}
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 { @@ -105,9 +105,6 @@ public class TradePriceCalculatorHelper {
105 if (!item.getSelected()) { 105 if (!item.getSelected()) {
106 return; 106 return;
107 } 107 }
108 - // TODO puhui: 需要在这里计算活动的价格  
109 - // ========== 一、活动情况 ==========  
110 - // ========== 二、正常情况 ==========  
111 price.setTotalPrice(price.getTotalPrice() + item.getPrice() * item.getCount()); 108 price.setTotalPrice(price.getTotalPrice() + item.getPrice() * item.getCount());
112 price.setDiscountPrice(price.getDiscountPrice() + item.getDiscountPrice()); 109 price.setDiscountPrice(price.getDiscountPrice() + item.getDiscountPrice());
113 price.setDeliveryPrice(price.getDeliveryPrice() + item.getDeliveryPrice()); 110 price.setDeliveryPrice(price.getDeliveryPrice() + item.getDeliveryPrice());
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeSeckillActivityPriceCalculator.java
1 package cn.iocoder.yudao.module.trade.service.price.calculator; 1 package cn.iocoder.yudao.module.trade.service.price.calculator;
2 2
  3 +import cn.hutool.core.lang.Assert;
  4 +import cn.hutool.core.util.StrUtil;
3 import cn.iocoder.yudao.module.promotion.api.seckill.SeckillActivityApi; 5 import cn.iocoder.yudao.module.promotion.api.seckill.SeckillActivityApi;
4 -import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityProductRespDTO; 6 +import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillValidateJoinRespDTO;
  7 +import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
  8 +import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
5 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; 9 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
6 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; 10 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
7 import org.springframework.core.annotation.Order; 11 import org.springframework.core.annotation.Order;
8 import org.springframework.stereotype.Component; 12 import org.springframework.stereotype.Component;
9 13
10 import javax.annotation.Resource; 14 import javax.annotation.Resource;
11 -import java.util.List;  
12 -import java.util.Map;  
13 15
14 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;  
15 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; 16 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  17 +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_SECKILL_TOTAL_LIMIT_COUNT;
16 18
  19 +// TODO huihui:单测需要补充
17 /** 20 /**
18 * 秒杀活动的 {@link TradePriceCalculator} 实现类 21 * 秒杀活动的 {@link TradePriceCalculator} 实现类
19 * 22 *
@@ -24,22 +27,45 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. @@ -24,22 +27,45 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
24 public class TradeSeckillActivityPriceCalculator implements TradePriceCalculator { 27 public class TradeSeckillActivityPriceCalculator implements TradePriceCalculator {
25 28
26 @Resource 29 @Resource
27 - private SeckillActivityApi activityApi; 30 + private SeckillActivityApi seckillActivityApi;
  31 +
  32 + @Resource
  33 + private TradeOrderQueryService tradeOrderQueryService;
28 34
29 @Override 35 @Override
30 public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) { 36 public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
31 - // 1、判断订单类型和是否具有秒杀活动编号 37 + // 1. 判断订单类型和是否具有秒杀活动编号
32 if (param.getSeckillActivityId() == null) { 38 if (param.getSeckillActivityId() == null) {
33 return; 39 return;
34 } 40 }
35 - // 2、获取秒杀活动商品信息  
36 - List<SeckillActivityProductRespDTO> productList = activityApi.getSeckillActivityProductList(param.getSeckillActivityId(), convertSet(param.getItems(),  
37 - TradePriceCalculateReqBO.Item::getSkuId));  
38 - Map<Long, SeckillActivityProductRespDTO> productMap = convertMap(productList, SeckillActivityProductRespDTO::getSkuId);  
39 - result.getItems().forEach(item -> {  
40 - SeckillActivityProductRespDTO product = productMap.get(item.getSkuId());  
41 - item.setActivityPrice(product.getSeckillPrice()); // 设置活动金额  
42 - }); 41 + Assert.isTrue(param.getItems().size() == 1, "秒杀时,只允许选择一个商品");
  42 + // 2. 校验是否可以参与秒杀
  43 + TradePriceCalculateRespBO.OrderItem orderItem = result.getItems().get(0);
  44 + SeckillValidateJoinRespDTO seckillActivity = validateJoinSeckill(
  45 + param.getUserId(), param.getSeckillActivityId(),
  46 + orderItem.getSkuId(), orderItem.getCount());
  47 +
  48 + // 3.1 记录优惠明细
  49 + Integer discountPrice = orderItem.getPayPrice() - seckillActivity.getSeckillPrice() * orderItem.getCount();
  50 + TradePriceCalculatorHelper.addPromotion(result, orderItem,
  51 + param.getSeckillActivityId(), seckillActivity.getName(), PromotionTypeEnum.SECKILL_ACTIVITY.getType(),
  52 + StrUtil.format("秒杀活动:省 {} 元", TradePriceCalculatorHelper.formatPrice(discountPrice)),
  53 + discountPrice);
  54 + // 3.2 更新 SKU 优惠金额
  55 + orderItem.setDiscountPrice(orderItem.getDiscountPrice() + discountPrice);
  56 + TradePriceCalculatorHelper.recountPayPrice(orderItem);
  57 + TradePriceCalculatorHelper.recountAllPrice(result);
  58 + }
  59 +
  60 + private SeckillValidateJoinRespDTO validateJoinSeckill(Long userId, Long activityId, Long skuId, Integer count) {
  61 + // 1. 校验是否可以参与秒杀
  62 + SeckillValidateJoinRespDTO seckillActivity = seckillActivityApi.validateJoinSeckill(activityId, skuId, count);
  63 + // 2. 校验总限购数量,目前只有 trade 有具体下单的数据,需要交给 trade 价格计算使用
  64 + int seckillProductCount = tradeOrderQueryService.getSeckillProductCount(userId, activityId);
  65 + if (seckillProductCount + count > seckillActivity.getTotalLimitCount()) {
  66 + throw exception(PRICE_CALCULATE_SECKILL_TOTAL_LIMIT_COUNT);
  67 + }
  68 + return seckillActivity;
43 } 69 }
44 70
45 } 71 }