Browse Source

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

puhui999 8 months ago
parent
commit
342c25c0dd

+ 4 - 1
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 {
 
     // ========== 积分商城活动 1-013-007-000 ==========
     ErrorCode POINT_ACTIVITY_NOT_EXISTS = new ErrorCode(1_013_007_000, "积分商城活动不存在");
-    ErrorCode POINT_PRODUCT_NOT_EXISTS = new ErrorCode(1_013_007_100, "积分商城商品不存在");
+    ErrorCode POINT_ACTIVITY_SPU_CONFLICTS = new ErrorCode(1_013_007_001, "存在商品参加了其它积分商城活动");
+    ErrorCode POINT_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_007_002, "积分商城活动已关闭,不能修改");
+    ErrorCode POINT_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END = new ErrorCode(1_013_007_003, "积分商城活动未关闭或未结束,不能删除");
+    ErrorCode POINT_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_007_004, "积分商城活动已关闭,不能重复关闭");
 
     // ========== 秒杀活动 1-013-008-000 ==========
     ErrorCode SECKILL_ACTIVITY_NOT_EXISTS = new ErrorCode(1_013_008_000, "秒杀活动不存在");

+ 9 - 0
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 {
         return success(true);
     }
 
+    @PutMapping("/close")
+    @Operation(summary = "关闭积分商城活动")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('promotion:point-activity:close')")
+    public CommonResult<Boolean> closeSeckillActivity(@RequestParam("id") Long id) {
+        pointActivityService.closePointActivity(id);
+        return success(true);
+    }
+
     @DeleteMapping("/delete")
     @Operation(summary = "删除积分商城活动")
     @Parameter(name = "id", description = "编号", required = true)

+ 4 - 4
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 {
 
     @Schema(description = "可兑换数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "3926")
     @NotNull(message = "可兑换数量不能为空")
-    private Integer maxCount;
+    private Integer count;
 
     @Schema(description = "兑换积分", requiredMode = Schema.RequiredMode.REQUIRED)
     @NotNull(message = "兑换积分不能为空")
@@ -35,9 +35,9 @@ public class PointProductSaveReqVO {
     @NotNull(message = "兑换金额,单位:分不能为空")
     private Integer price;
 
-    @Schema(description = "兑换类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
-    @NotNull(message = "兑换类型不能为空")
-    private Integer type;
+    @Schema(description = "积分商城商品库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+    @NotNull(message = "积分商城商品不能为空")
+    private Integer stock;
 
     @Schema(description = "积分商城商品状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
     @NotNull(message = "积分商城商品状态不能为空")

+ 9 - 0
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 {
      */
     private Integer sort;
 
+    /**
+     * 积分商城活动库存(剩余库存积分兑换时扣减)
+     */
+    private Integer stock;
+    /**
+     * 积分商城活动总库存
+     */
+    private Integer totalStock;
+
 }

+ 6 - 9
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 {
      */
     private Long skuId;
     /**
-     * 可兑换数
+     * 可兑换
      */
-    private Integer maxCount;
+    private Integer count;
     /**
-     * 兑换积分
+     * 所需兑换积分
      */
     private Integer point;
     /**
-     * 兑换金额,单位:分
+     * 所需兑换金额,单位:分
      */
     private Integer price;
     /**
-     * 兑换类型
-     * 1. 积分
-     * 2. 积分 + 钱
-     * 3. 直接购买
+     * 积分商城商品库存
      */
-    private Integer type;
+    private Integer stock;
     /**
      * 积分商城商品状态
      *

+ 0 - 62
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/pointproduct/PointProductDO.java

@@ -1,62 +0,0 @@
-package cn.iocoder.yudao.module.promotion.dal.dataobject.pointproduct;
-
-import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
-import com.baomidou.mybatisplus.annotation.KeySequence;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
-import lombok.*;
-
-/**
- * 积分商城商品 DO
- *
- * @author HUIHUI
- */
-@TableName("promotion_point_product")
-@KeySequence("promotion_point_product_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-public class PointProductDO extends BaseDO {
-
-    /**
-     * 积分商城商品编号
-     */
-    @TableId
-    private Long id;
-    /**
-     * 积分商城活动 id
-     */
-    private Long activityId;
-    /**
-     * 商品 SPU 编号
-     */
-    private Long spuId;
-    /**
-     * 商品 SKU 编号
-     */
-    private Long skuId;
-    /**
-     * 可兑换数量
-     */
-    private Integer maxCount;
-    /**
-     * 兑换积分
-     */
-    private Integer point;
-    /**
-     * 兑换金额,单位:分
-     */
-    private Integer price;
-    /**
-     * 兑换类型
-     */
-    private Integer type;
-    /**
-     * 积分商城商品状态
-     */
-    private Integer activityStatus;
-
-}

+ 18 - 3
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;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.product.PointProductPageReqVO;
-import cn.iocoder.yudao.module.promotion.dal.dataobject.pointproduct.PointProductDO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.point.PointProductDO;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import org.apache.ibatis.annotations.Mapper;
 
+import java.util.Collection;
+import java.util.List;
+
 /**
  * 积分商城商品 Mapper
  *
@@ -20,13 +24,24 @@ public interface PointProductMapper extends BaseMapperX<PointProductDO> {
                 .eqIfPresent(PointProductDO::getActivityId, reqVO.getActivityId())
                 .eqIfPresent(PointProductDO::getSpuId, reqVO.getSpuId())
                 .eqIfPresent(PointProductDO::getSkuId, reqVO.getSkuId())
-                .eqIfPresent(PointProductDO::getMaxCount, reqVO.getMaxCount())
                 .eqIfPresent(PointProductDO::getPoint, reqVO.getPoint())
                 .eqIfPresent(PointProductDO::getPrice, reqVO.getPrice())
-                .eqIfPresent(PointProductDO::getType, reqVO.getType())
                 .eqIfPresent(PointProductDO::getActivityStatus, reqVO.getActivityStatus())
                 .betweenIfPresent(PointProductDO::getCreateTime, reqVO.getCreateTime())
                 .orderByDesc(PointProductDO::getId));
     }
 
+    default List<PointProductDO> selectListByActivityId(Collection<Long> activityIds) {
+        return selectList(PointProductDO::getActivityId, activityIds);
+    }
+
+    default List<PointProductDO> selectListByActivityId(Long activityId) {
+        return selectList(PointProductDO::getActivityId, activityId);
+    }
+
+    default void updateByActivityId(PointProductDO pointProductDO) {
+        update(pointProductDO, new LambdaUpdateWrapper<PointProductDO>()
+                .eq(PointProductDO::getActivityId, pointProductDO.getActivityId()));
+    }
+
 }

+ 7 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/point/PointActivityService.java

@@ -28,6 +28,13 @@ public interface PointActivityService {
      */
     void updatePointActivity(@Valid PointActivitySaveReqVO updateReqVO);
 
+    /**
+     * 关闭积分商城活动
+     *
+     * @param id 编号
+     */
+    void closePointActivity(Long id);
+
     /**
      * 删除积分商城活动
      *

+ 126 - 14
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/point/PointActivityServiceImpl.java

@@ -1,5 +1,7 @@
 package cn.iocoder.yudao.module.promotion.service.point;
 
+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.object.BeanUtils;
 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
 import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.activity.PointActivitySaveReqVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.product.PointProductSaveReqVO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.point.PointActivityDO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.point.PointProductDO;
 import cn.iocoder.yudao.module.promotion.dal.mysql.point.PointActivityMapper;
 import cn.iocoder.yudao.module.promotion.dal.mysql.point.PointProductMapper;
 import jakarta.annotation.Resource;
@@ -19,15 +22,16 @@ import org.springframework.validation.annotation.Validated;
 import java.util.List;
 import java.util.Map;
 
+import static cn.hutool.core.collection.CollUtil.intersectionDistinct;
+import static cn.hutool.core.collection.CollUtil.isNotEmpty;
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-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.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.POINT_ACTIVITY_NOT_EXISTS;
+import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
 import static java.util.Collections.singletonList;
 
-// TODO @puhui999: 下次提交完善
-
 /**
  * 积分商城活动 Service 实现类
  *
@@ -47,13 +51,27 @@ public class PointActivityServiceImpl implements PointActivityService {
     @Resource
     private ProductSkuApi productSkuApi;
 
+    private static List<PointProductDO> buildPointProductDO(PointActivityDO pointActivity, List<PointProductSaveReqVO> products) {
+        return BeanUtils.toBean(products, PointProductDO.class, product -> {
+            product.setActivityId(pointActivity.getId()).setActivityStatus(pointActivity.getStatus());
+        });
+    }
+
     @Override
     public Long createPointActivity(PointActivitySaveReqVO createReqVO) {
-        // 1. 校验商品是否存在
+        // 1.1 校验商品是否存在
         validateProductExists(createReqVO.getSpuId(), createReqVO.getProducts());
-        // 插入
-        PointActivityDO pointActivity = BeanUtils.toBean(createReqVO, PointActivityDO.class);
+        // 1.2 校验商品是否已经参加别的活动
+        validatePointActivityProductConflicts(null, createReqVO.getProducts());
+
+        // 2.1 插入积分商城活动
+        PointActivityDO pointActivity = BeanUtils.toBean(createReqVO, PointActivityDO.class)
+                .setStatus(CommonStatusEnum.ENABLE.getStatus())
+                .setStock(getSumValue(createReqVO.getProducts(), PointProductSaveReqVO::getStock, Integer::sum));
+        pointActivity.setTotalStock(pointActivity.getStock());
         pointActivityMapper.insert(pointActivity);
+        // 2.2 插入积分商城活动商品
+        pointProductMapper.insertBatch(buildPointProductDO(pointActivity, createReqVO.getProducts()));
         // 返回
         return pointActivity.getId();
     }
@@ -61,27 +79,92 @@ public class PointActivityServiceImpl implements PointActivityService {
     @Override
     public void updatePointActivity(PointActivitySaveReqVO updateReqVO) {
         // 1.1 校验存在
-        validatePointActivityExists(updateReqVO.getId());
+        PointActivityDO activity = validatePointActivityExists(updateReqVO.getId());
+        if (CommonStatusEnum.DISABLE.getStatus().equals(activity.getStatus())) {
+            throw exception(POINT_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED);
+        }
         // 1.2 校验商品是否存在
         validateProductExists(updateReqVO.getSpuId(), updateReqVO.getProducts());
+        // 1.3 校验商品是否已经参加别的活动
+        validatePointActivityProductConflicts(updateReqVO.getId(), updateReqVO.getProducts());
+
+        // 2.1 更新积分商城活动
+        PointActivityDO updateObj = BeanUtils.toBean(updateReqVO, PointActivityDO.class)
+                .setStock(getSumValue(updateReqVO.getProducts(), PointProductSaveReqVO::getStock, Integer::sum));
+        if (updateObj.getStock() > activity.getTotalStock()) { // 如果更新的库存大于原来的库存,则更新总库存
+            updateObj.setTotalStock(updateObj.getStock());
+        }
+        pointActivityMapper.updateById(updateObj);
+        // 2.2 更新商品
+        updateSeckillProduct(updateObj, updateReqVO.getProducts());
+    }
+
+    @Override
+    public void closePointActivity(Long id) {
+        // 校验存在
+        PointActivityDO pointActivity = validatePointActivityExists(id);
+        if (CommonStatusEnum.DISABLE.getStatus().equals(pointActivity.getStatus())) {
+            throw exception(POINT_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED);
+        }
 
         // 更新
-        PointActivityDO updateObj = BeanUtils.toBean(updateReqVO, PointActivityDO.class);
-        pointActivityMapper.updateById(updateObj);
+        pointActivityMapper.updateById(new PointActivityDO().setId(id).setStatus(CommonStatusEnum.DISABLE.getStatus()));
+        // 更新活动商品状态
+        pointProductMapper.updateByActivityId(new PointProductDO().setActivityId(id).setActivityStatus(
+                CommonStatusEnum.DISABLE.getStatus()));
+    }
+
+    /**
+     * 更新秒杀商品
+     *
+     * @param activity 秒杀活动
+     * @param products 该活动的最新商品配置
+     */
+    private void updateSeckillProduct(PointActivityDO activity, List<PointProductSaveReqVO> products) {
+        // 第一步,对比新老数据,获得添加、修改、删除的列表
+        List<PointProductDO> newList = buildPointProductDO(activity, products);
+        List<PointProductDO> oldList = pointProductMapper.selectListByActivityId(activity.getId());
+        List<List<PointProductDO>> diffList = diffList(oldList, newList, (oldVal, newVal) -> {
+            boolean same = ObjectUtil.equal(oldVal.getSkuId(), newVal.getSkuId());
+            if (same) {
+                newVal.setId(oldVal.getId());
+            }
+            return same;
+        });
+
+        // 第二步,批量添加、修改、删除
+        if (isNotEmpty(diffList.get(0))) {
+            pointProductMapper.insertBatch(diffList.get(0));
+        }
+        if (isNotEmpty(diffList.get(1))) {
+            pointProductMapper.updateBatch(diffList.get(1));
+        }
+        if (isNotEmpty(diffList.get(2))) {
+            pointProductMapper.deleteByIds(convertList(diffList.get(2), PointProductDO::getId));
+        }
     }
 
     @Override
     public void deletePointActivity(Long id) {
         // 校验存在
-        validatePointActivityExists(id);
-        // 删除
+        PointActivityDO pointActivity = validatePointActivityExists(id);
+        if (CommonStatusEnum.ENABLE.getStatus().equals(pointActivity.getStatus())) {
+            throw exception(POINT_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END);
+        }
+
+        // 删除商城活动
         pointActivityMapper.deleteById(id);
+        // 删除活动商品
+        List<PointProductDO> products = pointProductMapper.selectListByActivityId(id);
+        pointProductMapper.deleteByIds(convertSet(products, PointProductDO::getId));
     }
 
-    private void validatePointActivityExists(Long id) {
-        if (pointActivityMapper.selectById(id) == null) {
+    private PointActivityDO validatePointActivityExists(Long id) {
+        PointActivityDO pointActivityDO = pointActivityMapper.selectById(id);
+        if (pointActivityDO == null) {
             throw exception(POINT_ACTIVITY_NOT_EXISTS);
         }
+        return pointActivityDO;
     }
 
     /**
@@ -107,6 +190,35 @@ public class PointActivityServiceImpl implements PointActivityService {
         });
     }
 
+    /**
+     * 校验商品是否冲突
+     *
+     * @param id       编号
+     * @param products 商品列表
+     */
+    private void validatePointActivityProductConflicts(Long id, List<PointProductSaveReqVO> products) {
+        // 1.1 查询所有开启的积分商城活动
+        List<PointActivityDO> activityList = pointActivityMapper.selectList(PointActivityDO::getStatus,
+                CommonStatusEnum.ENABLE.getStatus());
+        if (id != null) { // 更新时排除自己
+            activityList.removeIf(item -> ObjectUtil.equal(item.getId(), id));
+        }
+        // 1.2 查询活动下的所有商品
+        List<PointProductDO> productList = pointProductMapper.selectListByActivityId(
+                convertList(activityList, PointActivityDO::getId));
+        Map<Long, List<PointProductDO>> productListMap = convertMultiMap(productList, PointProductDO::getActivityId);
+
+        // 2. 校验商品是否冲突
+        activityList.forEach(item -> {
+            findAndThen(productListMap, item.getId(), discountProducts -> {
+                if (!intersectionDistinct(convertList(discountProducts, PointProductDO::getSpuId),
+                        convertList(products, PointProductSaveReqVO::getSpuId)).isEmpty()) {
+                    throw exception(POINT_ACTIVITY_SPU_CONFLICTS);
+                }
+            });
+        });
+    }
+
     @Override
     public PointActivityDO getPointActivity(Long id) {
         return pointActivityMapper.selectById(id);