进房间异步自动领取奖励

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 com.red.circle.wallet.inner.model.enums.GoldOrigin;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.math.BigDecimal; import java.math.BigDecimal;
@ -58,7 +59,7 @@ public class RocketRewardClaimCmdExe {
private final Random random = new Random(); private final Random random = new Random();
/** /**
* 执行奖励领取 * 执行奖励领取同步
*/ */
public RocketRewardCO execute(RocketRewardClaimCmd cmd) { public RocketRewardCO execute(RocketRewardClaimCmd cmd) {
Long userId = cmd.getReqUserId(); 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"); 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. 查询今天最新的火箭发射记录 // 1. 查询今天最新的火箭发射记录
RocketHistory latestHistory = rocketHistoryGateway.findLatestByRoomIdAndDate(roomId, today); RocketHistory latestHistory = rocketHistoryGateway.findLatestByRoomIdAndDate(roomId, today);
if (latestHistory == null) { 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(); Integer rocketLevel = latestHistory.getLevel();
String historyId = latestHistory.getId();
// 2. 检查是否已领取每天每个房间每个等级只能领一次 // 2. 检查是否已领取
boolean hasClaimed = rocketRewardLogGateway.existsByRocketAndUser(latestHistory.getId(), userId); boolean hasClaimed = rocketRewardLogGateway.existsByRocketAndUser(historyId, userId);
if (hasClaimed) { 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. 查询火箭配置 // 3. 查询火箭配置
RocketConfig config = rocketConfigGateway.findByLevel(rocketLevel); RocketConfig config = rocketConfigGateway.findByLevel(rocketLevel);
if (config == null) { if (config == null) {
throw new RuntimeException("火箭配置不存在"); return null;
} }
// 4. MongoDB原子递增检查 // 4. 检查库存查询不递增
int rewardQuantity = config.getRewardQuantity() != null ? config.getRewardQuantity() : 20; int rewardQuantity = config.getRewardQuantity() != null ? config.getRewardQuantity() : 20;
boolean success = rocketHistoryGateway.tryIncrementClaimCount(latestHistory.getId(), rewardQuantity); int currentClaimed = latestHistory.getClaimedCount() != null ? latestHistory.getClaimedCount() : 0;
if (!success) { if (currentClaimed >= rewardQuantity) {
throw new RuntimeException("The rewards have been collected, please come earlier next time"); return null;
} }
// 5. 解析奖励配置 // 5. 解析奖励配置
List<PropsActivityRewardConfigInfoDTO> rewardList = parseRewardConfig(config.getRewardConfig().toJSONString()); List<PropsActivityRewardConfigInfoDTO> rewardList = parseRewardConfig(config.getRewardConfig().toJSONString());
if (rewardList == null || rewardList.isEmpty()) { if (rewardList == null || rewardList.isEmpty()) {
throw new RuntimeException("奖励配置为空"); return null;
} }
// 6. 随机选择一个奖励 // 6. 随机选择一个奖励
PropsActivityRewardConfigInfoDTO selectedReward = rewardList.get(random.nextInt(rewardList.size())); PropsActivityRewardConfigInfoDTO selectedReward = rewardList.get(random.nextInt(rewardList.size()));
// 7. 发放奖励 // 7. 发放奖励先发放
String walletBizNo = RocketRewardLog.buildBizNo(roomId.toString(), userId); String walletBizNo = RocketRewardLog.buildBizNo(roomId.toString(), userId);
grantReward(userId, selectedReward, walletBizNo); grantReward(userId, selectedReward, walletBizNo);
// 8. 保存领取记录 // 8. 保存领取记录
saveRewardLog(roomId, userId, latestHistory, selectedReward, walletBizNo); saveRewardLog(roomId, userId, latestHistory, selectedReward, walletBizNo);
// 9. 递增领取人数发放成功后再递增
try {
rocketHistoryGateway.incrementClaimCount(historyId);
} catch (Exception e) {
log.error("递增领取人数失败: historyId={}", historyId, e);
}
// 10. 构建返回结果 // 10. 构建返回结果
RocketRewardCO rewardCO = new RocketRewardCO(); RocketRewardCO rewardCO = new RocketRewardCO();
rewardCO.setRewardType(selectedReward.getType()); rewardCO.setRewardType(selectedReward.getType());
@ -146,7 +180,7 @@ public class RocketRewardClaimCmdExe {
} }
/** /**
* 发放奖励手动实现 * 发放奖励
*/ */
private void grantReward(Long userId, PropsActivityRewardConfigInfoDTO reward, String bizNo) { private void grantReward(Long userId, PropsActivityRewardConfigInfoDTO reward, String bizNo) {
try { try {
@ -154,19 +188,16 @@ public class RocketRewardClaimCmdExe {
switch (rewardType) { switch (rewardType) {
case "GOLD" -> { case "GOLD" -> {
// 发放金币
Long goldAmount = Long.parseLong(reward.getContent()); Long goldAmount = Long.parseLong(reward.getContent());
grantGold(userId, goldAmount, bizNo); grantGold(userId, goldAmount, bizNo);
} }
case "PROPS" -> { case "PROPS" -> {
// 发放道具头饰/座驾等
Long propId = Long.parseLong(reward.getContent()); Long propId = Long.parseLong(reward.getContent());
Integer quantity = reward.getQuantity() != null ? reward.getQuantity() : 1; 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); grantProp(userId, propId, quantity, detailType, bizNo);
} }
case "BADGE" -> { case "BADGE" -> {
// 发放徽章
Long badgeId = Long.parseLong(reward.getContent()); Long badgeId = Long.parseLong(reward.getContent());
Integer quantity = reward.getQuantity() != null ? reward.getQuantity() : 1; Integer quantity = reward.getQuantity() != null ? reward.getQuantity() : 1;
grantBadge(userId, badgeId, quantity, bizNo); grantBadge(userId, badgeId, quantity, bizNo);
@ -187,7 +218,6 @@ public class RocketRewardClaimCmdExe {
*/ */
private void grantGold(Long userId, Long goldAmount, String bizNo) { private void grantGold(Long userId, Long goldAmount, String bizNo) {
try { try {
// 防止配错导致发送大量金币
BigDecimal amount = BigDecimal.valueOf(goldAmount); BigDecimal amount = BigDecimal.valueOf(goldAmount);
walletGoldClient.changeBalance( walletGoldClient.changeBalance(
GoldReceiptCmd.builder() GoldReceiptCmd.builder()
@ -201,7 +231,6 @@ public class RocketRewardClaimCmdExe {
.remark("") .remark("")
.build() .build()
); );
} catch (Exception e) { } catch (Exception e) {
log.error("金币发放失败: userId={}, amount={}", userId, goldAmount, e); log.error("金币发放失败: userId={}, amount={}", userId, goldAmount, e);
throw new RuntimeException("金币发放失败"); throw new RuntimeException("金币发放失败");
@ -209,11 +238,10 @@ public class RocketRewardClaimCmdExe {
} }
/** /**
* 发放道具头饰/座驾等 * 发放道具
*/ */
private void grantProp(Long userId, Long propId, Integer quantity, String detailType, String bizNo) { private void grantProp(Long userId, Long propId, Integer quantity, String detailType, String bizNo) {
try { try {
GivePropsBackpackCmd cmd = new GivePropsBackpackCmd() GivePropsBackpackCmd cmd = new GivePropsBackpackCmd()
.setAcceptUserId(userId) .setAcceptUserId(userId)
.setPropsId(propId) .setPropsId(propId)
@ -226,7 +254,6 @@ public class RocketRewardClaimCmdExe {
.setOpUser(userId); .setOpUser(userId);
propsBackpackClient.giveProps(cmd); propsBackpackClient.giveProps(cmd);
// 发送通知
propsBackpackClient.givePropsNotify(userId, detailType, quantity, propId); propsBackpackClient.givePropsNotify(userId, detailType, quantity, propId);
} catch (Exception e) { } catch (Exception e) {
log.error("道具发放失败: userId={}, propId={}", userId, propId, 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) { private void grantBadge(Long userId, Long badgeId, Integer quantity, String bizNo) {
try { try {
// 徽章赠送天数大于1000天则为永久赠送
BadgeBackpackExpireTypeEnum expireType = quantity >= 1000 BadgeBackpackExpireTypeEnum expireType = quantity >= 1000
? BadgeBackpackExpireTypeEnum.PERMANENT ? BadgeBackpackExpireTypeEnum.PERMANENT
: BadgeBackpackExpireTypeEnum.TEMPORARY; : BadgeBackpackExpireTypeEnum.TEMPORARY;
@ -253,7 +278,6 @@ public class RocketRewardClaimCmdExe {
.setBadgeType(SysBadgeConfigTypeEnum.ACTIVITY) .setBadgeType(SysBadgeConfigTypeEnum.ACTIVITY)
); );
// 发送通知
propsBackpackClient.givePropsNotify(userId, SysBadgeConfigTypeEnum.ACTIVITY.name(), quantity, badgeId); propsBackpackClient.givePropsNotify(userId, SysBadgeConfigTypeEnum.ACTIVITY.name(), quantity, badgeId);
} catch (Exception e) { } catch (Exception e) {
log.error("徽章发放失败: userId={}, badgeId={}", userId, badgeId, e); log.error("徽章发放失败: userId={}, badgeId={}", userId, badgeId, e);
@ -298,17 +322,4 @@ public class RocketRewardClaimCmdExe {
int quantity = reward.getQuantity() == null ? 0 : reward.getQuantity(); int quantity = reward.getQuantity() == null ? 0 : reward.getQuantity();
return Long.parseLong(Integer.toString(quantity)); 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.framework.core.response.ResponseErrorCode;
import com.red.circle.mq.business.model.event.task.TaskApprovalEvent; import com.red.circle.mq.business.model.event.task.TaskApprovalEvent;
import com.red.circle.mq.rocket.business.producer.TaskMqMessage; 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.live.RoomProfileAppConvertor;
import com.red.circle.other.app.convertor.user.UserProfileAppConvertor; import com.red.circle.other.app.convertor.user.UserProfileAppConvertor;
import com.red.circle.other.app.dto.clientobject.room.EntryRoomEntrantsCO; import com.red.circle.other.app.dto.clientobject.room.EntryRoomEntrantsCO;
@ -82,6 +83,7 @@ public class RoomEnterCmdExe {
private final UserAgoraTokenCacheService userAgoraTokenCacheService; private final UserAgoraTokenCacheService userAgoraTokenCacheService;
private final TaskMqMessage taskMqMessage; private final TaskMqMessage taskMqMessage;
private final AdministratorAuthService administratorAuthService; private final AdministratorAuthService administratorAuthService;
private final RocketRewardClaimCmdExe rocketRewardClaimCmdExe;
public EntryRoomResponseCO execute(RoomEntryCmd cmd) { public EntryRoomResponseCO execute(RoomEntryCmd cmd) {
//checkEntryUserId(cmd); //checkEntryUserId(cmd);
@ -124,6 +126,13 @@ public class RoomEnterCmdExe {
log.error("进房间添加足迹异常:{}", e.getMessage()); 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() return new EntryRoomResponseCO()
.setRoomProfile( .setRoomProfile(
new EntryRoomProfileCO() new EntryRoomProfileCO()

View File

@ -23,25 +23,26 @@ import java.util.concurrent.ThreadPoolExecutor;
public class RocketAsyncConfig { public class RocketAsyncConfig {
/** /**
* 火箭任务线程池 * 火箭发射专用线程池
* 并发场景100个活跃房间活动期间可能有20-30个房间同时触发发射
*/ */
@Bean("rocketTaskExecutor") @Bean("rocketLaunchExecutor")
public Executor rocketTaskExecutor() { public Executor rocketLaunchExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); 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()); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 线程空闲时间 // 线程空闲时间
@ -53,7 +54,46 @@ public class RocketAsyncConfig {
executor.initialize(); 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; return executor;
} }

View File

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

View File

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