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
This commit is contained in:
owen 2023-10-04 09:12:48 +08:00
commit 60ea719b46
35 changed files with 334 additions and 304 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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