Commit 3ccf8c7a5ccfd03a1b657db1a21c6cc48bf2224c

Authored by 芋道源码
Committed by Gitee
2 parents 51fdbb81 342c25c0

!1090 【代码优化】商城: 完善积分商城

Merge pull request !1090 from puhui999/develop
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java
... ... @@ -46,7 +46,10 @@ public interface ErrorCodeConstants {
46 46  
47 47 // ========== 积分商城活动 1-013-007-000 ==========
48 48 ErrorCode POINT_ACTIVITY_NOT_EXISTS = new ErrorCode(1_013_007_000, "积分商城活动不存在");
49   - ErrorCode POINT_PRODUCT_NOT_EXISTS = new ErrorCode(1_013_007_100, "积分商城商品不存在");
  49 + ErrorCode POINT_ACTIVITY_SPU_CONFLICTS = new ErrorCode(1_013_007_001, "存在商品参加了其它积分商城活动");
  50 + ErrorCode POINT_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_007_002, "积分商城活动已关闭,不能修改");
  51 + ErrorCode POINT_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END = new ErrorCode(1_013_007_003, "积分商城活动未关闭或未结束,不能删除");
  52 + ErrorCode POINT_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_007_004, "积分商城活动已关闭,不能重复关闭");
50 53  
51 54 // ========== 秒杀活动 1-013-008-000 ==========
52 55 ErrorCode SECKILL_ACTIVITY_NOT_EXISTS = new ErrorCode(1_013_008_000, "秒杀活动不存在");
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/PointActivityController.java
... ... @@ -51,6 +51,15 @@ public class PointActivityController {
51 51 return success(true);
52 52 }
53 53  
  54 + @PutMapping("/close")
  55 + @Operation(summary = "关闭积分商城活动")
  56 + @Parameter(name = "id", description = "编号", required = true)
  57 + @PreAuthorize("@ss.hasPermission('promotion:point-activity:close')")
  58 + public CommonResult<Boolean> closeSeckillActivity(@RequestParam("id") Long id) {
  59 + pointActivityService.closePointActivity(id);
  60 + return success(true);
  61 + }
  62 +
54 63 @DeleteMapping("/delete")
55 64 @Operation(summary = "删除积分商城活动")
56 65 @Parameter(name = "id", description = "编号", required = true)
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/product/PointProductSaveReqVO.java
... ... @@ -25,7 +25,7 @@ public class PointProductSaveReqVO {
25 25  
26 26 @Schema(description = "可兑换数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "3926")
27 27 @NotNull(message = "可兑换数量不能为空")
28   - private Integer maxCount;
  28 + private Integer count;
29 29  
30 30 @Schema(description = "兑换积分", requiredMode = Schema.RequiredMode.REQUIRED)
31 31 @NotNull(message = "兑换积分不能为空")
... ... @@ -35,9 +35,9 @@ public class PointProductSaveReqVO {
35 35 @NotNull(message = "兑换金额,单位:分不能为空")
36 36 private Integer price;
37 37  
38   - @Schema(description = "兑换类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
39   - @NotNull(message = "兑换类型不能为空")
40   - private Integer type;
  38 + @Schema(description = "积分商城商品库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
  39 + @NotNull(message = "积分商城商品不能为空")
  40 + private Integer stock;
41 41  
42 42 @Schema(description = "积分商城商品状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
43 43 @NotNull(message = "积分商城商品状态不能为空")
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/point/PointActivityDO.java
... ... @@ -45,4 +45,13 @@ public class PointActivityDO extends BaseDO {
45 45 */
46 46 private Integer sort;
47 47  
  48 + /**
  49 + * 积分商城活动库存(剩余库存积分兑换时扣减)
  50 + */
  51 + private Integer stock;
  52 + /**
  53 + * 积分商城活动总库存
  54 + */
  55 + private Integer totalStock;
  56 +
48 57 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/point/PointProductDO.java
... ... @@ -42,24 +42,21 @@ public class PointProductDO extends BaseDO {
42 42 */
43 43 private Long skuId;
44 44 /**
45   - * 可兑换数量
  45 + * 可兑换次数
46 46 */
47   - private Integer maxCount;
  47 + private Integer count;
48 48 /**
49   - * 兑换积分
  49 + * 所需兑换积分
50 50 */
51 51 private Integer point;
52 52 /**
53   - * 兑换金额,单位:分
  53 + * 所需兑换金额,单位:分
54 54 */
55 55 private Integer price;
56 56 /**
57   - * 兑换类型
58   - * 1. 积分
59   - * 2. 积分 + 钱
60   - * 3. 直接购买
  57 + * 积分商城商品库存
61 58 */
62   - private Integer type;
  59 + private Integer stock;
63 60 /**
64 61 * 积分商城商品状态
65 62 *
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/pointproduct/PointProductDO.java deleted
1   -package cn.iocoder.yudao.module.promotion.dal.dataobject.pointproduct;
2   -
3   -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4   -import com.baomidou.mybatisplus.annotation.KeySequence;
5   -import com.baomidou.mybatisplus.annotation.TableId;
6   -import com.baomidou.mybatisplus.annotation.TableName;
7   -import lombok.*;
8   -
9   -/**
10   - * 积分商城商品 DO
11   - *
12   - * @author HUIHUI
13   - */
14   -@TableName("promotion_point_product")
15   -@KeySequence("promotion_point_product_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
16   -@Data
17   -@EqualsAndHashCode(callSuper = true)
18   -@ToString(callSuper = true)
19   -@Builder
20   -@NoArgsConstructor
21   -@AllArgsConstructor
22   -public class PointProductDO extends BaseDO {
23   -
24   - /**
25   - * 积分商城商品编号
26   - */
27   - @TableId
28   - private Long id;
29   - /**
30   - * 积分商城活动 id
31   - */
32   - private Long activityId;
33   - /**
34   - * 商品 SPU 编号
35   - */
36   - private Long spuId;
37   - /**
38   - * 商品 SKU 编号
39   - */
40   - private Long skuId;
41   - /**
42   - * 可兑换数量
43   - */
44   - private Integer maxCount;
45   - /**
46   - * 兑换积分
47   - */
48   - private Integer point;
49   - /**
50   - * 兑换金额,单位:分
51   - */
52   - private Integer price;
53   - /**
54   - * 兑换类型
55   - */
56   - private Integer type;
57   - /**
58   - * 积分商城商品状态
59   - */
60   - private Integer activityStatus;
61   -
62   -}
63 0 \ No newline at end of file
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/point/PointProductMapper.java
... ... @@ -4,9 +4,13 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 6 import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.product.PointProductPageReqVO;
7   -import cn.iocoder.yudao.module.promotion.dal.dataobject.pointproduct.PointProductDO;
  7 +import cn.iocoder.yudao.module.promotion.dal.dataobject.point.PointProductDO;
  8 +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
8 9 import org.apache.ibatis.annotations.Mapper;
9 10  
  11 +import java.util.Collection;
  12 +import java.util.List;
  13 +
10 14 /**
11 15 * 积分商城商品 Mapper
12 16 *
... ... @@ -20,13 +24,24 @@ public interface PointProductMapper extends BaseMapperX&lt;PointProductDO&gt; {
20 24 .eqIfPresent(PointProductDO::getActivityId, reqVO.getActivityId())
21 25 .eqIfPresent(PointProductDO::getSpuId, reqVO.getSpuId())
22 26 .eqIfPresent(PointProductDO::getSkuId, reqVO.getSkuId())
23   - .eqIfPresent(PointProductDO::getMaxCount, reqVO.getMaxCount())
24 27 .eqIfPresent(PointProductDO::getPoint, reqVO.getPoint())
25 28 .eqIfPresent(PointProductDO::getPrice, reqVO.getPrice())
26   - .eqIfPresent(PointProductDO::getType, reqVO.getType())
27 29 .eqIfPresent(PointProductDO::getActivityStatus, reqVO.getActivityStatus())
28 30 .betweenIfPresent(PointProductDO::getCreateTime, reqVO.getCreateTime())
29 31 .orderByDesc(PointProductDO::getId));
30 32 }
31 33  
  34 + default List<PointProductDO> selectListByActivityId(Collection<Long> activityIds) {
  35 + return selectList(PointProductDO::getActivityId, activityIds);
  36 + }
  37 +
  38 + default List<PointProductDO> selectListByActivityId(Long activityId) {
  39 + return selectList(PointProductDO::getActivityId, activityId);
  40 + }
  41 +
  42 + default void updateByActivityId(PointProductDO pointProductDO) {
  43 + update(pointProductDO, new LambdaUpdateWrapper<PointProductDO>()
  44 + .eq(PointProductDO::getActivityId, pointProductDO.getActivityId()));
  45 + }
  46 +
32 47 }
33 48 \ No newline at end of file
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/point/PointActivityService.java
... ... @@ -29,6 +29,13 @@ public interface PointActivityService {
29 29 void updatePointActivity(@Valid PointActivitySaveReqVO updateReqVO);
30 30  
31 31 /**
  32 + * 关闭积分商城活动
  33 + *
  34 + * @param id 编号
  35 + */
  36 + void closePointActivity(Long id);
  37 +
  38 + /**
32 39 * 删除积分商城活动
33 40 *
34 41 * @param id 编号
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/point/PointActivityServiceImpl.java
1 1 package cn.iocoder.yudao.module.promotion.service.point;
2 2  
  3 +import cn.hutool.core.util.ObjectUtil;
  4 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
3 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
5 7 import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
... ... @@ -10,6 +12,7 @@ import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.activity.Poin
10 12 import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.activity.PointActivitySaveReqVO;
11 13 import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.product.PointProductSaveReqVO;
12 14 import cn.iocoder.yudao.module.promotion.dal.dataobject.point.PointActivityDO;
  15 +import cn.iocoder.yudao.module.promotion.dal.dataobject.point.PointProductDO;
13 16 import cn.iocoder.yudao.module.promotion.dal.mysql.point.PointActivityMapper;
14 17 import cn.iocoder.yudao.module.promotion.dal.mysql.point.PointProductMapper;
15 18 import jakarta.annotation.Resource;
... ... @@ -19,15 +22,16 @@ import org.springframework.validation.annotation.Validated;
19 22 import java.util.List;
20 23 import java.util.Map;
21 24  
  25 +import static cn.hutool.core.collection.CollUtil.intersectionDistinct;
  26 +import static cn.hutool.core.collection.CollUtil.isNotEmpty;
22 27 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
23   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
  28 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
  29 +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
24 30 import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
25 31 import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_EXISTS;
26   -import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.POINT_ACTIVITY_NOT_EXISTS;
  32 +import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
27 33 import static java.util.Collections.singletonList;
28 34  
29   -// TODO @puhui999: 下次提交完善
30   -
31 35 /**
32 36 * 积分商城活动 Service 实现类
33 37 *
... ... @@ -47,13 +51,27 @@ public class PointActivityServiceImpl implements PointActivityService {
47 51 @Resource
48 52 private ProductSkuApi productSkuApi;
49 53  
  54 + private static List<PointProductDO> buildPointProductDO(PointActivityDO pointActivity, List<PointProductSaveReqVO> products) {
  55 + return BeanUtils.toBean(products, PointProductDO.class, product -> {
  56 + product.setActivityId(pointActivity.getId()).setActivityStatus(pointActivity.getStatus());
  57 + });
  58 + }
  59 +
50 60 @Override
51 61 public Long createPointActivity(PointActivitySaveReqVO createReqVO) {
52   - // 1. 校验商品是否存在
  62 + // 1.1 校验商品是否存在
53 63 validateProductExists(createReqVO.getSpuId(), createReqVO.getProducts());
54   - // 插入
55   - PointActivityDO pointActivity = BeanUtils.toBean(createReqVO, PointActivityDO.class);
  64 + // 1.2 校验商品是否已经参加别的活动
  65 + validatePointActivityProductConflicts(null, createReqVO.getProducts());
  66 +
  67 + // 2.1 插入积分商城活动
  68 + PointActivityDO pointActivity = BeanUtils.toBean(createReqVO, PointActivityDO.class)
  69 + .setStatus(CommonStatusEnum.ENABLE.getStatus())
  70 + .setStock(getSumValue(createReqVO.getProducts(), PointProductSaveReqVO::getStock, Integer::sum));
  71 + pointActivity.setTotalStock(pointActivity.getStock());
56 72 pointActivityMapper.insert(pointActivity);
  73 + // 2.2 插入积分商城活动商品
  74 + pointProductMapper.insertBatch(buildPointProductDO(pointActivity, createReqVO.getProducts()));
57 75 // 返回
58 76 return pointActivity.getId();
59 77 }
... ... @@ -61,27 +79,92 @@ public class PointActivityServiceImpl implements PointActivityService {
61 79 @Override
62 80 public void updatePointActivity(PointActivitySaveReqVO updateReqVO) {
63 81 // 1.1 校验存在
64   - validatePointActivityExists(updateReqVO.getId());
  82 + PointActivityDO activity = validatePointActivityExists(updateReqVO.getId());
  83 + if (CommonStatusEnum.DISABLE.getStatus().equals(activity.getStatus())) {
  84 + throw exception(POINT_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED);
  85 + }
65 86 // 1.2 校验商品是否存在
66 87 validateProductExists(updateReqVO.getSpuId(), updateReqVO.getProducts());
  88 + // 1.3 校验商品是否已经参加别的活动
  89 + validatePointActivityProductConflicts(updateReqVO.getId(), updateReqVO.getProducts());
  90 +
  91 + // 2.1 更新积分商城活动
  92 + PointActivityDO updateObj = BeanUtils.toBean(updateReqVO, PointActivityDO.class)
  93 + .setStock(getSumValue(updateReqVO.getProducts(), PointProductSaveReqVO::getStock, Integer::sum));
  94 + if (updateObj.getStock() > activity.getTotalStock()) { // 如果更新的库存大于原来的库存,则更新总库存
  95 + updateObj.setTotalStock(updateObj.getStock());
  96 + }
  97 + pointActivityMapper.updateById(updateObj);
  98 + // 2.2 更新商品
  99 + updateSeckillProduct(updateObj, updateReqVO.getProducts());
  100 + }
  101 +
  102 + @Override
  103 + public void closePointActivity(Long id) {
  104 + // 校验存在
  105 + PointActivityDO pointActivity = validatePointActivityExists(id);
  106 + if (CommonStatusEnum.DISABLE.getStatus().equals(pointActivity.getStatus())) {
  107 + throw exception(POINT_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED);
  108 + }
67 109  
68 110 // 更新
69   - PointActivityDO updateObj = BeanUtils.toBean(updateReqVO, PointActivityDO.class);
70   - pointActivityMapper.updateById(updateObj);
  111 + pointActivityMapper.updateById(new PointActivityDO().setId(id).setStatus(CommonStatusEnum.DISABLE.getStatus()));
  112 + // 更新活动商品状态
  113 + pointProductMapper.updateByActivityId(new PointProductDO().setActivityId(id).setActivityStatus(
  114 + CommonStatusEnum.DISABLE.getStatus()));
  115 + }
  116 +
  117 + /**
  118 + * 更新秒杀商品
  119 + *
  120 + * @param activity 秒杀活动
  121 + * @param products 该活动的最新商品配置
  122 + */
  123 + private void updateSeckillProduct(PointActivityDO activity, List<PointProductSaveReqVO> products) {
  124 + // 第一步,对比新老数据,获得添加、修改、删除的列表
  125 + List<PointProductDO> newList = buildPointProductDO(activity, products);
  126 + List<PointProductDO> oldList = pointProductMapper.selectListByActivityId(activity.getId());
  127 + List<List<PointProductDO>> diffList = diffList(oldList, newList, (oldVal, newVal) -> {
  128 + boolean same = ObjectUtil.equal(oldVal.getSkuId(), newVal.getSkuId());
  129 + if (same) {
  130 + newVal.setId(oldVal.getId());
  131 + }
  132 + return same;
  133 + });
  134 +
  135 + // 第二步,批量添加、修改、删除
  136 + if (isNotEmpty(diffList.get(0))) {
  137 + pointProductMapper.insertBatch(diffList.get(0));
  138 + }
  139 + if (isNotEmpty(diffList.get(1))) {
  140 + pointProductMapper.updateBatch(diffList.get(1));
  141 + }
  142 + if (isNotEmpty(diffList.get(2))) {
  143 + pointProductMapper.deleteByIds(convertList(diffList.get(2), PointProductDO::getId));
  144 + }
71 145 }
72 146  
73 147 @Override
74 148 public void deletePointActivity(Long id) {
75 149 // 校验存在
76   - validatePointActivityExists(id);
77   - // 删除
  150 + PointActivityDO pointActivity = validatePointActivityExists(id);
  151 + if (CommonStatusEnum.ENABLE.getStatus().equals(pointActivity.getStatus())) {
  152 + throw exception(POINT_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END);
  153 + }
  154 +
  155 + // 删除商城活动
78 156 pointActivityMapper.deleteById(id);
  157 + // 删除活动商品
  158 + List<PointProductDO> products = pointProductMapper.selectListByActivityId(id);
  159 + pointProductMapper.deleteByIds(convertSet(products, PointProductDO::getId));
79 160 }
80 161  
81   - private void validatePointActivityExists(Long id) {
82   - if (pointActivityMapper.selectById(id) == null) {
  162 + private PointActivityDO validatePointActivityExists(Long id) {
  163 + PointActivityDO pointActivityDO = pointActivityMapper.selectById(id);
  164 + if (pointActivityDO == null) {
83 165 throw exception(POINT_ACTIVITY_NOT_EXISTS);
84 166 }
  167 + return pointActivityDO;
85 168 }
86 169  
87 170 /**
... ... @@ -107,6 +190,35 @@ public class PointActivityServiceImpl implements PointActivityService {
107 190 });
108 191 }
109 192  
  193 + /**
  194 + * 校验商品是否冲突
  195 + *
  196 + * @param id 编号
  197 + * @param products 商品列表
  198 + */
  199 + private void validatePointActivityProductConflicts(Long id, List<PointProductSaveReqVO> products) {
  200 + // 1.1 查询所有开启的积分商城活动
  201 + List<PointActivityDO> activityList = pointActivityMapper.selectList(PointActivityDO::getStatus,
  202 + CommonStatusEnum.ENABLE.getStatus());
  203 + if (id != null) { // 更新时排除自己
  204 + activityList.removeIf(item -> ObjectUtil.equal(item.getId(), id));
  205 + }
  206 + // 1.2 查询活动下的所有商品
  207 + List<PointProductDO> productList = pointProductMapper.selectListByActivityId(
  208 + convertList(activityList, PointActivityDO::getId));
  209 + Map<Long, List<PointProductDO>> productListMap = convertMultiMap(productList, PointProductDO::getActivityId);
  210 +
  211 + // 2. 校验商品是否冲突
  212 + activityList.forEach(item -> {
  213 + findAndThen(productListMap, item.getId(), discountProducts -> {
  214 + if (!intersectionDistinct(convertList(discountProducts, PointProductDO::getSpuId),
  215 + convertList(products, PointProductSaveReqVO::getSpuId)).isEmpty()) {
  216 + throw exception(POINT_ACTIVITY_SPU_CONFLICTS);
  217 + }
  218 + });
  219 + });
  220 + }
  221 +
110 222 @Override
111 223 public PointActivityDO getPointActivity(Long id) {
112 224 return pointActivityMapper.selectById(id);
... ...