增加徽章只中将一次处理

This commit is contained in:
tianfeng 2025-10-24 20:43:28 +08:00
parent 71cddc71fc
commit 783534636d
2 changed files with 162 additions and 11 deletions

View File

@ -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<LotteryPrize> 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<LotteryPrize> 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<LotteryPrize> filterAlreadyWonBadgePrizes(List<LotteryPrize> 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.
*/

View File

@ -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<LotteryPrize> 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<LotteryPrize> 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<LotteryDrawResultCO> 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<LotteryPrize> filterAlreadyWonBadgePrizes(List<LotteryPrize> 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);
}
}