diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/LotteryDrawExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/LotteryDrawExe.java index 7cad36c6..4392d56e 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/LotteryDrawExe.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/LotteryDrawExe.java @@ -1,5 +1,6 @@ package com.red.circle.other.app.command.activity; +import com.red.circle.component.redis.service.RedisService; import com.red.circle.framework.core.asserts.ResponseAssert; import com.red.circle.other.app.dto.clientobject.activity.LotteryDrawResultCO; import com.red.circle.other.app.dto.clientobject.activity.LotteryPrizeCO; @@ -55,6 +56,7 @@ public class LotteryDrawExe { private final LotteryUserCountService lotteryUserCountService; private final LotteryUserProgressService lotteryUserProgressService; private final LotteryPrizeGrantService lotteryPrizeGrantService; + private final RedisService redisService; @Transactional(rollbackFor = Exception.class) public LotteryDrawResultCO execute(LotteryDrawCmd cmd) { @@ -171,7 +173,7 @@ public class LotteryDrawExe { // 判断是否启用动态概率 if (activity.getEnableDynamicProbability() == null || activity.getEnableDynamicProbability() != 1) { // 未启用动态概率,使用普通抽奖 - return drawPrizeNormal(activityId); + return drawPrizeNormal(activityId, progress.getUserId()); } // 1. 判断用户所处阶段 @@ -182,24 +184,34 @@ public class LotteryDrawExe { // 2. 查询对应奖品池的奖品 List prizes = getPrizesByPool(activityId, stage); - // 3. 过滤会超过提现门槛的大奖 + // 3. 过滤用户已中过的BADGE类型奖品 + prizes = filterAlreadyWonBadgePrizes(prizes, progress.getUserId()); + + // 4. 过滤会超过提现门槛的大奖 prizes = filterOverThresholdPrizes(prizes, progress, activity.getWithdrawThreshold()); if (prizes.isEmpty()) { return null; } - // 4. 动态调整概率 + // 5. 动态调整概率 adjustProbability(prizes, progress, activity.getWithdrawThreshold()); - // 5. 执行抽奖 - return randomSelectByProbability(prizes); + // 6. 执行抽奖 + LotteryPrize selectedPrize = randomSelectByProbability(prizes); + + // 7. 如果抽中了BADGE类型奖品,记录到Redis + if (selectedPrize != null && "BADGE".equals(selectedPrize.getPrizeType())) { + markBadgePrizeAsWon(progress.getUserId(), selectedPrize.getId()); + } + + return selectedPrize; } /** * 普通抽奖(不使用动态概率). */ - private LotteryPrize drawPrizeNormal(Long activityId) { + private LotteryPrize drawPrizeNormal(Long activityId, Long userId) { List prizes = lotteryPrizeService.query() .eq(LotteryPrize::getActivityId, activityId) .gt(LotteryPrize::getRemainingStock, 0) @@ -210,7 +222,21 @@ public class LotteryDrawExe { return null; } - return randomSelectByProbability(prizes); + // 过滤用户已中过的BADGE类型奖品 + prizes = filterAlreadyWonBadgePrizes(prizes, userId); + + if (prizes.isEmpty()) { + return null; + } + + LotteryPrize selectedPrize = randomSelectByProbability(prizes); + + // 如果抽中了BADGE类型奖品,记录到Redis + if (selectedPrize != null && "BADGE".equals(selectedPrize.getPrizeType())) { + markBadgePrizeAsWon(userId, selectedPrize.getId()); + } + + return selectedPrize; } /** @@ -522,6 +548,53 @@ public class LotteryDrawExe { String.format("%06d", ThreadLocalRandom.current().nextInt(1000000)); } + /** + * 过滤用户已中过的BADGE类型奖品. + * + * @param prizes 奖品列表 + * @param userId 用户ID + * @return 过滤后的奖品列表 + */ + private List filterAlreadyWonBadgePrizes(List prizes, Long userId) { + if (prizes == null || prizes.isEmpty()) { + return prizes; + } + + return prizes.stream() + .filter(prize -> { + // 如果不是BADGE类型,直接通过 + if (!"BADGE".equals(prize.getPrizeType())) { + return true; + } + + // 检查用户是否已经中过这个BADGE + String redisKey = "lottery:badge:won:" + userId + ":" + prize.getId(); + String won = redisService.getString(redisKey); + + if (won != null) { + log.info("User already won this BADGE prize, filtering it out. userId: {}, prizeId: {}, prizeName: {}", + userId, prize.getId(), prize.getPrizeName()); + return false; + } + + return true; + }) + .collect(Collectors.toList()); + } + + /** + * 标记用户已中BADGE类型奖品(永久记录). + * + * @param userId 用户ID + * @param prizeId 奖品ID + */ + private void markBadgePrizeAsWon(Long userId, Long prizeId) { + String redisKey = "lottery:badge:won:" + userId + ":" + prizeId; + // 永久存储,表示该用户已经中过这个BADGE + redisService.setString(redisKey, "1"); + log.info("Mark BADGE prize as won. userId: {}, prizeId: {}", userId, prizeId); + } + /** * 转换为结果CO. */ diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/LotteryMultiDrawExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/LotteryMultiDrawExe.java index 39ccf7c4..723028b7 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/LotteryMultiDrawExe.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/LotteryMultiDrawExe.java @@ -1,5 +1,6 @@ package com.red.circle.other.app.command.activity; +import com.red.circle.component.redis.service.RedisService; import com.red.circle.framework.core.asserts.ResponseAssert; import com.red.circle.other.app.dto.clientobject.activity.LotteryDrawResultCO; import com.red.circle.other.app.dto.clientobject.activity.LotteryMultiDrawResultCO; @@ -56,6 +57,7 @@ public class LotteryMultiDrawExe { private final LotteryUserCountService lotteryUserCountService; private final LotteryUserProgressService lotteryUserProgressService; private final LotteryPrizeGrantService lotteryPrizeGrantService; + private final RedisService redisService; @Transactional(rollbackFor = Exception.class) public LotteryMultiDrawResultCO execute(LotteryMultiDrawCmd cmd) { @@ -202,12 +204,16 @@ public class LotteryMultiDrawExe { private LotteryPrize drawPrizeWithDynamicProbability(Long activityId, LotteryActivity activity, LotteryUserProgress progress) { if (activity.getEnableDynamicProbability() == null || activity.getEnableDynamicProbability() != 1) { - return drawPrizeWithMultiProbability(activityId); + return drawPrizeWithMultiProbability(activityId, progress.getUserId()); } String stage = determinePrizePool(progress, activity); log.debug("Multi-draw stage: {}, userId: {}, drawCount: {}, totalAmount: {}", stage, progress.getUserId(), progress.getTotalDrawCount(), progress.getTotalAmount()); List prizes = getPrizesByPool(activityId, stage); + + // 过滤用户已中过的BADGE类型奖品 + prizes = filterAlreadyWonBadgePrizes(prizes, progress.getUserId()); + prizes = filterOverThresholdPrizes(prizes, progress, activity.getWithdrawThreshold()); if (prizes.isEmpty()) { return null; @@ -218,7 +224,14 @@ public class LotteryMultiDrawExe { } } adjustProbability(prizes, progress, activity.getWithdrawThreshold()); - return randomSelectByProbability(prizes); + LotteryPrize selectedPrize = randomSelectByProbability(prizes); + + // 如果抽中了BADGE类型奖品,记录到Redis + if (selectedPrize != null && "BADGE".equals(selectedPrize.getPrizeType())) { + markBadgePrizeAsWon(progress.getUserId(), selectedPrize.getId()); + } + + return selectedPrize; } private String determinePrizePool(LotteryUserProgress progress, LotteryActivity activity) { @@ -302,7 +315,7 @@ public class LotteryMultiDrawExe { return prizes.get(prizes.size() - 1); } - private LotteryPrize drawPrizeWithMultiProbability(Long activityId) { + private LotteryPrize drawPrizeWithMultiProbability(Long activityId, Long userId) { List prizes = lotteryPrizeService.query() .eq(LotteryPrize::getActivityId, activityId) .gt(LotteryPrize::getRemainingStock, 0) @@ -311,6 +324,14 @@ public class LotteryMultiDrawExe { if (prizes.isEmpty()) { return null; } + + // 过滤用户已中过的BADGE类型奖品 + prizes = filterAlreadyWonBadgePrizes(prizes, userId); + + if (prizes.isEmpty()) { + return null; + } + BigDecimal totalProbability = prizes.stream() .map(prize -> prize.getMultiDrawProbability() != null ? prize.getMultiDrawProbability() : prize.getProbability()) .reduce(BigDecimal.ZERO, BigDecimal::add); @@ -320,10 +341,20 @@ public class LotteryMultiDrawExe { BigDecimal probability = prize.getMultiDrawProbability() != null ? prize.getMultiDrawProbability() : prize.getProbability(); cumulative += probability.doubleValue(); if (randomValue <= cumulative) { + // 如果抽中了BADGE类型奖品,记录到Redis + if ("BADGE".equals(prize.getPrizeType())) { + markBadgePrizeAsWon(userId, prize.getId()); + } return prize; } } - return prizes.get(prizes.size() - 1); + + LotteryPrize lastPrize = prizes.get(prizes.size() - 1); + // 如果抽中了BADGE类型奖品,记录到Redis + if (lastPrize != null && "BADGE".equals(lastPrize.getPrizeType())) { + markBadgePrizeAsWon(userId, lastPrize.getId()); + } + return lastPrize; } private void replaceWithGuaranteePrize(List results, LotteryActivity activity, Long userId, Long activityId, Timestamp now, String batchNo, LotteryUserProgress progress) { @@ -495,4 +526,51 @@ public class LotteryMultiDrawExe { return "LT" + System.currentTimeMillis() + String.format("%06d", ThreadLocalRandom.current().nextInt(1000000)); } + /** + * 过滤用户已中过的BADGE类型奖品. + * + * @param prizes 奖品列表 + * @param userId 用户ID + * @return 过滤后的奖品列表 + */ + private List filterAlreadyWonBadgePrizes(List prizes, Long userId) { + if (prizes == null || prizes.isEmpty()) { + return prizes; + } + + return prizes.stream() + .filter(prize -> { + // 如果不是BADGE类型,直接通过 + if (!"BADGE".equals(prize.getPrizeType())) { + return true; + } + + // 检查用户是否已经中过这个BADGE + String redisKey = "lottery:badge:won:" + userId + ":" + prize.getId(); + String won = redisService.getString(redisKey); + + if (won != null) { + log.info("User already won this BADGE prize in multi-draw, filtering it out. userId: {}, prizeId: {}, prizeName: {}", + userId, prize.getId(), prize.getPrizeName()); + return false; + } + + return true; + }) + .collect(Collectors.toList()); + } + + /** + * 标记用户已中BADGE类型奖品(永久记录). + * + * @param userId 用户ID + * @param prizeId 奖品ID + */ + private void markBadgePrizeAsWon(Long userId, Long prizeId) { + String redisKey = "lottery:badge:won:" + userId + ":" + prizeId; + // 永久存储,表示该用户已经中过这个BADGE + redisService.setString(redisKey, "1"); + log.info("Mark BADGE prize as won in multi-draw. userId: {}, prizeId: {}", userId, prizeId); + } + }