Commit 60ea719b46989511e0e6051d5e9371e11ea0051d
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 | 79 | return false; |
| 80 | 80 | } |
| 81 | 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 | 84 | LocalDateTime.of(nowDate, LocalTime.parse(endTime))); |
| 84 | 85 | } |
| 85 | 86 | |
| ... | ... | @@ -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 | 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 | 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 | 6 | * 秒杀活动 API 接口 |
| ... | ... | @@ -22,12 +19,14 @@ public interface SeckillActivityApi { |
| 22 | 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 | 56 | ErrorCode SECKILL_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END = new ErrorCode(1_013_008_004, "秒杀活动未关闭或未结束,不能删除"); |
| 57 | 57 | ErrorCode SECKILL_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_008_005, "秒杀活动已关闭,不能重复关闭"); |
| 58 | 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 | 64 | // ========== 秒杀时段 1-013-009-000 ========== |
| 61 | 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 | 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 | 4 | import cn.iocoder.yudao.module.promotion.service.seckill.SeckillActivityService; |
| 6 | 5 | import org.springframework.stereotype.Service; |
| 7 | 6 | |
| 8 | 7 | import javax.annotation.Resource; |
| 9 | -import java.util.Collection; | |
| 10 | -import java.util.List; | |
| 11 | 8 | |
| 12 | 9 | /** |
| 13 | 10 | * 秒杀活动接口 Api 接口实现类 |
| ... | ... | @@ -26,8 +23,8 @@ public class SeckillActivityApiImpl implements SeckillActivityApi { |
| 26 | 23 | } |
| 27 | 24 | |
| 28 | 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 | 5 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 6 | 6 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 7 | 7 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 8 | +import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; | |
| 8 | 9 | import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; |
| 9 | 10 | import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; |
| 10 | 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 | 14 | import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityRespVO; |
| 14 | 15 | import cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity.SeckillActivityConvert; |
| 15 | 16 | import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO; |
| 16 | -import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillProductDO; | |
| 17 | 17 | import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillConfigDO; |
| 18 | +import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillProductDO; | |
| 18 | 19 | import cn.iocoder.yudao.module.promotion.service.seckill.SeckillActivityService; |
| 19 | 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 | 23 | import io.swagger.v3.oas.annotations.Operation; |
| 21 | 24 | import io.swagger.v3.oas.annotations.Parameter; |
| 22 | 25 | import io.swagger.v3.oas.annotations.tags.Tag; |
| ... | ... | @@ -28,16 +31,37 @@ import org.springframework.web.bind.annotation.RequestParam; |
| 28 | 31 | import org.springframework.web.bind.annotation.RestController; |
| 29 | 32 | |
| 30 | 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 | 38 | import java.util.List; |
| 32 | 39 | |
| 33 | 40 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 41 | +import static cn.iocoder.yudao.framework.common.util.cache.CacheUtils.buildAsyncReloadingCache; | |
| 34 | 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 | 46 | @Tag(name = "用户 App - 秒杀活动") |
| 37 | 47 | @RestController |
| 38 | 48 | @RequestMapping("/promotion/seckill-activity") |
| 39 | 49 | @Validated |
| 40 | 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 | 65 | @Resource |
| 42 | 66 | private SeckillActivityService activityService; |
| 43 | 67 | @Resource |
| ... | ... | @@ -47,21 +71,25 @@ public class AppSeckillActivityController { |
| 47 | 71 | @Resource |
| 48 | 72 | private ProductSpuApi spuApi; |
| 49 | 73 | |
| 50 | - // TODO 芋艿:需要增加 spring cache | |
| 51 | 74 | @GetMapping("/get-now") |
| 52 | 75 | @Operation(summary = "获得当前秒杀活动", description = "获取当前正在进行的活动,提供给首页使用") |
| 53 | 76 | public CommonResult<AppSeckillActivityNowRespVO> getNowSeckillActivity() { |
| 77 | + return success(nowSeckillActivityCache.getUnchecked("")); | |
| 78 | + } | |
| 79 | + | |
| 80 | + private AppSeckillActivityNowRespVO getNowSeckillActivity0() { | |
| 54 | 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 | 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 | 95 | @GetMapping("/page") |
| ... | ... | @@ -72,31 +100,52 @@ public class AppSeckillActivityController { |
| 72 | 100 | if (CollUtil.isEmpty(pageResult.getList())) { |
| 73 | 101 | return success(PageResult.empty(pageResult.getTotal())); |
| 74 | 102 | } |
| 103 | + List<SeckillProductDO> productList = activityService.getSeckillProductListByActivityId( | |
| 104 | + convertList(pageResult.getList(), SeckillActivityDO::getId)); | |
| 105 | + | |
| 75 | 106 | // 2. 拼接数据 |
| 76 | 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 | 111 | @GetMapping("/get-detail") |
| 81 | 112 | @Operation(summary = "获得秒杀活动明细") |
| 82 | 113 | @Parameter(name = "id", description = "活动编号", required = true, example = "1024") |
| 83 | 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 | 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 | 4 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 5 | 5 | import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.config.AppSeckillConfigRespVO; |
| 6 | 6 | import cn.iocoder.yudao.module.promotion.convert.seckill.seckillconfig.SeckillConfigConvert; |
| 7 | +import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillConfigDO; | |
| 7 | 8 | import cn.iocoder.yudao.module.promotion.service.seckill.SeckillConfigService; |
| 8 | 9 | import io.swagger.v3.oas.annotations.Operation; |
| 9 | 10 | import io.swagger.v3.oas.annotations.tags.Tag; |
| ... | ... | @@ -28,8 +29,8 @@ public class AppSeckillConfigController { |
| 28 | 29 | @GetMapping("/list") |
| 29 | 30 | @Operation(summary = "获得秒杀时间段列表") |
| 30 | 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 | 19 | @Schema(description = "活动状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| 20 | 20 | private Integer status; |
| 21 | 21 | |
| 22 | - // TODO @芋艿:开始时间、结束时间,要和场次结合起来;就是要算到当前场次,是几点哈; | |
| 23 | - | |
| 24 | 22 | @Schema(description = "活动开始时间", requiredMode = Schema.RequiredMode.REQUIRED) |
| 25 | 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 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | 4 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |
| 5 | 5 | import cn.iocoder.yudao.framework.common.util.collection.MapUtils; |
| 6 | -import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; | |
| 7 | 6 | import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils; |
| 8 | 7 | import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; |
| 9 | 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 | 10 | import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO; |
| 12 | 11 | import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityDetailRespVO; |
| 13 | 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 | 18 | import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityRespVO; |
| 20 | 19 | import cn.iocoder.yudao.module.promotion.convert.seckill.seckillconfig.SeckillConfigConvert; |
| 21 | 20 | import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO; |
| 22 | -import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillProductDO; | |
| 23 | 21 | import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillConfigDO; |
| 22 | +import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillProductDO; | |
| 24 | 23 | import org.mapstruct.Mapper; |
| 25 | 24 | import org.mapstruct.Mapping; |
| 26 | 25 | import org.mapstruct.Mappings; |
| 27 | 26 | import org.mapstruct.factory.Mappers; |
| 28 | 27 | |
| 28 | +import java.time.LocalDateTime; | |
| 29 | 29 | import java.util.List; |
| 30 | 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 | 33 | import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; |
| 34 | 34 | |
| 35 | 35 | /** |
| ... | ... | @@ -93,16 +93,19 @@ public interface SeckillActivityConvert { |
| 93 | 93 | |
| 94 | 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 | 98 | AppSeckillActivityNowRespVO respVO = new AppSeckillActivityNowRespVO(); |
| 98 | 99 | respVO.setConfig(SeckillConfigConvert.INSTANCE.convert1(filteredConfig)); |
| 99 | 100 | Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId); |
| 101 | + Map<Long, List<SeckillProductDO>> productMap = convertMultiMap(productList, SeckillProductDO::getActivityId); | |
| 100 | 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 | 109 | return item; |
| 107 | 110 | })); |
| 108 | 111 | return respVO; |
| ... | ... | @@ -110,10 +113,14 @@ public interface SeckillActivityConvert { |
| 110 | 113 | |
| 111 | 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 | 117 | PageResult<AppSeckillActivityRespVO> result = convertPage1(pageResult); |
| 115 | 118 | Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId); |
| 119 | + Map<Long, List<SeckillProductDO>> productMap = convertMultiMap(productList, SeckillProductDO::getActivityId); | |
| 116 | 120 | List<AppSeckillActivityRespVO> list = CollectionUtils.convertList(result.getList(), item -> { |
| 121 | + // product 信息 | |
| 122 | + item.setSeckillPrice(getMinValue(productMap.get(item.getId()), SeckillProductDO::getSeckillPrice)); | |
| 123 | + // spu 信息 | |
| 117 | 124 | findAndThen(spuMap, item.getSpuId(), spu -> item.setPicUrl(spu.getPicUrl()).setMarketPrice(spu.getMarketPrice()) |
| 118 | 125 | .setUnitName(DictFrameworkUtils.getDictDataLabel(DictTypeConstants.PRODUCT_UNIT, spu.getUnit()))); |
| 119 | 126 | return item; |
| ... | ... | @@ -126,19 +133,13 @@ public interface SeckillActivityConvert { |
| 126 | 133 | |
| 127 | 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 | 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 | 66 | */ |
| 67 | 67 | @TableField(typeHandler = LongListTypeHandler.class) |
| 68 | 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 | 75 | * 单次限够数量 |
| 87 | 76 | */ |
| 88 | 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<SeckillActivityDO> { |
| 46 | 46 | .eq(SeckillActivityDO::getId, id) |
| 47 | 47 | .gt(SeckillActivityDO::getTotalStock, 0) |
| 48 | 48 | .setSql("stock = stock + " + count) |
| 49 | - .setSql("totalStock = totalStock - " + count)); | |
| 49 | + .setSql("total_stock = total_stock - " + count)); | |
| 50 | 50 | } |
| 51 | 51 | |
| 52 | 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 | 102 | return recordDO; |
| 103 | 103 | } |
| 104 | 104 | |
| 105 | + // TODO @芋艿:在详细预览下; | |
| 105 | 106 | @Override |
| 106 | 107 | public void validateCombinationRecord(Long activityId, Long userId, Long skuId, Integer count) { |
| 107 | 108 | // 1.1 校验拼团活动是否存在 |
| ... | ... | @@ -132,12 +133,14 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { |
| 132 | 133 | } |
| 133 | 134 | // 5.1、查询关联的订单是否已经支付 |
| 134 | 135 | // 当前 activityId 已经有未支付的订单,不允许在发起新的;要么支付,要么去掉先; |
| 136 | + // TODO 芋艿:看看是不是可以删除掉; | |
| 135 | 137 | Integer orderStatus = tradeOrderApi.getOrderStatus(record.getOrderId()); |
| 136 | 138 | if (ObjectUtil.equal(orderStatus, TradeOrderStatusEnum.UNPAID.getStatus())) { |
| 137 | 139 | throw exception(COMBINATION_RECORD_FAILED_ORDER_STATUS_UNPAID); |
| 138 | 140 | } |
| 139 | 141 | } |
| 140 | 142 | |
| 143 | + // TODO 芋艿:在详细 review 下; | |
| 141 | 144 | @Override |
| 142 | 145 | @Transactional(rollbackFor = Exception.class) |
| 143 | 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 | 1 | package cn.iocoder.yudao.module.promotion.service.seckill; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | +import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillValidateJoinRespDTO; | |
| 4 | 5 | import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO; |
| 5 | 6 | import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO; |
| 6 | 7 | import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityUpdateReqVO; |
| ... | ... | @@ -90,14 +91,6 @@ public interface SeckillActivityService { |
| 90 | 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 | 94 | * 通过活动时段编号获取指定 status 的秒杀活动 |
| 102 | 95 | * |
| 103 | 96 | * @param configId 时段配置编号 |
| ... | ... | @@ -123,4 +116,15 @@ public interface SeckillActivityService { |
| 123 | 116 | */ |
| 124 | 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 | 4 | import cn.hutool.core.util.ObjectUtil; |
| 5 | 5 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 6 | 6 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 7 | +import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; | |
| 7 | 8 | import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; |
| 8 | 9 | import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; |
| 9 | 10 | import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; |
| 10 | 11 | import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; |
| 12 | +import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillValidateJoinRespDTO; | |
| 11 | 13 | import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO; |
| 12 | 14 | import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO; |
| 13 | 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 | 17 | import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityPageReqVO; |
| 16 | 18 | import cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity.SeckillActivityConvert; |
| 17 | 19 | import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO; |
| 20 | +import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillConfigDO; | |
| 18 | 21 | import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillProductDO; |
| 19 | 22 | import cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity.SeckillActivityMapper; |
| 20 | 23 | import cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity.SeckillProductMapper; |
| 21 | -import cn.iocoder.yudao.module.promotion.util.PromotionUtils; | |
| 22 | 24 | import org.springframework.stereotype.Service; |
| 23 | 25 | import org.springframework.transaction.annotation.Transactional; |
| 24 | 26 | import org.springframework.validation.annotation.Validated; |
| ... | ... | @@ -60,17 +62,18 @@ public class SeckillActivityServiceImpl implements SeckillActivityService { |
| 60 | 62 | @Override |
| 61 | 63 | @Transactional(rollbackFor = Exception.class) |
| 62 | 64 | public Long createSeckillActivity(SeckillActivityCreateReqVO createReqVO) { |
| 63 | - // 校验商品秒杀时段是否冲突 | |
| 65 | + // 1.1 校验商品秒杀时段是否冲突 | |
| 64 | 66 | validateProductConflict(createReqVO.getConfigIds(), createReqVO.getSpuId(), null); |
| 65 | - // 校验商品是否存在 | |
| 67 | + // 1.2 校验商品是否存在 | |
| 66 | 68 | validateProductExists(createReqVO.getSpuId(), createReqVO.getProducts()); |
| 67 | 69 | |
| 68 | - // 插入秒杀活动 | |
| 70 | + // 2.1 插入秒杀活动 | |
| 69 | 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 | 75 | seckillActivityMapper.insert(activity); |
| 73 | - // 插入商品 | |
| 76 | + // 2.2 插入商品 | |
| 74 | 77 | List<SeckillProductDO> products = SeckillActivityConvert.INSTANCE.convertList(createReqVO.getProducts(), activity); |
| 75 | 78 | seckillProductMapper.insertBatch(products); |
| 76 | 79 | return activity.getId(); |
| ... | ... | @@ -128,22 +131,24 @@ public class SeckillActivityServiceImpl implements SeckillActivityService { |
| 128 | 131 | @Override |
| 129 | 132 | @Transactional(rollbackFor = Exception.class) |
| 130 | 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 | 137 | throw exception(SECKILL_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED); |
| 135 | 138 | } |
| 136 | - // 校验商品是否冲突 | |
| 139 | + // 1.2 校验商品是否冲突 | |
| 137 | 140 | validateProductConflict(updateReqVO.getConfigIds(), updateReqVO.getSpuId(), updateReqVO.getId()); |
| 138 | - // 校验商品是否存在 | |
| 141 | + // 1.3 校验商品是否存在 | |
| 139 | 142 | validateProductExists(updateReqVO.getSpuId(), updateReqVO.getProducts()); |
| 140 | 143 | |
| 141 | - // 更新活动 | |
| 144 | + // 2.1 更新活动 | |
| 142 | 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 | 150 | seckillActivityMapper.updateById(updateObj); |
| 146 | - // 更新商品 | |
| 151 | + // 2.2 更新商品 | |
| 147 | 152 | updateSeckillProduct(updateObj, updateReqVO.getProducts()); |
| 148 | 153 | } |
| 149 | 154 | |
| ... | ... | @@ -151,7 +156,7 @@ public class SeckillActivityServiceImpl implements SeckillActivityService { |
| 151 | 156 | @Transactional(rollbackFor = Exception.class) |
| 152 | 157 | public void updateSeckillStock(Long id, Long skuId, Integer count) { |
| 153 | 158 | // 1.1 校验活动库存是否充足 |
| 154 | - SeckillActivityDO seckillActivity = getSeckillActivity(id); | |
| 159 | + SeckillActivityDO seckillActivity = validateSeckillActivityExists(id); | |
| 155 | 160 | if (count > seckillActivity.getTotalStock()) { |
| 156 | 161 | throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL); |
| 157 | 162 | } |
| ... | ... | @@ -243,7 +248,7 @@ public class SeckillActivityServiceImpl implements SeckillActivityService { |
| 243 | 248 | |
| 244 | 249 | @Override |
| 245 | 250 | public SeckillActivityDO getSeckillActivity(Long id) { |
| 246 | - return validateSeckillActivityExists(id); | |
| 251 | + return seckillActivityMapper.selectById(id); | |
| 247 | 252 | } |
| 248 | 253 | |
| 249 | 254 | @Override |
| ... | ... | @@ -262,12 +267,6 @@ public class SeckillActivityServiceImpl implements SeckillActivityService { |
| 262 | 267 | } |
| 263 | 268 | |
| 264 | 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 | 270 | public List<SeckillActivityDO> getSeckillActivityListByConfigIdAndStatus(Long configId, Integer status) { |
| 272 | 271 | return filterList(seckillActivityMapper.selectList(SeckillActivityDO::getStatus, status), |
| 273 | 272 | item -> anyMatch(item.getConfigIds(), id -> ObjectUtil.equal(id, configId)) // 校验时段 |
| ... | ... | @@ -281,16 +280,46 @@ public class SeckillActivityServiceImpl implements SeckillActivityService { |
| 281 | 280 | |
| 282 | 281 | @Override |
| 283 | 282 | public List<SeckillProductDO> getSeckillActivityProductList(Long id, Collection<Long> skuIds) { |
| 284 | - // 1、校验秒杀活动是否存在 | |
| 285 | - validateSeckillActivityExists(id); | |
| 283 | + | |
| 286 | 284 | // 2、校验活动商品是否存在 |
| 287 | 285 | List<SeckillProductDO> productList = filterList(seckillProductMapper.selectListByActivityId(id), |
| 288 | 286 | item -> skuIds.contains(item.getSkuId())); |
| 289 | 287 | if (CollectionUtil.isEmpty(productList)) { |
| 290 | 288 | throw exception(SKU_NOT_EXISTS); |
| 291 | 289 | } |
| 292 | - | |
| 293 | 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 | 54 | */ |
| 55 | 55 | List<SeckillConfigDO> getSeckillConfigList(); |
| 56 | 56 | |
| 57 | - | |
| 58 | 57 | /** |
| 59 | 58 | * 校验秒杀时段是否存在 |
| 60 | 59 | * |
| ... | ... | @@ -87,11 +86,12 @@ public interface SeckillConfigService { |
| 87 | 86 | void updateSeckillConfigStatus(Long id, Integer status); |
| 88 | 87 | |
| 89 | 88 | /** |
| 90 | - * 获取当前日期时间处于的秒杀时段且状态为 status | |
| 89 | + * 获得当前的秒杀时段 | |
| 90 | + * | |
| 91 | + * 要求必须处于开启状态、且在当前时间段内 | |
| 91 | 92 | * |
| 92 | - * @param status 状态 | |
| 93 | 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 | 17 | import javax.annotation.Resource; |
| 18 | 18 | import java.time.LocalTime; |
| 19 | 19 | import java.util.Collection; |
| 20 | +import java.util.Comparator; | |
| 20 | 21 | import java.util.List; |
| 21 | 22 | |
| 22 | 23 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| ... | ... | @@ -70,9 +71,9 @@ public class SeckillConfigServiceImpl implements SeckillConfigService { |
| 70 | 71 | } |
| 71 | 72 | |
| 72 | 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 | 79 | @Override |
| ... | ... | @@ -151,7 +152,9 @@ public class SeckillConfigServiceImpl implements SeckillConfigService { |
| 151 | 152 | |
| 152 | 153 | @Override |
| 153 | 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
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 | 55 | ErrorCode PRICE_CALCULATE_PAY_PRICE_ILLEGAL = new ErrorCode(1_011_003_000, "支付价格计算异常,原因:价格小于等于 0"); |
| 56 | 56 | ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND = new ErrorCode(1_011_003_002, "计算快递运费异常,找不到对应的运费模板"); |
| 57 | 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 | 60 | // ========== 物流 Express 模块 1-011-004-000 ========== |
| 60 | 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 | 1 | package cn.iocoder.yudao.module.trade.controller.app.order.vo; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.util.ObjUtil; | |
| 3 | 4 | import cn.iocoder.yudao.framework.common.validation.InEnum; |
| 4 | 5 | import cn.iocoder.yudao.framework.common.validation.Mobile; |
| 5 | 6 | import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum; |
| ... | ... | @@ -67,7 +68,7 @@ public class AppTradeOrderSettlementReqVO { |
| 67 | 68 | @JsonIgnore |
| 68 | 69 | public boolean isValidActivityItems() { |
| 69 | 70 | // 校验是否是活动订单 |
| 70 | - if (seckillActivityId == null && combinationActivityId == null && combinationHeadId == null) { | |
| 71 | + if (ObjUtil.isAllEmpty(seckillActivityId, combinationActivityId, combinationHeadId)) { | |
| 71 | 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 | 14 | public class AppTradeOrderSettlementRespVO { |
| 15 | 15 | |
| 16 | 16 | @Schema(description = "交易类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") // 对应 TradeOrderTypeEnum 枚举 |
| 17 | - private Integer type = 1; // TODO 芋艿:改成计算 | |
| 17 | + private Integer type; | |
| 18 | 18 | |
| 19 | 19 | @Schema(description = "购物项数组", requiredMode = Schema.RequiredMode.REQUIRED) |
| 20 | 20 | private List<Item> items; |
| ... | ... | @@ -75,6 +75,9 @@ public class AppTradeOrderSettlementRespVO { |
| 75 | 75 | @Schema(description = "商品原价(总),单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "500") |
| 76 | 76 | private Integer totalPrice; |
| 77 | 77 | |
| 78 | + @Schema(description = "订单优惠(总),单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "66") | |
| 79 | + private Integer discountPrice; | |
| 80 | + | |
| 78 | 81 | @Schema(description = "运费金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "50") |
| 79 | 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 | 211 | .setCouponId(settlementReqVO.getCouponId()).setPointStatus(settlementReqVO.getPointStatus()) |
| 212 | 212 | .setDeliveryType(settlementReqVO.getDeliveryType()).setAddressId(settlementReqVO.getAddressId()) |
| 213 | 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 | 217 | Map<Long, CartDO> cartMap = convertMap(cartList, CartDO::getId); |
| 217 | 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
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderItemMapper.java
| 1 | 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 | 5 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 4 | 6 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| 5 | 7 | import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; |
| 8 | +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | |
| 6 | 9 | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| 7 | 10 | import org.apache.ibatis.annotations.Mapper; |
| 11 | +import org.apache.ibatis.annotations.Param; | |
| 8 | 12 | |
| 9 | 13 | import java.util.Collection; |
| 10 | 14 | import java.util.List; |
| 15 | +import java.util.Map; | |
| 16 | +import java.util.Set; | |
| 11 | 17 | |
| 12 | 18 | @Mapper |
| 13 | 19 | public interface TradeOrderItemMapper extends BaseMapperX<TradeOrderItemDO> { |
| ... | ... | @@ -38,4 +44,13 @@ public interface TradeOrderItemMapper extends BaseMapperX<TradeOrderItemDO> { |
| 38 | 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<TradeOrderDO> { |
| 86 | 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 | 95 | default TradeOrderSummaryRespDTO selectSummaryByPayTimeBetween(LocalDateTime beginTime, LocalDateTime endTime) { |
| 90 | 96 | return BeanUtil.copyProperties(CollUtil.get(selectMaps(MPJWrappers.<TradeOrderDO>lambdaJoin() |
| 91 | 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 | 84 | */ |
| 85 | 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 | 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 | 14 | import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; |
| 15 | 15 | import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper; |
| 16 | 16 | import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper; |
| 17 | +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum; | |
| 17 | 18 | import cn.iocoder.yudao.module.trade.framework.delivery.core.client.ExpressClientFactory; |
| 18 | 19 | import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackQueryReqDTO; |
| 19 | 20 | import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackRespDTO; |
| ... | ... | @@ -122,6 +123,18 @@ public class TradeOrderQueryServiceImpl implements TradeOrderQueryService { |
| 122 | 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 | 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 | 14 | * @author HUIHUI |
| 15 | 15 | */ |
| 16 | 16 | @Component |
| 17 | -public class TradeBargainHandler extends TradeOrderDefaultHandler { | |
| 17 | +public class TradeBargainHandler implements TradeOrderHandler { | |
| 18 | 18 | |
| 19 | 19 | @Resource |
| 20 | 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 | 17 | * @author HUIHUI |
| 18 | 18 | */ |
| 19 | 19 | @Component |
| 20 | -public class TradeCombinationHandler extends TradeOrderDefaultHandler { | |
| 20 | +public class TradeCombinationHandler implements TradeOrderHandler { | |
| 21 | 21 | |
| 22 | 22 | @Resource |
| 23 | 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 | 17 | * |
| 18 | 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 | 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 | 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 | 14 | * @author HUIHUI |
| 15 | 15 | */ |
| 16 | 16 | @Component |
| 17 | -public class TradeSeckillHandler extends TradeOrderDefaultHandler { | |
| 17 | +public class TradeSeckillHandler implements TradeOrderHandler { | |
| 18 | 18 | |
| 19 | 19 | @Resource |
| 20 | 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 | 99 | */ |
| 100 | 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 | 104 | * = {@link #totalPrice} |
| 115 | 105 | * - {@link #couponPrice} |
| 116 | 106 | * - {@link #pointPrice} |
| ... | ... | @@ -187,15 +177,8 @@ public class TradePriceCalculateRespBO { |
| 187 | 177 | */ |
| 188 | 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 | 182 | * = {@link #price} * {@link #count} |
| 200 | 183 | * - {@link #couponPrice} |
| 201 | 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 | 105 | if (!item.getSelected()) { |
| 106 | 106 | return; |
| 107 | 107 | } |
| 108 | - // TODO puhui: 需要在这里计算活动的价格 | |
| 109 | - // ========== 一、活动情况 ========== | |
| 110 | - // ========== 二、正常情况 ========== | |
| 111 | 108 | price.setTotalPrice(price.getTotalPrice() + item.getPrice() * item.getCount()); |
| 112 | 109 | price.setDiscountPrice(price.getDiscountPrice() + item.getDiscountPrice()); |
| 113 | 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 | 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 | 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 | 9 | import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; |
| 6 | 10 | import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; |
| 7 | 11 | import org.springframework.core.annotation.Order; |
| 8 | 12 | import org.springframework.stereotype.Component; |
| 9 | 13 | |
| 10 | 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 | 21 | * 秒杀活动的 {@link TradePriceCalculator} 实现类 |
| 19 | 22 | * |
| ... | ... | @@ -24,22 +27,45 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. |
| 24 | 27 | public class TradeSeckillActivityPriceCalculator implements TradePriceCalculator { |
| 25 | 28 | |
| 26 | 29 | @Resource |
| 27 | - private SeckillActivityApi activityApi; | |
| 30 | + private SeckillActivityApi seckillActivityApi; | |
| 31 | + | |
| 32 | + @Resource | |
| 33 | + private TradeOrderQueryService tradeOrderQueryService; | |
| 28 | 34 | |
| 29 | 35 | @Override |
| 30 | 36 | public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) { |
| 31 | - // 1、判断订单类型和是否具有秒杀活动编号 | |
| 37 | + // 1. 判断订单类型和是否具有秒杀活动编号 | |
| 32 | 38 | if (param.getSeckillActivityId() == null) { |
| 33 | 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 | } | ... | ... |