优化秒杀活动的管理后台代码

This commit is contained in:
YunaiV 2023-08-12 16:05:47 +08:00
parent be2ee905df
commit 6fdd4da0b3
20 changed files with 183 additions and 209 deletions

View File

@ -21,4 +21,11 @@ public interface ProductSpuApi {
*/
List<ProductSpuRespDTO> getSpuList(Collection<Long> ids);
/**
* 获得 SPU
*
* @return SPU
*/
ProductSpuRespDTO getSpu(Long id);
}

View File

@ -5,6 +5,8 @@ import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.product.convert.spu.ProductSpuConvert;
import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
import cn.iocoder.yudao.module.product.dal.mysql.spu.ProductSpuMapper;
import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@ -24,15 +26,19 @@ import java.util.List;
public class ProductSpuApiImpl implements ProductSpuApi {
@Resource
private ProductSpuMapper productSpuMapper;
private ProductSpuService spuService;
@Override
public List<ProductSpuRespDTO> getSpuList(Collection<Long> spuIds) {
if (CollectionUtil.isEmpty(spuIds)) {
public List<ProductSpuRespDTO> getSpuList(Collection<Long> ids) {
if (CollectionUtil.isEmpty(ids)) {
return Collections.emptyList();
}
List<ProductSpuDO> productSpuDOList = productSpuMapper.selectBatchIds(spuIds);
return ProductSpuConvert.INSTANCE.convertList2(productSpuDOList);
return ProductSpuConvert.INSTANCE.convertList2(spuService.getSpuList(ids));
}
@Override
public ProductSpuRespDTO getSpu(Long id) {
return ProductSpuConvert.INSTANCE.convert02(spuService.getSpu(id));
}
}

View File

@ -64,6 +64,8 @@ public interface ProductSpuConvert {
ProductSpuDetailRespVO convert03(ProductSpuDO spu);
ProductSpuRespDTO convert02(ProductSpuDO bean);
// ========== 用户 App 相关 ==========
PageResult<AppProductSpuPageRespVO> convertPageForGetSpuPage(PageResult<ProductSpuDO> page);

View File

@ -50,14 +50,11 @@ public interface ErrorCodeConstants {
ErrorCode SECKILL_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED = new ErrorCode(1013008003, "秒杀活动已关闭,不能修改");
ErrorCode SECKILL_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END = new ErrorCode(1013008004, "秒杀活动未关闭或未结束,不能删除");
ErrorCode SECKILL_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1013008005, "秒杀活动已关闭,不能重复关闭");
ErrorCode SECKILL_ACTIVITY_CLOSE_FAIL_STATUS_END = new ErrorCode(1013008006, "秒杀活动已结束,不能关闭");
// ========== 秒杀时段 1013009000 ==========
ErrorCode SECKILL_TIME_NOT_EXISTS = new ErrorCode(1013009000, "秒杀时段不存在");
ErrorCode SECKILL_TIME_CONFLICTS = new ErrorCode(1013009001, "秒杀时段冲突");
ErrorCode SECKILL_TIME_EQUAL = new ErrorCode(1013009002, "秒杀时段开始时间和结束时间不能相等");
ErrorCode SECKILL_START_TIME_BEFORE_END_TIME = new ErrorCode(1013009003, "秒杀时段开始时间不能在结束时间之后");
ErrorCode SECKILL_TIME_DISABLE = new ErrorCode(1013009004, "秒杀时段已关闭");
ErrorCode SECKILL_CONFIG_NOT_EXISTS = new ErrorCode(1013009000, "秒杀时段不存在");
ErrorCode SECKILL_CONFIG_TIME_CONFLICTS = new ErrorCode(1013009001, "秒杀时段冲突");
ErrorCode SECKILL_CONFIG_DISABLE = new ErrorCode(1013009004, "秒杀时段已关闭");
// ========== 拼团活动 1013010000 ==========
ErrorCode COMBINATION_ACTIVITY_NOT_EXISTS = new ErrorCode(1013010000, "拼团活动不存在");

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
@ -19,7 +20,6 @@ import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
@ -33,7 +33,7 @@ public class SeckillActivityController {
@Resource
private SeckillActivityService seckillActivityService;
@Resource
private ProductSpuApi spuApi;
private ProductSpuApi productSpuApi;
@PostMapping("/create")
@Operation(summary = "创建秒杀活动")
@ -73,21 +73,27 @@ public class SeckillActivityController {
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('promotion:seckill-activity:query')")
public CommonResult<SeckillActivityDetailRespVO> getSeckillActivity(@RequestParam("id") Long id) {
SeckillActivityDO seckillActivity = seckillActivityService.getSeckillActivity(id);
List<SeckillProductDO> seckillProducts = seckillActivityService.getSeckillProductListByActivityId(id);
return success(SeckillActivityConvert.INSTANCE.convert(seckillActivity, seckillProducts));
SeckillActivityDO activity = seckillActivityService.getSeckillActivity(id);
List<SeckillProductDO> products = seckillActivityService.getSeckillProductListByActivityId(id);
return success(SeckillActivityConvert.INSTANCE.convert(activity, products));
}
@GetMapping("/page")
@Operation(summary = "获得秒杀活动分页")
@PreAuthorize("@ss.hasPermission('promotion:seckill-activity:query')")
public CommonResult<PageResult<SeckillActivityRespVO>> getSeckillActivityPage(@Valid SeckillActivityPageReqVO pageVO) {
// 查询活动列表
PageResult<SeckillActivityDO> pageResult = seckillActivityService.getSeckillActivityPage(pageVO);
Set<Long> aIds = convertSet(pageResult.getList(), SeckillActivityDO::getId);
List<SeckillProductDO> seckillProducts = seckillActivityService.getSeckillProductListByActivityId(aIds);
Set<Long> spuIds = convertSet(pageResult.getList(), SeckillActivityDO::getSpuId);
List<ProductSpuRespDTO> spuList = spuApi.getSpuList(spuIds);
return success(SeckillActivityConvert.INSTANCE.convertPage(pageResult, seckillProducts, spuList));
if (CollUtil.isEmpty(pageResult.getList())) {
return success(PageResult.empty(pageResult.getTotal()));
}
// 拼接数据
List<SeckillProductDO> products = seckillActivityService.getSeckillProductListByActivityId(
convertSet(pageResult.getList(), SeckillActivityDO::getId));
List<ProductSpuRespDTO> spuList = productSpuApi.getSpuList(
convertSet(pageResult.getList(), SeckillActivityDO::getSpuId));
return success(SeckillActivityConvert.INSTANCE.convertPage(pageResult, products, spuList));
}
}

View File

@ -92,4 +92,5 @@ public class SeckillConfigController {
PageResult<SeckillConfigDO> pageResult = seckillConfigService.getSeckillConfigPage(pageVO);
return success(SeckillConfigConvert.INSTANCE.convertPage(pageResult));
}
}

View File

@ -20,7 +20,7 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
@Data
public class SeckillActivityBaseVO {
@Schema(description = "秒杀活动商品id", requiredMode = Schema.RequiredMode.REQUIRED, example = "[121,1212]")
@Schema(description = "秒杀活动商品 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "[121,1212]")
@NotNull(message = "秒杀活动商品不能为空")
private Long spuId;

View File

@ -1,7 +1,7 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product.SeckillProductCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product.SeckillProductBaseVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -16,6 +16,6 @@ import java.util.List;
public class SeckillActivityCreateReqVO extends SeckillActivityBaseVO {
@Schema(description = "秒杀商品", requiredMode = Schema.RequiredMode.REQUIRED)
private List<SeckillProductCreateReqVO> products;
private List<SeckillProductBaseVO> products;
}

View File

@ -21,28 +21,28 @@ public class SeckillActivityRespVO extends SeckillActivityBaseVO {
@Schema(description = "商品主图", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xx.png")
private String picUrl;
@Schema(description = "秒杀活动id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@Schema(description = "秒杀活动 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id;
@Schema(description = "秒杀商品", requiredMode = Schema.RequiredMode.REQUIRED)
private List<SeckillProductRespVO> products;
@Schema(description = "活动状态 开启0 禁用1", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@Schema(description = "活动状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
private Integer status;
@Schema(description = "订单实付金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "22354")
private Integer totalPrice;
@Schema(description = "秒杀库存", example = "10")
@Schema(description = "秒杀库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Integer stock;
@Schema(description = "秒杀总库存", example = "20")
@Schema(description = "秒杀总库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
private Integer totalStock;
@Schema(description = "新增订单数", example = "20")
@Schema(description = "新增订单数", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
private Integer orderCount;
@Schema(description = "付款人数", example = "20")
@Schema(description = "付款人数", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
private Integer userCount;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)

View File

@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product.SeckillProductUpdateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product.SeckillProductBaseVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -18,6 +18,6 @@ public class SeckillActivityUpdateReqVO extends SeckillActivityBaseVO {
private Long id;
@Schema(description = "秒杀商品", requiredMode = Schema.RequiredMode.REQUIRED)
private List<SeckillProductUpdateReqVO> products;
private List<SeckillProductBaseVO> products;
}

View File

@ -22,7 +22,7 @@ public class SeckillProductBaseVO {
@NotNull(message = "秒杀金额,单位:分不能为空")
private Integer seckillPrice;
@Schema(description = "秒杀库存", requiredMode = Schema.RequiredMode.REQUIRED)
@Schema(description = "秒杀库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
@NotNull(message = "秒杀库存不能为空")
private Integer stock;

View File

@ -1,13 +0,0 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - 秒杀参与商品创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SeckillProductCreateReqVO extends SeckillProductBaseVO {
}

View File

@ -1,14 +0,0 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - 秒杀参与商品更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SeckillProductUpdateReqVO extends SeckillProductBaseVO {
}

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityDetailRespVO;
@ -19,6 +20,8 @@ import org.mapstruct.factory.Mappers;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
/**
* 秒杀活动 Convert
*
@ -37,43 +40,43 @@ public interface SeckillActivityConvert {
List<SeckillActivityRespVO> convertList(List<SeckillActivityDO> list);
PageResult<SeckillActivityRespVO> convertPage(PageResult<SeckillActivityDO> page);
default PageResult<SeckillActivityRespVO> convertPage(PageResult<SeckillActivityDO> page, List<SeckillProductDO> seckillProducts, List<ProductSpuRespDTO> spuList) {
Map<Long, ProductSpuRespDTO> spuMap = CollectionUtils.convertMap(spuList, ProductSpuRespDTO::getId, c -> c);
default PageResult<SeckillActivityRespVO> convertPage(PageResult<SeckillActivityDO> page,
List<SeckillProductDO> seckillProducts,
List<ProductSpuRespDTO> spuList) {
PageResult<SeckillActivityRespVO> pageResult = convertPage(page);
// 拼接商品
Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId);
pageResult.getList().forEach(item -> {
item.setSpuName(spuMap.get(item.getSpuId()).getName());
item.setPicUrl(spuMap.get(item.getSpuId()).getPicUrl());
item.setProducts(convertList2(seckillProducts));
MapUtils.findAndThen(spuMap, item.getSpuId(),
spu -> item.setSpuName(spu.getName()).setPicUrl(spu.getPicUrl()));
});
return pageResult;
}
PageResult<SeckillActivityRespVO> convertPage(PageResult<SeckillActivityDO> page);
SeckillActivityDetailRespVO convert1(SeckillActivityDO seckillActivity);
default SeckillActivityDetailRespVO convert(SeckillActivityDO seckillActivity, List<SeckillProductDO> seckillProducts) {
return convert1(seckillActivity).setProducts(convertList2(seckillProducts));
default SeckillActivityDetailRespVO convert(SeckillActivityDO activity,
List<SeckillProductDO> products) {
return convert1(activity).setProducts(convertList2(products));
}
// TODO @puhui999参数改成 activityproduct 会不会干净一点哈
@Mappings({
@Mapping(target = "id", ignore = true),
@Mapping(target = "activityId", source = "activityDO.id"),
@Mapping(target = "configIds", source = "activityDO.configIds"),
@Mapping(target = "spuId", source = "activityDO.spuId"),
@Mapping(target = "skuId", source = "vo.skuId"),
@Mapping(target = "seckillPrice", source = "vo.seckillPrice"),
@Mapping(target = "stock", source = "vo.stock"),
@Mapping(target = "activityStartTime", source = "activityDO.startTime"),
@Mapping(target = "activityEndTime", source = "activityDO.endTime")
})
SeckillProductDO convert(SeckillActivityDO activityDO, SeckillProductBaseVO vo);
List<SeckillProductRespVO> convertList2(List<SeckillProductDO> productDOs);
default List<SeckillProductDO> convertList(List<? extends SeckillProductBaseVO> products, SeckillActivityDO activityDO) {
return CollectionUtils.convertList(products, item -> convert(activityDO, item).setActivityStatus(activityDO.getStatus()));
}
List<SeckillProductRespVO> convertList2(List<SeckillProductDO> productDOs);
@Mappings({
@Mapping(target = "id", ignore = true),
@Mapping(target = "activityId", source = "activity.id"),
@Mapping(target = "configIds", source = "activity.configIds"),
@Mapping(target = "spuId", source = "activity.spuId"),
@Mapping(target = "skuId", source = "product.skuId"),
@Mapping(target = "seckillPrice", source = "product.seckillPrice"),
@Mapping(target = "stock", source = "product.stock"),
@Mapping(target = "activityStartTime", source = "activity.startTime"),
@Mapping(target = "activityEndTime", source = "activity.endTime")
})
SeckillProductDO convert(SeckillActivityDO activity, SeckillProductBaseVO product);
}

View File

@ -1,9 +1,7 @@
package cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillProductDO;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
@ -25,15 +23,4 @@ public interface SeckillProductMapper extends BaseMapperX<SeckillProductDO> {
return selectList(SeckillProductDO::getActivityId, ids);
}
default List<SeckillProductDO> selectListBySkuIds(Collection<Long> skuIds) {
return selectList(SeckillProductDO::getSkuId, skuIds);
}
default void updateTimeIdsByActivityId(Long id, List<Long> timeIds) {
new LambdaUpdateChainWrapper<>(this)
.set(SeckillProductDO::getConfigIds, CollUtil.join(timeIds, ","))
.eq(SeckillProductDO::getActivityId, id)
.update();
}
}

View File

@ -55,14 +55,6 @@ public interface SeckillActivityService {
*/
SeckillActivityDO getSeckillActivity(Long id);
/**
* 获得秒杀活动列表
*
* @param ids 编号
* @return 秒杀活动列表
*/
List<SeckillActivityDO> getSeckillActivityList(Collection<Long> ids);
/**
* 获得秒杀活动分页
*
@ -74,17 +66,17 @@ public interface SeckillActivityService {
/**
* 通过活动编号获取活动商品
*
* @param id 活动编号
* @param activityId 活动编号
* @return 活动商品列表
*/
List<SeckillProductDO> getSeckillProductListByActivityId(Long id);
List<SeckillProductDO> getSeckillProductListByActivityId(Long activityId);
/**
* 通过活动编号获取活动商品
*
* @param ids 活动编号
* @param activityIds 活动编号
* @return 活动商品列表
*/
List<SeckillProductDO> getSeckillProductListByActivityId(Collection<Long> ids);
List<SeckillProductDO> getSeckillProductListByActivityId(Collection<Long> activityIds);
}

View File

@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.promotion.service.seckill.seckillactivity;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
@ -11,8 +10,7 @@ import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityUpdateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product.SeckillProductCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product.SeckillProductUpdateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product.SeckillProductBaseVO;
import cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity.SeckillActivityConvert;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillActivityDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillProductDO;
@ -27,7 +25,7 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.Map;
import static cn.hutool.core.collection.CollUtil.isNotEmpty;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@ -35,7 +33,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_EXISTS;
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.promotion.util.PromotionUtils.validateProductSkuAllExists;
import static java.util.Collections.singletonList;
/**
* 秒杀活动 Service 实现类
@ -60,19 +58,15 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
@Override
@Transactional(rollbackFor = Exception.class)
public Long createSeckillActivity(SeckillActivityCreateReqVO createReqVO) {
// 校验商品秒秒杀时段是否冲突
validateProductSpuSeckillConflict(createReqVO.getConfigIds(), createReqVO.getSpuId(), null);
// 获取所选 spu 下的所有 sku
List<ProductSkuRespDTO> skus = productSkuApi.getSkuListBySpuId(CollUtil.newArrayList(createReqVO.getSpuId()));
// 校验商品 sku 是否存在
if (skus.size() != createReqVO.getProducts().size()) {
throw exception(SKU_NOT_EXISTS);
}
// 校验商品秒杀时段是否冲突
validateProductConflict(createReqVO.getConfigIds(), createReqVO.getSpuId(), null);
// 校验商品是否存在
validateProductExists(createReqVO.getSpuId(), createReqVO.getProducts());
// 插入秒杀活动
SeckillActivityDO activity = SeckillActivityConvert.INSTANCE.convert(createReqVO)
.setStatus(PromotionUtils.calculateActivityStatus(createReqVO.getEndTime()))
.setTotalStock(getSumValue(createReqVO.getProducts(), SeckillProductCreateReqVO::getStock, Integer::sum));
.setTotalStock(getSumValue(createReqVO.getProducts(), SeckillProductBaseVO::getStock, Integer::sum));
seckillActivityMapper.insert(activity);
// 插入商品
List<SeckillProductDO> products = SeckillActivityConvert.INSTANCE.convertList(createReqVO.getProducts(), activity);
@ -80,35 +74,62 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
return activity.getId();
}
private void validateProductSpuSeckillConflict(List<Long> configIds, Long spuId, Long activityId) {
// 校验秒杀时段是否存在
/**
* 校验秒杀商品参与的活动是否存在冲突
*
* 1. 校验秒杀时段是否存在
* 2. 校验商品 spu 是否存在
* 3. 秒杀商品是否参加其它活动
*
* @param configIds 秒杀时段数组
* @param spuId 商品 SPU 编号
* @param activityId 秒杀活动编号
*/
private void validateProductConflict(List<Long> configIds, Long spuId, Long activityId) {
// 1. 校验秒杀时段是否存在
seckillConfigService.validateSeckillConfigExists(configIds);
// 校验商品 spu 是否存在
List<ProductSpuRespDTO> spuList = productSpuApi.getSpuList(CollUtil.newArrayList(spuId));
if (CollUtil.isEmpty(spuList)) {
throw exception(SPU_NOT_EXISTS);
// 2.1 查询所有开启的秒杀活动
List<SeckillActivityDO> activityList = seckillActivityMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus());
if (activityId != null) { // 排除自己
activityList.removeIf(item -> ObjectUtil.equal(item.getId(), activityId));
}
// 查询所有开启的秒杀活动
List<SeckillActivityDO> activityDOs = seckillActivityMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus());
if (activityId != null) {
// 更新时移除本活动
activityDOs.removeIf(item -> ObjectUtil.equal(item.getId(), activityId));
}
// 过滤出所有 spuId 有交集的活动
List<SeckillActivityDO> activityDOs1 = convertList(activityDOs, c -> c, s -> ObjectUtil.equal(s.getSpuId(), spuId));
// TODO @puhui999一个 spu参与两个活动应该没关系关键是活动时间不充能重叠
// 2.2 过滤出所有 spuId 有交集的活动判断是否存在重叠
List<SeckillActivityDO> activityDOs1 = filterList(activityList, s -> ObjectUtil.equal(s.getSpuId(), spuId));
if (isNotEmpty(activityDOs1)) {
throw exception(SECKILL_ACTIVITY_SPU_CONFLICTS);
}
List<SeckillActivityDO> activityDOs2 = convertList(activityDOs, c -> c, s -> {
// 判断秒杀时段是否有交集
return containsAny(s.getConfigIds(), configIds);
});
// 2.3 过滤出所有 configIds 有交集的活动判断是否存在重叠
List<SeckillActivityDO> activityDOs2 = filterList(activityList, s -> containsAny(s.getConfigIds(), configIds));
if (isNotEmpty(activityDOs2)) {
throw exception(SECKILL_TIME_CONFLICTS);
throw exception(SECKILL_ACTIVITY_SPU_CONFLICTS);
}
}
/**
* 校验秒杀商品是否都存在
*
* @param spuId 商品 SPU 编号
* @param products 秒杀商品
*/
private void validateProductExists(Long spuId, List<SeckillProductBaseVO> products) {
// 1. 校验商品 spu 是否存在
ProductSpuRespDTO spu = productSpuApi.getSpu(spuId);
if (spu == null) {
throw exception(SPU_NOT_EXISTS);
}
// 2. 校验商品 sku 都存在
Map<Long, ProductSkuRespDTO> skuMap = convertMap(productSkuApi.getSkuListBySpuId(singletonList(spuId)),
ProductSkuRespDTO::getId);
products.forEach(product -> {
if (!skuMap.containsKey(product.getSkuId())) {
throw exception(SKU_NOT_EXISTS);
}
});
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateSeckillActivity(SeckillActivityUpdateReqVO updateReqVO) {
@ -118,29 +139,26 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
throw exception(SECKILL_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED);
}
// 校验商品是否冲突
validateProductSpuSeckillConflict(updateReqVO.getConfigIds(), updateReqVO.getSpuId(), updateReqVO.getId());
// 获取所选 spu下的所有 sku
List<ProductSkuRespDTO> skus = productSkuApi.getSkuListBySpuId(CollUtil.newArrayList(updateReqVO.getSpuId()));
// 校验商品 sku 是否存在
validateProductSkuAllExists(skus, updateReqVO.getProducts(), SeckillProductUpdateReqVO::getSkuId);
validateProductConflict(updateReqVO.getConfigIds(), updateReqVO.getSpuId(), updateReqVO.getId());
// 校验商品是否存在
validateProductExists(updateReqVO.getSpuId(), updateReqVO.getProducts());
// 更新活动
SeckillActivityDO updateObj = SeckillActivityConvert.INSTANCE.convert(updateReqVO)
.setStatus(PromotionUtils.calculateActivityStatus(updateReqVO.getEndTime()))
.setTotalStock(getSumValue(updateReqVO.getProducts(), SeckillProductUpdateReqVO::getStock, Integer::sum));
.setTotalStock(getSumValue(updateReqVO.getProducts(), SeckillProductBaseVO::getStock, Integer::sum));
seckillActivityMapper.updateById(updateObj);
// 更新商品
updateSeckillProduct(updateObj, updateReqVO.getProducts());
}
/**
* 更新秒杀商品
*
* @param activity 秒杀活动
* @param products 该活动的最新商品配置
*/
private void updateSeckillProduct(SeckillActivityDO activity, List<SeckillProductUpdateReqVO> products) {
private void updateSeckillProduct(SeckillActivityDO activity, List<SeckillProductBaseVO> products) {
// 第一步对比新老数据获得添加修改删除的列表
List<SeckillProductDO> newList = SeckillActivityConvert.INSTANCE.convertList(products, activity);
List<SeckillProductDO> oldList = seckillProductMapper.selectListByActivityId(activity.getId());
@ -159,7 +177,6 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
if (isNotEmpty(diffList.get(1))) {
seckillProductMapper.updateBatch(diffList.get(1));
}
// delete
if (isNotEmpty(diffList.get(2))) {
seckillProductMapper.deleteBatchIds(convertList(diffList.get(2), SeckillProductDO::getId));
}
@ -167,7 +184,6 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
@Override
public void closeSeckillActivity(Long id) {
// TODO 待验证没使用过
// 校验存在
SeckillActivityDO activity = validateSeckillActivityExists(id);
if (CommonStatusEnum.DISABLE.getStatus().equals(activity.getStatus())) {
@ -191,9 +207,8 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
// 删除活动
seckillActivityMapper.deleteById(id);
// 删除活动商品
List<SeckillProductDO> productDOs = seckillProductMapper.selectListByActivityId(id);
Set<Long> convertSet = convertSet(productDOs, SeckillProductDO::getSkuId);
seckillProductMapper.deleteBatchIds(convertSet);
List<SeckillProductDO> products = seckillProductMapper.selectListByActivityId(id);
seckillProductMapper.deleteBatchIds(convertSet(products, SeckillProductDO::getId));
}
private SeckillActivityDO validateSeckillActivityExists(Long id) {
@ -209,24 +224,19 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
return validateSeckillActivityExists(id);
}
@Override
public List<SeckillActivityDO> getSeckillActivityList(Collection<Long> ids) {
return seckillActivityMapper.selectBatchIds(ids);
}
@Override
public PageResult<SeckillActivityDO> getSeckillActivityPage(SeckillActivityPageReqVO pageReqVO) {
return seckillActivityMapper.selectPage(pageReqVO);
}
@Override
public List<SeckillProductDO> getSeckillProductListByActivityId(Long id) {
return seckillProductMapper.selectListByActivityId(id);
public List<SeckillProductDO> getSeckillProductListByActivityId(Long activityId) {
return seckillProductMapper.selectListByActivityId(activityId);
}
@Override
public List<SeckillProductDO> getSeckillProductListByActivityId(Collection<Long> ids) {
return seckillProductMapper.selectListByActivityId(ids);
public List<SeckillProductDO> getSeckillProductListByActivityId(Collection<Long> activityIds) {
return seckillProductMapper.selectListByActivityId(activityIds);
}
}

View File

@ -57,10 +57,9 @@ public interface SeckillConfigService {
/**
* 校验秒杀时段是否存在
*
* @param timeIds 秒杀时段id集合
* @param ids 秒杀时段 id 集合
*/
void validateSeckillConfigExists(Collection<Long> timeIds);
void validateSeckillConfigExists(Collection<Long> ids);
/**
* 获得秒杀时间段配置分页数据
@ -85,4 +84,5 @@ public interface SeckillConfigService {
* @param status 状态
*/
void updateSeckillConfigStatus(Long id, Integer status);
}

View File

@ -4,7 +4,6 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.config.SeckillConfigCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.config.SeckillConfigPageReqVO;
@ -13,7 +12,6 @@ import cn.iocoder.yudao.module.promotion.convert.seckill.seckillconfig.SeckillCo
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillconfig.SeckillConfigDO;
import cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillconfig.SeckillConfigMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
@ -37,7 +35,6 @@ public class SeckillConfigServiceImpl implements SeckillConfigService {
private SeckillConfigMapper seckillConfigMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public Long createSeckillConfig(SeckillConfigCreateReqVO createReqVO) {
// 校验时间段是否冲突
validateSeckillConfigConflict(createReqVO.getStartTime(), createReqVO.getEndTime(), null);
@ -50,7 +47,6 @@ public class SeckillConfigServiceImpl implements SeckillConfigService {
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateSeckillConfig(SeckillConfigUpdateReqVO updateReqVO) {
// 校验存在
validateSeckillConfigExists(updateReqVO.getId());
@ -62,7 +58,6 @@ public class SeckillConfigServiceImpl implements SeckillConfigService {
seckillConfigMapper.updateById(updateObj);
}
// TODO @puhui999: 这个要不合并到更新操作里? 不单独有个操作咧; fix: 更新状态不用那么多必须的参数更新的时候需要校验时间段
@Override
public void updateSeckillConfigStatus(Long id, Integer status) {
// 校验秒杀时段是否存在
@ -73,7 +68,6 @@ public class SeckillConfigServiceImpl implements SeckillConfigService {
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteSeckillConfig(Long id) {
// 校验存在
validateSeckillConfigExists(id);
@ -84,35 +78,31 @@ public class SeckillConfigServiceImpl implements SeckillConfigService {
private void validateSeckillConfigExists(Long id) {
if (seckillConfigMapper.selectById(id) == null) {
throw exception(SECKILL_TIME_NOT_EXISTS);
throw exception(SECKILL_CONFIG_NOT_EXISTS);
}
}
/**
* 校验时间是否存在冲突
*
* @param startTime 开始时间
* @param endTime 结束时间
* @param startTimeStr 开始时间
* @param endTimeStr 结束时间
*/
private void validateSeckillConfigConflict(String startTime, String endTime, Long seckillConfigId) {
LocalTime startTime1 = LocalTime.parse(startTime);
LocalTime endTime1 = LocalTime.parse(endTime);
// 查询出所有的时段配置
List<SeckillConfigDO> configDOs = seckillConfigMapper.selectList();
private void validateSeckillConfigConflict(String startTimeStr, String endTimeStr, Long id) {
// 1. 查询出所有的时段配置
LocalTime startTime = LocalTime.parse(startTimeStr);
LocalTime endTime = LocalTime.parse(endTimeStr);
List<SeckillConfigDO> configs = seckillConfigMapper.selectList();
// 更新时排除自己
if (seckillConfigId != null) {
configDOs.removeIf(item -> ObjectUtil.equal(item.getId(), seckillConfigId));
if (id != null) {
configs.removeIf(item -> ObjectUtil.equal(item.getId(), id));
}
// 过滤出重叠的时段 ids
boolean hasConflict = configDOs.stream().anyMatch(config -> {
LocalTime startTime2 = LocalTime.parse(config.getStartTime());
LocalTime endTime2 = LocalTime.parse(config.getEndTime());
// 判断时间是否重叠
return LocalDateTimeUtils.isOverlap(startTime1, endTime1, startTime2, endTime2);
});
// 2. 判断是否有重叠的时间
boolean hasConflict = configs.stream().anyMatch(config -> LocalDateTimeUtils.isOverlap(startTime, endTime,
LocalTime.parse(config.getStartTime()), LocalTime.parse(config.getEndTime())));
if (hasConflict) {
throw exception(SECKILL_TIME_CONFLICTS);
throw exception(SECKILL_CONFIG_TIME_CONFLICTS);
}
}
@ -128,22 +118,22 @@ public class SeckillConfigServiceImpl implements SeckillConfigService {
}
@Override
public void validateSeckillConfigExists(Collection<Long> configIds) {
if (CollUtil.isEmpty(configIds)) {
throw exception(SECKILL_TIME_NOT_EXISTS);
public void validateSeckillConfigExists(Collection<Long> ids) {
if (CollUtil.isEmpty(ids)) {
return;
}
List<SeckillConfigDO> configDOs = seckillConfigMapper.selectBatchIds(configIds);
if (CollUtil.isEmpty(configDOs)) {
throw exception(SECKILL_TIME_NOT_EXISTS);
}
// 过滤出关闭的时段
List<SeckillConfigDO> filterList = CollectionUtils.filterList(configDOs, item -> ObjectUtil.equal(item.getStatus(), CommonStatusEnum.DISABLE.getStatus()));
if (CollUtil.isNotEmpty(filterList)) {
throw exception(SECKILL_TIME_DISABLE);
}
if (configDOs.size() != configIds.size()) {
throw exception(SECKILL_TIME_NOT_EXISTS);
// 1. 如果有数量不匹配说明有不存在的则抛出 SECKILL_CONFIG_NOT_EXISTS 业务异常
List<SeckillConfigDO> configs = seckillConfigMapper.selectBatchIds(ids);
if (configs.size() != ids.size()) {
throw exception(SECKILL_CONFIG_NOT_EXISTS);
}
// 2. 如果存在关闭则抛出 SECKILL_CONFIG_DISABLE 业务异常
configs.forEach(config -> {
if (ObjectUtil.equal(config.getStatus(), CommonStatusEnum.DISABLE.getStatus())) {
throw exception(SECKILL_CONFIG_DISABLE);
}
});
}
@Override

View File

@ -19,7 +19,7 @@ import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEq
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.SECKILL_TIME_NOT_EXISTS;
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.SECKILL_CONFIG_NOT_EXISTS;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
@ -94,7 +94,7 @@ public class SeckillConfigServiceImplTest extends BaseDbUnitTest {
SeckillConfigUpdateReqVO reqVO = randomPojo(SeckillConfigUpdateReqVO.class);
// 调用, 并断言异常
assertServiceException(() -> SeckillConfigService.updateSeckillConfig(reqVO), SECKILL_TIME_NOT_EXISTS);
assertServiceException(() -> SeckillConfigService.updateSeckillConfig(reqVO), SECKILL_CONFIG_NOT_EXISTS);
}
@Test
@ -117,7 +117,7 @@ public class SeckillConfigServiceImplTest extends BaseDbUnitTest {
Long id = randomLongId();
// 调用, 并断言异常
assertServiceException(() -> SeckillConfigService.deleteSeckillConfig(id), SECKILL_TIME_NOT_EXISTS);
assertServiceException(() -> SeckillConfigService.deleteSeckillConfig(id), SECKILL_CONFIG_NOT_EXISTS);
}
@Test