进房间异步自动领取奖励

This commit is contained in:
tianfeng 2025-11-13 14:23:56 +08:00
parent ca77bcf74c
commit 3e360af785
5 changed files with 111 additions and 48 deletions

View File

@ -26,6 +26,7 @@ import com.red.circle.wallet.inner.model.cmd.GoldReceiptCmd;
import com.red.circle.wallet.inner.model.enums.GoldOrigin;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
@ -58,7 +59,7 @@ public class RocketRewardClaimCmdExe {
private final Random random = new Random();
/**
* 执行奖励领取
* 执行奖励领取同步
*/
public RocketRewardCO execute(RocketRewardClaimCmd cmd) {
Long userId = cmd.getReqUserId();
@ -67,6 +68,31 @@ public class RocketRewardClaimCmdExe {
return distributedLockUtil.executeWithLock(lockKey, 5L, () -> doClaim(cmd), "Reward collection operations are too frequent. Please try again later");
}
/**
* 异步自动领取奖励进房触发
* 无返回值静默领取
*/
@Async("rocketRewardExecutor")
public void executeAsync(Long roomId, Long userId) {
try {
log.debug("异步自动领取火箭奖励: roomId={}, userId={}", roomId, userId);
RocketRewardClaimCmd cmd = new RocketRewardClaimCmd();
cmd.setRoomId(roomId);
cmd.setReqUserId(userId);
RocketRewardCO result = execute(cmd);
if (result != null) {
log.info("异步自动领取成功: roomId={}, userId={}, reward={}",
roomId, userId, result.getRewardType());
}
} catch (Exception e) {
// 静默失败不影响进房
log.error("异步自动领取火箭奖励失败: roomId={}, userId={}", roomId, userId, e);
}
}
/**
* 执行领取逻辑
*/
@ -78,46 +104,54 @@ public class RocketRewardClaimCmdExe {
// 1. 查询今天最新的火箭发射记录
RocketHistory latestHistory = rocketHistoryGateway.findLatestByRoomIdAndDate(roomId, today);
if (latestHistory == null) {
throw new RuntimeException("There are no rockets launched today, and there are no rewards to receive");
return null;
}
Integer rocketLevel = latestHistory.getLevel();
String historyId = latestHistory.getId();
// 2. 检查是否已领取每天每个房间每个等级只能领一次
boolean hasClaimed = rocketRewardLogGateway.existsByRocketAndUser(latestHistory.getId(), userId);
// 2. 检查是否已领取
boolean hasClaimed = rocketRewardLogGateway.existsByRocketAndUser(historyId, userId);
if (hasClaimed) {
throw new RuntimeException("This class of rocket has been collected today. Please wait for the next stage or come back tomorrow.");
return null;
}
// 3. 查询火箭配置
RocketConfig config = rocketConfigGateway.findByLevel(rocketLevel);
if (config == null) {
throw new RuntimeException("火箭配置不存在");
return null;
}
// 4. MongoDB原子递增检查
// 4. 检查库存查询不递增
int rewardQuantity = config.getRewardQuantity() != null ? config.getRewardQuantity() : 20;
boolean success = rocketHistoryGateway.tryIncrementClaimCount(latestHistory.getId(), rewardQuantity);
if (!success) {
throw new RuntimeException("The rewards have been collected, please come earlier next time");
int currentClaimed = latestHistory.getClaimedCount() != null ? latestHistory.getClaimedCount() : 0;
if (currentClaimed >= rewardQuantity) {
return null;
}
// 5. 解析奖励配置
List<PropsActivityRewardConfigInfoDTO> rewardList = parseRewardConfig(config.getRewardConfig().toJSONString());
if (rewardList == null || rewardList.isEmpty()) {
throw new RuntimeException("奖励配置为空");
return null;
}
// 6. 随机选择一个奖励
PropsActivityRewardConfigInfoDTO selectedReward = rewardList.get(random.nextInt(rewardList.size()));
// 7. 发放奖励
// 7. 发放奖励先发放
String walletBizNo = RocketRewardLog.buildBizNo(roomId.toString(), userId);
grantReward(userId, selectedReward, walletBizNo);
// 8. 保存领取记录
saveRewardLog(roomId, userId, latestHistory, selectedReward, walletBizNo);
// 9. 递增领取人数发放成功后再递增
try {
rocketHistoryGateway.incrementClaimCount(historyId);
} catch (Exception e) {
log.error("递增领取人数失败: historyId={}", historyId, e);
}
// 10. 构建返回结果
RocketRewardCO rewardCO = new RocketRewardCO();
rewardCO.setRewardType(selectedReward.getType());
@ -146,7 +180,7 @@ public class RocketRewardClaimCmdExe {
}
/**
* 发放奖励手动实现
* 发放奖励
*/
private void grantReward(Long userId, PropsActivityRewardConfigInfoDTO reward, String bizNo) {
try {
@ -154,19 +188,16 @@ public class RocketRewardClaimCmdExe {
switch (rewardType) {
case "GOLD" -> {
// 发放金币
Long goldAmount = Long.parseLong(reward.getContent());
grantGold(userId, goldAmount, bizNo);
}
case "PROPS" -> {
// 发放道具头饰/座驾等
Long propId = Long.parseLong(reward.getContent());
Integer quantity = reward.getQuantity() != null ? reward.getQuantity() : 1;
String detailType = reward.getDetailType(); // AVATAR_FRAME / RIDE
String detailType = reward.getDetailType();
grantProp(userId, propId, quantity, detailType, bizNo);
}
case "BADGE" -> {
// 发放徽章
Long badgeId = Long.parseLong(reward.getContent());
Integer quantity = reward.getQuantity() != null ? reward.getQuantity() : 1;
grantBadge(userId, badgeId, quantity, bizNo);
@ -187,7 +218,6 @@ public class RocketRewardClaimCmdExe {
*/
private void grantGold(Long userId, Long goldAmount, String bizNo) {
try {
// 防止配错导致发送大量金币
BigDecimal amount = BigDecimal.valueOf(goldAmount);
walletGoldClient.changeBalance(
GoldReceiptCmd.builder()
@ -201,7 +231,6 @@ public class RocketRewardClaimCmdExe {
.remark("")
.build()
);
} catch (Exception e) {
log.error("金币发放失败: userId={}, amount={}", userId, goldAmount, e);
throw new RuntimeException("金币发放失败");
@ -209,11 +238,10 @@ public class RocketRewardClaimCmdExe {
}
/**
* 发放道具头饰/座驾等
* 发放道具
*/
private void grantProp(Long userId, Long propId, Integer quantity, String detailType, String bizNo) {
try {
GivePropsBackpackCmd cmd = new GivePropsBackpackCmd()
.setAcceptUserId(userId)
.setPropsId(propId)
@ -226,7 +254,6 @@ public class RocketRewardClaimCmdExe {
.setOpUser(userId);
propsBackpackClient.giveProps(cmd);
// 发送通知
propsBackpackClient.givePropsNotify(userId, detailType, quantity, propId);
} catch (Exception e) {
log.error("道具发放失败: userId={}, propId={}", userId, propId, e);
@ -239,8 +266,6 @@ public class RocketRewardClaimCmdExe {
*/
private void grantBadge(Long userId, Long badgeId, Integer quantity, String bizNo) {
try {
// 徽章赠送天数大于1000天则为永久赠送
BadgeBackpackExpireTypeEnum expireType = quantity >= 1000
? BadgeBackpackExpireTypeEnum.PERMANENT
: BadgeBackpackExpireTypeEnum.TEMPORARY;
@ -253,7 +278,6 @@ public class RocketRewardClaimCmdExe {
.setBadgeType(SysBadgeConfigTypeEnum.ACTIVITY)
);
// 发送通知
propsBackpackClient.givePropsNotify(userId, SysBadgeConfigTypeEnum.ACTIVITY.name(), quantity, badgeId);
} catch (Exception e) {
log.error("徽章发放失败: userId={}, badgeId={}", userId, badgeId, e);
@ -298,17 +322,4 @@ public class RocketRewardClaimCmdExe {
int quantity = reward.getQuantity() == null ? 0 : reward.getQuantity();
return Long.parseLong(Integer.toString(quantity));
}
/**
* 查找贡献者备用
*/
private RocketContributor findContributor(RocketStatus rocketStatus, Long userId) {
if (rocketStatus.getContributors() == null) {
return null;
}
return rocketStatus.getContributors().stream()
.filter(c -> c.getUserId().equals(userId))
.findFirst()
.orElse(null);
}
}

View File

@ -8,6 +8,7 @@ import com.red.circle.framework.core.response.CommonErrorCode;
import com.red.circle.framework.core.response.ResponseErrorCode;
import com.red.circle.mq.business.model.event.task.TaskApprovalEvent;
import com.red.circle.mq.rocket.business.producer.TaskMqMessage;
import com.red.circle.other.app.command.rocket.RocketRewardClaimCmdExe;
import com.red.circle.other.app.convertor.live.RoomProfileAppConvertor;
import com.red.circle.other.app.convertor.user.UserProfileAppConvertor;
import com.red.circle.other.app.dto.clientobject.room.EntryRoomEntrantsCO;
@ -82,6 +83,7 @@ public class RoomEnterCmdExe {
private final UserAgoraTokenCacheService userAgoraTokenCacheService;
private final TaskMqMessage taskMqMessage;
private final AdministratorAuthService administratorAuthService;
private final RocketRewardClaimCmdExe rocketRewardClaimCmdExe;
public EntryRoomResponseCO execute(RoomEntryCmd cmd) {
//checkEntryUserId(cmd);
@ -124,6 +126,13 @@ public class RoomEnterCmdExe {
log.error("进房间添加足迹异常:{}", e.getMessage());
}
// 自动领取火箭奖励
try {
rocketRewardClaimCmdExe.executeAsync(cmd.getRoomId(), cmd.getReqUserId());
} catch (Exception e) {
log.error("触发自动领取火箭奖励失败: roomId={}, userId={}", cmd.getRoomId(), cmd.getReqUserId(), e);
}
return new EntryRoomResponseCO()
.setRoomProfile(
new EntryRoomProfileCO()

View File

@ -23,25 +23,26 @@ import java.util.concurrent.ThreadPoolExecutor;
public class RocketAsyncConfig {
/**
* 火箭任务线程池
* 火箭发射专用线程池
* 并发场景100个活跃房间活动期间可能有20-30个房间同时触发发射
*/
@Bean("rocketTaskExecutor")
public Executor rocketTaskExecutor() {
@Bean("rocketLaunchExecutor")
public Executor rocketLaunchExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数
executor.setCorePoolSize(5);
executor.setCorePoolSize(10);
// 最大线程数
executor.setMaxPoolSize(10);
executor.setMaxPoolSize(30);
// 队列容量
executor.setQueueCapacity(100);
executor.setQueueCapacity(50);
// 线程名前缀
executor.setThreadNamePrefix("rocket-task-");
executor.setThreadNamePrefix("rocket-launch-");
// 拒绝策略调用者运行
// 拒绝策略发射任务重要使用调用者运行策略保证执
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 线程空闲时间
@ -53,7 +54,46 @@ public class RocketAsyncConfig {
executor.initialize();
log.info("火箭任务线程池初始化完成");
log.info("火箭发射线程池初始化完成: core={}, max={}, queue={}",
executor.getCorePoolSize(), executor.getMaxPoolSize(), executor.getQueueCapacity());
return executor;
}
/**
* 火箭领奖专用线程池
* 使用场景用户进房自动领奖
*/
@Bean("rocketRewardExecutor")
public Executor rocketRewardExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 线程数多一些应对200-500活跃用户
executor.setCorePoolSize(15);
// 最大线程数
executor.setMaxPoolSize(30);
// 大容量队列缓冲进房峰值请求
executor.setQueueCapacity(500);
// 线程名前缀
executor.setThreadNamePrefix("rocket-reward-");
// 拒绝策略领奖可以丢弃避免阻塞进房接口
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
// 线程空闲时间
executor.setKeepAliveSeconds(60);
// 关闭时等待任务完成
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.initialize();
log.info("火箭领奖线程池初始化完成: core={}, max={}, queue={}",
executor.getCorePoolSize(), executor.getMaxPoolSize(), executor.getQueueCapacity());
return executor;
}

View File

@ -25,7 +25,7 @@ public class RocketLaunchManager {
* @param roomId 房间ID
* @param triggerUserId 触发用户ID
*/
@Async("rocketTaskExecutor")
@Async("rocketLaunchExecutor")
public void launchAsync(Long roomId, Long triggerUserId) {
try {
log.info("异步执行火箭发射: roomId={}, 触发用户={}", roomId, triggerUserId);

View File

@ -50,6 +50,9 @@ public class RocketHistoryDocument {
@Field("claim_stats")
private ClaimStats claimStats;
@Field("claimedCount")
private Integer claimedCount;
@Field("launch_time")
private LocalDateTime launchTime;