package cn.lili.modules.lmk.service.impl; import cn.lili.base.Result; import cn.lili.common.exception.ServiceException; import cn.lili.common.properties.RocketmqCustomProperties; import cn.lili.common.security.AuthUser; import cn.lili.common.security.context.UserContext; import cn.lili.common.utils.StringUtils; import cn.lili.modules.lmk.domain.entity.*; import cn.lili.modules.lmk.domain.query.PrizeRecordTimeQuery; import cn.lili.modules.lmk.domain.vo.PrizeProbabilityVO; import cn.lili.modules.lmk.domain.vo.PrizeRecordTimeVO; import cn.lili.modules.lmk.enums.general.*; import cn.lili.modules.lmk.service.*; import cn.lili.mybatis.BaseEntity; import cn.lili.rocketmq.RocketmqSendCallbackBuilder; import cn.lili.rocketmq.tags.CommentTagsEnum; import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.toolkit.IdWorker; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; import org.apache.rocketmq.spring.core.RocketMQTemplate; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationManager; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZoneId; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.stream.Collectors; @Service @RequiredArgsConstructor public class PrizeServiceImpl implements PrizeService { private final PrizeActivityService prizeActivityService; private final PrizeNumberService prizeNumberService; private final PrizeDrawService prizeDrawService; private final ActivityRefPrizeService activityRefPrizeService; private final PrizeRecordService prizeRecordService; private static final String PRIZE_PREFIX = "prize_draw:"; private static final String PRIZE_NUMBER = "prize_number:"; private final RedisTemplate redisTemplate; private final RedissonClient redissonClient; private final RocketmqCustomProperties rocketmqCustomProperties; private final RocketMQTemplate rocketMQTemplate; @Override @Transactional(rollbackFor = Exception.class) public Result prize(String prizeId) { AuthUser currentUser = UserContext.getCurrentUser(); if (currentUser == null) { throw new RuntimeException("当前用户没有登录无法抽奖"); } String userId = currentUser.getId(); if (StringUtils.isBlank(userId)) { throw new RuntimeException("当前用户没有登录无法抽奖"); } Result result; String lock = IdWorker.get32UUID(); RLock redissonLock = redissonClient.getLock(PRIZE_PREFIX + prizeId); try { // Boolean lockResult = redisTemplate.opsForValue().setIfAbsent(PRIZE_PREFIX + prizeId, lock, 60, TimeUnit.SECONDS); // if (Boolean.FALSE.equals(lockResult)) { // throw new ServiceException("当前活动太火爆了请稍后再试"); // } redissonLock.lock(); result = prizeDraw(prizeId, userId, lock); } finally { if (redissonLock.isHeldByCurrentThread()) { redissonLock.unlock(); } // //释放锁 // String newLock = redisTemplate.opsForValue().get(PRIZE_PREFIX + prizeId); // if (lock.equals(newLock)) // redisTemplate.delete(PRIZE_PREFIX + prizeId); } return result; } @Transactional(rollbackFor = Exception.class) public Result prizeDraw(String prizeId, String userId, String lock) { //活动 PrizeActivity prizeActivity; //待使用的抽奖机会 PrizeNumber waitUserPrize; //当天开始时间 Date beginTime; //当天结束时间 Date endTime; //使用了的抽奖机会 List usedPrize; //没被使用的抽奖机会 List canUsedPrize; //抽奖商品集合 List refPrizes; //查询当天抽奖记录入参 PrizeRecordTimeQuery prizeRecordTimeQuery; //当天抽奖记录汇总 List prizeRecordListByTime; //还能抽奖商品map集合 Map canPrizeMap; //待构建抽奖概率商品集合 List refPrizeList; //概率集合 List prizeProbabilityList; //中奖商品id String prizeWon = null; prizeActivity = prizeActivityService.getById(prizeId); if (prizeActivity == null) { throw new ServiceException("当前活动不存在"); } Date activityEndtime = prizeActivity.getEndTime(); if (new Date().after(activityEndtime)) { throw new ServiceException("当前活动已经结束"); } if (!PrizeActivityStatusEnum.ON.name().equals(prizeActivity.getEnableStatus())) { throw new ServiceException("活动还没有启用"); } //查询当天的抽奖卷 LocalDate now = LocalDate.now(); LocalDateTime maxTime = LocalDateTime.of(now, LocalTime.MAX); LocalDateTime minTime = LocalDateTime.of(now, LocalTime.MIN); beginTime = Date.from( minTime.atZone(ZoneId.systemDefault()).toInstant() ); endTime = Date.from( maxTime.atZone(ZoneId.systemDefault()).toInstant() ); LambdaQueryWrapper queryNumber = Wrappers.lambdaQuery(); queryNumber.eq(PrizeNumber::getUserId, userId).eq(PrizeNumber::getActivityPrizeId, prizeId); queryNumber.between(BaseEntity::getCreateTime, beginTime, endTime); //判断用户是否有抽奖机会有取出一条未抽奖的数据待使用 List list = prizeNumberService.list(queryNumber); if (list == null || list.isEmpty()) { throw new ServiceException("抽奖次数用完了"); } usedPrize = list.stream().filter(item -> { return PrizeNumberUseEnum.USED.name().equals(item.getUseStatus()); }).collect(Collectors.toList()); canUsedPrize = list.stream().filter(item -> { return PrizeNumberUseEnum.WAIT.name().equals(item.getUseStatus()); }).collect(Collectors.toList()); if (usedPrize.size() > prizeActivity.getMaxPrize()) { throw new ServiceException("抽奖已达到上限"); } if (canUsedPrize.isEmpty()) { throw new ServiceException("抽奖次数用完了"); } waitUserPrize = canUsedPrize.get(0); //判断奖品是否还有以及当天是否还有奖品 LambdaQueryWrapper prizeRefQuery = Wrappers.lambdaQuery(); prizeRefQuery.eq(ActivityRefPrize::getPrizeActivityId, prizeId); refPrizes = activityRefPrizeService.list(prizeRefQuery); canPrizeMap = refPrizes.stream().filter(item -> { return item.getRemainNum() > 0; }).collect(Collectors.toMap(ActivityRefPrize::getPrizeId, Function.identity())); prizeRecordTimeQuery = new PrizeRecordTimeQuery(); prizeRecordTimeQuery.setStartTime(beginTime); prizeRecordTimeQuery.setEndTime(endTime); prizeRecordTimeQuery.setRecordActivityId(prizeId); prizeRecordListByTime = prizeRecordService.getPrizeRecordListByTime(prizeRecordTimeQuery); prizeRecordListByTime.stream().filter(item->{ return item.getPrizeId() != null; }).forEach(item -> { ActivityRefPrize activityRefPrize = canPrizeMap.get(Long.parseLong(item.getPrizeId())); if (activityRefPrize != null) { //移除当日上限的奖品并且添加到不能抽奖商品中去 if (activityRefPrize.getMaxPreDay() <= item.getTotal()) { canPrizeMap.remove(Long.parseLong(item.getPrizeId())); } } }); //准备构建范围集合 refPrizeList = new ArrayList<>(canPrizeMap.values()); //当前中奖比例 BigDecimal currentProbability = refPrizeList.stream().map(ActivityRefPrize::getPrizeProbability).reduce(BigDecimal.ZERO, BigDecimal::add); //用不能中奖的商品补齐 BigDecimal subtract = new BigDecimal(100).subtract(currentProbability); if (subtract.compareTo(BigDecimal.ZERO) > 0) { ActivityRefPrize activityRefPrize = new ActivityRefPrize(); activityRefPrize.setPrizeId(null); activityRefPrize.setPrizeProbability(subtract); refPrizeList.add(activityRefPrize); currentProbability = new BigDecimal(100); } //打乱排序 Collections.shuffle(refPrizeList); //构建抽奖范围集合 prizeProbabilityList = new ArrayList<>(); BigDecimal probabilityBegin = BigDecimal.ZERO; for (ActivityRefPrize item : refPrizeList) { PrizeProbabilityVO prizeProbabilityVO = new PrizeProbabilityVO(); prizeProbabilityVO.setPrizeId(item.getPrizeId()); BigDecimal multiply = item.getPrizeProbability().multiply(BigDecimal.valueOf(100)); BigDecimal[][] position = {{probabilityBegin, multiply.add(probabilityBegin)}}; prizeProbabilityVO.setProbability(position); probabilityBegin = multiply.add(probabilityBegin); prizeProbabilityList.add(prizeProbabilityVO); } BigDecimal max = currentProbability.multiply(BigDecimal.valueOf(100)); BigDecimal bigDecimal = generateRandom(BigDecimal.ONE, max); for (PrizeProbabilityVO prizeProbabilityVO : prizeProbabilityList) { BigDecimal minP = prizeProbabilityVO.getProbability()[0][0]; BigDecimal maxP = prizeProbabilityVO.getProbability()[0][1]; if (bigDecimal.compareTo(minP) > 0 && bigDecimal.compareTo(maxP) <= 0) { prizeWon = prizeProbabilityVO.getPrizeId(); break; } } //未中奖的情况 if (prizeWon == null) { //写入抽奖记录返回没有中奖信息 //写入抽奖记录 PrizeRecord prizeRecord = new PrizeRecord(); prizeRecord.setUserId(Long.parseLong(userId)); prizeRecord.setNickName(Objects.requireNonNull(UserContext.getCurrentUser()).getNickName()); prizeRecord.setPrizeActivityId(Long.parseLong(prizeId)); prizeRecord.setPrizeActivityName(prizeActivity.getActivityName()); prizeRecord.setPrizeActivityCover(prizeActivity.getActivityCover()); prizeRecord.setPrizeStatus(PrizeStatusEnum.NOT_WIN.name()); prizeRecord.setPrizeContent(null); prizeRecord.setPrizeId(null); prizeRecord.setPrizeNumId(Long.parseLong(waitUserPrize.getId())); prizeRecord.setActivityPrizeRefId(null); prizeRecord.setDistributeStatus(PrizeDistributeStatusEnum.NOT_WAIT.name()); prizeRecordService.save(prizeRecord); waitUserPrize.setUseStatus(PrizeNumberUseEnum.USED.name()); prizeNumberService.updateById(waitUserPrize); return Result.error("未中奖"); } ActivityRefPrize activityRefPrize; String nowLock; activityRefPrize = canPrizeMap.get(prizeWon); //将活动当前活动奖品库存使用排他锁锁住 activityRefPrizeService.lockPrizeRef(activityRefPrize.getId()); //修改抽奖机会状态 waitUserPrize.setUseStatus(PrizeNumberUseEnum.USED.name()); prizeNumberService.updateById(waitUserPrize); //扣库存 LambdaUpdateWrapper set = Wrappers.lambdaUpdate(ActivityRefPrize.class) .eq(ActivityRefPrize::getId, activityRefPrize.getId()) .eq(ActivityRefPrize::getRemainNum, activityRefPrize.getRemainNum()) .eq(ActivityRefPrize::getVersion, activityRefPrize.getVersion()) .gt(ActivityRefPrize::getRemainNum, 0) .set(ActivityRefPrize::getRemainNum, activityRefPrize.getRemainNum() - 1) .set(ActivityRefPrize::getVersion, activityRefPrize.getVersion() + 1); boolean update = activityRefPrizeService.update(set); if (!update) { throw new ServiceException("当前活动太火爆了请稍后再试"); } PrizeDraw prizeDraw = prizeDrawService.getById(prizeWon); //写入抽奖记录 PrizeRecord prizeRecord = new PrizeRecord(); prizeRecord.setUserId(Long.parseLong(userId)); prizeRecord.setNickName(Objects.requireNonNull(UserContext.getCurrentUser()).getNickName()); prizeRecord.setPrizeActivityId(Long.parseLong(prizeId)); prizeRecord.setPrizeActivityName(prizeActivity.getActivityName()); prizeRecord.setPrizeActivityCover(prizeActivity.getActivityCover()); prizeRecord.setPrizeStatus(PrizeStatusEnum.WIN.name()); prizeRecord.setPrizeContent(prizeDraw.getPrizeContent()); prizeRecord.setPrizeImg(prizeActivity.getActivityImg()); prizeRecord.setPrizeNumId(Long.parseLong(waitUserPrize.getId())); prizeRecord.setActivityPrizeRefId(Long.parseLong(activityRefPrize.getId())); prizeRecord.setDistributeStatus(PrizeDistributeStatusEnum.WAIT.name()); prizeRecord.setPrizeId(prizeWon); prizeRecord.setPrizeName(prizeDraw.getPrizeName()); prizeRecord.setPrizeActivityCover(prizeActivity.getActivityCover()); prizeRecord.setPrizeImg(prizeDraw.getPrizeImg()); prizeRecordService.save(prizeRecord); //判断当前锁还是不是当前的锁如果不是直接抛出异常回滚 // nowLock = redisTemplate.opsForValue().get(PRIZE_PREFIX + prizeId); // if (!lock.equals(nowLock)) { // throw new RuntimeException("当前活动太火爆了请稍后再试"); // } // 走mq异步处理 TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { @Override public void afterCommit() { String destination = rocketmqCustomProperties.getPrizeTopic() + ":" + PrizeStatusEnum.WIN.name(); rocketMQTemplate.asyncSend(destination, JSON.toJSONString(prizeRecord), RocketmqSendCallbackBuilder.commonCallback()); } }); return Result.ok().data(activityRefPrize); } public static BigDecimal generateRandom(BigDecimal min, BigDecimal max) { if (min.compareTo(max) >= 0) { throw new IllegalArgumentException("最小值必须小于最大值"); } // 计算范围差值 BigDecimal range = max.subtract(min); // 生成0-1之间的随机double数 double randomDouble = new Random().nextDouble(); // 计算随机数并设置精度 return min.add(range.multiply(new BigDecimal(randomDouble))) .setScale(0, RoundingMode.HALF_UP); } @Override @Transactional(rollbackFor = Exception.class) public Result prizeNum(String prizeId) { String userId; AuthUser currentUser = UserContext.getCurrentUser(); if (currentUser == null) { throw new RuntimeException("当前用户没有登录"); } userId = currentUser.getId(); //获取redis锁 RLock lock = redissonClient.getLock(PRIZE_NUMBER + userId); PrizeActivity activity = prizeActivityService.getById(prizeId); if (activity == null) { throw new RuntimeException("当前活动不存在"); } //获取用户抽奖次数 Integer prizeNum = activity.getPrizeNum(); //加锁判断添加系统赠送次数 List prizeNumberList; try { lock.lock(); prizeNumberList = getPrizeNumberList(prizeId, userId); // 当前用户没有初始化抽奖数据需要初始化当天抽奖数据 if (prizeNumberList == null || prizeNumberList.isEmpty()) { //没有默认抽奖次数直接返回0次 if (prizeNum == null || prizeNum <= 0) { return Result.ok().data(0); } prizeNumberList = new ArrayList<>(); //设置默认抽奖次数并返回默认抽奖次数 for (int i = 0; i < prizeNum; i++) { PrizeNumber prizeNumber = new PrizeNumber(); prizeNumber.setActivityPrizeId(Long.parseLong(prizeId)); prizeNumber.setUserId(Long.parseLong(userId)); prizeNumber.setUserAction(PrizeUserActionEnum.SYSTEM.name()); prizeNumber.setUseStatus(PrizeNumberUseEnum.WAIT.name()); prizeNumberList.add(prizeNumber); } //添加抽奖次数并返回 prizeNumberService.saveBatch(prizeNumberList); return Result.ok().data(prizeNumberList.size()); } } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } Integer maxPrize = activity.getMaxPrize(); //其他情况 int useNum = 0; int notUseNum = 0; for (PrizeNumber prizeNumber : prizeNumberList) { if (PrizeNumberUseEnum.WAIT.name().equals(prizeNumber.getUseStatus())) { notUseNum++; } else if (PrizeNumberUseEnum.USED.name().equals(prizeNumber.getUseStatus())) { useNum++; } } int userPrizeNum = prizeNumberList.size(); if (useNum >= maxPrize) { return Result.ok().data(0); } else { return Result.ok().data(userPrizeNum>maxPrize?maxPrize-useNum:notUseNum); } } /** * 获取当天某个用户的所有的优惠卷 * * @param prizeId 活动id * @param userId 用户id * @return 抽奖次数集合 */ public List getPrizeNumberList(String prizeId, String userId) { Date beginTime; Date endTime; //查询当天的优惠卷 LocalDate now = LocalDate.now(); LocalDateTime maxTime = LocalDateTime.of(now, LocalTime.MAX); LocalDateTime minTime = LocalDateTime.of(now, LocalTime.MIN); beginTime = Date.from( minTime.atZone(ZoneId.systemDefault()).toInstant() ); endTime = Date.from( maxTime.atZone(ZoneId.systemDefault()).toInstant() ); LambdaQueryWrapper queryNumber = Wrappers.lambdaQuery(); queryNumber.eq(PrizeNumber::getUserId, userId).eq(PrizeNumber::getActivityPrizeId, prizeId); queryNumber.between(BaseEntity::getCreateTime, beginTime, endTime); return prizeNumberService.list(queryNumber); } @Override public Result prizeInfo(String prizeActivityId) { return null; } }