From 4eed1940b9e0ec8189b0c6608e263c5f83b3d770 Mon Sep 17 00:00:00 2001 From: sonjinyon <2476687577@qq.com> Date: Tue, 8 Oct 2024 20:36:57 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=A7=AF=E5=88=86=E6=A8=A1?= =?UTF-8?q?=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../point/activity/PointActivityForm.vue | 227 +++++++++++++ .../mall/promotion/point/activity/index.vue | 219 +++++++++++++ .../point/activity/pointActivity.data.ts | 55 ++++ .../point/components/PointShowcase.vue | 154 +++++++++ .../point/components/PointTableSelect.vue | 300 +++++++++++++++++ .../util/collection/CollectionUtils.java | 8 + .../promotion/api/point/PointActivityApi.java | 42 +++ .../point/dto/PointValidateJoinRespDTO.java | 24 ++ .../promotion/enums/ErrorCodeConstants.java | 12 + .../api/point/PointActivityApiImpl.java | 37 +++ .../admin/diy/DiyPageController.java | 8 + .../admin/point/PointActivityController.java | 141 ++++++++ .../vo/activity/PointActivityPageReqVO.java | 36 ++ .../vo/activity/PointActivityRespVO.java | 72 ++++ .../vo/activity/PointActivitySaveReqVO.java | 31 ++ .../point/vo/product/PointProductRespVO.java | 39 +++ .../vo/product/PointProductSaveReqVO.java | 47 +++ .../app/diy/AppDiyPageController.java | 5 - .../app/point/AppPointActivityController.java | 121 +++++++ .../vo/AppPointActivityDetailRespVO.java | 65 ++++ .../point/vo/AppPointActivityPageReqVO.java | 15 + .../app/point/vo/AppPointActivityRespVO.java | 67 ++++ .../dal/dataobject/point/PointActivityDO.java | 57 ++++ .../dal/dataobject/point/PointProductDO.java | 67 ++++ .../dal/mysql/point/PointActivityMapper.java | 59 ++++ .../dal/mysql/point/PointProductMapper.java | 66 ++++ .../service/point/PointActivityService.java | 112 +++++++ .../point/PointActivityServiceImpl.java | 309 ++++++++++++++++++ 28 files changed, 2390 insertions(+), 5 deletions(-) create mode 100644 yudao-admin-vue3/src/views/mall/promotion/point/activity/PointActivityForm.vue create mode 100644 yudao-admin-vue3/src/views/mall/promotion/point/activity/index.vue create mode 100644 yudao-admin-vue3/src/views/mall/promotion/point/activity/pointActivity.data.ts create mode 100644 yudao-admin-vue3/src/views/mall/promotion/point/components/PointShowcase.vue create mode 100644 yudao-admin-vue3/src/views/mall/promotion/point/components/PointTableSelect.vue create mode 100644 yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/point/PointActivityApi.java create mode 100644 yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/point/dto/PointValidateJoinRespDTO.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/point/PointActivityApiImpl.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/PointActivityController.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/activity/PointActivityPageReqVO.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/activity/PointActivityRespVO.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/activity/PointActivitySaveReqVO.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/product/PointProductRespVO.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/product/PointProductSaveReqVO.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/AppPointActivityController.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/vo/AppPointActivityDetailRespVO.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/vo/AppPointActivityPageReqVO.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/vo/AppPointActivityRespVO.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/point/PointActivityDO.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/point/PointProductDO.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/point/PointActivityMapper.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/point/PointProductMapper.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/point/PointActivityService.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/point/PointActivityServiceImpl.java diff --git a/yudao-admin-vue3/src/views/mall/promotion/point/activity/PointActivityForm.vue b/yudao-admin-vue3/src/views/mall/promotion/point/activity/PointActivityForm.vue new file mode 100644 index 0000000..a09565c --- /dev/null +++ b/yudao-admin-vue3/src/views/mall/promotion/point/activity/PointActivityForm.vue @@ -0,0 +1,227 @@ + + diff --git a/yudao-admin-vue3/src/views/mall/promotion/point/activity/index.vue b/yudao-admin-vue3/src/views/mall/promotion/point/activity/index.vue new file mode 100644 index 0000000..ceceb7b --- /dev/null +++ b/yudao-admin-vue3/src/views/mall/promotion/point/activity/index.vue @@ -0,0 +1,219 @@ + + + diff --git a/yudao-admin-vue3/src/views/mall/promotion/point/activity/pointActivity.data.ts b/yudao-admin-vue3/src/views/mall/promotion/point/activity/pointActivity.data.ts new file mode 100644 index 0000000..a3334ea --- /dev/null +++ b/yudao-admin-vue3/src/views/mall/promotion/point/activity/pointActivity.data.ts @@ -0,0 +1,55 @@ +import type { CrudSchema } from '@/hooks/web/useCrudSchemas' + +// 表单校验 +export const rules = reactive({ + spuId: [required], + sort: [required] +}) + +// CrudSchema https://doc.iocoder.cn/vue3/crud-schema/ +const crudSchemas = reactive([ + { + label: '排序', + field: 'sort', + form: { + component: 'InputNumber', + value: 0 + }, + table: { + width: 80 + } + }, + { + label: '积分商城活动商品', + field: 'spuId', + isTable: true, + isSearch: false, + form: { + colProps: { + span: 24 + } + }, + table: { + width: 300 + } + }, + { + label: '备注', + field: 'remark', + isSearch: false, + form: { + component: 'Input', + componentProps: { + type: 'textarea', + rows: 4 + }, + colProps: { + span: 24 + } + }, + table: { + width: 300 + } + } +]) +export const { allSchemas } = useCrudSchemas(crudSchemas) diff --git a/yudao-admin-vue3/src/views/mall/promotion/point/components/PointShowcase.vue b/yudao-admin-vue3/src/views/mall/promotion/point/components/PointShowcase.vue new file mode 100644 index 0000000..82e490c --- /dev/null +++ b/yudao-admin-vue3/src/views/mall/promotion/point/components/PointShowcase.vue @@ -0,0 +1,154 @@ + + + + diff --git a/yudao-admin-vue3/src/views/mall/promotion/point/components/PointTableSelect.vue b/yudao-admin-vue3/src/views/mall/promotion/point/components/PointTableSelect.vue new file mode 100644 index 0000000..d68b5f1 --- /dev/null +++ b/yudao-admin-vue3/src/views/mall/promotion/point/components/PointTableSelect.vue @@ -0,0 +1,300 @@ + + + diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java index 91f5347..2a581d1 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java @@ -290,6 +290,14 @@ public class CollectionUtils { return valueFunc.apply(t); } + public static > T getMinObject(List from, Function valueFunc) { + if (CollUtil.isEmpty(from)) { + return null; + } + assert from.size() > 0; // 断言,避免告警 + return from.stream().min(Comparator.comparing(valueFunc)).get(); + } + public static > V getSumValue(List from, Function valueFunc, BinaryOperator accumulator) { return getSumValue(from, valueFunc, accumulator, null); diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/point/PointActivityApi.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/point/PointActivityApi.java new file mode 100644 index 0000000..0b612cf --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/point/PointActivityApi.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.module.promotion.api.point; + +import cn.iocoder.yudao.module.promotion.api.point.dto.PointValidateJoinRespDTO; + +/** + * 积分商城活动 API 接口 + * + * @author HUIHUI + */ +public interface PointActivityApi { + + /** + * 【下单前】校验是否参与积分商城活动 + * + * 如果校验失败,则抛出业务异常 + * + * @param activityId 活动编号 + * @param skuId SKU 编号 + * @param count 数量 + * @return 积分商城商品信息 + */ + PointValidateJoinRespDTO validateJoinPointActivity(Long activityId, Long skuId, Integer count); + + /** + * 更新积分商城商品库存(减少) + * + * @param id 活动编号 + * @param skuId sku 编号 + * @param count 数量(正数) + */ + void updatePointStockDecr(Long id, Long skuId, Integer count); + + /** + * 更新积分商城商品库存(增加) + * + * @param id 活动编号 + * @param skuId sku 编号 + * @param count 数量(正数) + */ + void updatePointStockIncr(Long id, Long skuId, Integer count); + +} diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/point/dto/PointValidateJoinRespDTO.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/point/dto/PointValidateJoinRespDTO.java new file mode 100644 index 0000000..e3b9934 --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/point/dto/PointValidateJoinRespDTO.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.promotion.api.point.dto; + +import lombok.Data; + +/** + * 校验参与积分商城 Response DTO + */ +@Data +public class PointValidateJoinRespDTO { + + /** + * 可兑换次数 + */ + private Integer count; + /** + * 所需兑换积分 + */ + private Integer point; + /** + * 所需兑换金额,单位:分 + */ + private Integer price; + +} diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java index 9f3ba83..1038bad 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java @@ -137,4 +137,16 @@ public interface ErrorCodeConstants { ErrorCode POINT_MALL_NOT_EXISTS = new ErrorCode(1_013_023_000, "积分商城不存在"); + + // ========== 积分商城活动 1-013-007-000 ========== + ErrorCode POINT_ACTIVITY_NOT_EXISTS = new ErrorCode(1_013_007_000, "积分商城活动不存在"); + 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, "积分商城活动已关闭,不能重复关闭"); + ErrorCode POINT_ACTIVITY_JOIN_ACTIVITY_STATUS_CLOSED = new ErrorCode(1_013_007_005, "积分商品兑换失败,原因:积分商城活动已关闭"); + ErrorCode POINT_ACTIVITY_JOIN_ACTIVITY_SINGLE_LIMIT_COUNT_EXCEED = new ErrorCode(1_013_007_006, "积分商品兑换失败,原因:单次限购超出"); + ErrorCode POINT_ACTIVITY_JOIN_ACTIVITY_PRODUCT_NOT_EXISTS = new ErrorCode(1_013_007_007, "积分商品兑换失败,原因:商品不存在"); + ErrorCode POINT_ACTIVITY_UPDATE_STOCK_FAIL = new ErrorCode(1_013_007_008, "积分商品兑换失败,原因:积分商品库存不足"); + } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/point/PointActivityApiImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/point/PointActivityApiImpl.java new file mode 100644 index 0000000..8b0ef1e --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/point/PointActivityApiImpl.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.promotion.api.point; + +import cn.iocoder.yudao.module.promotion.api.point.dto.PointValidateJoinRespDTO; +import cn.iocoder.yudao.module.promotion.service.point.PointActivityService; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; + +/** + * 积分商城活动 Api 接口实现类 + * + * @author HUIHUI + */ +@Service +@Validated +public class PointActivityApiImpl implements PointActivityApi { + + @Resource + private PointActivityService pointActivityService; + + @Override + public PointValidateJoinRespDTO validateJoinPointActivity(Long activityId, Long skuId, Integer count) { + return pointActivityService.validateJoinPointActivity(activityId, skuId, count); + } + + @Override + public void updatePointStockDecr(Long id, Long skuId, Integer count) { + pointActivityService.updatePointStockDecr(id, skuId, count); + } + + @Override + public void updatePointStockIncr(Long id, Long skuId, Integer count) { + pointActivityService.updatePointStockIncr(id, skuId, count); + } + +} diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/diy/DiyPageController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/diy/DiyPageController.java index 4da7064..f819630 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/diy/DiyPageController.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/diy/DiyPageController.java @@ -2,7 +2,9 @@ package cn.iocoder.yudao.module.promotion.controller.admin.diy; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.promotion.controller.admin.diy.vo.page.*; +import cn.iocoder.yudao.module.promotion.controller.app.diy.vo.AppDiyPagePropertyRespVO; import cn.iocoder.yudao.module.promotion.convert.diy.DiyPageConvert; import cn.iocoder.yudao.module.promotion.dal.dataobject.diy.DiyPageDO; import cn.iocoder.yudao.module.promotion.service.diy.DiyPageService; @@ -96,4 +98,10 @@ public class DiyPageController { return success(true); } + @GetMapping("/getDiyPage") + public CommonResult> getPromotion() { + List diyPage = diyPageService.getDiyPage(); + return success(BeanUtils.toBean(diyPage, AppDiyPagePropertyRespVO.class)); + } + } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/PointActivityController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/PointActivityController.java new file mode 100644 index 0000000..1a16698 --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/PointActivityController.java @@ -0,0 +1,141 @@ +package cn.iocoder.yudao.module.promotion.controller.admin.point; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; +import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; +import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.activity.PointActivityPageReqVO; +import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.activity.PointActivityRespVO; +import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.activity.PointActivitySaveReqVO; +import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.product.PointProductRespVO; +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.service.point.PointActivityService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.validation.Valid; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; + +@Tag(name = "管理后台 - 积分商城活动") +@RestController +@RequestMapping("/promotion/point-activity") +@Validated +public class PointActivityController { + + @Resource + private PointActivityService pointActivityService; + @Resource + private ProductSpuApi productSpuApi; + + @PostMapping("/create") + @Operation(summary = "创建积分商城活动") + @PreAuthorize("@ss.hasPermission('promotion:point-activity:create')") + public CommonResult createPointActivity(@Valid @RequestBody PointActivitySaveReqVO createReqVO) { + return success(pointActivityService.createPointActivity(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新积分商城活动") + @PreAuthorize("@ss.hasPermission('promotion:point-activity:update')") + public CommonResult updatePointActivity(@Valid @RequestBody PointActivitySaveReqVO updateReqVO) { + pointActivityService.updatePointActivity(updateReqVO); + return success(true); + } + + @PutMapping("/close") + @Operation(summary = "关闭积分商城活动") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('promotion:point-activity:close')") + public CommonResult closeSeckillActivity(@RequestParam("id") Long id) { + pointActivityService.closePointActivity(id); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除积分商城活动") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('promotion:point-activity:delete')") + public CommonResult deletePointActivity(@RequestParam("id") Long id) { + pointActivityService.deletePointActivity(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得积分商城活动") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('promotion:point-activity:query')") + public CommonResult getPointActivity(@RequestParam("id") Long id) { + PointActivityDO pointActivity = pointActivityService.getPointActivity(id); + if (pointActivity == null) { + return success(null); + } + + List products = pointActivityService.getPointProductListByActivityIds(Collections.singletonList(id)); + PointActivityRespVO respVO = BeanUtils.toBean(pointActivity, PointActivityRespVO.class); + respVO.setProducts(BeanUtils.toBean(products, PointProductRespVO.class)); + return success(respVO); + } + + @GetMapping("/page") + @Operation(summary = "获得积分商城活动分页") + @PreAuthorize("@ss.hasPermission('promotion:point-activity:query')") + public CommonResult> getPointActivityPage(@Valid PointActivityPageReqVO pageReqVO) { + PageResult pageResult = pointActivityService.getPointActivityPage(pageReqVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(PageResult.empty(pageResult.getTotal())); + } + + // 拼接数据 + List resultList = buildPointActivityRespVOList(pageResult.getList()); + return success(new PageResult<>(resultList, pageResult.getTotal())); + } + + @GetMapping("/list-by-ids") + @Operation(summary = "获得积分商城活动列表,基于活动编号数组") + @Parameter(name = "ids", description = "活动编号数组", required = true, example = "[1024, 1025]") + public CommonResult> getPointActivityListByIds(@RequestParam("ids") List ids) { + // 1. 获得开启的活动列表 + List activityList = pointActivityService.getPointActivityListByIds(ids); + activityList.removeIf(activity -> CommonStatusEnum.isDisable(activity.getStatus())); + if (CollUtil.isEmpty(activityList)) { + return success(Collections.emptyList()); + } + // 2. 拼接返回 + List result = buildPointActivityRespVOList(activityList); + return success(result); + } + + private List buildPointActivityRespVOList(List activityList) { + List products = pointActivityService.getPointProductListByActivityIds( + convertSet(activityList, PointActivityDO::getId)); + Map> productsMap = convertMultiMap(products, PointProductDO::getActivityId); + Map spuMap = productSpuApi.getSpusMap( + convertSet(activityList, PointActivityDO::getSpuId)); + List result = BeanUtils.toBean(activityList, PointActivityRespVO.class); + result.forEach(activity -> { + // 设置 product 信息 + PointProductDO minProduct = getMinObject(productsMap.get(activity.getId()), PointProductDO::getPoint); + assert minProduct != null; + activity.setPoint(minProduct.getPoint()).setPrice(minProduct.getPrice()); + findAndThen(spuMap, activity.getSpuId(), + spu -> activity.setSpuName(spu.getName()).setPicUrl(spu.getPicUrl()).setMarketPrice(spu.getMarketPrice())); + }); + return result; + } + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/activity/PointActivityPageReqVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/activity/PointActivityPageReqVO.java new file mode 100644 index 0000000..89786c2 --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/activity/PointActivityPageReqVO.java @@ -0,0 +1,36 @@ +package cn.iocoder.yudao.module.promotion.controller.admin.point.vo.activity; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 积分商城活动分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PointActivityPageReqVO extends PageParam { + + @Schema(description = "积分商城活动商品", example = "19509") + private Long spuId; + + @Schema(description = "活动状态", example = "2") + private Integer status; + + @Schema(description = "备注", example = "你说的对") + private String remark; + + @Schema(description = "排序") + private Integer sort; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/activity/PointActivityRespVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/activity/PointActivityRespVO.java new file mode 100644 index 0000000..d81b3d6 --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/activity/PointActivityRespVO.java @@ -0,0 +1,72 @@ +package cn.iocoder.yudao.module.promotion.controller.admin.point.vo.activity; + +import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.product.PointProductRespVO; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - 积分商城活动 Response VO") +@Data +@ExcelIgnoreUnannotated +public class PointActivityRespVO { + + @Schema(description = "积分商城活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11373") + @ExcelProperty("积分商城活动编号") + private Long id; + + @Schema(description = "积分商城活动商品", requiredMode = Schema.RequiredMode.REQUIRED, example = "19509") + @ExcelProperty("积分商城活动商品") + private Long spuId; + + @Schema(description = "活动状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @ExcelProperty("活动状态") + private Integer status; + + @Schema(description = "积分商城活动库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @ExcelProperty("积分商城活动库存") + private Integer stock; // 剩余库存积分兑换时扣减 + + @Schema(description = "积分商城活动总库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @ExcelProperty("积分商城活动总库存") + private Integer totalStock; + + @Schema(description = "备注", example = "你说的对") + @ExcelProperty("备注") + private String remark; + + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("排序") + private Integer sort; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @Schema(description = "积分商城商品", requiredMode = Schema.RequiredMode.REQUIRED) + private List products; + + // ========== 商品字段 ========== + + @Schema(description = "商品名称", requiredMode = Schema.RequiredMode.REQUIRED, // 从 SPU 的 name 读取 + example = "618大促") + private String spuName; + @Schema(description = "商品主图", requiredMode = Schema.RequiredMode.REQUIRED, // 从 SPU 的 picUrl 读取 + example = "https://www.iocoder.cn/xx.png") + private String picUrl; + @Schema(description = "商品市场价,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, // 从 SPU 的 marketPrice 读取 + example = "50") + private Integer marketPrice; + + //======================= 显示所需兑换积分最少的 sku 信息 ======================= + + @Schema(description = "兑换积分", requiredMode = Schema.RequiredMode.REQUIRED) + private Integer point; + + @Schema(description = "兑换金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "15860") + private Integer price; + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/activity/PointActivitySaveReqVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/activity/PointActivitySaveReqVO.java new file mode 100644 index 0000000..c81db10 --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/activity/PointActivitySaveReqVO.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.promotion.controller.admin.point.vo.activity; + +import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.product.PointProductSaveReqVO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.util.List; + +@Schema(description = "管理后台 - 积分商城活动新增/修改 Request VO") +@Data +public class PointActivitySaveReqVO { + + @Schema(description = "积分商城活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11373") + private Long id; + + @Schema(description = "积分商城活动商品", requiredMode = Schema.RequiredMode.REQUIRED, example = "19509") + @NotNull(message = "积分商城活动商品不能为空") + private Long spuId; + + @Schema(description = "备注", example = "你说的对") + private String remark; + + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "排序不能为空") + private Integer sort; + + @Schema(description = "积分商城商品", requiredMode = Schema.RequiredMode.REQUIRED) + private List products; + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/product/PointProductRespVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/product/PointProductRespVO.java new file mode 100644 index 0000000..8e8250b --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/product/PointProductRespVO.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.promotion.controller.admin.point.vo.product; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 积分商城商品 Response VO") +@Data +@ExcelIgnoreUnannotated +public class PointProductRespVO { + + @Schema(description = "积分商城商品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "31718") + private Long id; + + @Schema(description = "积分商城活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29388") + private Long activityId; + + @Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "8112") + private Long spuId; + + @Schema(description = "商品 SKU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2736") + private Long skuId; + + @Schema(description = "可兑换数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "3926") + private Integer count; + + @Schema(description = "兑换积分", requiredMode = Schema.RequiredMode.REQUIRED) + private Integer point; + + @Schema(description = "兑换金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "15860") + private Integer price; + + @Schema(description = "积分商城商品库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") + private Integer stock; + + @Schema(description = "积分商城商品状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Integer activityStatus; + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/product/PointProductSaveReqVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/product/PointProductSaveReqVO.java new file mode 100644 index 0000000..9863e73 --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/product/PointProductSaveReqVO.java @@ -0,0 +1,47 @@ +package cn.iocoder.yudao.module.promotion.controller.admin.point.vo.product; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 积分商城商品新增/修改 Request VO") +@Data +public class PointProductSaveReqVO { + + @Schema(description = "积分商城商品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "31718") + private Long id; + + @Schema(description = "积分商城活动 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "29388") + @NotNull(message = "积分商城活动 id不能为空") + private Long activityId; + + @Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "8112") + @NotNull(message = "商品 SPU 编号不能为空") + private Long spuId; + + @Schema(description = "商品 SKU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2736") + @NotNull(message = "商品 SKU 编号不能为空") + private Long skuId; + + @Schema(description = "可兑换数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "3926") + @NotNull(message = "可兑换数量不能为空") + private Integer count; + + @Schema(description = "兑换积分", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "兑换积分不能为空") + private Integer point; + + @Schema(description = "兑换金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "15860") + @NotNull(message = "兑换金额,单位:分不能为空") + private Integer price; + + @Schema(description = "积分商城商品库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") + @NotNull(message = "积分商城商品不能为空") + private Integer stock; + + @Schema(description = "积分商城商品状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "积分商城商品状态不能为空") + private Integer activityStatus; + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/diy/AppDiyPageController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/diy/AppDiyPageController.java index c9a1f1c..c683ef3 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/diy/AppDiyPageController.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/diy/AppDiyPageController.java @@ -38,11 +38,6 @@ public class AppDiyPageController { } - @GetMapping("/getDiyPage") - public CommonResult> getPromotion() { - List diyPage = diyPageService.getDiyPage(); - return success(BeanUtils.toBean(diyPage, AppDiyPagePropertyRespVO.class)); - } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/AppPointActivityController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/AppPointActivityController.java new file mode 100644 index 0000000..8c2f3e3 --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/AppPointActivityController.java @@ -0,0 +1,121 @@ +package cn.iocoder.yudao.module.promotion.controller.app.point; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; +import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; +import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.activity.PointActivityPageReqVO; +import cn.iocoder.yudao.module.promotion.controller.app.point.vo.AppPointActivityDetailRespVO; +import cn.iocoder.yudao.module.promotion.controller.app.point.vo.AppPointActivityPageReqVO; +import cn.iocoder.yudao.module.promotion.controller.app.point.vo.AppPointActivityRespVO; +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.service.point.PointActivityService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import javax.annotation.security.PermitAll; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; + +@Tag(name = "用户 App - 积分商城活动") +@RestController +@RequestMapping("/promotion/point-activity") +@Validated +public class AppPointActivityController { + + @Resource + private PointActivityService pointActivityService; + + @Resource + private ProductSpuApi productSpuApi; + + @GetMapping("/page") + @Operation(summary = "获得积分商城活动分页") + @PermitAll + public CommonResult> getPointActivityPage(AppPointActivityPageReqVO pageReqVO) { + // 1. 查询满足当前阶段的活动 + PageResult pageResult = pointActivityService.getPointActivityPage( + BeanUtils.toBean(pageReqVO, PointActivityPageReqVO.class)); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(PageResult.empty(pageResult.getTotal())); + } + + // 2. 拼接数据 + List resultList = buildAppPointActivityRespVOList(pageResult.getList()); + return success(new PageResult<>(resultList, pageResult.getTotal())); + } + + @GetMapping("/get-detail") + @Operation(summary = "获得积分商城活动明细") + @Parameter(name = "id", description = "活动编号", required = true, example = "1024") + @PermitAll + public CommonResult getPointActivity(@RequestParam("id") Long id) { + // 1. 获取活动 + PointActivityDO activity = pointActivityService.getPointActivity(id); + if (activity == null + || ObjUtil.equal(activity.getStatus(), CommonStatusEnum.DISABLE.getStatus())) { + return success(null); + } + + // 2. 拼接数据 + List products = pointActivityService.getPointProductListByActivityIds(Collections.singletonList(id)); + PointProductDO minProduct = getMinObject(products, PointProductDO::getPoint); + assert minProduct != null; + AppPointActivityDetailRespVO respVO = BeanUtils.toBean(activity, AppPointActivityDetailRespVO.class) + .setProducts(BeanUtils.toBean(products, AppPointActivityDetailRespVO.Product.class)) + .setPoint(minProduct.getPoint()).setPrice(minProduct.getPrice()); + return success(respVO); + } + + @GetMapping("/list-by-ids") + @Operation(summary = "获得积分商城活动列表,基于活动编号数组") + @Parameter(name = "ids", description = "活动编号数组", required = true, example = "[1024, 1025]") + @PermitAll + public CommonResult> getCombinationActivityListByIds(@RequestParam("ids") List ids) { + // 1. 获得开启的活动列表 + List activityList = pointActivityService.getPointActivityListByIds(ids); + activityList.removeIf(activity -> CommonStatusEnum.isDisable(activity.getStatus())); + if (CollUtil.isEmpty(activityList)) { + return success(Collections.emptyList()); + } + // 2. 拼接返回 + List result = buildAppPointActivityRespVOList(activityList); + return success(result); + } + + private List buildAppPointActivityRespVOList(List activityList) { + List products = pointActivityService.getPointProductListByActivityIds( + convertSet(activityList, PointActivityDO::getId)); + Map> productsMap = convertMultiMap(products, PointProductDO::getActivityId); + Map spuMap = productSpuApi.getSpusMap( + convertSet(activityList, PointActivityDO::getSpuId)); + List result = BeanUtils.toBean(activityList, AppPointActivityRespVO.class); + result.forEach(activity -> { + // 设置 product 信息 + PointProductDO minProduct = getMinObject(productsMap.get(activity.getId()), PointProductDO::getPoint); + assert minProduct != null; + activity.setPoint(minProduct.getPoint()).setPrice(minProduct.getPrice()); + findAndThen(spuMap, activity.getSpuId(), + spu -> activity.setSpuName(spu.getName()).setPicUrl(spu.getPicUrl()).setMarketPrice(spu.getMarketPrice())); + }); + return result; + } + +} diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/vo/AppPointActivityDetailRespVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/vo/AppPointActivityDetailRespVO.java new file mode 100644 index 0000000..2e02f10 --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/vo/AppPointActivityDetailRespVO.java @@ -0,0 +1,65 @@ +package cn.iocoder.yudao.module.promotion.controller.app.point.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Schema(description = "用户 App - 积分商城活动的详细 Response VO") +@Data +public class AppPointActivityDetailRespVO { + + @Schema(description = "积分商城活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11373") + private Long id; + + @Schema(description = "积分商城活动商品", requiredMode = Schema.RequiredMode.REQUIRED, example = "19509") + private Long spuId; + + @Schema(description = "活动状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Integer status; + + @Schema(description = "积分商城活动库存(剩余库存积分兑换时扣减)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Integer stock; + + @Schema(description = "积分商城活动总库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Integer totalStock; + + @Schema(description = "备注", example = "你说的对") + private String remark; + + @Schema(description = "商品信息数组", requiredMode = Schema.RequiredMode.REQUIRED) + private List products; + + //======================= 显示所需兑换积分最少的 SKU 信息 ======================= + + @Schema(description = "兑换积分", requiredMode = Schema.RequiredMode.REQUIRED) + private Integer point; + + @Schema(description = "兑换金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "15860") + private Integer price; + + @Schema(description = "商品信息") + @Data + public static class Product { + + @Schema(description = "积分商城商品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "31718") + private Long id; + + @Schema(description = "商品 SKU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2736") + private Long skuId; + + @Schema(description = "可兑换数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "3926") + private Integer count; + + @Schema(description = "兑换积分", requiredMode = Schema.RequiredMode.REQUIRED) + private Integer point; + + @Schema(description = "兑换金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "15860") + private Integer price; + + @Schema(description = "积分商城商品库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") + private Integer stock; + + } + +} diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/vo/AppPointActivityPageReqVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/vo/AppPointActivityPageReqVO.java new file mode 100644 index 0000000..6a41195 --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/vo/AppPointActivityPageReqVO.java @@ -0,0 +1,15 @@ +package cn.iocoder.yudao.module.promotion.controller.app.point.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "用户 App - 积分商城活动分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class AppPointActivityPageReqVO extends PageParam { + +} diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/vo/AppPointActivityRespVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/vo/AppPointActivityRespVO.java new file mode 100644 index 0000000..29f4f97 --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/vo/AppPointActivityRespVO.java @@ -0,0 +1,67 @@ +package cn.iocoder.yudao.module.promotion.controller.app.point.vo; + +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "用户 App - 积分商城活动 Response VO") +@Data +public class AppPointActivityRespVO { + + @Schema(description = "积分商城活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11373") + @ExcelProperty("积分商城活动编号") + private Long id; + + @Schema(description = "积分商城活动商品", requiredMode = Schema.RequiredMode.REQUIRED, example = "19509") + @ExcelProperty("积分商城活动商品") + private Long spuId; + + @Schema(description = "活动状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @ExcelProperty("活动状态") + private Integer status; + + @Schema(description = "积分商城活动库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @ExcelProperty("积分商城活动库存") + private Integer stock; // 剩余库存积分兑换时扣减 + + @Schema(description = "积分商城活动总库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @ExcelProperty("积分商城活动总库存") + private Integer totalStock; + + // TODO @puhui999:只返回必要的字段,例如说 remark、sort、createTime 应该是不需要的呢。也可以看看别的也不需要哈。 + + @Schema(description = "备注", example = "你说的对") + @ExcelProperty("备注") + private String remark; + + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("排序") + private Integer sort; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + // ========== 商品字段 ========== + + @Schema(description = "商品名称", requiredMode = Schema.RequiredMode.REQUIRED, // 从 SPU 的 name 读取 + example = "618大促") + private String spuName; + @Schema(description = "商品主图", requiredMode = Schema.RequiredMode.REQUIRED, // 从 SPU 的 picUrl 读取 + example = "https://www.iocoder.cn/xx.png") + private String picUrl; + @Schema(description = "商品市场价,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, // 从 SPU 的 marketPrice 读取 + example = "50") + private Integer marketPrice; + + //======================= 显示所需兑换积分最少的 sku 信息 ======================= + + @Schema(description = "兑换积分", requiredMode = Schema.RequiredMode.REQUIRED) + private Integer point; + + @Schema(description = "兑换金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "15860") + private Integer price; + +} diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/point/PointActivityDO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/point/PointActivityDO.java new file mode 100644 index 0000000..c3345be --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/point/PointActivityDO.java @@ -0,0 +1,57 @@ +package cn.iocoder.yudao.module.promotion.dal.dataobject.point; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +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.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +/** + * 积分商城活动 DO + * + * @author HUIHUI + */ +@TableName(value = "promotion_point_activity", autoResultMap = true) +@KeySequence("promotion_point_activity_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PointActivityDO extends BaseDO { + + /** + * 积分商城活动编号 + */ + @TableId + private Long id; + /** + * 积分商城活动商品 + */ + private Long spuId; + /** + * 活动状态 + * + * 枚举 {@link CommonStatusEnum 对应的类} + */ + private Integer status; + /** + * 备注 + */ + private String remark; + /** + * 排序 + */ + private Integer sort; + + /** + * 积分商城活动库存(剩余库存积分兑换时扣减) + */ + private Integer stock; + /** + * 积分商城活动总库存 + */ + private Integer totalStock; + +} diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/point/PointProductDO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/point/PointProductDO.java new file mode 100644 index 0000000..041ac5a --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/point/PointProductDO.java @@ -0,0 +1,67 @@ +package cn.iocoder.yudao.module.promotion.dal.dataobject.point; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +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 + * + * 关联 {@link PointActivityDO#getId()} + */ + private Long activityId; + /** + * 商品 SPU 编号 + */ + private Long spuId; + /** + * 商品 SKU 编号 + */ + private Long skuId; + /** + * 可兑换次数 + */ + private Integer count; + /** + * 所需兑换积分 + */ + private Integer point; + /** + * 所需兑换金额,单位:分 + */ + private Integer price; + /** + * 积分商城商品库存 + */ + private Integer stock; + /** + * 积分商城商品状态 + * + * 枚举 {@link CommonStatusEnum 对应的类} + */ + private Integer activityStatus; + +} diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/point/PointActivityMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/point/PointActivityMapper.java new file mode 100644 index 0000000..d724c32 --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/point/PointActivityMapper.java @@ -0,0 +1,59 @@ +package cn.iocoder.yudao.module.promotion.dal.mysql.point; + +import cn.hutool.core.lang.Assert; +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.activity.PointActivityPageReqVO; +import cn.iocoder.yudao.module.promotion.dal.dataobject.point.PointActivityDO; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * 积分商城活动 Mapper + * + * @author HUIHUI + */ +@Mapper +public interface PointActivityMapper extends BaseMapperX { + + default PageResult selectPage(PointActivityPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(PointActivityDO::getSpuId, reqVO.getSpuId()) + .eqIfPresent(PointActivityDO::getStatus, reqVO.getStatus()) + .eqIfPresent(PointActivityDO::getRemark, reqVO.getRemark()) + .eqIfPresent(PointActivityDO::getSort, reqVO.getSort()) + .betweenIfPresent(PointActivityDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(PointActivityDO::getId)); + } + + /** + * 更新活动库存(减少) + * + * @param id 活动编号 + * @param count 扣减的库存数量(正数) + * @return 影响的行数 + */ + default int updateStockDecr(Long id, int count) { + Assert.isTrue(count > 0); + return update(null, new LambdaUpdateWrapper() + .eq(PointActivityDO::getId, id) + .ge(PointActivityDO::getStock, count) + .setSql("stock = stock - " + count)); + } + + /** + * 更新活动库存(增加) + * + * @param id 活动编号 + * @param count 增加的库存数量(正数) + * @return 影响的行数 + */ + default int updateStockIncr(Long id, int count) { + Assert.isTrue(count > 0); + return update(null, new LambdaUpdateWrapper() + .eq(PointActivityDO::getId, id) + .setSql("stock = stock + " + count)); + } + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/point/PointProductMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/point/PointProductMapper.java new file mode 100644 index 0000000..3c6d469 --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/point/PointProductMapper.java @@ -0,0 +1,66 @@ +package cn.iocoder.yudao.module.promotion.dal.mysql.point; + +import cn.hutool.core.lang.Assert; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +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 + * + * @author HUIHUI + */ +@Mapper +public interface PointProductMapper extends BaseMapperX { + + default List selectListByActivityId(Collection activityIds) { + return selectList(PointProductDO::getActivityId, activityIds); + } + + default List selectListByActivityId(Long activityId) { + return selectList(PointProductDO::getActivityId, activityId); + } + + default void updateByActivityId(PointProductDO pointProductDO) { + update(pointProductDO, new LambdaUpdateWrapper() + .eq(PointProductDO::getActivityId, pointProductDO.getActivityId())); + } + + default PointProductDO selectListByActivityIdAndSkuId(Long activityId, Long skuId) { + return selectOne(PointProductDO::getActivityId, activityId, + PointProductDO::getSkuId, skuId); + } + + /** + * 更新活动库存(减少) + * + * @param id 活动编号 + * @param count 扣减的库存数量(减少库存) + * @return 影响的行数 + */ + default int updateStockDecr(Long id, int count) { + Assert.isTrue(count > 0); + return update(null, new LambdaUpdateWrapper() + .eq(PointProductDO::getId, id) + .ge(PointProductDO::getStock, count) + .setSql("stock = stock - " + count)); + } + + /** + * 更新活动库存(增加) + * + * @param id 活动编号 + * @param count 需要增加的库存(增加库存) + * @return 影响的行数 + */ + default int updateStockIncr(Long id, int count) { + Assert.isTrue(count > 0); + return update(null, new LambdaUpdateWrapper() + .eq(PointProductDO::getId, id) + .setSql("stock = stock + " + count)); + } +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/point/PointActivityService.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/point/PointActivityService.java new file mode 100644 index 0000000..e27b227 --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/point/PointActivityService.java @@ -0,0 +1,112 @@ +package cn.iocoder.yudao.module.promotion.service.point; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.promotion.api.point.dto.PointValidateJoinRespDTO; +import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.activity.PointActivityPageReqVO; +import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.activity.PointActivitySaveReqVO; +import cn.iocoder.yudao.module.promotion.dal.dataobject.point.PointActivityDO; +import cn.iocoder.yudao.module.promotion.dal.dataobject.point.PointProductDO; + +import javax.validation.Valid; +import java.util.Collection; +import java.util.List; + +/** + * 积分商城活动 Service 接口 + * + * @author HUIHUI + */ +public interface PointActivityService { + + /** + * 创建积分商城活动 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createPointActivity(@Valid PointActivitySaveReqVO createReqVO); + + /** + * 更新积分商城活动 + * + * @param updateReqVO 更新信息 + */ + void updatePointActivity(@Valid PointActivitySaveReqVO updateReqVO); + + /** + * 更新积分商城商品库存(减少) + * + * @param id 活动编号 + * @param skuId sku 编号 + * @param count 数量(正数) + */ + void updatePointStockDecr(Long id, Long skuId, Integer count); + + /** + * 更新积分商城商品库存(增加) + * + * @param id 活动编号 + * @param skuId sku 编号 + * @param count 数量(正数) + */ + void updatePointStockIncr(Long id, Long skuId, Integer count); + + /** + * 关闭积分商城活动 + * + * @param id 编号 + */ + void closePointActivity(Long id); + + /** + * 删除积分商城活动 + * + * @param id 编号 + */ + void deletePointActivity(Long id); + + /** + * 获得积分商城活动 + * + * @param id 编号 + * @return 积分商城活动 + */ + PointActivityDO getPointActivity(Long id); + + /** + * 获得积分商城活动分页 + * + * @param pageReqVO 分页查询 + * @return 积分商城活动分页 + */ + PageResult getPointActivityPage(PointActivityPageReqVO pageReqVO); + + /** + * 获得积分商城活动列表 + * + * @param ids 活动编号 + * @return 积分商城活动列表 + */ + List getPointActivityListByIds(Collection ids); + + /** + * 获得活动商品 + * + * @param activityIds 活动编号 + * @return 获得活动商品 + */ + List getPointProductListByActivityIds(Collection activityIds); + + /** + * 【下单前】校验是否参与积分商城活动 + * + * 如果校验失败,则抛出业务异常 + * + * @param activityId 活动编号 + * @param skuId SKU 编号 + * @param count 数量 + * @return 积分商城商品信息 + */ + PointValidateJoinRespDTO validateJoinPointActivity(Long activityId, Long skuId, Integer count); + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/point/PointActivityServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/point/PointActivityServiceImpl.java new file mode 100644 index 0000000..697279c --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/point/PointActivityServiceImpl.java @@ -0,0 +1,309 @@ +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; +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.dto.ProductSpuRespDTO; +import cn.iocoder.yudao.module.promotion.api.point.dto.PointValidateJoinRespDTO; +import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.activity.PointActivityPageReqVO; +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 org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.util.Collection; +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.*; +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.*; +import static java.util.Collections.singletonList; + +/** + * 积分商城活动 Service 实现类 + * + * @author HUIHUI + */ +@Service +@Validated +public class PointActivityServiceImpl implements PointActivityService { + + @Resource + private PointActivityMapper pointActivityMapper; + @Resource + private PointProductMapper pointProductMapper; + + @Resource + private ProductSpuApi productSpuApi; + @Resource + private ProductSkuApi productSkuApi; + + private static List buildPointProductDO(PointActivityDO pointActivity, List products) { + return BeanUtils.toBean(products, PointProductDO.class, product -> + product.setSpuId(pointActivity.getSpuId()).setActivityId(pointActivity.getId()) + .setActivityStatus(pointActivity.getStatus())); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createPointActivity(PointActivitySaveReqVO createReqVO) { + // 1.1 校验商品是否存在 + validateProductExists(createReqVO.getSpuId(), createReqVO.getProducts()); + // 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(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updatePointActivity(PointActivitySaveReqVO updateReqVO) { + // 1.1 校验存在 + 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 + @Transactional(rollbackFor = Exception.class) + public void updatePointStockDecr(Long id, Long skuId, Integer count) { + // 1.1 校验活动库存是否充足 + PointActivityDO activity = validatePointActivityExists(id); + if (count > activity.getStock()) { + throw exception(POINT_ACTIVITY_UPDATE_STOCK_FAIL); + } + // 1.2 校验商品库存是否充足 + PointProductDO product = pointProductMapper.selectListByActivityIdAndSkuId(id, skuId); + if (product == null || count > product.getStock()) { + throw exception(POINT_ACTIVITY_UPDATE_STOCK_FAIL); + } + + // 2.1 更新活动商品库存 + int updateCount = pointProductMapper.updateStockDecr(product.getId(), count); + if (updateCount == 0) { + throw exception(POINT_ACTIVITY_UPDATE_STOCK_FAIL); + } + + // 2.2 更新活动库存 + updateCount = pointActivityMapper.updateStockDecr(id, count); + if (updateCount == 0) { + throw exception(POINT_ACTIVITY_UPDATE_STOCK_FAIL); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updatePointStockIncr(Long id, Long skuId, Integer count) { + PointProductDO product = pointProductMapper.selectListByActivityIdAndSkuId(id, skuId); + // 更新活动商品库存 + pointProductMapper.updateStockIncr(product.getId(), count); + // 更新活动库存 + pointActivityMapper.updateStockIncr(id, count); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void closePointActivity(Long id) { + // 校验存在 + PointActivityDO pointActivity = validatePointActivityExists(id); + if (CommonStatusEnum.DISABLE.getStatus().equals(pointActivity.getStatus())) { + throw exception(POINT_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED); + } + + // 更新 + 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 products) { + // 第一步,对比新老数据,获得添加、修改、删除的列表 + List newList = buildPointProductDO(activity, products); + List oldList = pointProductMapper.selectListByActivityId(activity.getId()); + List> 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 + @Transactional(rollbackFor = Exception.class) + public void deletePointActivity(Long 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 products = pointProductMapper.selectListByActivityId(id); + pointProductMapper.deleteByIds(convertSet(products, PointProductDO::getId)); + } + + private PointActivityDO validatePointActivityExists(Long id) { + PointActivityDO pointActivityDO = pointActivityMapper.selectById(id); + if (pointActivityDO == null) { + throw exception(POINT_ACTIVITY_NOT_EXISTS); + } + return pointActivityDO; + } + + /** + * 校验秒杀商品是否都存在 + * + * @param spuId 商品 SPU 编号 + * @param products 秒杀商品 + */ + private void validateProductExists(Long spuId, List products) { + // 1. 校验商品 spu 是否存在 + ProductSpuRespDTO spu = productSpuApi.getSpu(spuId); + if (spu == null) { + throw exception(SPU_NOT_EXISTS); + } + + // 2. 校验商品 sku 都存在 + List skus = productSkuApi.getSkuListBySpuId(singletonList(spuId)); + Map skuMap = convertMap(skus, ProductSkuRespDTO::getId); + products.forEach(product -> { + if (!skuMap.containsKey(product.getSkuId())) { + throw exception(SKU_NOT_EXISTS); + } + }); + } + + /** + * 校验商品是否冲突 + * + * @param id 编号 + * @param products 商品列表 + */ + private void validatePointActivityProductConflicts(Long id, List products) { + // 1.1 查询所有开启的积分商城活动 + List activityList = pointActivityMapper.selectList(PointActivityDO::getStatus, + CommonStatusEnum.ENABLE.getStatus()); + if (id != null) { // 更新时排除自己 + activityList.removeIf(item -> ObjectUtil.equal(item.getId(), id)); + } + // 1.2 查询活动下的所有商品 + List productList = pointProductMapper.selectListByActivityId( + convertList(activityList, PointActivityDO::getId)); + Map> 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); + } + + @Override + public PageResult getPointActivityPage(PointActivityPageReqVO pageReqVO) { + return pointActivityMapper.selectPage(pageReqVO); + } + + @Override + public List getPointActivityListByIds(Collection ids) { + return pointActivityMapper.selectList(PointActivityDO::getId, ids); + } + + @Override + public List getPointProductListByActivityIds(Collection activityIds) { + return pointProductMapper.selectListByActivityId(activityIds); + } + + @Override + public PointValidateJoinRespDTO validateJoinPointActivity(Long activityId, Long skuId, Integer count) { + // 1. 校验积分商城活动是否存在 + PointActivityDO activity = validatePointActivityExists(activityId); + if (CommonStatusEnum.isDisable(activity.getStatus())) { + throw exception(POINT_ACTIVITY_JOIN_ACTIVITY_STATUS_CLOSED); + } + + // 2.1 校验积分商城商品是否存在 + PointProductDO product = pointProductMapper.selectListByActivityIdAndSkuId(activityId, skuId); + if (product == null) { + throw exception(POINT_ACTIVITY_JOIN_ACTIVITY_PRODUCT_NOT_EXISTS); + } + // 2.2 超过单次购买限制 + if (count > product.getCount()) { + throw exception(POINT_ACTIVITY_JOIN_ACTIVITY_SINGLE_LIMIT_COUNT_EXCEED); + } + // 2.2 校验库存是否充足 + if (count > product.getStock()) { + throw exception(POINT_ACTIVITY_UPDATE_STOCK_FAIL); + } + return BeanUtils.toBean(product, PointValidateJoinRespDTO.class); + } + +} \ No newline at end of file