package cn.lili.modules.promotion.serviceimpl; import cn.hutool.json.JSONArray; import cn.hutool.json.JSONUtil; import cn.lili.cache.Cache; import cn.lili.cache.CachePrefix; import cn.lili.common.enums.PromotionTypeEnum; import cn.lili.common.enums.ResultCode; import cn.lili.common.exception.ServiceException; import cn.lili.common.properties.RocketmqCustomProperties; import cn.lili.common.security.AuthUser; import cn.lili.modules.member.service.MemberService; import cn.lili.modules.promotion.entity.dos.Coupon; import cn.lili.modules.promotion.entity.dos.CouponActivity; import cn.lili.modules.promotion.entity.dos.CouponActivityItem; import cn.lili.modules.promotion.entity.dos.MemberCoupon; import cn.lili.modules.promotion.entity.dto.CouponActivityDTO; import cn.lili.modules.promotion.entity.dto.CouponActivityTrigger; import cn.lili.modules.promotion.entity.enums.*; import cn.lili.modules.promotion.entity.vos.CouponActivityItemVO; import cn.lili.modules.promotion.entity.vos.CouponActivityVO; import cn.lili.modules.promotion.mapper.CouponActivityMapper; import cn.lili.modules.promotion.service.*; import cn.lili.modules.promotion.tools.PromotionTools; import cn.lili.trigger.enums.DelayTypeEnums; import cn.lili.trigger.interfaces.TimeTrigger; import cn.lili.trigger.message.CouponActivityMessage; import cn.lili.trigger.model.TimeExecuteConstant; import cn.lili.trigger.model.TimeTriggerMsg; import cn.lili.trigger.util.DelayQueueTools; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * 优惠券活动业务层实现 * * @author Bulbasaur * @since 2021/5/20 6:10 下午 */ @Service @Slf4j public class CouponActivityServiceImpl extends AbstractPromotionsServiceImpl implements CouponActivityService { @Autowired private CouponService couponService; @Autowired private MemberCouponService memberCouponService; @Autowired private CouponActivityItemService couponActivityItemService; @Autowired private MemberCouponSignService memberCouponSignService; @Autowired private MemberService memberService; @Autowired private Cache> cache; /** * 延时任务 */ @Autowired private TimeTrigger timeTrigger; /** * RocketMQ */ @Autowired private RocketmqCustomProperties rocketmqCustomProperties; @Override public CouponActivityVO getCouponActivityVO(String couponActivityId) { CouponActivity couponActivity = this.getById(couponActivityId); return new CouponActivityVO(couponActivity, couponActivityItemService.getCouponActivityItemListVO(couponActivityId)); } @Override public void specify(CouponActivity couponActivity) { //如果开始时间为空,则表示活动关闭 if (couponActivity.getStartTime() == null) { return; } TimeTriggerMsg timeTriggerMsg = new TimeTriggerMsg(TimeExecuteConstant.COUPON_ACTIVITY_EXECUTOR, couponActivity.getStartTime().getTime(), CouponActivityMessage.builder().couponActivityId(couponActivity.getId()).build(), DelayQueueTools.wrapperUniqueKey(DelayTypeEnums.COUPON_ACTIVITY, couponActivity.getId()), rocketmqCustomProperties.getPromotionTopic()); //发送促销活动开始的延时任务 timeTrigger.addDelay(timeTriggerMsg); } @Override @Transactional(rollbackFor = Exception.class) public void specifyCoupon(String couponActivityId) { //获取优惠券活动 CouponActivity couponActivity = this.getById(couponActivityId); //获取活动优惠券发送范围 List> member = this.getMemberList(couponActivity); //如果指定会员发券,则当下直接进行发送,如果是全体会员发券,则变更为用户登录首页进行请求发券 //PS:即不主动发券,需要用户在活动时间内登录自动领取优惠券,类似美团、饿了么 的发放方式 if (couponActivity.getActivityScope().equals(CouponActivitySendTypeEnum.DESIGNATED.name())) { //会员拆成多个小组进行发送 List>> memberGroup = new ArrayList<>(); //循环分组 for (int i = 0; i < (member.size() / 100 + (member.size() % 100 == 0 ? 0 : 1)); i++) { int endPoint = Math.min((100 + (i * 100)), member.size()); memberGroup.add(member.subList((i * 100), endPoint)); } //优惠优惠券活动的优惠券列表 List couponActivityItems = couponActivityItemService.getCouponActivityList(couponActivity.getId()); //发送优惠券 for (List> memberList : memberGroup) { sendCoupon(memberList, couponActivityItems); } } } /** * 初始化促销字段 * * @param promotions 促销实体 */ @Override public void initPromotion(CouponActivity promotions) { super.initPromotion(promotions); if (promotions instanceof CouponActivityDTO) { CouponActivityDTO couponActivityDTO = (CouponActivityDTO) promotions; //如果有会员,则写入会员信息 if (couponActivityDTO.getMemberDTOS() != null && !couponActivityDTO.getMemberDTOS().isEmpty()) { couponActivityDTO.setActivityScopeInfo(JSONUtil.toJsonStr(couponActivityDTO.getMemberDTOS())); } } } /** * 检查优惠券活动参数 * * @param couponActivity 优惠券活动实体 */ @Override public void checkPromotions(CouponActivity couponActivity) { if (couponActivity instanceof CouponActivityDTO) { CouponActivityDTO couponActivityDTO = (CouponActivityDTO) couponActivity; //指定会员判定 if (couponActivity.getActivityScope().equals(CouponActivitySendTypeEnum.DESIGNATED.name()) && couponActivityDTO.getMemberDTOS().isEmpty()) { throw new ServiceException(ResultCode.COUPON_ACTIVITY_MEMBER_ERROR); } // 检查优惠券 this.checkCouponActivityItem(couponActivityDTO.getCouponActivityItems()); } } /** * 更新优惠券活动商品信息 * * @param couponActivity 优惠券活动实体 * @return 是否更新成功 */ @Override @Transactional(rollbackFor = {Exception.class}) public boolean updatePromotionsGoods(CouponActivity couponActivity) { boolean result = super.updatePromotionsGoods(couponActivity); if (couponActivity instanceof CouponActivityDTO && !PromotionsStatusEnum.CLOSE.name().equals(couponActivity.getPromotionStatus()) && PromotionsScopeTypeEnum.PORTION_GOODS.name().equals(couponActivity.getScopeType())) { CouponActivityDTO couponActivityDTO = (CouponActivityDTO) couponActivity; //创建优惠券活动子列表 for (CouponActivityItem couponActivityItem : couponActivityDTO.getCouponActivityItems()) { couponActivityItem.setActivityId(couponActivityDTO.getId()); } // 更新优惠券活动项信息 result = couponActivityItemService.saveBatch(couponActivityDTO.getCouponActivityItems()); } return result; } /** * 更新优惠券活动信息到商品索引 * * @param couponActivity 促销实体 */ @Override @Transactional(rollbackFor = Exception.class) public void updateEsGoodsIndex(CouponActivity couponActivity) { switch (CouponActivityTypeEnum.valueOf(couponActivity.getCouponActivityType())) { // 精准发券 则立即发放 case SPECIFY: this.specify(couponActivity); this.resetCache(couponActivity.getCouponActivityType()); break; //其他活动则是缓存模块,根据缓存中的优惠券活动信息来确认发放优惠券测略 case INVITE_NEW: case AUTO_COUPON: case REGISTERED: this.resetCache(couponActivity.getCouponActivityType()); break; } } @Override public List trigger(CouponActivityTrigger couponActivityTrigger) { //获取当前正在进行的优惠券活动 List couponActivities = currentCouponActivity(couponActivityTrigger.getCouponActivityTypeEnum().name()); /** * 自动发送优惠券则需要补足日志 */ if (couponActivityTrigger.getCouponActivityTypeEnum().equals(CouponActivityTypeEnum.AUTO_COUPON) || couponActivityTrigger.getCouponActivityTypeEnum().equals(CouponActivityTypeEnum.SPECIFY)) { couponActivities = memberCouponSignService.receiveCoupon(couponActivities); } //优惠券发放列表 log.info("当前用户的优惠券活动信息:{}", couponActivityTrigger); log.info("当前进行的优惠券活动:{}", couponActivities); List couponActivityItemVOS = new ArrayList<>(); //准备发放优惠券活动的列表 couponActivities.forEach(item -> couponActivityItemVOS.addAll(item.getCouponActivityItems())); AuthUser authUser = new AuthUser(); authUser.setId(couponActivityTrigger.getUserId()); authUser.setNickName(couponActivityTrigger.getNickName()); return this.sendCoupon(authUser, couponActivityItemVOS); } /** * 当前促销类型 * * @return 当前促销类型 */ @Override public PromotionTypeEnum getPromotionType() { return PromotionTypeEnum.COUPON_ACTIVITY; } /** * 缓存key生成策略 * * @param couponActivityType 优惠券活动类型 * @return 缓存key */ private String cacheKey(String couponActivityType) { return CachePrefix.CURRENT_COUPON_ACTIVITY.getPrefix() + couponActivityType; } /** * 当前进行的活动 * * @return 当前进行的活动列表 */ private List currentCouponActivity() { return currentCouponActivity(CouponActivityTypeEnum.AUTO_COUPON.name()); } /** * 当前进行的活动 * * @return 当前进行的活动列表 */ private List currentCouponActivity(String couponActivityTypeEnum) { //获取缓存中的活动 List couponActivityList = cache.get(cacheKey(couponActivityTypeEnum)); if (couponActivityList == null) { return ongoingActivities(resetCache(couponActivityTypeEnum)); } return ongoingActivities(couponActivityList); } /** * 从生效的活动优惠券中,过滤出正在进行的活动列表 * * @param activityVOS * @return */ private List ongoingActivities(List activityVOS) { if (activityVOS == null || activityVOS.isEmpty()) { return new ArrayList<>(); } return activityVOS.stream().filter(item -> { return item.getPromotionStatus().equals(PromotionsStatusEnum.START.name()); }).collect(Collectors.toList()); } /** * 重写缓存中的活动优惠券信息 */ private List resetCache(String couponActivityType) { LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); //如果结束时间大于当前时间,则表示有效 lambdaQueryWrapper.gt(CouponActivity::getEndTime, new Date()); //发送策略 lambdaQueryWrapper.eq(CouponActivity::getActivityScope, CouponActivitySendTypeEnum.ALL); //活动类型 lambdaQueryWrapper.eq(CouponActivity::getCouponActivityType, couponActivityType); //查询出结果,缓存后返回 List couponActivities = list(lambdaQueryWrapper); List couponActivityVOS = new ArrayList<>(); for (CouponActivity couponActivity : couponActivities) { couponActivityVOS.add(new CouponActivityVO(couponActivity, couponActivityItemService.getCouponActivityItemListVO(couponActivity.getId()))); } cache.put(cacheKey(couponActivityType), couponActivityVOS); return couponActivityVOS; } /** * 定向发送优惠券 * * @param memberList 用户列表 * @param couponActivityItems 优惠券列表 */ private void sendCoupon(List> memberList, List couponActivityItems) { for (Map map : memberList) { AuthUser authUser = new AuthUser(); authUser.setId(map.get("id").toString()); authUser.setNickName(map.get("nick_name").toString()); sendCoupon(authUser, couponActivityItems); } } /** * 给当前用户发送优惠券 * 1.循环优惠券列表 * 2.判断优惠券每个会员发送数量 * 3.记录优惠券发送数量 * * @param authUser 发送目标用户 * @param couponActivityItems 优惠券列表 */ private List sendCoupon(AuthUser authUser, List couponActivityItems) { //最终优惠券列表 List finalCoupons = new ArrayList<>(); //循环优惠券赠送列表 for (CouponActivityItem couponActivityItem : couponActivityItems) { //获取优惠券 Coupon coupon = couponService.getById(couponActivityItem.getCouponId()); //判断优惠券是否存在 if (coupon != null) { //循环优惠券的领取数量 int activitySendNum = couponActivityItem.getNum(); List memberCouponList = new ArrayList<>(); for (int i = 1; i <= activitySendNum; i++) { MemberCoupon memberCoupon = new MemberCoupon(coupon); memberCoupon.setMemberId(authUser.getId()); memberCoupon.setMemberName(authUser.getNickName()); memberCoupon.setMemberCouponStatus(MemberCouponStatusEnum.NEW.name()); memberCoupon.setPlatformFlag(PromotionTools.PLATFORM_ID.equals(coupon.getStoreId())); memberCouponList.add(memberCoupon); } finalCoupons.addAll(memberCouponList); //批量添加优惠券 memberCouponService.saveBatch(memberCouponList); //添加优惠券已领取数量 couponService.receiveCoupon(couponActivityItem.getCouponId(), memberCouponList.size()); } else { log.error("赠送优惠券失败,当前优惠券不存在:" + couponActivityItem.getCouponId()); } } if (finalCoupons.isEmpty()) { return new ArrayList<>(); } return finalCoupons; } /** * 获取优惠券的范围范围 * 此方法用于精准发券 * * @param couponActivity 优惠券活动 * @return 获取优惠券的会员列表 */ private List> getMemberList(CouponActivity couponActivity) { //判断优惠券的发送范围,获取会员列表 List ids = new ArrayList<>(); if (JSONUtil.isJsonArray(couponActivity.getActivityScopeInfo())) { JSONArray array = JSONUtil.parseArray(couponActivity.getActivityScopeInfo()); ids = array.toList(Map.class).stream().map(i -> i.get("id").toString()).collect(Collectors.toList()); } return memberService.listFieldsByMemberIds("id,nick_name", ids); } /** * 检查优惠券 * * @param couponActivityItems 优惠券列表 */ private void checkCouponActivityItem(List couponActivityItems) { //优惠券数量判定 if (couponActivityItems.isEmpty()) { throw new ServiceException(ResultCode.COUPON_ACTIVITY_ITEM_ERROR); } else if (couponActivityItems.size() > 10) { throw new ServiceException(ResultCode.COUPON_ACTIVITY_ITEM_MUST_NUM_ERROR); } else { for (CouponActivityItem item : couponActivityItems) { if (item.getNum() == null || item.getNum() <= 0) { throw new ServiceException(ResultCode.COUPON_ACTIVITY_ITEM_NUM_ERROR); } if (item.getNum() > 2) { throw new ServiceException(ResultCode.COUPON_ACTIVITY_ITEM_NUM_MAX_VALUE_2); } } } } }