Commit 76f510f2471a6996d0bcabb13036a8d57edeca97

Authored by YunaiV
1 parent af0771a1

code review:优惠劵发放

yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java
@@ -27,8 +27,8 @@ public interface ErrorCodeConstants { @@ -27,8 +27,8 @@ public interface ErrorCodeConstants {
27 // ========== 优惠劵模板 1013004000 ========== 27 // ========== 优惠劵模板 1013004000 ==========
28 ErrorCode COUPON_TEMPLATE_NOT_EXISTS = new ErrorCode(1013004000, "优惠劵模板不存在"); 28 ErrorCode COUPON_TEMPLATE_NOT_EXISTS = new ErrorCode(1013004000, "优惠劵模板不存在");
29 ErrorCode COUPON_TEMPLATE_TOTAL_COUNT_TOO_SMALL = new ErrorCode(1013004001, "发放数量不能小于已领取数量({})"); 29 ErrorCode COUPON_TEMPLATE_TOTAL_COUNT_TOO_SMALL = new ErrorCode(1013004001, "发放数量不能小于已领取数量({})");
30 - ErrorCode COUPON_TEMPLATE_TASK_EMPTY = new ErrorCode(1013004002, "当前剩余数量不够领取");  
31 - ErrorCode COUPON_TEMPLATE_USER_TASKED = new ErrorCode(1013004003, "用户已领取过此优惠券"); 30 + ErrorCode COUPON_TEMPLATE_NOT_ENOUGH = new ErrorCode(1013004002, "当前剩余数量不够领取");
  31 + ErrorCode COUPON_TEMPLATE_USER_ALREADY_TAKE = new ErrorCode(1013004003, "用户已领取过此优惠券");
32 ErrorCode COUPON_TEMPLATE_EXPIRED = new ErrorCode(1013004004, "优惠券已过期"); 32 ErrorCode COUPON_TEMPLATE_EXPIRED = new ErrorCode(1013004004, "优惠券已过期");
33 ErrorCode COUPON_TEMPLATE_CANNOT_TAKE = new ErrorCode(1013004005, "领取方式不正确"); 33 ErrorCode COUPON_TEMPLATE_CANNOT_TAKE = new ErrorCode(1013004005, "领取方式不正确");
34 34
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/coupon/CouponTakeTypeEnum.java
@@ -14,7 +14,8 @@ import java.util.Arrays; @@ -14,7 +14,8 @@ import java.util.Arrays;
14 @AllArgsConstructor 14 @AllArgsConstructor
15 @Getter 15 @Getter
16 public enum CouponTakeTypeEnum implements IntArrayValuable { 16 public enum CouponTakeTypeEnum implements IntArrayValuable {
17 - COMMON(0, "通用"), 17 +
  18 + COMMON(0, "通用"), // TODO @疯狂:要不去掉“通用"和“兑换”,保持和 crmeb 一致;就手动领取、指定发送、新人券
18 BY_USER(1, "直接领取"), // 用户可在首页、每日领劵直接领取 19 BY_USER(1, "直接领取"), // 用户可在首页、每日领劵直接领取
19 BY_ADMIN(2, "指定发放"), // 后台指定会员赠送优惠劵 20 BY_ADMIN(2, "指定发放"), // 后台指定会员赠送优惠劵
20 BY_REGISTER(3, "新人券"), // 注册时自动领取 21 BY_REGISTER(3, "新人券"), // 注册时自动领取
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/CouponController.java
@@ -22,7 +22,6 @@ import org.springframework.web.bind.annotation.*; @@ -22,7 +22,6 @@ import org.springframework.web.bind.annotation.*;
22 import javax.annotation.Resource; 22 import javax.annotation.Resource;
23 import javax.validation.Valid; 23 import javax.validation.Valid;
24 import java.util.Map; 24 import java.util.Map;
25 -import java.util.Set;  
26 25
27 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 26 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
28 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; 27 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
@@ -65,9 +64,9 @@ public class CouponController { @@ -65,9 +64,9 @@ public class CouponController {
65 if (CollUtil.isEmpty(pageResulVO.getList())) { 64 if (CollUtil.isEmpty(pageResulVO.getList())) {
66 return success(pageResulVO); 65 return success(pageResulVO);
67 } 66 }
  67 +
68 // 读取用户信息,进行拼接 68 // 读取用户信息,进行拼接
69 - Set<Long> userIds = convertSet(pageResult.getList(), CouponDO::getUserId);  
70 - Map<Long, MemberUserRespDTO> userMap = memberUserApi.getUserMap(userIds); 69 + Map<Long, MemberUserRespDTO> userMap = memberUserApi.getUserMap(convertSet(pageResult.getList(), CouponDO::getUserId));
71 pageResulVO.getList().forEach(itemRespVO -> MapUtils.findAndThen(userMap, itemRespVO.getUserId(), 70 pageResulVO.getList().forEach(itemRespVO -> MapUtils.findAndThen(userMap, itemRespVO.getUserId(),
72 userRespDTO -> itemRespVO.setNickname(userRespDTO.getNickname()))); 71 userRespDTO -> itemRespVO.setNickname(userRespDTO.getNickname())));
73 return success(pageResulVO); 72 return success(pageResulVO);
@@ -80,4 +79,5 @@ public class CouponController { @@ -80,4 +79,5 @@ public class CouponController {
80 Boolean result = couponService.sendCoupon(reqVO.getTemplateId(), reqVO.getUserIds()); 79 Boolean result = couponService.sendCoupon(reqVO.getTemplateId(), reqVO.getUserIds());
81 return success(result); 80 return success(result);
82 } 81 }
  82 +
83 } 83 }
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/CouponTemplateController.java
@@ -79,6 +79,7 @@ public class CouponTemplateController { @@ -79,6 +79,7 @@ public class CouponTemplateController {
79 return success(CouponTemplateConvert.INSTANCE.convertPage(pageResult)); 79 return success(CouponTemplateConvert.INSTANCE.convertPage(pageResult));
80 } 80 }
81 81
  82 + // TODO @疯狂:是不是可以合并到 getCouponTemplatePage 接口,作为一个参数选择
82 @GetMapping("/can-take-page") 83 @GetMapping("/can-take-page")
83 @Operation(summary = "获得可用于领取的优惠劵模板分页") 84 @Operation(summary = "获得可用于领取的优惠劵模板分页")
84 @PreAuthorize("@ss.hasPermission('promotion:coupon-template:query')") 85 @PreAuthorize("@ss.hasPermission('promotion:coupon-template:query')")
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponConvert.java
1 package cn.iocoder.yudao.module.promotion.convert.coupon; 1 package cn.iocoder.yudao.module.promotion.convert.coupon;
2 2
3 -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;  
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO; 4 import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO;
6 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageItemRespVO; 5 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageItemRespVO;
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java
@@ -55,6 +55,7 @@ public interface CouponMapper extends BaseMapperX&lt;CouponDO&gt; { @@ -55,6 +55,7 @@ public interface CouponMapper extends BaseMapperX&lt;CouponDO&gt; {
55 .eq(CouponDO::getStatus, status)); 55 .eq(CouponDO::getStatus, status));
56 } 56 }
57 57
  58 + // TODO @疯狂:要 selectList 哈;
58 default List<CouponDO> selectByTemplateIdAndUserId(Long templateId, Collection<Long> userIds) { 59 default List<CouponDO> selectByTemplateIdAndUserId(Long templateId, Collection<Long> userIds) {
59 return selectList(new LambdaQueryWrapperX<CouponDO>() 60 return selectList(new LambdaQueryWrapperX<CouponDO>()
60 .eq(CouponDO::getTemplateId, templateId) 61 .eq(CouponDO::getTemplateId, templateId)
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java
@@ -34,17 +34,11 @@ public interface CouponTemplateMapper extends BaseMapperX&lt;CouponTemplateDO&gt; { @@ -34,17 +34,11 @@ public interface CouponTemplateMapper extends BaseMapperX&lt;CouponTemplateDO&gt; {
34 default PageResult<CouponTemplateDO> selectCanTakePage(CouponTemplatePageReqVO reqVO, Collection<Integer> takeTypes) { 34 default PageResult<CouponTemplateDO> selectCanTakePage(CouponTemplatePageReqVO reqVO, Collection<Integer> takeTypes) {
35 // 构建可领取的查询条件, 好啰嗦 ( ╯-_-)╯┴—┴ 35 // 构建可领取的查询条件, 好啰嗦 ( ╯-_-)╯┴—┴
36 Consumer<LambdaQueryWrapper<CouponTemplateDO>> canTakeConsumer = w -> 36 Consumer<LambdaQueryWrapper<CouponTemplateDO>> canTakeConsumer = w ->
37 - // 1.状态为可用的  
38 - w.eq(CouponTemplateDO::getStatus, CommonStatusEnum.ENABLE.getStatus())  
39 - // 2.领取方式一致  
40 - .in(CouponTemplateDO::getTakeType, takeTypes)  
41 - // 3.未过期  
42 - .and(ww -> ww.isNull(CouponTemplateDO::getValidEndTime)  
43 - .or()  
44 - .gt(CouponTemplateDO::getValidEndTime, LocalDateTime.now()))  
45 - // 4.剩余数量大于0  
46 - .apply(" take_count < total_count ");  
47 - 37 + w.eq(CouponTemplateDO::getStatus, CommonStatusEnum.ENABLE.getStatus()) // 1. 状态为可用的
  38 + .in(CouponTemplateDO::getTakeType, takeTypes) // 2. 领取方式一致
  39 + .and(ww -> ww.isNull(CouponTemplateDO::getValidEndTime) // 3. 未过期
  40 + .or().gt(CouponTemplateDO::getValidEndTime, LocalDateTime.now()))
  41 + .apply(" take_count < total_count "); // 4. 剩余数量大于 0
48 return selectPage(reqVO, new LambdaQueryWrapperX<CouponTemplateDO>() 42 return selectPage(reqVO, new LambdaQueryWrapperX<CouponTemplateDO>()
49 .likeIfPresent(CouponTemplateDO::getName, reqVO.getName()) 43 .likeIfPresent(CouponTemplateDO::getName, reqVO.getName())
50 .eqIfPresent(CouponTemplateDO::getDiscountType, reqVO.getDiscountType()) 44 .eqIfPresent(CouponTemplateDO::getDiscountType, reqVO.getDiscountType())
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java
@@ -78,18 +78,20 @@ public interface CouponService { @@ -78,18 +78,20 @@ public interface CouponService {
78 */ 78 */
79 Long getUnusedCouponCount(Long userId); 79 Long getUnusedCouponCount(Long userId);
80 80
  81 + // TODO @疯狂:可以返回 void;因为都是 true = =
81 /** 82 /**
82 * 领取优惠券 83 * 领取优惠券
83 * 84 *
84 * @param templateId 优惠券模板编号 85 * @param templateId 优惠券模板编号
85 * @param userIds 用户编号列表 86 * @param userIds 用户编号列表
86 - * @param takType 领取方式 87 + * @param takeType 领取方式
87 * @return 领取结果 88 * @return 领取结果
88 */ 89 */
89 - Boolean takeCoupon(Long templateId, Set<Long> userIds, CouponTakeTypeEnum takType); 90 + Boolean takeCoupon(Long templateId, Set<Long> userIds, CouponTakeTypeEnum takeType);
90 91
  92 + // TODO @疯狂:感觉 3 个方法的命名,改成 takeCouponByAdmin;takeCouponByUser;takeCouponByRegister 会更容易理解哈;现在两个都叫 sendCoupon ,感觉不太好懂
91 /** 93 /**
92 - * 【管理员】 给用户发送优惠券 94 + * 【管理员】给用户发送优惠券
93 * 95 *
94 * @param templateId 优惠券模板编号 96 * @param templateId 优惠券模板编号
95 * @param userIds 用户编号列表 97 * @param userIds 用户编号列表
@@ -100,7 +102,7 @@ public interface CouponService { @@ -100,7 +102,7 @@ public interface CouponService {
100 } 102 }
101 103
102 /** 104 /**
103 - * 【会员】 领取优惠券 105 + * 【会员】领取优惠券
104 * 106 *
105 * @param templateId 优惠券模板编号 107 * @param templateId 优惠券模板编号
106 * @param userId 用户编号 108 * @param userId 用户编号
@@ -111,7 +113,7 @@ public interface CouponService { @@ -111,7 +113,7 @@ public interface CouponService {
111 } 113 }
112 114
113 /** 115 /**
114 - * 【系统】 给用户发送新人券 116 + * 【系统】给用户发送新人券
115 * 117 *
116 * @param templateId 优惠券模板编号 118 * @param templateId 优惠券模板编号
117 * @param userId 用户编号列表 119 * @param userId 用户编号列表
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
@@ -134,20 +134,20 @@ public class CouponServiceImpl implements CouponService { @@ -134,20 +134,20 @@ public class CouponServiceImpl implements CouponService {
134 } 134 }
135 135
136 @Override 136 @Override
137 - public Boolean takeCoupon(Long templateId, Set<Long> userIds, CouponTakeTypeEnum takType) { 137 + public Boolean takeCoupon(Long templateId, Set<Long> userIds, CouponTakeTypeEnum takeType) {
  138 + // 1. 校验并过滤用户
138 CouponTemplateDO template = couponTemplateService.getCouponTemplate(templateId); 139 CouponTemplateDO template = couponTemplateService.getCouponTemplate(templateId);
139 - // 校验并过滤用户  
140 - userIds = validateAndFilterTakeUserId(template, userIds, takType); 140 + userIds = validateAndFilterTakeUserId(template, userIds, takeType);
141 141
  142 + // 2. 批量保存优惠劵
  143 + // TODO @疯狂:这里可以使用 CollectionUtils.convertList 更简洁;stream 可以简化很多代码,常用的 stream 操作,使用 util 可以进一步简洁,同时提升可读性
142 List<CouponDO> couponList = userIds.stream() 144 List<CouponDO> couponList = userIds.stream()
143 .map(userId -> CouponConvert.INSTANCE.convert(template, userId)) 145 .map(userId -> CouponConvert.INSTANCE.convert(template, userId))
144 .collect(Collectors.toList()); 146 .collect(Collectors.toList());
145 - // 批量保存  
146 couponMapper.insertBatch(couponList); 147 couponMapper.insertBatch(couponList);
147 148
148 - // 增加优惠劵模板的领取数量 149 + // 3. 增加优惠劵模板的领取数量
149 couponTemplateService.updateCouponTemplateTakeCount(templateId, userIds.size()); 150 couponTemplateService.updateCouponTemplateTakeCount(templateId, userIds.size());
150 -  
151 return true; 151 return true;
152 } 152 }
153 153
@@ -159,55 +159,48 @@ public class CouponServiceImpl implements CouponService { @@ -159,55 +159,48 @@ public class CouponServiceImpl implements CouponService {
159 * @param takeType 领取方式 159 * @param takeType 领取方式
160 * @return 可领取此券的用户列表 160 * @return 可领取此券的用户列表
161 */ 161 */
  162 + // TODO @疯狂:我建议哈,校验模版,和过滤用户分成两个方法;混在一起,有点小重,后续单测可能也比较难写哈;
162 private Set<Long> validateAndFilterTakeUserId(CouponTemplateDO couponTemplate, Set<Long> userIds, CouponTakeTypeEnum takeType) { 163 private Set<Long> validateAndFilterTakeUserId(CouponTemplateDO couponTemplate, Set<Long> userIds, CouponTakeTypeEnum takeType) {
163 - // 校验模板 164 + // 1.1 校验模板
164 if (couponTemplate == null) { 165 if (couponTemplate == null) {
165 throw exception(COUPON_TEMPLATE_NOT_EXISTS); 166 throw exception(COUPON_TEMPLATE_NOT_EXISTS);
166 } 167 }
167 -  
168 - if (couponTemplate.getTotalCount() > 0) {  
169 - // 校验剩余数量  
170 - if (couponTemplate.getTakeCount() + userIds.size() > couponTemplate.getTotalCount()) {  
171 - throw exception(COUPON_TEMPLATE_TASK_EMPTY);  
172 - } 168 + // 1.2 校验剩余数量
  169 + if (couponTemplate.getTakeCount() + userIds.size() > couponTemplate.getTotalCount()) {
  170 + throw exception(COUPON_TEMPLATE_NOT_ENOUGH);
173 } 171 }
174 -  
175 - // 校验"固定日期"的有效期类型是否过期 172 + // 1.3 校验"固定日期"的有效期类型是否过期
176 if (CouponTemplateValidityTypeEnum.DATE.getType().equals(couponTemplate.getValidityType())) { 173 if (CouponTemplateValidityTypeEnum.DATE.getType().equals(couponTemplate.getValidityType())) {
177 if (LocalDateTimeUtils.beforeNow(couponTemplate.getValidEndTime())) { 174 if (LocalDateTimeUtils.beforeNow(couponTemplate.getValidEndTime())) {
178 throw exception(COUPON_TEMPLATE_EXPIRED); 175 throw exception(COUPON_TEMPLATE_EXPIRED);
179 } 176 }
180 } 177 }
181 -  
182 - // 校验领取方式 178 + // 1.4 校验领取方式
  179 + // TODO @疯狂:如果要做这样的判断,使用 !ObjectUtils.equalsAny() 会更简洁
183 if (!CouponTakeTypeEnum.COMMON.getValue().equals(couponTemplate.getTakeType())) { 180 if (!CouponTakeTypeEnum.COMMON.getValue().equals(couponTemplate.getTakeType())) {
184 if (ObjectUtil.notEqual(couponTemplate.getTakeType(), takeType.getValue())) { 181 if (ObjectUtil.notEqual(couponTemplate.getTakeType(), takeType.getValue())) {
185 throw exception(COUPON_TEMPLATE_CANNOT_TAKE); 182 throw exception(COUPON_TEMPLATE_CANNOT_TAKE);
186 } 183 }
187 } 184 }
188 185
189 - // 获取领取过此券的用户  
190 - List<CouponDO> takedList = couponMapper.selectByTemplateIdAndUserId(couponTemplate.getId(), userIds);  
191 - 186 + // 2.1 过滤掉,已经领取到上限的用户
  187 + List<CouponDO> alreadyTakeCoupons = couponMapper.selectByTemplateIdAndUserId(couponTemplate.getId(), userIds);
192 // 校验新人券 188 // 校验新人券
  189 + // TODO @疯狂:我在想,这个判断,是不是和下面的 couponTemplate.getTakeLimitCount() > 0 冗余了;可以先都过滤,然后最终去判断 userIds 是不是空了;
193 if (CouponTakeTypeEnum.BY_REGISTER.equals(takeType)) { 190 if (CouponTakeTypeEnum.BY_REGISTER.equals(takeType)) {
194 - if (!takedList.isEmpty()) {  
195 - throw exception(COUPON_TEMPLATE_USER_TASKED); 191 + if (!alreadyTakeCoupons.isEmpty()) {
  192 + throw exception(COUPON_TEMPLATE_USER_ALREADY_TAKE);
196 } 193 }
197 } 194 }
198 -  
199 // 校验领取数量限制 195 // 校验领取数量限制
200 if (couponTemplate.getTakeLimitCount() > 0) { 196 if (couponTemplate.getTakeLimitCount() > 0) {
201 - // 统计用户的领取数量  
202 - Map<Long, Integer> userTakeCountMap = CollStreamUtil.groupBy(takedList, CouponDO::getUserId, Collectors.summingInt(c -> 1));  
203 - //过滤掉达到领取数量限制的用户 197 + Map<Long, Integer> userTakeCountMap = CollStreamUtil.groupBy(alreadyTakeCoupons, CouponDO::getUserId, Collectors.summingInt(c -> 1));
204 userIds.removeIf(userId -> MapUtil.getInt(userTakeCountMap, userId, 0) >= couponTemplate.getTakeLimitCount()); 198 userIds.removeIf(userId -> MapUtil.getInt(userTakeCountMap, userId, 0) >= couponTemplate.getTakeLimitCount());
205 - // 用户全部领取过此优惠券 199 + // 2.2 如果所有用户都领取过,则抛出异常
206 if (userIds.isEmpty()) { 200 if (userIds.isEmpty()) {
207 - throw exception(COUPON_TEMPLATE_USER_TASKED); 201 + throw exception(COUPON_TEMPLATE_USER_ALREADY_TAKE);
208 } 202 }
209 } 203 }
210 -  
211 return userIds; 204 return userIds;
212 } 205 }
213 206