火箭升级完善
This commit is contained in:
parent
c76526d3c0
commit
f91f550e8e
@ -131,4 +131,14 @@ public enum GroupMessageTypeEnum {
|
|||||||
*/
|
*/
|
||||||
GAME_BAISHUN_WIN,
|
GAME_BAISHUN_WIN,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 火箭能力值更新
|
||||||
|
*/
|
||||||
|
ROCKET_ENERGY_UPDATE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 火箭发射
|
||||||
|
*/
|
||||||
|
ROCKET_ENERGY_LAUNCH,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,18 +1,14 @@
|
|||||||
package com.red.circle.other.app.command.rocket;
|
package com.red.circle.other.app.command.rocket;
|
||||||
|
|
||||||
import com.red.circle.other.app.dto.cmd.RocketEnergyAddCmd;
|
import com.red.circle.other.app.dto.cmd.RocketEnergyAddCmd;
|
||||||
import com.red.circle.other.app.util.DistributedLockUtil;
|
import com.red.circle.other.app.manager.RocketEnergyAggregator;
|
||||||
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 lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 火箭能量增加命令执行器
|
* 火箭能量增加命令执行器
|
||||||
|
* 优化版:使用异步聚合,避免频繁锁竞争和推送
|
||||||
*
|
*
|
||||||
* @author system
|
* @author system
|
||||||
* @date 2025-01-15
|
* @date 2025-01-15
|
||||||
@ -22,89 +18,16 @@ import java.time.LocalDate;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class RocketEnergyAddCmdExe {
|
public class RocketEnergyAddCmdExe {
|
||||||
|
|
||||||
private final RocketStatusGateway rocketStatusGateway;
|
private final RocketEnergyAggregator rocketEnergyAggregator;
|
||||||
private final DistributedLockUtil distributedLockUtil;
|
|
||||||
private final RocketLaunchCmdExe rocketLaunchCmdExe;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 幸运礼物加成比例
|
* 执行能量增加(异步聚合)
|
||||||
*/
|
|
||||||
private static final double LUCKY_BONUS_RATE = 0.04;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 执行能量增加
|
|
||||||
*/
|
*/
|
||||||
public void execute(RocketEnergyAddCmd cmd) {
|
public void execute(RocketEnergyAddCmd cmd) {
|
||||||
// 构建锁Key
|
// 直接提交到聚合器,不加锁
|
||||||
String lockKey = RocketKeys.LOCK.getKey(cmd.getRoomId());
|
rocketEnergyAggregator.addEnergy(cmd);
|
||||||
|
|
||||||
// 使用分布式锁执行
|
log.debug("提交火箭能量增加任务: roomId={}, userId={}, energy={}",
|
||||||
distributedLockUtil.executeWithLock(lockKey, 5L, () -> {
|
cmd.getRoomId(), cmd.getUserId(), cmd.getGiftGoldValue());
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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.dto.clientobject.RocketStatusCO;
|
||||||
import com.red.circle.other.app.manager.RocketImPushManager;
|
import com.red.circle.other.app.manager.RocketImPushManager;
|
||||||
import com.red.circle.other.app.util.DistributedLockUtil;
|
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.RocketHistoryGateway;
|
||||||
import com.red.circle.other.domain.gateway.RocketStatusGateway;
|
import com.red.circle.other.domain.gateway.RocketStatusGateway;
|
||||||
import com.red.circle.other.domain.rocket.RocketHistory;
|
import com.red.circle.other.domain.rocket.RocketHistory;
|
||||||
import com.red.circle.other.domain.rocket.RocketStatus;
|
import com.red.circle.other.domain.rocket.RocketStatus;
|
||||||
import com.red.circle.other.domain.rocket.RocketStatusEnum;
|
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.cache.key.RocketKeys;
|
||||||
|
import com.red.circle.other.infra.database.mongo.service.live.RoomProfileManagerService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
@ -18,6 +20,7 @@ import java.time.LocalDate;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 火箭发射命令执行器
|
* 火箭发射命令执行器
|
||||||
|
* 优化版:发射后自动升级到下一级火箭
|
||||||
*
|
*
|
||||||
* @author system
|
* @author system
|
||||||
* @date 2025-01-15
|
* @date 2025-01-15
|
||||||
@ -31,16 +34,16 @@ public class RocketLaunchCmdExe {
|
|||||||
private final RocketHistoryGateway rocketHistoryGateway;
|
private final RocketHistoryGateway rocketHistoryGateway;
|
||||||
private final DistributedLockUtil distributedLockUtil;
|
private final DistributedLockUtil distributedLockUtil;
|
||||||
private final RocketImPushManager rocketImPushManager;
|
private final RocketImPushManager rocketImPushManager;
|
||||||
|
private final RocketConfigGateway rocketConfigGateway;
|
||||||
|
private final RoomProfileManagerService roomProfileManagerService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行火箭发射
|
* 执行火箭发射
|
||||||
*/
|
*/
|
||||||
public void execute(Long roomId) {
|
public void execute(Long roomId) {
|
||||||
// 构建锁Key
|
|
||||||
String lockKey = RocketKeys.LOCK.getKey(roomId);
|
String lockKey = RocketKeys.LOCK.getKey(roomId);
|
||||||
|
|
||||||
// 使用分布式锁执行
|
distributedLockUtil.executeWithLock(lockKey, 10L, () -> {
|
||||||
distributedLockUtil.executeWithLock(lockKey, 5L, () -> {
|
|
||||||
doLaunch(roomId);
|
doLaunch(roomId);
|
||||||
return null;
|
return null;
|
||||||
}, "火箭发射操作过于频繁,请稍后重试");
|
}, "火箭发射操作过于频繁,请稍后重试");
|
||||||
@ -50,7 +53,7 @@ public class RocketLaunchCmdExe {
|
|||||||
* 执行发射逻辑
|
* 执行发射逻辑
|
||||||
*/
|
*/
|
||||||
private void doLaunch(Long roomId) {
|
private void doLaunch(Long roomId) {
|
||||||
// 1. 查询当前火箭状态
|
// 1. 查询当前火箭状态(需要完整数据,包含贡献者)
|
||||||
String today = LocalDate.now().toString();
|
String today = LocalDate.now().toString();
|
||||||
RocketStatus rocketStatus = rocketStatusGateway.findByRoomIdAndDate(roomId, today);
|
RocketStatus rocketStatus = rocketStatusGateway.findByRoomIdAndDate(roomId, today);
|
||||||
|
|
||||||
@ -72,37 +75,53 @@ public class RocketLaunchCmdExe {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 发射火箭
|
// 4. 记录发射前的等级(用于日志和历史)
|
||||||
rocketStatus.launch();
|
int launchedLevel = rocketStatus.getLevel().getLevel();
|
||||||
|
long finalEnergy = rocketStatus.getCurrentEnergy();
|
||||||
|
int contributorCount = rocketStatus.getContributors() != null ? rocketStatus.getContributors().size() : 0;
|
||||||
|
|
||||||
// 5. 立即进入领取状态
|
// 5. 保存历史记录(发射前保存,保留贡献者信息)
|
||||||
rocketStatus.startClaiming();
|
|
||||||
|
|
||||||
// 6. 保存状态
|
|
||||||
rocketStatusGateway.save(rocketStatus);
|
|
||||||
|
|
||||||
// 7. 保存历史记录
|
|
||||||
try {
|
try {
|
||||||
RocketHistory history = RocketHistory.fromRocketStatus(rocketStatus);
|
RocketHistory history = RocketHistory.fromRocketStatus(rocketStatus);
|
||||||
String historyId = rocketHistoryGateway.save(history);
|
String historyId = rocketHistoryGateway.save(history);
|
||||||
log.info("保存火箭历史记录成功: historyId={}, roomId={}", historyId, roomId);
|
log.info("保存火箭历史记录成功: historyId={}, roomId={}, level={}", historyId, roomId, launchedLevel);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("保存火箭历史记录失败: roomId={}", roomId, e);
|
log.error("保存火箭历史记录失败: roomId={}", roomId, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 8. 推送发射动画消息
|
// 6. 推送发射动画消息(发射前推送,包含贡献者信息)
|
||||||
try {
|
try {
|
||||||
RocketStatusCO statusCO = RocketConvertor.toStatusCO(rocketStatus);
|
RocketStatusCO statusCO = RocketConvertor.toStatusCO(rocketStatus);
|
||||||
rocketImPushManager.pushRocketLaunch(roomId, statusCO);
|
rocketImPushManager.pushRocketLaunch( statusCO);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("推送火箭发射消息失败: roomId={}", roomId, 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,
|
roomId,
|
||||||
rocketStatus.getLevel().getLevel(),
|
launchedLevel,
|
||||||
rocketStatus.getCurrentEnergy(),
|
finalEnergy,
|
||||||
rocketStatus.getContributors() != null ? rocketStatus.getContributors().size() : 0,
|
contributorCount,
|
||||||
rocketStatus.getLaunchTime());
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -400,7 +400,7 @@ public class GiveGiftsListener implements MessageListener {
|
|||||||
offlineProcessGiftEvent(OfflineStatisticsGiftEventType.RANK_COUNT, runningWater);
|
offlineProcessGiftEvent(OfflineStatisticsGiftEventType.RANK_COUNT, runningWater);
|
||||||
|
|
||||||
// 钻石统计相关
|
// 钻石统计相关
|
||||||
offlineProcessGiftEvent(OfflineStatisticsGiftEventType.DIAMOND_COUNT, runningWater);
|
offlineProcessGiftEvent(OfflineStatisticsGiftEventType.ROCKET_COUNT, runningWater);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Pair<GiftGiveRunningWater,Boolean> saveRunningWater(GiveAwayGiftBatchEvent event) {
|
private Pair<GiftGiveRunningWater,Boolean> saveRunningWater(GiveAwayGiftBatchEvent event) {
|
||||||
|
|||||||
@ -1,14 +1,28 @@
|
|||||||
package com.red.circle.other.app.listener.gift.strategy;
|
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.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.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 火箭统计相关
|
* 火箭统计相关 - 礼物送出时增加火箭能量
|
||||||
*
|
*
|
||||||
* @author system
|
* @author system
|
||||||
* @date 2025-01-15
|
* @date 2025-01-15
|
||||||
@ -16,14 +30,100 @@ import org.springframework.stereotype.Service;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
@Service("ROCKET_COUNT_LISTENER")
|
@Service("ROCKET_COUNT_LISTENER")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class GiftSendRocketListener implements GiftStrategy {
|
public class GiftSendRocketListener implements GiftStrategy {
|
||||||
|
|
||||||
private final RocketService rocketService;
|
|
||||||
|
|
||||||
|
private final RocketEnergyAddCmdExe rocketEnergyAddCmdExe;
|
||||||
|
private final GiftGiveRunningWaterService giftGiveRunningWaterService;
|
||||||
|
private final GameLuckyGiftCommon gameLuckyGiftCommon;
|
||||||
|
private final RoomProfileManagerService roomProfileManagerService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void processor(OfflineProcessGiftEvent event) {
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -31,45 +31,39 @@ public class RocketImPushManager {
|
|||||||
/**
|
/**
|
||||||
* 推送能量更新消息
|
* 推送能量更新消息
|
||||||
*/
|
*/
|
||||||
public void pushEnergyUpdate(Long roomId, RocketStatusCO status) {
|
public void pushEnergyUpdate(String roomAccount, RocketStatusCO status) {
|
||||||
try {
|
try {
|
||||||
// 构建消息
|
imGroupClient.sendCustomMessage(roomAccount,
|
||||||
ImMessage message = buildEnergyUpdateMessage(status);
|
|
||||||
|
|
||||||
imGroupClient.sendCustomMessage(String.valueOf(roomId),
|
|
||||||
CustomGroupMsgBodyCmd.builder()
|
CustomGroupMsgBodyCmd.builder()
|
||||||
.type(GroupMessageTypeEnum.GAME_LUCKY_GIFT)
|
.type(GroupMessageTypeEnum.ROCKET_ENERGY_UPDATE)
|
||||||
.data(message)
|
.data(status)
|
||||||
.build());
|
.build());
|
||||||
|
|
||||||
log.info("推送火箭能量更新消息成功: roomId={}, level={}, energy={}/{}",
|
log.info("推送火箭能量更新消息成功: roomId={}, level={}, energy={}/{}",
|
||||||
roomId, status.getLevel(), status.getCurrentEnergy(), status.getMaxEnergy());
|
roomAccount, status.getLevel(), status.getCurrentEnergy(), status.getMaxEnergy());
|
||||||
|
|
||||||
} catch (Exception e) {
|
} 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 {
|
try {
|
||||||
// 构建消息
|
|
||||||
ImMessage message = buildLaunchMessage(status);
|
|
||||||
|
|
||||||
imGroupClient.sendMessageBroadcast(
|
imGroupClient.sendMessageBroadcast(
|
||||||
BroadcastGroupMsgBodyCmd.builder()
|
BroadcastGroupMsgBodyCmd.builder()
|
||||||
.toPlatform(SysOriginPlatformEnum.LIKEI)
|
.toPlatform(SysOriginPlatformEnum.LIKEI)
|
||||||
.type(GroupMessageTypeEnum.GAME_LUCKY_GIFT)
|
.type(GroupMessageTypeEnum.ROCKET_ENERGY_LAUNCH)
|
||||||
.data(message)
|
.data(status)
|
||||||
.build()
|
.build()
|
||||||
);
|
);
|
||||||
|
|
||||||
log.info("推送火箭发射消息成功: roomId={}, level={}", roomId, status.getLevel());
|
log.info("推送火箭发射消息成功: roomId={}, level={}", status.getRoomId(), status.getLevel());
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("推送火箭发射消息失败: roomId={}", roomId, e);
|
log.error("推送火箭发射消息失败: roomId={}", status.getRoomId(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,8 +149,7 @@ public class RocketImPushManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IM消息对象(示例)
|
* IM消息对象
|
||||||
* TODO: 替换为实际的IM消息类
|
|
||||||
*/
|
*/
|
||||||
private static class ImMessage {
|
private static class ImMessage {
|
||||||
private String type;
|
private String type;
|
||||||
|
|||||||
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -55,6 +55,25 @@ public enum RocketLevel {
|
|||||||
throw new IllegalArgumentException("不支持的火箭等级: " + level);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据能量值判断等级
|
* 根据能量值判断等级
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
package com.red.circle.other.domain.rocket;
|
package com.red.circle.other.domain.rocket;
|
||||||
|
|
||||||
|
import com.red.circle.other.domain.gateway.RocketConfigGateway;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.RoundingMode;
|
import java.math.RoundingMode;
|
||||||
@ -17,6 +19,7 @@ import java.util.List;
|
|||||||
* @date 2025-01-15
|
* @date 2025-01-15
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
|
@Slf4j
|
||||||
public class RocketStatus {
|
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 rocketStatus = new RocketStatus();
|
||||||
rocketStatus.setRoomId(roomId);
|
rocketStatus.setRoomId(roomId);
|
||||||
rocketStatus.setDate(LocalDate.now().toString());
|
rocketStatus.setDate(LocalDate.now().toString());
|
||||||
rocketStatus.setLevel(RocketLevel.LEVEL_1);
|
rocketStatus.setLevel(RocketLevel.of(config.getLevel()));
|
||||||
rocketStatus.setCurrentEnergy(0L);
|
rocketStatus.setCurrentEnergy(0L);
|
||||||
rocketStatus.setMaxEnergy(RocketLevel.LEVEL_1.getMaxEnergy());
|
rocketStatus.setMaxEnergy(config.getMaxEnergy());
|
||||||
rocketStatus.setStatus(RocketStatusEnum.CHARGING);
|
rocketStatus.setStatus(RocketStatusEnum.CHARGING);
|
||||||
rocketStatus.setContributors(new ArrayList<>());
|
rocketStatus.setContributors(new ArrayList<>());
|
||||||
rocketStatus.setClaimCount(0);
|
rocketStatus.setClaimCount(0);
|
||||||
@ -101,52 +104,100 @@ public class RocketStatus {
|
|||||||
rocketStatus.setUpdatedAt(LocalDateTime.now());
|
rocketStatus.setUpdatedAt(LocalDateTime.now());
|
||||||
return rocketStatus;
|
return rocketStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 增加能量
|
* 增加能量
|
||||||
*/
|
*/
|
||||||
public void addEnergy(Long userId, String userName, String userAvatar, Long energyValue) {
|
public void addEnergy(Long userId,Long energyValue) {
|
||||||
// 增加总能量
|
|
||||||
this.currentEnergy += energyValue;
|
this.currentEnergy += energyValue;
|
||||||
|
|
||||||
// 更新贡献者
|
|
||||||
RocketContributor contributor = findContributor(userId);
|
RocketContributor contributor = findContributor(userId);
|
||||||
if (contributor == null) {
|
if (contributor == null) {
|
||||||
contributor = new RocketContributor();
|
contributor = new RocketContributor();
|
||||||
contributor.setUserId(userId);
|
contributor.setUserId(userId);
|
||||||
contributor.setUserName(userName);
|
|
||||||
contributor.setUserAvatar(userAvatar);
|
|
||||||
contributor.setEnergy(0L);
|
contributor.setEnergy(0L);
|
||||||
contributor.setGiftCount(0);
|
contributor.setGiftCount(0);
|
||||||
|
contributor.setFirstContributeTime(LocalDateTime.now());
|
||||||
this.contributors.add(contributor);
|
this.contributors.add(contributor);
|
||||||
}
|
}
|
||||||
contributor.addEnergy(energyValue);
|
contributor.addEnergy(energyValue);
|
||||||
|
contributor.setLastContributeTime(LocalDateTime.now());
|
||||||
|
|
||||||
// 重新计算贡献率
|
|
||||||
recalculateContributionRate();
|
recalculateContributionRate();
|
||||||
|
|
||||||
// 检查等级提升
|
|
||||||
checkAndUpgradeLevel();
|
|
||||||
|
|
||||||
this.updatedAt = LocalDateTime.now();
|
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.status = RocketStatusEnum.LAUNCHED;
|
||||||
this.launchTime = LocalDateTime.now();
|
this.launchTime = LocalDateTime.now();
|
||||||
// 领取过期时间:发射后1分钟
|
|
||||||
this.claimExpireTime = LocalDateTime.now().plusMinutes(1);
|
|
||||||
this.updatedAt = LocalDateTime.now();
|
this.updatedAt = LocalDateTime.now();
|
||||||
|
|
||||||
|
// 升级到下一级
|
||||||
|
upgradeToNextLevel(configGateway);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 开始领取
|
* 重置状态(使用一级配置)
|
||||||
*/
|
*/
|
||||||
public void startClaiming() {
|
public void reset(RocketConfigGateway configGateway) {
|
||||||
this.status = RocketStatusEnum.CLAIMING;
|
// 查询一级配置
|
||||||
|
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();
|
this.updatedAt = LocalDateTime.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,7 +213,7 @@ public class RocketStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重置状态
|
* 重置状态(三级发射后重置为一级)
|
||||||
*/
|
*/
|
||||||
public void reset() {
|
public void reset() {
|
||||||
this.date = LocalDate.now().toString();
|
this.date = LocalDate.now().toString();
|
||||||
@ -192,12 +243,10 @@ public class RocketStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否可以领取
|
* 是否可以领取(已移除CLAIMING状态)
|
||||||
*/
|
*/
|
||||||
public boolean canClaim() {
|
public boolean canClaim() {
|
||||||
return this.status == RocketStatusEnum.CLAIMING
|
return false; // 暂时隐藏领取功能
|
||||||
&& this.claimExpireTime != null
|
|
||||||
&& LocalDateTime.now().isBefore(this.claimExpireTime);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -249,18 +298,6 @@ public class RocketStatus {
|
|||||||
contributor.setContributionRate(rate);
|
contributor.setContributionRate(rate);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 按贡献能量排序
|
|
||||||
this.contributors.sort(Comparator.comparing(RocketContributor::getEnergy).reversed());
|
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,14 +19,9 @@ public enum RocketStatusEnum {
|
|||||||
CHARGING(1, "充能中"),
|
CHARGING(1, "充能中"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 已发射
|
* 已发射(等待下一级)
|
||||||
*/
|
*/
|
||||||
LAUNCHED(2, "已发射"),
|
LAUNCHED(2, "已发射");
|
||||||
|
|
||||||
/**
|
|
||||||
* 领取中
|
|
||||||
*/
|
|
||||||
CLAIMING(3, "领取中");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 状态码
|
* 状态码
|
||||||
|
|||||||
@ -27,21 +27,21 @@ public interface RocketStatusRepository extends MongoRepository<RocketStatusDocu
|
|||||||
* 根据房间ID和日期查询(排除贡献者列表,性能优化)
|
* 根据房间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);
|
Optional<RocketStatusDocument> findByRoomIdAndDateWithoutContributors(Long roomId, String date);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据房间ID和日期查询(只包含Top N贡献者)
|
* 根据房间ID和日期查询(只包含Top N贡献者)
|
||||||
* 用于:前端展示Top榜单的场景
|
* 用于:前端展示Top榜单的场景
|
||||||
*/
|
*/
|
||||||
@Query(value = "{ 'room_id': ?0, 'date': ?1 }",
|
@Query(value = "{ 'roomId': ?0, 'date': ?1 }",
|
||||||
fields = "{ 'contributors': { $slice: ?2 } }")
|
fields = "{ 'contributors': { $slice: ?2 } }")
|
||||||
Optional<RocketStatusDocument> findByRoomIdAndDateWithTopContributors(Long roomId, String date, Integer topN);
|
Optional<RocketStatusDocument> findByRoomIdAndDateWithTopContributors(Long roomId, String date, Integer topN);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据房间ID列表和日期查询(排除贡献者列表)
|
* 根据房间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);
|
List<RocketStatusDocument> findByRoomIdInAndDateWithoutContributors(List<Long> roomIds, String date);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -17,6 +17,8 @@ import java.util.Set;
|
|||||||
*/
|
*/
|
||||||
public interface RoomProfileManagerService {
|
public interface RoomProfileManagerService {
|
||||||
|
|
||||||
|
String getRoomAccount(Long roomId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户是否存在房间.
|
* 用户是否存在房间.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -26,14 +26,18 @@ import java.util.Map;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.data.domain.Sort;
|
import org.springframework.data.domain.Sort;
|
||||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||||
import org.springframework.data.mongodb.core.query.Criteria;
|
import org.springframework.data.mongodb.core.query.Criteria;
|
||||||
import org.springframework.data.mongodb.core.query.Query;
|
import org.springframework.data.mongodb.core.query.Query;
|
||||||
import org.springframework.data.mongodb.core.query.Update;
|
import org.springframework.data.mongodb.core.query.Update;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -43,9 +47,58 @@ import org.springframework.stereotype.Service;
|
|||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
public class RoomProfileManagerServiceImpl implements RoomProfileManagerService {
|
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
|
@Override
|
||||||
public boolean existsUserRoom(Long userId) {
|
public boolean existsUserRoom(Long userId) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user