火箭升级完善

This commit is contained in:
tianfeng 2025-11-12 18:08:28 +08:00
parent c76526d3c0
commit f91f550e8e
16 changed files with 692 additions and 238 deletions

View File

@ -131,4 +131,14 @@ public enum GroupMessageTypeEnum {
*/
GAME_BAISHUN_WIN,
/**
* 火箭能力值更新
*/
ROCKET_ENERGY_UPDATE,
/**
* 火箭发射
*/
ROCKET_ENERGY_LAUNCH,
}

View File

@ -1,18 +1,14 @@
package com.red.circle.other.app.command.rocket;
import com.red.circle.other.app.dto.cmd.RocketEnergyAddCmd;
import com.red.circle.other.app.util.DistributedLockUtil;
import com.red.circle.other.domain.gateway.RocketStatusGateway;
import com.red.circle.other.domain.rocket.RocketStatus;
import com.red.circle.other.infra.database.cache.key.RocketKeys;
import com.red.circle.other.app.manager.RocketEnergyAggregator;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
/**
* 火箭能量增加命令执行器
* 优化版使用异步聚合避免频繁锁竞争和推送
*
* @author system
* @date 2025-01-15
@ -22,89 +18,16 @@ import java.time.LocalDate;
@RequiredArgsConstructor
public class RocketEnergyAddCmdExe {
private final RocketStatusGateway rocketStatusGateway;
private final DistributedLockUtil distributedLockUtil;
private final RocketLaunchCmdExe rocketLaunchCmdExe;
private final RocketEnergyAggregator rocketEnergyAggregator;
/**
* 幸运礼物加成比例
*/
private static final double LUCKY_BONUS_RATE = 0.04;
/**
* 执行能量增加
* 执行能量增加异步聚合
*/
public void execute(RocketEnergyAddCmd cmd) {
// 构建锁Key
String lockKey = RocketKeys.LOCK.getKey(cmd.getRoomId());
// 直接提交到聚合器不加锁
rocketEnergyAggregator.addEnergy(cmd);
// 使用分布式锁执行
distributedLockUtil.executeWithLock(lockKey, 5L, () -> {
doAddEnergy(cmd);
return null;
}, "火箭能量增加操作过于频繁,请稍后重试");
}
/**
* 执行能量增加逻辑
*/
private void doAddEnergy(RocketEnergyAddCmd cmd) {
// 1. 查询当前火箭状态
String today = LocalDate.now().toString();
RocketStatus rocketStatus = rocketStatusGateway.findByRoomIdAndDate(cmd.getRoomId(), today);
if (rocketStatus == null) {
// 初始化火箭
rocketStatus = RocketStatus.init(cmd.getRoomId());
log.info("初始化房间火箭, roomId={}", cmd.getRoomId());
}
// 2. 校验状态
if (!rocketStatus.canAddEnergy()) {
log.warn("火箭不可充能, roomId={}, status={}", cmd.getRoomId(), rocketStatus.getStatus());
return;
}
// 3. 计算能量值
long energyValue = calculateEnergy(cmd);
// 4. 增加能量
rocketStatus.addEnergy(
cmd.getUserId(),
cmd.getUserName(),
cmd.getUserAvatar(),
energyValue
);
// 5. 保存状态
rocketStatusGateway.save(rocketStatus);
log.info("火箭能量增加成功, roomId={}, userId={}, energy={}, currentEnergy={}, level={}",
cmd.getRoomId(), cmd.getUserId(), energyValue,
rocketStatus.getCurrentEnergy(), rocketStatus.getLevel().getLevel());
// 6. 检查是否需要发射
if (rocketStatus.isFullEnergy()) {
log.info("火箭能量已满, 准备发射, roomId={}, level={}, finalEnergy={}",
cmd.getRoomId(), rocketStatus.getLevel().getLevel(), rocketStatus.getCurrentEnergy());
// 触发发射
rocketLaunchCmdExe.execute(cmd.getRoomId());
}
}
/**
* 计算能量值
*/
private long calculateEnergy(RocketEnergyAddCmd cmd) {
long baseEnergy = cmd.getGiftGoldValue();
if (Boolean.TRUE.equals(cmd.getIsLucky())) {
// 幸运礼物额外增加4%
long luckyBonus = (long) (baseEnergy * LUCKY_BONUS_RATE);
return baseEnergy + luckyBonus;
}
return baseEnergy;
log.debug("提交火箭能量增加任务: roomId={}, userId={}, energy={}",
cmd.getRoomId(), cmd.getUserId(), cmd.getGiftGoldValue());
}
}

View File

@ -4,12 +4,14 @@ import com.red.circle.other.app.convertor.RocketConvertor;
import com.red.circle.other.app.dto.clientobject.RocketStatusCO;
import com.red.circle.other.app.manager.RocketImPushManager;
import com.red.circle.other.app.util.DistributedLockUtil;
import com.red.circle.other.domain.gateway.RocketConfigGateway;
import com.red.circle.other.domain.gateway.RocketHistoryGateway;
import com.red.circle.other.domain.gateway.RocketStatusGateway;
import com.red.circle.other.domain.rocket.RocketHistory;
import com.red.circle.other.domain.rocket.RocketStatus;
import com.red.circle.other.domain.rocket.RocketStatusEnum;
import com.red.circle.other.infra.database.cache.key.RocketKeys;
import com.red.circle.other.infra.database.mongo.service.live.RoomProfileManagerService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@ -18,6 +20,7 @@ import java.time.LocalDate;
/**
* 火箭发射命令执行器
* 优化版发射后自动升级到下一级火箭
*
* @author system
* @date 2025-01-15
@ -31,16 +34,16 @@ public class RocketLaunchCmdExe {
private final RocketHistoryGateway rocketHistoryGateway;
private final DistributedLockUtil distributedLockUtil;
private final RocketImPushManager rocketImPushManager;
private final RocketConfigGateway rocketConfigGateway;
private final RoomProfileManagerService roomProfileManagerService;
/**
* 执行火箭发射
*/
public void execute(Long roomId) {
// 构建锁Key
String lockKey = RocketKeys.LOCK.getKey(roomId);
// 使用分布式锁执行
distributedLockUtil.executeWithLock(lockKey, 5L, () -> {
distributedLockUtil.executeWithLock(lockKey, 10L, () -> {
doLaunch(roomId);
return null;
}, "火箭发射操作过于频繁,请稍后重试");
@ -50,7 +53,7 @@ public class RocketLaunchCmdExe {
* 执行发射逻辑
*/
private void doLaunch(Long roomId) {
// 1. 查询当前火箭状态
// 1. 查询当前火箭状态需要完整数据包含贡献者
String today = LocalDate.now().toString();
RocketStatus rocketStatus = rocketStatusGateway.findByRoomIdAndDate(roomId, today);
@ -72,37 +75,53 @@ public class RocketLaunchCmdExe {
return;
}
// 4. 发射火箭
rocketStatus.launch();
// 4. 记录发射前的等级用于日志和历史
int launchedLevel = rocketStatus.getLevel().getLevel();
long finalEnergy = rocketStatus.getCurrentEnergy();
int contributorCount = rocketStatus.getContributors() != null ? rocketStatus.getContributors().size() : 0;
// 5. 立即进入领取状态
rocketStatus.startClaiming();
// 6. 保存状态
rocketStatusGateway.save(rocketStatus);
// 7. 保存历史记录
// 5. 保存历史记录发射前保存保留贡献者信息
try {
RocketHistory history = RocketHistory.fromRocketStatus(rocketStatus);
String historyId = rocketHistoryGateway.save(history);
log.info("保存火箭历史记录成功: historyId={}, roomId={}", historyId, roomId);
log.info("保存火箭历史记录成功: historyId={}, roomId={}, level={}", historyId, roomId, launchedLevel);
} catch (Exception e) {
log.error("保存火箭历史记录失败: roomId={}", roomId, e);
}
// 8. 推送发射动画消息
// 6. 推送发射动画消息发射前推送包含贡献者信息
try {
RocketStatusCO statusCO = RocketConvertor.toStatusCO(rocketStatus);
rocketImPushManager.pushRocketLaunch(roomId, statusCO);
rocketImPushManager.pushRocketLaunch( statusCO);
} catch (Exception e) {
log.error("推送火箭发射消息失败: roomId={}", roomId, e);
}
log.info("火箭发射成功, roomId={}, level={}, finalEnergy={}, contributors={}, launchTime={}",
// 7. 发射并自动升级到下一级
rocketStatus.launchAndUpgrade(rocketConfigGateway);
// 8. 保存新状态
rocketStatusGateway.save(rocketStatus);
log.info("火箭发射并升级成功: roomId={}, 发射等级={}, 最终能量={}, 贡献者数={}, 升级到={}",
roomId,
rocketStatus.getLevel().getLevel(),
rocketStatus.getCurrentEnergy(),
rocketStatus.getContributors() != null ? rocketStatus.getContributors().size() : 0,
rocketStatus.getLaunchTime());
launchedLevel,
finalEnergy,
contributorCount,
rocketStatus.getLevel().getLevel());
// 9. 如果升级成功推送升级消息
if (rocketStatus.getLevel().getLevel() > launchedLevel) {
try {
RocketStatusCO newStatusCO = RocketConvertor.toStatusCO(rocketStatus);
String roomAccount = roomProfileManagerService.getRoomAccount(roomId);
rocketImPushManager.pushEnergyUpdate(roomAccount, newStatusCO);
String upgradeMsg = launchedLevel == 3 ? "重置为一级火箭" : "升级到" + rocketStatus.getLevel().getName();
log.info("推送火箭升级消息: roomId={}, {}", roomId, upgradeMsg);
} catch (Exception e) {
log.error("推送火箭升级消息失败: roomId={}", roomId, e);
}
}
}
}

View File

@ -0,0 +1,60 @@
package com.red.circle.other.app.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 火箭异步任务配置
*
* @author system
* @date 2025-01-15
*/
@Slf4j
@Configuration
@EnableAsync
@EnableScheduling
public class RocketAsyncConfig {
/**
* 火箭任务线程池
*/
@Bean("rocketTaskExecutor")
public Executor rocketTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数
executor.setCorePoolSize(5);
// 最大线程数
executor.setMaxPoolSize(10);
// 队列容量
executor.setQueueCapacity(100);
// 线程名前缀
executor.setThreadNamePrefix("rocket-task-");
// 拒绝策略调用者运行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 线程空闲时间
executor.setKeepAliveSeconds(60);
// 关闭时等待任务完成
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.initialize();
log.info("火箭任务线程池初始化完成");
return executor;
}
}

View File

@ -400,7 +400,7 @@ public class GiveGiftsListener implements MessageListener {
offlineProcessGiftEvent(OfflineStatisticsGiftEventType.RANK_COUNT, runningWater);
// 钻石统计相关
offlineProcessGiftEvent(OfflineStatisticsGiftEventType.DIAMOND_COUNT, runningWater);
offlineProcessGiftEvent(OfflineStatisticsGiftEventType.ROCKET_COUNT, runningWater);
}
private Pair<GiftGiveRunningWater,Boolean> saveRunningWater(GiveAwayGiftBatchEvent event) {

View File

@ -1,14 +1,28 @@
package com.red.circle.other.app.listener.gift.strategy;
import com.alibaba.fastjson.JSON;
import com.red.circle.mq.business.model.event.gift.OfflineProcessGiftEvent;
import com.red.circle.other.app.service.RocketService;
import com.red.circle.other.app.command.rocket.RocketEnergyAddCmdExe;
import com.red.circle.other.app.common.gift.GameLuckyGiftCommon;
import com.red.circle.other.app.dto.cmd.RocketEnergyAddCmd;
import com.red.circle.other.infra.database.cache.service.other.RoomManagerCacheService;
import com.red.circle.other.infra.database.mongo.entity.gift.GiftAcceptUser;
import com.red.circle.other.infra.database.mongo.entity.gift.GiftGiveRunningWater;
import com.red.circle.other.infra.database.mongo.entity.gift.GiftValue;
import com.red.circle.other.infra.database.mongo.service.gift.GiftGiveRunningWaterService;
import com.red.circle.other.infra.database.mongo.service.live.RoomProfileManagerService;
import com.red.circle.other.inner.enums.material.GiftCurrencyType;
import com.red.circle.other.inner.enums.material.GiftTabEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Objects;
/**
* 火箭统计相关
* 火箭统计相关 - 礼物送出时增加火箭能量
*
* @author system
* @date 2025-01-15
@ -16,14 +30,100 @@ import org.springframework.stereotype.Service;
@Slf4j
@Service("ROCKET_COUNT_LISTENER")
@RequiredArgsConstructor
public class GiftSendRocketListener implements GiftStrategy {
private final RocketService rocketService;
public class GiftSendRocketListener implements GiftStrategy {
private final RocketEnergyAddCmdExe rocketEnergyAddCmdExe;
private final GiftGiveRunningWaterService giftGiveRunningWaterService;
private final GameLuckyGiftCommon gameLuckyGiftCommon;
private final RoomProfileManagerService roomProfileManagerService;
@Override
public void processor(OfflineProcessGiftEvent event) {
log.info("【火箭能量增加】接收礼物事件: {}", JSON.toJSONString(event));
// 1. 查询礼物流水
GiftGiveRunningWater runningWater = giftGiveRunningWaterService.getByIdPrimary(
event.getRunningWaterId());
if (Objects.isNull(runningWater)) {
log.warn("【火箭能量增加】没有找到流水信息,忽略处理");
return;
}
// 2. 只处理金币礼物
if (!GiftCurrencyType.GOLD.eq(runningWater.getGiftValue().getCurrencyType())) {
log.debug("【火箭能量增加】非金币礼物,跳过处理");
return;
}
giftGiveRunningWaterService.addLog(runningWater.getId(), "开始:火箭能量增加");
try {
// 3. 获取礼物信息
GiftValue giftValue = runningWater.getGiftValue();
// 4. 判断是否为幸运礼物
boolean isLuckyGift = isLuckyGift(giftValue);
// 5. 获取幸运礼物比例
BigDecimal luckyGiftRatio = BigDecimal.ZERO;
if (isLuckyGift) {
luckyGiftRatio = gameLuckyGiftCommon.getLuckyGiftShareRatio(runningWater.getSysOrigin());
log.info("【火箭能量增加】幸运礼物比例:{}", luckyGiftRatio);
}
// 6. 计算实际金币价值考虑幸运礼物加成
Long actualGoldValue = getActualAmount(isLuckyGift, giftValue.getActualAmount(), luckyGiftRatio);
// 7. 只处理在房间内送出的礼物
String originId = runningWater.getOriginId();
if (originId == null || !originId.matches("\\d+")) {
log.debug("【火箭能量增加】非房间场景,跳过处理");
return;
}
Long roomId = Long.parseLong(originId);
// 8. 获取送礼用户信息
Long userId = runningWater.getUserId();
// 9. 构建命令并执行
RocketEnergyAddCmd cmd = new RocketEnergyAddCmd();
cmd.setRoomId(roomId);
cmd.setUserId(userId);
cmd.setGiftGoldValue(actualGoldValue);
cmd.setIsLucky(isLuckyGift);
rocketEnergyAddCmdExe.execute(cmd);
log.info("【火箭能量增加】处理成功: roomId={}, userId={}, goldValue={}, isLucky={}",
roomId, userId, actualGoldValue, isLuckyGift);
} catch (Exception e) {
log.error("【火箭能量增加】处理异常: runningWaterId={}", runningWater.getId(), e);
} finally {
giftGiveRunningWaterService.addLog(runningWater.getId(), "结束:火箭能量增加");
}
}
/**
* 判断是否为幸运礼物
*/
private boolean isLuckyGift(GiftValue giftValue) {
return giftValue.getGiftType() != null &&
giftValue.getGiftType().contains(GiftTabEnum.LUCKY_GIFT.name());
}
/**
* 计算实际金额考虑幸运礼物加成
*/
private Long getActualAmount(boolean isLuckyGift, BigDecimal amount, BigDecimal luckyGiftRatio) {
if (isLuckyGift && luckyGiftRatio != null && luckyGiftRatio.compareTo(BigDecimal.ZERO) > 0) {
// 幸运礼物按比例计算
return amount.multiply(luckyGiftRatio)
.setScale(0, RoundingMode.DOWN)
.longValue();
}
return amount.longValue();
}
}

View File

@ -0,0 +1,265 @@
package com.red.circle.other.app.manager;
import com.red.circle.other.app.convertor.RocketConvertor;
import com.red.circle.other.app.dto.clientobject.RocketStatusCO;
import com.red.circle.other.app.dto.cmd.RocketEnergyAddCmd;
import com.red.circle.other.app.util.DistributedLockUtil;
import com.red.circle.other.domain.gateway.RocketConfigGateway;
import com.red.circle.other.domain.gateway.RocketStatusGateway;
import com.red.circle.other.domain.rocket.RocketConfig;
import com.red.circle.other.domain.rocket.RocketStatus;
import com.red.circle.other.infra.database.cache.key.RocketKeys;
import com.red.circle.other.infra.database.mongo.entity.live.RoomProfileManager;
import com.red.circle.other.infra.database.mongo.service.live.RoomProfileManagerService;
import com.red.circle.other.infra.gateway.RocketStatusGatewayImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
/**
* 火箭能量聚合器
* 职责
* 1. 接收能量增加请求缓存到内存
* 2. 定时批量刷新到数据库
* 3. 定时推送能量更新消息
*
* @author system
* @date 2025-01-15
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class RocketEnergyAggregator {
private final RocketStatusGateway rocketStatusGateway;
private final RocketStatusGatewayImpl rocketStatusGatewayImpl;
private final DistributedLockUtil distributedLockUtil;
private final RocketConfigGateway rocketConfigGateway;
private final RocketImPushManager rocketImPushManager;
private final RocketLaunchManager rocketLaunchManager;
private final RoomProfileManagerService roomProfileManagerService;
/**
* 房间能量聚合缓存
* Key: roomId
* Value: 该房间待聚合的能量列表
*/
private final Map<Long, List<RocketEnergyAddCmd>> energyBuffer = new ConcurrentHashMap<>();
/**
* 房间级别的细粒度锁
* Key: roomId
* Value: 该房间的锁
*/
private final Map<Long, ReentrantLock> roomLocks = new ConcurrentHashMap<>();
/**
* 推送间隔毫秒
*/
private static final long PUSH_INTERVAL = 3000L; // 3秒推送一次
/**
* 批量刷新间隔毫秒
*/
private static final long FLUSH_INTERVAL = 5000L; // 5秒刷新一次
/**
* 上次推送时间
* Key: roomId
* Value: 上次推送的时间戳
*/
private final Map<Long, Long> lastPushTime = new ConcurrentHashMap<>();
/**
* 接收能量增加请求无锁直接缓存
*/
public void addEnergy(RocketEnergyAddCmd cmd) {
energyBuffer.computeIfAbsent(cmd.getRoomId(), k -> new ArrayList<>()).add(cmd);
log.debug("缓存火箭能量: roomId={}, userId={}, energy={}, bufferSize={}",
cmd.getRoomId(), cmd.getUserId(), cmd.getGiftGoldValue(),
energyBuffer.get(cmd.getRoomId()).size());
}
/**
* 定时批量刷新到数据库每5秒
*/
@Scheduled(fixedDelay = FLUSH_INTERVAL)
public void flushToDatabase() {
if (energyBuffer.isEmpty()) {
return;
}
log.info("开始批量刷新火箭能量, 待处理房间数={}", energyBuffer.size());
// 遍历所有房间
energyBuffer.forEach((roomId, cmdList) -> {
if (cmdList.isEmpty()) {
return;
}
try {
// 获取该房间的细粒度锁
ReentrantLock roomLock = roomLocks.computeIfAbsent(roomId, k -> new ReentrantLock());
if (roomLock.tryLock()) {
try {
// 取出当前批次的数据
List<RocketEnergyAddCmd> batch = new ArrayList<>(cmdList);
cmdList.clear();
// 处理这个房间的能量增加
processRoomEnergy(roomId, batch);
} finally {
roomLock.unlock();
}
} else {
log.debug("房间{}正在处理中,跳过本次刷新", roomId);
}
} catch (Exception e) {
log.error("刷新房间{}火箭能量失败", roomId, e);
}
});
}
/**
* 处理单个房间的能量增加
*/
private void processRoomEnergy(Long roomId, List<RocketEnergyAddCmd> cmdList) {
if (cmdList.isEmpty()) {
return;
}
String lockKey = RocketKeys.LOCK.getKey(roomId);
// 使用Redis分布式锁避免多实例冲突
distributedLockUtil.executeWithLock(lockKey, 10L, () -> {
// 1. 查询当前火箭状态不包含贡献者列表性能优化
String today = LocalDate.now().toString();
RocketStatus rocketStatus = rocketStatusGatewayImpl
.findByRoomIdAndDateWithoutContributors(roomId, today);
if (rocketStatus == null) {
// 查询一级火箭配置
RocketConfig level1Config = rocketConfigGateway.findByLevel(1);
if (level1Config == null || level1Config.getStatus() != 1) {
log.error("一级火箭配置不存在或未启用");
return null;
}
// 使用配置初始化
rocketStatus = RocketStatus.init(roomId, level1Config);
log.info("初始化房间火箭, roomId={}, maxEnergy={}", roomId, level1Config.getMaxEnergy());
}
// 2. 检查状态
if (!rocketStatus.canAddEnergy()) {
log.warn("火箭不可充能, roomId={}, status={}", roomId, rocketStatus.getStatus());
return null;
}
// 3. 批量增加能量
long totalEnergy = 0L;
for (RocketEnergyAddCmd cmd : cmdList) {
rocketStatus.addEnergy(
cmd.getUserId(),
cmd.getGiftGoldValue()
);
totalEnergy += cmd.getGiftGoldValue();
}
// 4. 保存状态一次性保存
rocketStatusGateway.save(rocketStatus);
log.info("批量增加火箭能量: roomId={}, 批次数量={}, 总能量={}, 当前能量={}/{}",
roomId, cmdList.size(), totalEnergy,
rocketStatus.getCurrentEnergy(), rocketStatus.getMaxEnergy());
// 5. 推送更新消息节流
String roomAccount = roomProfileManagerService.getRoomAccount(roomId);
pushEnergyUpdate(roomId, roomAccount, rocketStatus);
// 6. 检查是否需要发射
if (rocketStatus.isFullEnergy()) {
log.info("火箭能量已满, 准备发射, roomId={}, level={}",
roomId, rocketStatus.getLevel().getLevel());
// 异步触发发射
rocketLaunchManager.launchAsync(roomId);
}
return null;
}, "火箭能量刷新操作中,请稍后");
}
/**
* 推送能量更新节流3秒推送一次
*/
private void pushEnergyUpdate(Long roomId, String roomAccount, RocketStatus rocketStatus) {
long now = System.currentTimeMillis();
Long lastPush = lastPushTime.get(roomId);
// 节流距离上次推送不足3秒跳过
if (lastPush != null && (now - lastPush) < PUSH_INTERVAL) {
log.debug("推送节流:距离上次推送{}ms跳过本次推送", now - lastPush);
return;
}
try {
// 查询火箭状态
RocketStatus fullStatus = rocketStatusGatewayImpl
.findByRoomIdAndDateWithoutContributors(roomId, rocketStatus.getDate());
if (fullStatus != null) {
RocketStatusCO statusCO = RocketConvertor.toStatusCO(fullStatus);
rocketImPushManager.pushEnergyUpdate(roomAccount, statusCO);
// 更新推送时间
lastPushTime.put(roomId, now);
log.info("推送火箭能量更新: roomId={}, energy={}/{}",
roomId, fullStatus.getCurrentEnergy(), fullStatus.getMaxEnergy());
}
} catch (Exception e) {
log.error("推送火箭能量更新失败: roomId={}", roomId, e);
}
}
/**
* 定时清理空闲房间的锁每小时
*/
@Scheduled(fixedRate = 3600000)
public void cleanupIdleRooms() {
// 清理buffer中的空列表
energyBuffer.entrySet().removeIf(entry -> entry.getValue().isEmpty());
// 清理不在buffer中的锁
roomLocks.keySet().removeIf(roomId -> !energyBuffer.containsKey(roomId));
// 清理推送时间
lastPushTime.keySet().removeIf(roomId -> !energyBuffer.containsKey(roomId));
log.info("清理空闲房间资源, 当前活跃房间数={}", energyBuffer.size());
}
/**
* 应用关闭时强制刷新
*/
@javax.annotation.PreDestroy
public void shutdown() {
log.info("应用关闭,强制刷新所有待处理能量");
flushToDatabase();
}
}

View File

@ -31,45 +31,39 @@ public class RocketImPushManager {
/**
* 推送能量更新消息
*/
public void pushEnergyUpdate(Long roomId, RocketStatusCO status) {
public void pushEnergyUpdate(String roomAccount, RocketStatusCO status) {
try {
// 构建消息
ImMessage message = buildEnergyUpdateMessage(status);
imGroupClient.sendCustomMessage(String.valueOf(roomId),
imGroupClient.sendCustomMessage(roomAccount,
CustomGroupMsgBodyCmd.builder()
.type(GroupMessageTypeEnum.GAME_LUCKY_GIFT)
.data(message)
.type(GroupMessageTypeEnum.ROCKET_ENERGY_UPDATE)
.data(status)
.build());
log.info("推送火箭能量更新消息成功: roomId={}, level={}, energy={}/{}",
roomId, status.getLevel(), status.getCurrentEnergy(), status.getMaxEnergy());
roomAccount, status.getLevel(), status.getCurrentEnergy(), status.getMaxEnergy());
} catch (Exception e) {
log.error("推送火箭能量更新消息失败: roomId={}", roomId, e);
log.error("推送火箭能量更新消息失败: roomId={}", roomAccount, e);
}
}
/**
* 推送火箭发射消息
*/
public void pushRocketLaunch(Long roomId, RocketStatusCO status) {
public void pushRocketLaunch(RocketStatusCO status) {
try {
// 构建消息
ImMessage message = buildLaunchMessage(status);
imGroupClient.sendMessageBroadcast(
BroadcastGroupMsgBodyCmd.builder()
.toPlatform(SysOriginPlatformEnum.LIKEI)
.type(GroupMessageTypeEnum.GAME_LUCKY_GIFT)
.data(message)
.type(GroupMessageTypeEnum.ROCKET_ENERGY_LAUNCH)
.data(status)
.build()
);
log.info("推送火箭发射消息成功: roomId={}, level={}", roomId, status.getLevel());
log.info("推送火箭发射消息成功: roomId={}, level={}", status.getRoomId(), status.getLevel());
} catch (Exception e) {
log.error("推送火箭发射消息失败: roomId={}", roomId, e);
log.error("推送火箭发射消息失败: roomId={}", status.getRoomId(), e);
}
}
@ -155,8 +149,7 @@ public class RocketImPushManager {
}
/**
* IM消息对象示例
* TODO: 替换为实际的IM消息类
* IM消息对象
*/
private static class ImMessage {
private String type;

View File

@ -0,0 +1,35 @@
package com.red.circle.other.app.manager;
import com.red.circle.other.app.command.rocket.RocketLaunchCmdExe;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
/**
* 火箭发射管理器
* 职责异步处理火箭发射
*
* @author system
* @date 2025-01-15
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class RocketLaunchManager {
private final RocketLaunchCmdExe rocketLaunchCmdExe;
/**
* 异步发射火箭
*/
@Async("rocketTaskExecutor")
public void launchAsync(Long roomId) {
try {
log.info("异步执行火箭发射: roomId={}", roomId);
rocketLaunchCmdExe.execute(roomId);
} catch (Exception e) {
log.error("异步火箭发射失败: roomId={}", roomId, e);
}
}
}

View File

@ -1,57 +0,0 @@
package com.red.circle.other.app.scheduler;
import com.red.circle.other.domain.gateway.RocketStatusGateway;
import com.red.circle.other.domain.rocket.RocketStatus;
import com.red.circle.other.domain.rocket.RocketStatusEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.util.List;
/**
* 火箭领取过期检查任务
*
* @author system
* @date 2025-01-15
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class RocketClaimExpireTask {
private final RocketStatusGateway rocketStatusGateway;
/**
* 每分钟检查一次过期状态
*/
@Scheduled(fixedRate = 60000)
public void checkExpire() {
try {
String today = LocalDate.now().toString();
// 查询所有处于充能状态的火箭 (包含CLAIMING状态)
List<RocketStatus> allRockets = rocketStatusGateway.findAllCharging(today);
for (RocketStatus rocketStatus : allRockets) {
// 检查是否过期
if (rocketStatus.getStatus() == RocketStatusEnum.CLAIMING && rocketStatus.isExpired()) {
log.info("火箭领取已过期, 开始重置, roomId={}, claimExpireTime={}",
rocketStatus.getRoomId(), rocketStatus.getClaimExpireTime());
// TODO: 保存历史记录
// 重置火箭
rocketStatus.reset();
rocketStatusGateway.save(rocketStatus);
log.info("过期火箭重置成功, roomId={}", rocketStatus.getRoomId());
}
}
} catch (Exception e) {
log.error("火箭过期检查任务执行失败", e);
}
}
}

View File

@ -55,6 +55,25 @@ public enum RocketLevel {
throw new IllegalArgumentException("不支持的火箭等级: " + level);
}
/**
* 获取下一级火箭
* @return 下一级火箭如果已是最高级则返回null
*/
public RocketLevel getNextLevel() {
return switch (this) {
case LEVEL_1 -> LEVEL_2;
case LEVEL_2 -> LEVEL_3;
case LEVEL_3 -> null; // 三级是最高级返回null表示需要重置
};
}
/**
* 是否是最高级
*/
public boolean isMaxLevel() {
return this == LEVEL_3;
}
/**
* 根据能量值判断等级
*/

View File

@ -1,6 +1,8 @@
package com.red.circle.other.domain.rocket;
import com.red.circle.other.domain.gateway.RocketConfigGateway;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
import java.math.RoundingMode;
@ -17,6 +19,7 @@ import java.util.List;
* @date 2025-01-15
*/
@Data
@Slf4j
public class RocketStatus {
/**
@ -87,13 +90,13 @@ public class RocketStatus {
/**
* 初始化火箭
*/
public static RocketStatus init(Long roomId) {
public static RocketStatus init(Long roomId, RocketConfig config) {
RocketStatus rocketStatus = new RocketStatus();
rocketStatus.setRoomId(roomId);
rocketStatus.setDate(LocalDate.now().toString());
rocketStatus.setLevel(RocketLevel.LEVEL_1);
rocketStatus.setLevel(RocketLevel.of(config.getLevel()));
rocketStatus.setCurrentEnergy(0L);
rocketStatus.setMaxEnergy(RocketLevel.LEVEL_1.getMaxEnergy());
rocketStatus.setMaxEnergy(config.getMaxEnergy());
rocketStatus.setStatus(RocketStatusEnum.CHARGING);
rocketStatus.setContributors(new ArrayList<>());
rocketStatus.setClaimCount(0);
@ -101,52 +104,100 @@ public class RocketStatus {
rocketStatus.setUpdatedAt(LocalDateTime.now());
return rocketStatus;
}
/**
* 增加能量
*/
public void addEnergy(Long userId, String userName, String userAvatar, Long energyValue) {
// 增加总能量
public void addEnergy(Long userId,Long energyValue) {
this.currentEnergy += energyValue;
// 更新贡献者
RocketContributor contributor = findContributor(userId);
if (contributor == null) {
contributor = new RocketContributor();
contributor.setUserId(userId);
contributor.setUserName(userName);
contributor.setUserAvatar(userAvatar);
contributor.setEnergy(0L);
contributor.setGiftCount(0);
contributor.setFirstContributeTime(LocalDateTime.now());
this.contributors.add(contributor);
}
contributor.addEnergy(energyValue);
contributor.setLastContributeTime(LocalDateTime.now());
// 重新计算贡献率
recalculateContributionRate();
// 检查等级提升
checkAndUpgradeLevel();
this.updatedAt = LocalDateTime.now();
}
// RocketStatus.java
/**
* 发射火箭
* 升级到下一级火箭使用配置
*/
public void launch() {
private void upgradeToNextLevel(RocketConfigGateway configGateway) {
RocketLevel nextLevel = this.level.getNextLevel();
if (nextLevel != null) {
// 查询下一级配置
RocketConfig nextConfig = configGateway.findByLevel(nextLevel.getLevel());
if (nextConfig == null || nextConfig.getStatus() != 1) {
log.warn("下一级火箭配置不存在或未启用, level={}", nextLevel.getLevel());
return;
}
// 计算溢出能量当前能量 - 当前等级最大能量
long overflowEnergy = this.currentEnergy - this.maxEnergy;
if (overflowEnergy < 0) {
overflowEnergy = 0L;
}
// 升级到下一级
this.level = nextLevel;
this.maxEnergy = nextConfig.getMaxEnergy();
this.currentEnergy = overflowEnergy; // 保留溢出能量
this.status = RocketStatusEnum.CHARGING;
// 清空贡献者新一轮
this.contributors = new ArrayList<>();
this.launchTime = null;
this.claimExpireTime = null;
log.info("火箭升级成功, 升至{}级, 溢出能量={}, 新目标={}",
nextLevel.getLevel(), overflowEnergy, this.maxEnergy);
} else {
// 已经是最高级重置为一级
// reset(configGateway);
}
}
/**
* 发射火箭并自动升级需要传入配置网关
*/
public void launchAndUpgrade(RocketConfigGateway configGateway) {
this.status = RocketStatusEnum.LAUNCHED;
this.launchTime = LocalDateTime.now();
// 领取过期时间发射后1分钟
this.claimExpireTime = LocalDateTime.now().plusMinutes(1);
this.updatedAt = LocalDateTime.now();
// 升级到下一级
upgradeToNextLevel(configGateway);
}
/**
* 开始领取
* 重置状态使用一级配置
*/
public void startClaiming() {
this.status = RocketStatusEnum.CLAIMING;
public void reset(RocketConfigGateway configGateway) {
// 查询一级配置
RocketConfig level1Config = configGateway.findByLevel(1);
if (level1Config == null) {
throw new RuntimeException("一级火箭配置不存在");
}
this.date = LocalDate.now().toString();
this.level = RocketLevel.LEVEL_1;
this.currentEnergy = 0L;
this.maxEnergy = level1Config.getMaxEnergy();
this.status = RocketStatusEnum.CHARGING;
this.contributors = new ArrayList<>();
this.launchTime = null;
this.claimExpireTime = null;
this.claimCount = 0;
this.updatedAt = LocalDateTime.now();
}
@ -162,7 +213,7 @@ public class RocketStatus {
}
/**
* 重置状态
* 重置状态三级发射后重置为一级
*/
public void reset() {
this.date = LocalDate.now().toString();
@ -192,12 +243,10 @@ public class RocketStatus {
}
/**
* 是否可以领取
* 是否可以领取已移除CLAIMING状态
*/
public boolean canClaim() {
return this.status == RocketStatusEnum.CLAIMING
&& this.claimExpireTime != null
&& LocalDateTime.now().isBefore(this.claimExpireTime);
return false; // 暂时隐藏领取功能
}
/**
@ -249,18 +298,6 @@ public class RocketStatus {
contributor.setContributionRate(rate);
});
// 按贡献能量排序
this.contributors.sort(Comparator.comparing(RocketContributor::getEnergy).reversed());
}
/**
* 检查并升级等级
*/
private void checkAndUpgradeLevel() {
RocketLevel newLevel = RocketLevel.determineLevel(this.currentEnergy);
if (newLevel != this.level) {
this.level = newLevel;
this.maxEnergy = newLevel.getMaxEnergy();
}
}
}

View File

@ -19,14 +19,9 @@ public enum RocketStatusEnum {
CHARGING(1, "充能中"),
/**
* 已发射
* 已发射等待下一级
*/
LAUNCHED(2, "已发射"),
/**
* 领取中
*/
CLAIMING(3, "领取中");
LAUNCHED(2, "已发射");
/**
* 状态码

View File

@ -27,21 +27,21 @@ public interface RocketStatusRepository extends MongoRepository<RocketStatusDocu
* 根据房间ID和日期查询排除贡献者列表性能优化
* 用于仅需要火箭状态不需要贡献者详情的场景
*/
@Query(value = "{ 'room_id': ?0, 'date': ?1 }", fields = "{ 'contributors': 0 }")
@Query(value = "{ 'roomId': ?0, 'date': ?1 }", fields = "{ 'contributors': 0 }")
Optional<RocketStatusDocument> findByRoomIdAndDateWithoutContributors(Long roomId, String date);
/**
* 根据房间ID和日期查询只包含Top N贡献者
* 用于前端展示Top榜单的场景
*/
@Query(value = "{ 'room_id': ?0, 'date': ?1 }",
@Query(value = "{ 'roomId': ?0, 'date': ?1 }",
fields = "{ 'contributors': { $slice: ?2 } }")
Optional<RocketStatusDocument> findByRoomIdAndDateWithTopContributors(Long roomId, String date, Integer topN);
/**
* 根据房间ID列表和日期查询排除贡献者列表
*/
@Query(value = "{ 'room_id': { $in: ?0 }, 'date': ?1 }", fields = "{ 'contributors': 0 }")
@Query(value = "{ 'roomId': { $in: ?0 }, 'date': ?1 }", fields = "{ 'contributors': 0 }")
List<RocketStatusDocument> findByRoomIdInAndDateWithoutContributors(List<Long> roomIds, String date);
/**

View File

@ -17,6 +17,8 @@ import java.util.Set;
*/
public interface RoomProfileManagerService {
String getRoomAccount(Long roomId);
/**
* 用户是否存在房间.
*/

View File

@ -26,14 +26,18 @@ import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
/**
@ -43,9 +47,58 @@ import org.springframework.stereotype.Service;
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class RoomProfileManagerServiceImpl implements RoomProfileManagerService {
private final MongoTemplate mongoTemplate;
private final MongoTemplate mongoTemplate;
private final RedisTemplate<String, String> redisTemplate;
/**
* Redis缓存Key前缀
*/
private static final String ROOM_ACCOUNT_CACHE_KEY = "rocket:room:account:";
/**
* Redis缓存过期时间1小时
*/
private static final long ROOM_ACCOUNT_CACHE_EXPIRE = 3600L;
/**
* 获取房间Account带Redis缓存
*
* @param roomId 房间ID
* @return 房间Account
*/
@Override
public String getRoomAccount(Long roomId) {
if (roomId == null) {
return "";
}
// 1. 先查Redis缓存
String cacheKey = ROOM_ACCOUNT_CACHE_KEY + roomId;
String account = redisTemplate.opsForValue().get(cacheKey);
if (account != null) {
return account;
}
// 2. 查MongoDB
try {
RoomProfile roomProfile = getById(roomId);
if (roomProfile != null && roomProfile.getRoomAccount() != null) {
account = roomProfile.getRoomAccount();
// 写入Redis缓存
redisTemplate.opsForValue().set(cacheKey, account, ROOM_ACCOUNT_CACHE_EXPIRE, TimeUnit.SECONDS);
return account;
}
} catch (Exception e) {
log.error("查询房间Account失败: roomId={}", roomId, e);
}
return "";
}
@Override
public boolean existsUserRoom(Long userId) {