Commit d2c5eaf916bd0d1f4d25fae1b20bf8b90545f9dd

Authored by owen
1 parent aaa01de2

优惠券:重构是否领取检查方式

yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponTemplateController.java
1 1 package cn.iocoder.yudao.module.promotion.controller.app.coupon;
2 2  
3   -import cn.hutool.core.util.ObjUtil;
4 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 5 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
... ... @@ -25,11 +24,9 @@ import org.springframework.web.bind.annotation.RequestParam;
25 24 import org.springframework.web.bind.annotation.RestController;
26 25  
27 26 import javax.annotation.Resource;
28   -import java.time.LocalDateTime;
29 27 import java.util.*;
30 28  
31 29 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
32   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
33 30 import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
34 31  
35 32 @Tag(name = "用户 App - 优惠劵模板")
... ... @@ -46,7 +43,6 @@ public class AppCouponTemplateController {
46 43 @Resource
47 44 private ProductSpuApi productSpuApi;
48 45  
49   - // TODO 疯狂:这里应该还有个 list 接口哈;获得优惠劵模版列表,用于首页、商品页的优惠劵
50 46 @GetMapping("/list")
51 47 @Operation(summary = "获得优惠劵模版列表")
52 48 @Parameters({
... ... @@ -56,46 +52,28 @@ public class AppCouponTemplateController {
56 52 })
57 53 public CommonResult<List<AppCouponTemplateRespVO>> getCouponTemplateList(
58 54 @RequestParam(value = "spuId", required = false) Long spuId,
59   - @RequestParam(value = "useType", required = false) Integer useType,
  55 + @RequestParam(value = "productScope", required = false) Integer productScope,
60 56 @RequestParam(value = "count", required = false, defaultValue = "10") Integer count) {
61   - List<AppCouponTemplateRespVO> list = new ArrayList<>();
62   - Random random = new Random();
63   - for (int i = 0; i < 10; i++) {
64   - AppCouponTemplateRespVO vo = new AppCouponTemplateRespVO();
65   - vo.setId(i + 1L);
66   - vo.setName("优惠劵" + (i + 1));
67   - vo.setTakeLimitCount(random.nextInt(10) + 1);
68   - vo.setUsePrice(random.nextInt(100) * 100);
69   - vo.setValidityType(random.nextInt(2) + 1);
70   - if (vo.getValidityType() == 1) {
71   - vo.setValidStartTime(LocalDateTime.now().plusDays(random.nextInt(10)));
72   - vo.setValidEndTime(LocalDateTime.now().plusDays(random.nextInt(20) + 10));
73   - } else {
74   - vo.setFixedStartTerm(random.nextInt(10));
75   - vo.setFixedEndTerm(random.nextInt(10) + vo.getFixedStartTerm() + 1);
76   - }
77   - vo.setDiscountType(random.nextInt(2) + 1);
78   - if (vo.getDiscountType() == 1) {
79   - vo.setDiscountPercent(null);
80   - vo.setDiscountPrice(random.nextInt(50) * 100);
81   - vo.setDiscountLimitPrice(null);
82   - } else {
83   - vo.setDiscountPercent(random.nextInt(90) + 10);
84   - vo.setDiscountPrice(null);
85   - vo.setDiscountLimitPrice(random.nextInt(200) * 100);
86   - }
87   - // TODO @疯狂:是否已领取,要不在 TemplateService 搞个 static 方法,让它基于 countMap 这种去计算,这样好点?
88   - vo.setTakeStatus(random.nextBoolean());
89   - list.add(vo);
90   - }
91   - return success(list);
  57 + // 1.1 处理查询条件:商品范围编号
  58 + Long productScopeValue = getProductScopeValue(productScope, spuId);
  59 + // 1.2 处理查询条件:领取方式 = 直接领取
  60 + List<Integer> canTakeTypes = Collections.singletonList(CouponTakeTypeEnum.USER.getValue());
  61 +
  62 + // 2. 查询
  63 + List<CouponTemplateDO> list = couponTemplateService.getCouponTemplateList(canTakeTypes, productScope,
  64 + productScopeValue, count);
  65 +
  66 + // 3.1 领取数量
  67 + Map<Long, Boolean> canCanTakeMap = couponService.getUserCanCanTakeMap(getLoginUserId(), list);
  68 + // 3.2 拼接返回
  69 + return success(CouponTemplateConvert.INSTANCE.convertAppList(list, canCanTakeMap));
92 70 }
93 71  
94 72 @GetMapping("/page")
95 73 @Operation(summary = "获得优惠劵模版分页")
96 74 public CommonResult<PageResult<AppCouponTemplateRespVO>> getCouponTemplatePage(AppCouponTemplatePageReqVO pageReqVO) {
97 75 // 1.1 处理查询条件:商品范围编号
98   - Long productScopeValue = getProductScopeValue(pageReqVO);
  76 + Long productScopeValue = getProductScopeValue(pageReqVO.getProductScope(), pageReqVO.getSpuId());
99 77 // 1.2 处理查询条件:领取方式 = 直接领取
100 78 List<Integer> canTakeTypes = Collections.singletonList(CouponTakeTypeEnum.USER.getValue());
101 79  
... ... @@ -104,35 +82,30 @@ public class AppCouponTemplateController {
104 82 CouponTemplateConvert.INSTANCE.convert(pageReqVO, canTakeTypes, pageReqVO.getProductScope(), productScopeValue));
105 83  
106 84 // 3.1 领取数量
107   - Map<Long, Integer> couponTakeCountMap = new HashMap<>(0);
108   - Long userId = getLoginUserId();
109   - if (userId != null) {
110   - List<Long> templateIds = convertList(pageResult.getList(), CouponTemplateDO::getId,
111   - t -> ObjUtil.notEqual(t.getTakeLimitCount(), -1)); // 只查有设置“每人限领个数”的
112   - couponTakeCountMap = couponService.getTakeCountMapByTemplateIds(templateIds, userId);
113   - }
  85 + Map<Long, Boolean> canCanTakeMap = couponService.getUserCanCanTakeMap(getLoginUserId(), pageResult.getList());
114 86 // 3.2 拼接返回
115   - return success(CouponTemplateConvert.INSTANCE.convertAppPage(pageResult, couponTakeCountMap));
  87 + return success(CouponTemplateConvert.INSTANCE.convertAppPage(pageResult, canCanTakeMap));
116 88 }
117 89  
118 90 /**
119   - * 获得分页查询的商品范围
  91 + * 获得商品的使用范围编号
120 92 *
121   - * @param pageReqVO 分页查询
122   - * @return 商品范围
  93 + * @param productScope 商品范围
  94 + * @param spuId 商品 SPU 编号
  95 + * @return 商品范围编号
123 96 */
124   - private Long getProductScopeValue(AppCouponTemplatePageReqVO pageReqVO) {
125   - // 通用券:清除商品范围
126   - if (pageReqVO.getProductScope() == null || ObjectUtils.equalsAny(pageReqVO.getProductScope(), PromotionProductScopeEnum.ALL.getScope(), null)) {
  97 + private Long getProductScopeValue(Integer productScope, Long spuId) {
  98 + // 通用券:没有商品范围
  99 + if (productScope == null || ObjectUtils.equalsAny(productScope, PromotionProductScopeEnum.ALL.getScope(), null)) {
127 100 return null;
128 101 }
129   - // 品类券:查询商品的品类
130   - if (Objects.equals(pageReqVO.getProductScope(), PromotionProductScopeEnum.CATEGORY.getScope()) && pageReqVO.getSpuId() != null) {
131   - return Optional.ofNullable(productSpuApi.getSpu(pageReqVO.getSpuId()))
  102 + // 品类券:查询商品的品类编号
  103 + if (Objects.equals(productScope, PromotionProductScopeEnum.CATEGORY.getScope()) && spuId != null) {
  104 + return Optional.ofNullable(productSpuApi.getSpu(spuId))
132 105 .map(ProductSpuRespDTO::getCategoryId).orElse(null);
133 106 }
134 107 // 商品卷:直接返回
135   - return pageReqVO.getSpuId();
  108 + return spuId;
136 109 }
137 110  
138 111 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/template/AppCouponTemplateRespVO.java
... ... @@ -55,7 +55,7 @@ public class AppCouponTemplateRespVO {
55 55  
56 56 // ========== 用户相关字段 ==========
57 57  
58   - @Schema(description = "是否已领取", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
59   - private Boolean takeStatus;
  58 + @Schema(description = "是否可以领取", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
  59 + private Boolean canTake;
60 60  
61 61 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponTemplateConvert.java
... ... @@ -37,21 +37,25 @@ public interface CouponTemplateConvert {
37 37  
38 38 PageResult<AppCouponTemplateRespVO> convertAppPage(PageResult<CouponTemplateDO> pageResult);
39 39  
40   - default PageResult<AppCouponTemplateRespVO> convertAppPage(PageResult<CouponTemplateDO> pageResult, Map<Long, Integer> couponTakeCountMap) {
  40 + List<AppCouponTemplateRespVO> convertAppList(List<CouponTemplateDO> list);
  41 +
  42 + default PageResult<AppCouponTemplateRespVO> convertAppPage(PageResult<CouponTemplateDO> pageResult, Map<Long, Boolean> userCanTakeMap) {
41 43 PageResult<AppCouponTemplateRespVO> result = convertAppPage(pageResult);
42   - if (MapUtil.isEmpty(couponTakeCountMap)) {
43   - return result;
44   - }
45   - for (AppCouponTemplateRespVO template : result.getList()) {
46   - // 每人领取数量无限制
47   - if (template.getTakeLimitCount() == -1) {
48   - template.setTakeStatus(false);
49   - continue;
50   - }
  44 + copyTo(result.getList(), userCanTakeMap);
  45 + return result;
  46 + }
  47 +
  48 + default List<AppCouponTemplateRespVO> convertAppList(List<CouponTemplateDO> list, Map<Long, Boolean> userCanTakeMap) {
  49 + List<AppCouponTemplateRespVO> result = convertAppList(list);
  50 + copyTo(result, userCanTakeMap);
  51 + return result;
  52 + }
  53 +
  54 + default void copyTo(List<AppCouponTemplateRespVO> list, Map<Long, Boolean> userCanTakeMap) {
  55 + for (AppCouponTemplateRespVO template : list) {
51 56 // 检查已领取数量是否超过限领数量
52   - template.setTakeStatus(MapUtil.getInt(couponTakeCountMap, template.getId(), 0) >= template.getTakeLimitCount());
  57 + template.setCanTake(MapUtil.getBool(userCanTakeMap, template.getId(), false));
53 58 }
54   - return result;
55 59 }
56 60  
57 61 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java
... ... @@ -24,16 +24,8 @@ import java.util.function.Consumer;
24 24 public interface CouponTemplateMapper extends BaseMapperX<CouponTemplateDO> {
25 25  
26 26 default PageResult<CouponTemplateDO> selectPage(CouponTemplatePageReqVO reqVO) {
27   - // 构建可领取的查询条件, 好啰嗦 ( ╯-_-)╯┴—┴
28   - Consumer<LambdaQueryWrapper<CouponTemplateDO>> canTakeConsumer = null;
29   - if (CollUtil.isNotEmpty(reqVO.getCanTakeTypes())) {
30   - canTakeConsumer = w ->
31   - w.eq(CouponTemplateDO::getStatus, CommonStatusEnum.ENABLE.getStatus()) // 1. 状态为可用的
32   - .in(CouponTemplateDO::getTakeType, reqVO.getCanTakeTypes()) // 2. 领取方式一致
33   - .and(ww -> ww.isNull(CouponTemplateDO::getValidEndTime) // 3. 未过期
34   - .or().gt(CouponTemplateDO::getValidEndTime, LocalDateTime.now()))
35   - .apply(" take_count < total_count "); // 4. 剩余数量大于 0
36   - }
  27 + // 构建可领取的查询条件
  28 + Consumer<LambdaQueryWrapper<CouponTemplateDO>> canTakeConsumer = buildCanTakeQueryConsumer(reqVO.getCanTakeTypes());
37 29 // 执行分页查询
38 30 return selectPage(reqVO, new LambdaQueryWrapperX<CouponTemplateDO>()
39 31 .likeIfPresent(CouponTemplateDO::getName, reqVO.getName())
... ... @@ -52,4 +44,30 @@ public interface CouponTemplateMapper extends BaseMapperX&lt;CouponTemplateDO&gt; {
52 44 default List<CouponTemplateDO> selectListByTakeType(Integer takeType) {
53 45 return selectList(CouponTemplateDO::getTakeType, takeType);
54 46 }
  47 +
  48 + default List<CouponTemplateDO> selectList(List<Integer> canTakeTypes, Integer productScope, Long productScopeValue, Integer count) {
  49 + // 构建可领取的查询条件
  50 + Consumer<LambdaQueryWrapper<CouponTemplateDO>> canTakeConsumer = buildCanTakeQueryConsumer(canTakeTypes);
  51 + return selectList(new LambdaQueryWrapperX<CouponTemplateDO>()
  52 + .eqIfPresent(CouponTemplateDO::getProductScope, productScope)
  53 + .and(productScopeValue != null, w -> w.apply("FIND_IN_SET({0}, product_scope_values)",
  54 + productScopeValue))
  55 + .and(canTakeConsumer != null, canTakeConsumer)
  56 + .last(" LIMIT " + count)
  57 + .orderByDesc(CouponTemplateDO::getId));
  58 + }
  59 +
  60 + static Consumer<LambdaQueryWrapper<CouponTemplateDO>> buildCanTakeQueryConsumer(List<Integer> canTakeTypes) {
  61 + Consumer<LambdaQueryWrapper<CouponTemplateDO>> canTakeConsumer = null;
  62 + if (CollUtil.isNotEmpty(canTakeTypes)) {
  63 + canTakeConsumer = w ->
  64 + w.eq(CouponTemplateDO::getStatus, CommonStatusEnum.ENABLE.getStatus()) // 1. 状态为可用的
  65 + .in(CouponTemplateDO::getTakeType, canTakeTypes) // 2. 领取方式一致
  66 + .and(ww -> ww.isNull(CouponTemplateDO::getValidEndTime) // 3. 未过期
  67 + .or().gt(CouponTemplateDO::getValidEndTime, LocalDateTime.now()))
  68 + .apply(" take_count < total_count "); // 4. 剩余数量大于 0
  69 + }
  70 + return canTakeConsumer;
  71 + }
  72 +
55 73 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java
... ... @@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 5 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
6 6 import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchReqVO;
7 7 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
  8 +import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
8 9 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
9 10 import cn.iocoder.yudao.module.promotion.service.coupon.bo.CouponTakeCountBO;
10 11  
... ... @@ -175,4 +176,13 @@ public interface CouponService {
175 176 */
176 177 int expireCoupon();
177 178  
  179 + /**
  180 + * 获取用户是否可以领取优惠券
  181 + *
  182 + * @param userId 用户编号
  183 + * @param templates 优惠券列表
  184 + * @return 是否可以领取
  185 + */
  186 + Map<Long, Boolean> getUserCanCanTakeMap(Long userId, List<CouponTemplateDO> templates);
  187 +
178 188 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
... ... @@ -36,7 +36,7 @@ import java.util.Set;
36 36 import java.util.stream.Collectors;
37 37  
38 38 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
39   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  39 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
40 40 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
41 41 import static java.util.Arrays.asList;
42 42  
... ... @@ -229,6 +229,29 @@ public class CouponServiceImpl implements CouponService {
229 229 return count;
230 230 }
231 231  
  232 + @Override
  233 + public Map<Long, Boolean> getUserCanCanTakeMap(Long userId, List<CouponTemplateDO> templates) {
  234 + Map<Long, Boolean> userCanTakeMap = convertMap(templates, CouponTemplateDO::getId, templateId -> true);
  235 + // 未登录时,都显示可以领取
  236 + if (userId == null) {
  237 + return userCanTakeMap;
  238 + }
  239 +
  240 + // 过滤领取数量无限制的
  241 + Set<Long> templateIds = convertSet(templates, CouponTemplateDO::getId, template -> template.getTakeLimitCount() != -1);
  242 +
  243 + // 检查用户领取的数量是否超过限制
  244 + if (CollUtil.isNotEmpty(templateIds)) {
  245 + Map<Long, Integer> couponTakeCountMap = this.getTakeCountMapByTemplateIds(templateIds, userId);
  246 + for (CouponTemplateDO template : templates) {
  247 + Integer takeCount = couponTakeCountMap.get(template.getId());
  248 + userCanTakeMap.put(template.getId(), takeCount == null || takeCount < template.getTakeLimitCount());
  249 + }
  250 + }
  251 +
  252 + return userCanTakeMap;
  253 + }
  254 +
232 255 /**
233 256 * 过期单个优惠劵
234 257 *
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateService.java
... ... @@ -78,4 +78,17 @@ public interface CouponTemplateService {
78 78 * @return 优惠券模板列表
79 79 */
80 80 List<CouponTemplateDO> getCouponTemplateByTakeType(CouponTakeTypeEnum takeType);
  81 +
  82 + /**
  83 + * 获得优惠券模板列表
  84 + *
  85 + * @param canTakeTypes 可领取的类型列表
  86 + * @param productScope 商品使用范围类型
  87 + * @param productScopeValue 商品使用范围编号
  88 + * @param count 查询数量
  89 + * @return 优惠券模板列表
  90 + */
  91 + List<CouponTemplateDO> getCouponTemplateList(List<Integer> canTakeTypes, Integer productScope,
  92 + Long productScopeValue, Integer count);
  93 +
81 94 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateServiceImpl.java
... ... @@ -120,4 +120,10 @@ public class CouponTemplateServiceImpl implements CouponTemplateService {
120 120 return couponTemplateMapper.selectListByTakeType(takeType.getValue());
121 121 }
122 122  
  123 + @Override
  124 + public List<CouponTemplateDO> getCouponTemplateList(List<Integer> canTakeTypes, Integer productScope,
  125 + Long productScopeValue, Integer count) {
  126 + return couponTemplateMapper.selectList(canTakeTypes, productScope, productScopeValue, count);
  127 + }
  128 +
123 129 }
... ...