package com.mzl.flower.service.impl.coupon; import cn.hutool.core.util.IdUtil; import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.mzl.flower.config.exception.ValidationException; import com.mzl.flower.config.security.SecurityUtils; import com.mzl.flower.dto.request.coupon.*; import com.mzl.flower.dto.response.coupon.CouponRecordVO; import com.mzl.flower.entity.coupon.CouponRecordDO; import com.mzl.flower.entity.coupon.CouponTemplateDO; import com.mzl.flower.entity.customer.Customer; import com.mzl.flower.enums.*; import com.mzl.flower.mapper.coupon.CouponRecordMapper; import com.mzl.flower.mapper.coupon.CouponRecordMapperCustom; import com.mzl.flower.mapper.customer.CustomerMapper; import com.mzl.flower.service.coupon.CouponRecordService; import com.mzl.flower.service.coupon.CouponTemplateService2; import com.mzl.flower.service.customer.CustomerService; import com.mzl.flower.service.system.UserService; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.time.LocalDateTime; import java.time.temporal.TemporalAdjusters; import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** *

* 服务实现类 *

* * @author @TaoJie * @since 2024-08-27 */ @Slf4j @Service public class CouponRecordServiceImpl extends ServiceImpl implements CouponRecordService { @Autowired private CouponRecordMapperCustom couponRecordMapperCustom; @Autowired private CouponTemplateService2 couponTemplateService; @Autowired private UserService userService; @Autowired private CustomerMapper customerMapper; @Autowired private CustomerService customerService; @Override public List getList(QueryCouponRecordDTO dto) { return couponRecordMapperCustom.getList(dto); } @Autowired RedissonClient redissonClient; private static final String COUPON_KEY="com:mzl:flower:service:impl:coupon:%s"; @Transactional @Override public boolean createCouponRecord(CreateCouponRecordDTO dto) { final CouponTemplateDO couponTemplateDO = couponTemplateService.getById(dto.getCouponId()); if(couponTemplateDO==null){ throw new ValidationException("优惠券不存在"); } final Customer customer = customerMapper.selectById(dto.getCustomerId()); if(null==customer){ throw new ValidationException("商户信息不存在"); } RLock lock = redissonClient.getLock(String.format(COUPON_KEY, couponTemplateDO.getId())); try { // 获取锁,最多等待 10 秒,锁自动释放时间 30 秒 if (lock.tryLock(10, 30, TimeUnit.SECONDS)) { try { // 活动优惠券和积分优惠券需要根据库存来控制- 根据优惠券的发放数量来控制有没有超发 if(StringUtils.isNotBlank(couponTemplateDO.getCategory()) && ( couponTemplateDO.getCategory().equals(CouponCategoryEnum.ACTIVITY.getStatus()) || couponTemplateDO.getCategory().equals(CouponCategoryEnum.POINT.getStatus()) )){ // 获取当前优惠券已经领取的数量 final Integer gainTotal = getExistGainCouponRecordAmountById(couponTemplateDO.getId()); if(couponTemplateDO.getCouponAmount().compareTo(gainTotal)<=0){ throw new ValidationException("当前优惠券已经领完!"); } } // 根据用户领取设置的getLimit 查看当前用户是否已经超领优惠券 if(StringUtils.isNotBlank(couponTemplateDO.getCategory()) && couponTemplateDO.getCategory().equals(CouponCategoryEnum.ACTIVITY.getStatus()) ){ // 查看当前优惠券的领取时间在不在设置的领取时间范围类 LocalDateTime now = LocalDateTime.now(); // 判断当前时间是否在领取时间范围内 if (!(now.isAfter(couponTemplateDO.getGetStartDate()) && now.isBefore(couponTemplateDO.getGetEndDate()))) { throw new ValidationException("当前时间不在优惠券的领取时间范围内,无法操作!"); } // 获取当前优惠券已经领取的数量 final Integer customGainTotal = getUserGainCouponRecordAmountById(couponTemplateDO.getId(),customer.getId()); if(couponTemplateDO.getGetLimit().compareTo(customGainTotal)<=0){ throw new ValidationException("超出个人领取限制,每人限领"+couponTemplateDO.getGetLimit()+"张!"); } } CouponRecordDO couponRecordDO=new CouponRecordDO(); BeanUtils.copyProperties(dto,couponRecordDO); couponRecordDO.create(SecurityUtils.getUserId()); // 设置为待使用状态 couponRecordDO.setStatus(CouponUsedStatusEnum.UNUSED.getType()); // 根据商户设置用户id if(StringUtils.isBlank(dto.getUserId()) && StringUtils.isNotBlank(customer.getUserId())){ couponRecordDO.setUserId(customer.getUserId()); } // 优惠券字段冗余 couponRecordDO.setCategory(couponTemplateDO.getCategory()); couponRecordDO.setCouponCode(couponTemplateDO.getCouponCode()); couponRecordDO.setCouponName(couponTemplateDO.getCouponName()); couponRecordDO.setCouponDiscountValue(couponTemplateDO.getCouponDiscountValue()); couponRecordDO.setMinOrderAmount(couponTemplateDO.getMinOrderAmount()); couponRecordDO.setGetType(couponTemplateDO.getGetType()); couponRecordDO.setCouponDiscountType(couponTemplateDO.getCouponDiscountType()); couponRecordDO.setGetUserType(couponTemplateDO.getGetUserType()); couponRecordDO.setPoint(couponTemplateDO.getPoint()); couponRecordDO.setMemberId(couponTemplateDO.getMemberId()); couponRecordDO.setImageUrl(couponTemplateDO.getImageUrl()); // 根据优惠券模板来计算优惠券的生效开始时间和结束时间 if(StringUtils.isNotBlank(couponTemplateDO.getCategory()) && couponTemplateDO.getCategory().equals(CouponCategoryEnum.MEMBER.getStatus())){ // 如果是会员优惠券的话,则设置为优惠券的使用条件为优惠券的时间 couponRecordDO.setEffectiveStart(couponTemplateDO.getUsageStartDate()); couponRecordDO.setEffectiveEnd(couponTemplateDO.getUsageEndDate()); }else{ // 非会员的根据领取时间类型来计算优惠券的时间 if(StringUtils.isNotBlank(couponTemplateDO.getUsageType()) && couponTemplateDO.getUsageType().equals(CouponUsageTypeEnum.GET.getType())){ // 与领取时间一致 couponRecordDO.setEffectiveStart(couponTemplateDO.getGetStartDate()); couponRecordDO.setEffectiveEnd(couponTemplateDO.getGetEndDate()); } if(StringUtils.isNotBlank(couponTemplateDO.getUsageType()) && couponTemplateDO.getUsageType().equals(CouponUsageTypeEnum.FIXED.getType())){ // 固定时间 couponRecordDO.setEffectiveStart(couponTemplateDO.getUsageStartDate()); couponRecordDO.setEffectiveEnd(couponTemplateDO.getUsageEndDate()); } if(StringUtils.isNotBlank(couponTemplateDO.getUsageType()) && couponTemplateDO.getUsageType().equals(CouponUsageTypeEnum.GET_AFTER_TIME.getType())){ // 领取后有段时间 领取后有效时间 // 根据发放后有效期来设置时间 if (couponTemplateDO.getUsageTimeNum() == null || couponTemplateDO.getUsageTimeNum() <= 0) { throw new ValidationException("使用时间数量必须为正整数"); } LocalDateTime currentTime = LocalDateTime.now(); couponRecordDO.setEffectiveStart(currentTime); if (couponTemplateDO.getUsageTimeType().equals(CouponUsageTimeTypeEnum.DAY.getType())) { // 天 couponRecordDO.setEffectiveEnd(currentTime.plusDays(couponTemplateDO.getUsageTimeNum())); } if (couponTemplateDO.getUsageTimeType().equals(CouponUsageTimeTypeEnum.HOUR.getType())) { // 小时 couponRecordDO.setEffectiveEnd(currentTime.plusHours(couponTemplateDO.getUsageTimeNum())); } if (couponTemplateDO.getUsageTimeType().equals(CouponUsageTimeTypeEnum.MINUTE.getType())) { // 分钟 couponRecordDO.setEffectiveEnd(currentTime.plusMinutes(couponTemplateDO.getUsageTimeNum())); } } } // 手动设置ID couponRecordDO.setId(IdUtil.simpleUUID()); return baseMapper.insert(couponRecordDO)>0; } finally { lock.unlock(); } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 处理异常 } return false; } @Override public boolean updateCouponRecord(CreateCouponRecordDTO dto) { CouponRecordDO couponRecordDO=baseMapper.selectById(dto.getId()); BeanUtils.copyProperties(dto,couponRecordDO); couponRecordDO.update(SecurityUtils.getUserId()); // 优惠券字段冗余 final CouponTemplateDO couponTemplateDO = couponTemplateService.getById(dto.getCouponId()); if(null!=couponTemplateDO){ couponRecordDO.setCategory(couponTemplateDO.getCategory()); couponRecordDO.setCouponCode(couponTemplateDO.getCouponCode()); couponRecordDO.setCouponName(couponTemplateDO.getCouponName()); couponRecordDO.setCouponDiscountValue(couponTemplateDO.getCouponDiscountValue()); couponRecordDO.setMinOrderAmount(couponTemplateDO.getMinOrderAmount()); } return baseMapper.updateById(couponRecordDO)>0; } @Override public boolean deleteCouponRecord(String id) { return baseMapper.deleteById(id)>0; } @Override public CouponRecordVO getCouponRecordById(String id) { QueryCouponRecordDTO dto=new QueryCouponRecordDTO(); dto.setId(id); final List list = couponRecordMapperCustom.getList(dto); if(CollectionUtils.isNotEmpty(list)){ return list.get(0); } return null; } @Override public Page getPage(Page page, QueryCouponRecordDTO dto) { List result=couponRecordMapperCustom.getPage(page,dto); return page.setRecords(result); } @Transactional @Override public boolean grantVipCouponRecordList() { try{ LocalDateTime now = LocalDateTime.now(); LocalDateTime firstDayStart = now.with(TemporalAdjusters.firstDayOfMonth()).withHour(0).withMinute(0).withSecond(0).withNano(0); LocalDateTime lastDayEnd = now.with(TemporalAdjusters.lastDayOfMonth()).withHour(23).withMinute(59).withSecond(59).withNano(0); // 获取所有会员模版列表 List vipTemplateList= couponTemplateService.getVipCouponTemplate(); // 遍历所有相同等级用户信息,并根据优惠券设置的规则构造优惠券 final List updateCouponTemplateList = vipTemplateList.stream().map(couponTemplateDO -> { // 获取当前等级下的所有用户 final List customerList = customerService.getCustomerListByLevelId(couponTemplateDO.getMemberId()); final List gradeCouponRecordList = customerList.stream().map(customer -> { CouponRecordDO couponRecordDO = new CouponRecordDO(); BeanUtils.copyProperties(couponTemplateDO, couponRecordDO); couponRecordDO.setId(IdUtil.simpleUUID()); couponRecordDO.setCouponId(couponTemplateDO.getId()); couponRecordDO.setUserId(customer.getUserId()); couponRecordDO.setCustomerId(customer.getId()); couponRecordDO.setStatus(CouponUsedStatusEnum.UNUSED.getType()); couponRecordDO.setEffectiveStart(firstDayStart); couponRecordDO.setEffectiveEnd(lastDayEnd); couponRecordDO.setMemberId(couponTemplateDO.getMemberId()); couponRecordDO.setImageUrl(couponTemplateDO.getImageUrl()); // 创建信息 couponRecordDO.create(); if(!checkCurMonVipCouponExists(couponRecordDO.getCouponId(),couponRecordDO.getCustomerId(),firstDayStart,lastDayEnd)){ return couponRecordDO; }else{ return null; } }).filter(Objects::nonNull) .collect(Collectors.toList()); // 批量保存等级下的优惠券信息 saveBatch(gradeCouponRecordList); couponTemplateDO.setUsageStartDate(firstDayStart); couponTemplateDO.setUsageEndDate(lastDayEnd); couponTemplateDO.setGetStartDate(firstDayStart); couponTemplateDO.setGetEndDate(lastDayEnd); // 设置总数为当前会员的人数 couponTemplateDO.setCouponAmount(CollectionUtils.isNotEmpty(customerList)?customerList.size():0); // 设置默认类型固定 couponTemplateDO.setUsageType(CouponUsageTypeEnum.FIXED.getType()); return couponTemplateDO; }).collect(Collectors.toList()); // 批量更新原模版时间 couponTemplateService.updateBatchById(updateCouponTemplateList); return true; }catch (Exception e){ // 报错日志信息报错 log.error(e.getMessage()); return false; } } @Override public boolean expiredCouponRecordLastMon() { try{ LocalDateTime now = LocalDateTime.now(); LocalDateTime firstDayStart = now.with(TemporalAdjusters.firstDayOfMonth()).withHour(0).withMinute(0).withSecond(0).withNano(0); LocalDateTime lastDayEnd = now.with(TemporalAdjusters.lastDayOfMonth()).withHour(23).withMinute(59).withSecond(59).withNano(0); // 只要找出时间小于当月的首天的设置成过期就可以 QueryWrapper queryWrapper=new QueryWrapper<>(); queryWrapper.lambda().eq(CouponRecordDO::getOrderId,"") .lt(CouponRecordDO::getEffectiveEnd,firstDayStart); final List couponRecordDOS = baseMapper.selectList(queryWrapper); final List expiredCouponRecordList = couponRecordDOS.stream().map(couponRecordDO -> { couponRecordDO.setStatus(CouponUsedStatusEnum.EXPIRED.getType()); return couponRecordDO; }).collect(Collectors.toList()); //更新 updateBatchById(expiredCouponRecordList); return true; }catch (Exception e){ log.error(e.getMessage()); return false; } } @Override public Integer statisCouponTemplateCount(QueryCouponStatisticsBO queryCouponStatisticsBO) { return couponRecordMapperCustom.statisCouponTemplateCount(queryCouponStatisticsBO); } @Override public Integer statisCouponTemplateCurMonCount(QueryCouponStatisticsBO queryCouponStatisticsBO) { return couponRecordMapperCustom.statisCouponTemplateCurMonCount(queryCouponStatisticsBO); } @Override public Integer statisCouponPointCurMonPontAmonut(QueryCouponStatisticsBO queryCouponStatisticsBO) { return couponRecordMapperCustom.statisCouponPointCurMonPointAmonut(queryCouponStatisticsBO); } @Override public Integer getExistCouponAmount(QueryExistCouponDTO dto) { QueryWrapper queryWrapper=new QueryWrapper<>(); queryWrapper.lambda().eq(CouponRecordDO::getDeleted, TrueOrFalseEnum.FALSE.isFlag()) .eq(StringUtils.isNotBlank(dto.getCouponId()), CouponRecordDO::getCouponId,dto.getCouponId()) .eq(null!=dto.getCustomerId(),CouponRecordDO::getCustomerId,dto.getCustomerId()) .eq(StringUtils.isNotBlank(dto.getCategory()),CouponRecordDO::getCategory,dto.getCategory()); return baseMapper.selectCount(queryWrapper); } @Override public Integer getExistGainCouponRecordAmountById(String couponId) { QueryWrapper queryWrapper=new QueryWrapper<>(); queryWrapper.lambda().eq(CouponRecordDO::getDeleted, TrueOrFalseEnum.FALSE.isFlag()) .eq(StringUtils.isNotBlank(couponId), CouponRecordDO::getCouponId,couponId) ; return baseMapper.selectCount(queryWrapper); } @Override public Integer getUserGainCouponRecordAmountById(String couponId, Long customerId) { QueryWrapper queryWrapper=new QueryWrapper<>(); queryWrapper.lambda().eq(CouponRecordDO::getDeleted, TrueOrFalseEnum.FALSE.isFlag()) .eq(StringUtils.isNotBlank(couponId), CouponRecordDO::getCouponId,couponId) .eq(null!=customerId,CouponRecordDO::getCustomerId,customerId) ; return baseMapper.selectCount(queryWrapper); } @Override public Integer getUserGainCouponRecordAmountByUserId(String couponId, String userId) { QueryWrapper queryWrapper=new QueryWrapper<>(); queryWrapper.lambda().eq(CouponRecordDO::getDeleted, TrueOrFalseEnum.FALSE.isFlag()) .eq(StringUtils.isNotBlank(couponId), CouponRecordDO::getCouponId,couponId) .eq(null!=userId,CouponRecordDO::getUserId,userId) ; return baseMapper.selectCount(queryWrapper); } @Override public List getMineCouponRecordList(QueryMineCouponRecordDTO dto) { checkCouponExpired(dto); return couponRecordMapperCustom.getMineCouponRecordList(dto); } @Override public void checkCouponExpired(QueryMineCouponRecordDTO dto) { if(StringUtils.isBlank(dto.getUserId())){ dto.setUserId(SecurityUtils.getUserId()); } // 将未使用的优惠券直接过期 couponRecordMapperCustom.checkCouponExpired(dto); } @Override public boolean checkCurMonVipCouponExists(String couponId, Long customId, LocalDateTime startDateTime, LocalDateTime endDateTime) { LocalDateTime now = LocalDateTime.now(); LocalDateTime firstDayStart = now.with(TemporalAdjusters.firstDayOfMonth()).withHour(0).withMinute(0).withSecond(0).withNano(0); LocalDateTime lastDayEnd = now.with(TemporalAdjusters.lastDayOfMonth()).withHour(23).withMinute(59).withSecond(59).withNano(0); if(null==startDateTime){ startDateTime=firstDayStart; } if(null==endDateTime){ endDateTime=lastDayEnd; } QueryWrapper queryWrapper=new QueryWrapper<>(); queryWrapper.lambda().eq(CouponRecordDO::getDeleted,TrueOrFalseEnum.FALSE.isFlag()) .eq(CouponRecordDO::getCouponId,couponId) .eq(CouponRecordDO::getCustomerId,customId) .eq(CouponRecordDO::getEffectiveStart,startDateTime) .eq(CouponRecordDO::getEffectiveEnd,endDateTime); return baseMapper.selectCount(queryWrapper)>0; } @Transactional @Override public boolean useCoupon(String couponId, String orderId, BigDecimal orderMount) { // 优惠券为空 if(StringUtils.isBlank(couponId)){ throw new ValidationException("无效的优惠券"); } if(StringUtils.isBlank(orderId)){ throw new ValidationException("订单id不能为空"); } if(orderMount.compareTo(BigDecimal.ZERO)<=0){ throw new ValidationException("订单金额不能小于0"); } // 验证优惠券存在且有效 final CouponRecordDO couponRecordDO = baseMapper.selectById(couponId); if(null==couponRecordDO || StringUtils.isNotBlank(couponRecordDO.getOrderId()) ){ throw new ValidationException("无效的优惠券"); } if(couponRecordDO.getStatus().equals(CouponUsedStatusEnum.USED.getType())){ throw new ValidationException("优惠券已经被使用"); } if(couponRecordDO.getStatus().equals(CouponUsedStatusEnum.EXPIRED.getType()) || LocalDateTime.now().isAfter(couponRecordDO.getEffectiveEnd())){ throw new ValidationException("优惠券已过期"); } // 根据类型判断是无门槛还是满减,如果是无门槛 if(couponRecordDO.getCouponDiscountType().equals(CouponTypeEnum.ZERO.getType())){ // 无门槛,查看金额是否大于满减值 if(orderMount.compareTo(couponRecordDO.getCouponDiscountValue())<0){ throw new ValidationException(String.format("订单金额(%s)小于无门槛的金额(%s)", orderMount, couponRecordDO.getCouponDiscountValue())); } } if(couponRecordDO.getCouponDiscountType().equals(CouponTypeEnum.DISCOUNT.getType())){ //满减,查看金额是否满足最小订单额 if(orderMount.compareTo(couponRecordDO.getMinOrderAmount())<0){ throw new ValidationException(String.format("订单金额(%s)小于最低折扣订单金额(%s)", orderMount, couponRecordDO.getMinOrderAmount())); } if(orderMount.compareTo(couponRecordDO.getCouponDiscountValue())<0){ throw new ValidationException(String.format("订单金额(%s)小于折扣的金额(%s)", orderMount, couponRecordDO.getCouponDiscountValue())); } } // 查看当前的优惠券是否是当前人员的 if(!SecurityUtils.getUserId().equals(couponRecordDO.getUserId())){ throw new ValidationException("优惠券不属于当前人员"); } // 优惠券使用操作 couponRecordDO.setStatus(CouponUsedStatusEnum.USED.getType()); couponRecordDO.setUsedTime(LocalDateTime.now()); couponRecordDO.setOrderId(orderId); return baseMapper.updateById(couponRecordDO)>0; } @Transactional @Override public boolean cancelCouponUsage(String orderId) { // 查询订单使用的优惠券 final CouponRecordDO couponRecordDO = getCouponByOrderId(orderId); if(null==couponRecordDO){ return false; } log.info("优惠券退回之前:"+ JSON.toJSONString(couponRecordDO)); couponRecordDO.setStatus(CouponUsedStatusEnum.UNUSED.getType()); couponRecordDO.setUsedTime(null); couponRecordDO.setOrderId(null); log.info("优惠券退回之后:"+ JSON.toJSONString(couponRecordDO)); return baseMapper.updateById(couponRecordDO)>0; } @Override public List getCouponListByOrderId(String orderId) { QueryWrapper queryWrapper=new QueryWrapper<>(); queryWrapper.lambda().eq(CouponRecordDO::getDeleted,TrueOrFalseEnum.FALSE.isFlag()) .eq(CouponRecordDO::getOrderId,orderId); return baseMapper.selectList(queryWrapper); } @Override public CouponRecordDO getCouponByOrderId(String orderId) { final List couponRecordDOList = getCouponListByOrderId(orderId); if(CollectionUtils.isNotEmpty(couponRecordDOList)){ return couponRecordDOList.get(0); } return null; } @Override public boolean checkUserCouponExists(String couponId, Long customId) { QueryWrapper queryWrapper=new QueryWrapper<>(); queryWrapper.lambda().eq(CouponRecordDO::getDeleted,TrueOrFalseEnum.FALSE.isFlag()) .eq(CouponRecordDO::getCouponId,couponId) .eq(CouponRecordDO::getCustomerId,customId) ; return baseMapper.selectCount(queryWrapper)>0; } }