火箭基础功能
This commit is contained in:
parent
5328936e27
commit
4746377fc6
@ -0,0 +1,48 @@
|
||||
package com.red.circle.other.adapter.app;
|
||||
|
||||
import com.red.circle.other.app.dto.clientobject.RocketRewardCO;
|
||||
import com.red.circle.other.app.dto.clientobject.RocketStatusCO;
|
||||
import com.red.circle.other.app.dto.cmd.RocketRewardClaimCmd;
|
||||
import com.red.circle.other.app.dto.cmd.RocketStatusQueryCmd;
|
||||
import com.red.circle.other.app.service.RocketService;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 火箭REST控制器
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-15
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/rocket")
|
||||
@RequiredArgsConstructor
|
||||
public class RocketRestController {
|
||||
|
||||
private final RocketService rocketService;
|
||||
|
||||
/**
|
||||
* 查询火箭状态
|
||||
*/
|
||||
@GetMapping("/status")
|
||||
public RocketStatusCO queryStatus(@Valid RocketStatusQueryCmd cmd) {
|
||||
return rocketService.queryStatus(cmd);
|
||||
}
|
||||
|
||||
/**
|
||||
* 领取奖励
|
||||
*/
|
||||
@PostMapping("/claim")
|
||||
public RocketRewardCO claimReward(@Valid @RequestBody RocketRewardClaimCmd cmd) {
|
||||
return rocketService.claimReward(cmd);
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动触发发射 (测试用)
|
||||
*/
|
||||
@PostMapping("/manual-launch")
|
||||
public void manualLaunch(@RequestParam Long roomId) {
|
||||
rocketService.manualLaunch(roomId);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,110 @@
|
||||
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 lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 火箭能量增加命令执行器
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-15
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class RocketEnergyAddCmdExe {
|
||||
|
||||
private final RocketStatusGateway rocketStatusGateway;
|
||||
private final DistributedLockUtil distributedLockUtil;
|
||||
private final RocketLaunchCmdExe rocketLaunchCmdExe;
|
||||
|
||||
/**
|
||||
* 幸运礼物加成比例
|
||||
*/
|
||||
private static final double LUCKY_BONUS_RATE = 0.04;
|
||||
|
||||
/**
|
||||
* 执行能量增加
|
||||
*/
|
||||
public void execute(RocketEnergyAddCmd cmd) {
|
||||
// 构建锁Key
|
||||
String lockKey = RocketKeys.LOCK.getKey(cmd.getRoomId());
|
||||
|
||||
// 使用分布式锁执行
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,87 @@
|
||||
package com.red.circle.other.app.command.rocket;
|
||||
|
||||
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.domain.rocket.RocketStatusEnum;
|
||||
import com.red.circle.other.infra.database.cache.key.RocketKeys;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 火箭发射命令执行器
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-15
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class RocketLaunchCmdExe {
|
||||
|
||||
private final RocketStatusGateway rocketStatusGateway;
|
||||
private final DistributedLockUtil distributedLockUtil;
|
||||
|
||||
/**
|
||||
* 执行火箭发射
|
||||
*/
|
||||
public void execute(Long roomId) {
|
||||
// 构建锁Key
|
||||
String lockKey = RocketKeys.LOCK.getKey(roomId);
|
||||
|
||||
// 使用分布式锁执行
|
||||
distributedLockUtil.executeWithLock(lockKey, 5L, () -> {
|
||||
doLaunch(roomId);
|
||||
return null;
|
||||
}, "火箭发射操作过于频繁,请稍后重试");
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行发射逻辑
|
||||
*/
|
||||
private void doLaunch(Long roomId) {
|
||||
// 1. 查询当前火箭状态
|
||||
String today = LocalDate.now().toString();
|
||||
RocketStatus rocketStatus = rocketStatusGateway.findByRoomIdAndDate(roomId, today);
|
||||
|
||||
if (rocketStatus == null) {
|
||||
log.warn("火箭状态不存在, roomId={}", roomId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 校验状态
|
||||
if (rocketStatus.getStatus() != RocketStatusEnum.CHARGING) {
|
||||
log.warn("火箭状态不正确, 无法发射, roomId={}, status={}", roomId, rocketStatus.getStatus());
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 校验能量
|
||||
if (!rocketStatus.isFullEnergy()) {
|
||||
log.warn("火箭能量不足, 无法发射, roomId={}, currentEnergy={}, maxEnergy={}",
|
||||
roomId, rocketStatus.getCurrentEnergy(), rocketStatus.getMaxEnergy());
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. 发射火箭
|
||||
rocketStatus.launch();
|
||||
|
||||
// 5. 立即进入领取状态
|
||||
rocketStatus.startClaiming();
|
||||
|
||||
// 6. 保存状态
|
||||
rocketStatusGateway.save(rocketStatus);
|
||||
|
||||
log.info("火箭发射成功, roomId={}, level={}, finalEnergy={}, contributors={}, launchTime={}",
|
||||
roomId,
|
||||
rocketStatus.getLevel().getLevel(),
|
||||
rocketStatus.getCurrentEnergy(),
|
||||
rocketStatus.getContributors() != null ? rocketStatus.getContributors().size() : 0,
|
||||
rocketStatus.getLaunchTime());
|
||||
|
||||
// TODO: 7. 推送发射动画 (WebSocket)
|
||||
// TODO: 8. 保存历史记录
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,119 @@
|
||||
package com.red.circle.other.app.command.rocket;
|
||||
|
||||
import com.red.circle.other.app.dto.clientobject.RocketRewardCO;
|
||||
import com.red.circle.other.app.dto.cmd.RocketRewardClaimCmd;
|
||||
import com.red.circle.other.app.util.DistributedLockUtil;
|
||||
import com.red.circle.other.domain.gateway.RocketStatusGateway;
|
||||
import com.red.circle.other.domain.rocket.RocketContributor;
|
||||
import com.red.circle.other.domain.rocket.RocketStatus;
|
||||
import com.red.circle.other.infra.database.cache.key.RocketKeys;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 火箭奖励领取命令执行器
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-15
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class RocketRewardClaimCmdExe {
|
||||
|
||||
private final RocketStatusGateway rocketStatusGateway;
|
||||
private final DistributedLockUtil distributedLockUtil;
|
||||
// TODO: 后续需要注入 WalletGoldClient 调用钱包服务
|
||||
|
||||
/**
|
||||
* 执行奖励领取
|
||||
*/
|
||||
public RocketRewardCO execute(RocketRewardClaimCmd cmd) {
|
||||
// 构建锁Key (防止同一用户并发领取)
|
||||
String lockKey = RocketKeys.LOCK.getKey(cmd.getRoomId(), "claim", cmd.getUserId());
|
||||
|
||||
return distributedLockUtil.executeWithLock(lockKey, 3L, () -> {
|
||||
return doClaim(cmd);
|
||||
}, "奖励领取操作过于频繁,请稍后重试");
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行领取逻辑
|
||||
*/
|
||||
private RocketRewardCO doClaim(RocketRewardClaimCmd cmd) {
|
||||
// 1. 查询火箭状态
|
||||
String today = LocalDate.now().toString();
|
||||
RocketStatus rocketStatus = rocketStatusGateway.findByRoomIdAndDate(cmd.getRoomId(), today);
|
||||
|
||||
if (rocketStatus == null) {
|
||||
throw new RuntimeException("火箭状态不存在");
|
||||
}
|
||||
|
||||
// 2. 校验领取资格
|
||||
if (!rocketStatus.canClaim()) {
|
||||
if (rocketStatus.isExpired()) {
|
||||
throw new RuntimeException("奖励领取已过期");
|
||||
}
|
||||
throw new RuntimeException("当前状态不可领取奖励");
|
||||
}
|
||||
|
||||
// 3. 检查是否已领取 (TODO: 需要查询MySQL防重)
|
||||
// boolean hasClaimed = checkHasClaimed(cmd.getRoomId(), cmd.getUserId(), rocketStatus.getId());
|
||||
// if (hasClaimed) {
|
||||
// throw new RuntimeException("您已经领取过奖励了");
|
||||
// }
|
||||
|
||||
// 4. 判断是否为贡献者
|
||||
RocketContributor contributor = findContributor(rocketStatus, cmd.getUserId());
|
||||
boolean isContributor = contributor != null;
|
||||
|
||||
// 5. 计算奖励 (TODO: 奖励配置后续补充)
|
||||
RocketRewardCO rewardCO = new RocketRewardCO();
|
||||
rewardCO.setIsContributor(isContributor);
|
||||
|
||||
if (isContributor) {
|
||||
// 贡献者奖励
|
||||
rewardCO.setContributionEnergy(contributor.getEnergy());
|
||||
rewardCO.setContributionRate(contributor.getContributionRate());
|
||||
rewardCO.setRewardType("GOLD");
|
||||
rewardCO.setRewardAmount(1000L); // TODO: 根据配置计算
|
||||
rewardCO.setMessage("恭喜您获得贡献者奖励!");
|
||||
} else {
|
||||
// 普通用户奖励
|
||||
rewardCO.setRewardType("GOLD");
|
||||
rewardCO.setRewardAmount(100L); // TODO: 根据配置计算
|
||||
rewardCO.setMessage("恭喜您获得参与奖励!");
|
||||
}
|
||||
|
||||
// 6. 发放奖励 (TODO: 调用钱包服务)
|
||||
// String bizNo = "ROCKET_" + rocketStatus.getId() + "_" + cmd.getUserId();
|
||||
// walletGoldClient.income(...);
|
||||
|
||||
// 7. 记录领取日志 (TODO: 保存到MySQL)
|
||||
|
||||
// 8. 更新领取人数
|
||||
rocketStatus.incrementClaimCount();
|
||||
rocketStatusGateway.save(rocketStatus);
|
||||
|
||||
log.info("火箭奖励领取成功, roomId={}, userId={}, isContributor={}, rewardAmount={}",
|
||||
cmd.getRoomId(), cmd.getUserId(), isContributor, rewardCO.getRewardAmount());
|
||||
|
||||
return rewardCO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找贡献者
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,159 @@
|
||||
package com.red.circle.other.app.command.rocket;
|
||||
|
||||
import com.red.circle.other.app.dto.clientobject.RocketContributorCO;
|
||||
import com.red.circle.other.app.dto.clientobject.RocketStatusCO;
|
||||
import com.red.circle.other.app.dto.cmd.RocketStatusQueryCmd;
|
||||
import com.red.circle.other.domain.gateway.RocketStatusGateway;
|
||||
import com.red.circle.other.domain.rocket.RocketContributor;
|
||||
import com.red.circle.other.domain.rocket.RocketStatus;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 火箭状态查询执行器
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-15
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class RocketStatusQryExe {
|
||||
|
||||
private final RocketStatusGateway rocketStatusGateway;
|
||||
|
||||
/**
|
||||
* Top排行显示数量
|
||||
*/
|
||||
private static final int TOP_DISPLAY_COUNT = 10;
|
||||
|
||||
/**
|
||||
* 执行查询
|
||||
*/
|
||||
public RocketStatusCO execute(RocketStatusQueryCmd cmd) {
|
||||
// 1. 查询火箭状态
|
||||
String today = LocalDate.now().toString();
|
||||
RocketStatus rocketStatus = rocketStatusGateway.findByRoomIdAndDate(cmd.getRoomId(), today);
|
||||
|
||||
if (rocketStatus == null) {
|
||||
// 返回初始状态
|
||||
return buildInitialStatus(cmd.getRoomId());
|
||||
}
|
||||
|
||||
// 2. 转换为CO
|
||||
RocketStatusCO statusCO = convertToStatusCO(rocketStatus);
|
||||
|
||||
// 3. 填充当前用户相关信息
|
||||
if (cmd.getCurrentUserId() != null) {
|
||||
fillUserInfo(statusCO, rocketStatus, cmd.getCurrentUserId());
|
||||
}
|
||||
|
||||
return statusCO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建初始状态
|
||||
*/
|
||||
private RocketStatusCO buildInitialStatus(Long roomId) {
|
||||
RocketStatusCO statusCO = new RocketStatusCO();
|
||||
statusCO.setRoomId(roomId);
|
||||
statusCO.setDate(LocalDate.now().toString());
|
||||
statusCO.setLevel(1);
|
||||
statusCO.setLevelName("一级火箭");
|
||||
statusCO.setCurrentEnergy(0L);
|
||||
statusCO.setMaxEnergy(150000L);
|
||||
statusCO.setEnergyPercent(java.math.BigDecimal.ZERO);
|
||||
statusCO.setStatus("CHARGING");
|
||||
statusCO.setStatusDesc("充能中");
|
||||
statusCO.setTopContributors(new ArrayList<>());
|
||||
statusCO.setTotalContributors(0);
|
||||
statusCO.setClaimCount(0);
|
||||
statusCO.setCanClaim(false);
|
||||
statusCO.setHasClaimed(false);
|
||||
return statusCO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为CO
|
||||
*/
|
||||
private RocketStatusCO convertToStatusCO(RocketStatus rocketStatus) {
|
||||
RocketStatusCO statusCO = new RocketStatusCO();
|
||||
statusCO.setRoomId(rocketStatus.getRoomId());
|
||||
statusCO.setDate(rocketStatus.getDate());
|
||||
statusCO.setLevel(rocketStatus.getLevel().getLevel());
|
||||
statusCO.setLevelName(rocketStatus.getLevel().getName());
|
||||
statusCO.setCurrentEnergy(rocketStatus.getCurrentEnergy());
|
||||
statusCO.setMaxEnergy(rocketStatus.getMaxEnergy());
|
||||
statusCO.setEnergyPercent(rocketStatus.getEnergyPercent());
|
||||
statusCO.setStatus(rocketStatus.getStatus().name());
|
||||
statusCO.setStatusDesc(rocketStatus.getStatus().getDesc());
|
||||
statusCO.setLaunchTime(rocketStatus.getLaunchTime());
|
||||
statusCO.setClaimExpireTime(rocketStatus.getClaimExpireTime());
|
||||
statusCO.setClaimCount(rocketStatus.getClaimCount());
|
||||
|
||||
// 转换Top贡献者
|
||||
if (rocketStatus.getContributors() != null && !rocketStatus.getContributors().isEmpty()) {
|
||||
List<RocketContributorCO> topContributors = rocketStatus.getContributors().stream()
|
||||
.sorted(Comparator.comparing(RocketContributor::getEnergy).reversed())
|
||||
.limit(TOP_DISPLAY_COUNT)
|
||||
.map(this::convertToContributorCO)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 设置排名
|
||||
for (int i = 0; i < topContributors.size(); i++) {
|
||||
topContributors.get(i).setRank(i + 1);
|
||||
}
|
||||
|
||||
statusCO.setTopContributors(topContributors);
|
||||
statusCO.setTotalContributors(rocketStatus.getContributors().size());
|
||||
} else {
|
||||
statusCO.setTopContributors(new ArrayList<>());
|
||||
statusCO.setTotalContributors(0);
|
||||
}
|
||||
|
||||
return statusCO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换贡献者CO
|
||||
*/
|
||||
private RocketContributorCO convertToContributorCO(RocketContributor contributor) {
|
||||
RocketContributorCO contributorCO = new RocketContributorCO();
|
||||
contributorCO.setUserId(contributor.getUserId());
|
||||
contributorCO.setUserName(contributor.getUserName());
|
||||
contributorCO.setUserAvatar(contributor.getUserAvatar());
|
||||
contributorCO.setEnergy(contributor.getEnergy());
|
||||
contributorCO.setContributionRate(contributor.getContributionRate());
|
||||
contributorCO.setGiftCount(contributor.getGiftCount());
|
||||
return contributorCO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充当前用户信息
|
||||
*/
|
||||
private void fillUserInfo(RocketStatusCO statusCO, RocketStatus rocketStatus, Long userId) {
|
||||
// 判断是否可领取
|
||||
statusCO.setCanClaim(rocketStatus.canClaim());
|
||||
|
||||
// TODO: 查询是否已领取 (需要查询MySQL)
|
||||
statusCO.setHasClaimed(false);
|
||||
|
||||
// 查找当前用户贡献
|
||||
if (rocketStatus.getContributors() != null) {
|
||||
rocketStatus.getContributors().stream()
|
||||
.filter(c -> c.getUserId().equals(userId))
|
||||
.findFirst()
|
||||
.ifPresent(contributor -> {
|
||||
statusCO.setMyContribution(contributor.getEnergy());
|
||||
statusCO.setMyContributionRate(contributor.getContributionRate());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
package com.red.circle.other.app.scheduler;
|
||||
|
||||
import com.red.circle.other.domain.gateway.RocketStatusGateway;
|
||||
import com.red.circle.other.domain.rocket.RocketStatus;
|
||||
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 RocketDailyResetTask {
|
||||
|
||||
private final RocketStatusGateway rocketStatusGateway;
|
||||
|
||||
/**
|
||||
* 每天00:00执行重置
|
||||
*/
|
||||
@Scheduled(cron = "0 0 0 * * ?")
|
||||
public void resetDaily() {
|
||||
log.info("开始执行火箭每日重置任务");
|
||||
|
||||
try {
|
||||
String today = LocalDate.now().toString();
|
||||
|
||||
// 查询所有处于充能状态的火箭
|
||||
List<RocketStatus> chargingRockets = rocketStatusGateway.findAllCharging(today);
|
||||
|
||||
log.info("找到需要重置的火箭数量: {}", chargingRockets.size());
|
||||
|
||||
// 重置每个火箭
|
||||
for (RocketStatus rocketStatus : chargingRockets) {
|
||||
try {
|
||||
// TODO: 保存昨日历史记录
|
||||
|
||||
// 重置状态
|
||||
rocketStatus.reset();
|
||||
rocketStatusGateway.save(rocketStatus);
|
||||
|
||||
log.info("火箭重置成功, roomId={}", rocketStatus.getRoomId());
|
||||
} catch (Exception e) {
|
||||
log.error("火箭重置失败, roomId={}", rocketStatus.getRoomId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("火箭每日重置任务执行完成");
|
||||
} catch (Exception e) {
|
||||
log.error("火箭每日重置任务执行失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
package com.red.circle.other.app.service;
|
||||
|
||||
import com.red.circle.other.app.command.rocket.RocketEnergyAddCmdExe;
|
||||
import com.red.circle.other.app.command.rocket.RocketLaunchCmdExe;
|
||||
import com.red.circle.other.app.command.rocket.RocketRewardClaimCmdExe;
|
||||
import com.red.circle.other.app.command.rocket.RocketStatusQryExe;
|
||||
import com.red.circle.other.app.dto.clientobject.RocketRewardCO;
|
||||
import com.red.circle.other.app.dto.clientobject.RocketStatusCO;
|
||||
import com.red.circle.other.app.dto.cmd.RocketEnergyAddCmd;
|
||||
import com.red.circle.other.app.dto.cmd.RocketRewardClaimCmd;
|
||||
import com.red.circle.other.app.dto.cmd.RocketStatusQueryCmd;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 火箭服务实现
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-15
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class RocketServiceImpl implements RocketService {
|
||||
|
||||
private final RocketEnergyAddCmdExe rocketEnergyAddCmdExe;
|
||||
private final RocketStatusQryExe rocketStatusQryExe;
|
||||
private final RocketRewardClaimCmdExe rocketRewardClaimCmdExe;
|
||||
private final RocketLaunchCmdExe rocketLaunchCmdExe;
|
||||
|
||||
@Override
|
||||
public void addEnergy(RocketEnergyAddCmd cmd) {
|
||||
rocketEnergyAddCmdExe.execute(cmd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RocketStatusCO queryStatus(RocketStatusQueryCmd cmd) {
|
||||
return rocketStatusQryExe.execute(cmd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RocketRewardCO claimReward(RocketRewardClaimCmd cmd) {
|
||||
return rocketRewardClaimCmdExe.execute(cmd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void manualLaunch(Long roomId) {
|
||||
rocketLaunchCmdExe.execute(roomId);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
package com.red.circle.other.app.dto.clientobject;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 火箭贡献者客户端对象
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-15
|
||||
*/
|
||||
@Data
|
||||
public class RocketContributorCO {
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户名称
|
||||
*/
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 用户头像
|
||||
*/
|
||||
private String userAvatar;
|
||||
|
||||
/**
|
||||
* 贡献能量
|
||||
*/
|
||||
private Long energy;
|
||||
|
||||
/**
|
||||
* 贡献率 (%)
|
||||
*/
|
||||
private BigDecimal contributionRate;
|
||||
|
||||
/**
|
||||
* 排名
|
||||
*/
|
||||
private Integer rank;
|
||||
|
||||
/**
|
||||
* 送礼次数
|
||||
*/
|
||||
private Integer giftCount;
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
package com.red.circle.other.app.dto.clientobject;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 火箭奖励客户端对象
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-15
|
||||
*/
|
||||
@Data
|
||||
public class RocketRewardCO {
|
||||
|
||||
/**
|
||||
* 奖励类型 (GOLD/DIAMOND/PROP)
|
||||
*/
|
||||
private String rewardType;
|
||||
|
||||
/**
|
||||
* 奖励物品ID
|
||||
*/
|
||||
private Long rewardItemId;
|
||||
|
||||
/**
|
||||
* 奖励数量
|
||||
*/
|
||||
private Long rewardAmount;
|
||||
|
||||
/**
|
||||
* 是否贡献者
|
||||
*/
|
||||
private Boolean isContributor;
|
||||
|
||||
/**
|
||||
* 贡献能量
|
||||
*/
|
||||
private Long contributionEnergy;
|
||||
|
||||
/**
|
||||
* 贡献率
|
||||
*/
|
||||
private BigDecimal contributionRate;
|
||||
|
||||
/**
|
||||
* 提示信息
|
||||
*/
|
||||
private String message;
|
||||
}
|
||||
@ -0,0 +1,107 @@
|
||||
package com.red.circle.other.app.dto.clientobject;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 火箭状态客户端对象
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-15
|
||||
*/
|
||||
@Data
|
||||
public class RocketStatusCO {
|
||||
|
||||
/**
|
||||
* 房间ID
|
||||
*/
|
||||
private Long roomId;
|
||||
|
||||
/**
|
||||
* 日期
|
||||
*/
|
||||
private String date;
|
||||
|
||||
/**
|
||||
* 当前等级
|
||||
*/
|
||||
private Integer level;
|
||||
|
||||
/**
|
||||
* 等级名称
|
||||
*/
|
||||
private String levelName;
|
||||
|
||||
/**
|
||||
* 当前能量值
|
||||
*/
|
||||
private Long currentEnergy;
|
||||
|
||||
/**
|
||||
* 最大能量值
|
||||
*/
|
||||
private Long maxEnergy;
|
||||
|
||||
/**
|
||||
* 能量百分比
|
||||
*/
|
||||
private BigDecimal energyPercent;
|
||||
|
||||
/**
|
||||
* 状态 (CHARGING/LAUNCHED/CLAIMING)
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 状态描述
|
||||
*/
|
||||
private String statusDesc;
|
||||
|
||||
/**
|
||||
* 贡献者排行 (Top 10)
|
||||
*/
|
||||
private List<RocketContributorCO> topContributors;
|
||||
|
||||
/**
|
||||
* 总贡献人数
|
||||
*/
|
||||
private Integer totalContributors;
|
||||
|
||||
/**
|
||||
* 发射时间
|
||||
*/
|
||||
private LocalDateTime launchTime;
|
||||
|
||||
/**
|
||||
* 领取过期时间
|
||||
*/
|
||||
private LocalDateTime claimExpireTime;
|
||||
|
||||
/**
|
||||
* 已领取人数
|
||||
*/
|
||||
private Integer claimCount;
|
||||
|
||||
/**
|
||||
* 当前用户是否可领取
|
||||
*/
|
||||
private Boolean canClaim;
|
||||
|
||||
/**
|
||||
* 当前用户是否已领取
|
||||
*/
|
||||
private Boolean hasClaimed;
|
||||
|
||||
/**
|
||||
* 当前用户贡献能量
|
||||
*/
|
||||
private Long myContribution;
|
||||
|
||||
/**
|
||||
* 当前用户贡献率
|
||||
*/
|
||||
private BigDecimal myContributionRate;
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
package com.red.circle.other.app.dto.cmd;
|
||||
|
||||
import com.red.circle.common.business.dto.cmd.AppExtCommand;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 火箭能量增加命令
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-15
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class RocketEnergyAddCmd extends AppExtCommand {
|
||||
|
||||
/**
|
||||
* 房间ID
|
||||
*/
|
||||
@NotNull(message = "房间ID不能为空")
|
||||
private Long roomId;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
@NotNull(message = "用户ID不能为空")
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户名称
|
||||
*/
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 用户头像
|
||||
*/
|
||||
private String userAvatar;
|
||||
|
||||
/**
|
||||
* 礼物金币价值
|
||||
*/
|
||||
@NotNull(message = "礼物价值不能为空")
|
||||
private Long giftGoldValue;
|
||||
|
||||
/**
|
||||
* 是否幸运礼物
|
||||
*/
|
||||
private Boolean isLucky = false;
|
||||
|
||||
/**
|
||||
* 业务单号 (幂等)
|
||||
*/
|
||||
@NotBlank(message = "业务单号不能为空")
|
||||
private String bizNo;
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
package com.red.circle.other.app.dto.cmd;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 火箭奖励领取命令
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-15
|
||||
*/
|
||||
@Data
|
||||
public class RocketRewardClaimCmd {
|
||||
|
||||
/**
|
||||
* 房间ID
|
||||
*/
|
||||
@NotNull(message = "房间ID不能为空")
|
||||
private Long roomId;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
@NotNull(message = "用户ID不能为空")
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 火箭历史ID (MongoDB _id)
|
||||
*/
|
||||
@NotBlank(message = "火箭历史ID不能为空")
|
||||
private String rocketHistoryId;
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
package com.red.circle.other.app.dto.cmd;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 火箭状态查询命令
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-15
|
||||
*/
|
||||
@Data
|
||||
public class RocketStatusQueryCmd {
|
||||
|
||||
/**
|
||||
* 房间ID
|
||||
*/
|
||||
@NotNull(message = "房间ID不能为空")
|
||||
private Long roomId;
|
||||
|
||||
/**
|
||||
* 当前用户ID (可选,用于判断是否可领取)
|
||||
*/
|
||||
private Long currentUserId;
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
package com.red.circle.other.app.service;
|
||||
|
||||
import com.red.circle.other.app.dto.clientobject.RocketRewardCO;
|
||||
import com.red.circle.other.app.dto.clientobject.RocketStatusCO;
|
||||
import com.red.circle.other.app.dto.cmd.RocketEnergyAddCmd;
|
||||
import com.red.circle.other.app.dto.cmd.RocketRewardClaimCmd;
|
||||
import com.red.circle.other.app.dto.cmd.RocketStatusQueryCmd;
|
||||
|
||||
/**
|
||||
* 火箭服务接口
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-15
|
||||
*/
|
||||
public interface RocketService {
|
||||
|
||||
/**
|
||||
* 增加火箭能量
|
||||
*/
|
||||
void addEnergy(RocketEnergyAddCmd cmd);
|
||||
|
||||
/**
|
||||
* 查询火箭状态
|
||||
*/
|
||||
RocketStatusCO queryStatus(RocketStatusQueryCmd cmd);
|
||||
|
||||
/**
|
||||
* 领取奖励
|
||||
*/
|
||||
RocketRewardCO claimReward(RocketRewardClaimCmd cmd);
|
||||
|
||||
/**
|
||||
* 手动触发火箭发射 (测试用)
|
||||
*/
|
||||
void manualLaunch(Long roomId);
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
package com.red.circle.other.domain.gateway;
|
||||
|
||||
import com.red.circle.other.domain.rocket.RocketStatus;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 火箭状态Gateway
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-15
|
||||
*/
|
||||
public interface RocketStatusGateway {
|
||||
|
||||
/**
|
||||
* 根据房间ID和日期查询火箭状态
|
||||
*/
|
||||
RocketStatus findByRoomIdAndDate(Long roomId, String date);
|
||||
|
||||
/**
|
||||
* 保存火箭状态
|
||||
*/
|
||||
void save(RocketStatus rocketStatus);
|
||||
|
||||
/**
|
||||
* 批量查询房间火箭状态
|
||||
*/
|
||||
List<RocketStatus> findByRoomIds(List<Long> roomIds, String date);
|
||||
|
||||
/**
|
||||
* 查询所有处于充能状态的火箭
|
||||
*/
|
||||
List<RocketStatus> findAllCharging(String date);
|
||||
|
||||
/**
|
||||
* 删除火箭状态
|
||||
*/
|
||||
void delete(String id);
|
||||
}
|
||||
@ -0,0 +1,76 @@
|
||||
package com.red.circle.other.domain.rocket;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 贡献者值对象
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-15
|
||||
*/
|
||||
@Data
|
||||
public class RocketContributor {
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户名称
|
||||
*/
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 用户头像
|
||||
*/
|
||||
private String userAvatar;
|
||||
|
||||
/**
|
||||
* 贡献能量值
|
||||
*/
|
||||
private Long energy;
|
||||
|
||||
/**
|
||||
* 贡献率 (%)
|
||||
*/
|
||||
private BigDecimal contributionRate;
|
||||
|
||||
/**
|
||||
* 送礼次数
|
||||
*/
|
||||
private Integer giftCount;
|
||||
|
||||
/**
|
||||
* 首次贡献时间
|
||||
*/
|
||||
private LocalDateTime firstContributeTime;
|
||||
|
||||
/**
|
||||
* 最后贡献时间
|
||||
*/
|
||||
private LocalDateTime lastContributeTime;
|
||||
|
||||
/**
|
||||
* 增加能量
|
||||
*/
|
||||
public void addEnergy(Long addEnergy) {
|
||||
if (this.energy == null) {
|
||||
this.energy = 0L;
|
||||
}
|
||||
this.energy += addEnergy;
|
||||
|
||||
if (this.giftCount == null) {
|
||||
this.giftCount = 0;
|
||||
}
|
||||
this.giftCount++;
|
||||
|
||||
this.lastContributeTime = LocalDateTime.now();
|
||||
if (this.firstContributeTime == null) {
|
||||
this.firstContributeTime = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
package com.red.circle.other.domain.rocket;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 火箭等级枚举
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-15
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum RocketLevel {
|
||||
|
||||
/**
|
||||
* 1级火箭:15万能量
|
||||
*/
|
||||
LEVEL_1(1, 150000L, "一级火箭"),
|
||||
|
||||
/**
|
||||
* 2级火箭:50万能量
|
||||
*/
|
||||
LEVEL_2(2, 500000L, "二级火箭"),
|
||||
|
||||
/**
|
||||
* 3级火箭:100万能量
|
||||
*/
|
||||
LEVEL_3(3, 1000000L, "三级火箭");
|
||||
|
||||
/**
|
||||
* 等级值
|
||||
*/
|
||||
private final Integer level;
|
||||
|
||||
/**
|
||||
* 最大能量值
|
||||
*/
|
||||
private final Long maxEnergy;
|
||||
|
||||
/**
|
||||
* 等级名称
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* 根据等级获取枚举
|
||||
*/
|
||||
public static RocketLevel of(Integer level) {
|
||||
for (RocketLevel rocketLevel : values()) {
|
||||
if (rocketLevel.getLevel().equals(level)) {
|
||||
return rocketLevel;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("不支持的火箭等级: " + level);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据能量值判断等级
|
||||
*/
|
||||
public static RocketLevel determineLevel(Long energy) {
|
||||
if (energy >= LEVEL_3.getMaxEnergy()) {
|
||||
return LEVEL_3;
|
||||
} else if (energy >= LEVEL_2.getMaxEnergy()) {
|
||||
return LEVEL_2;
|
||||
} else if (energy >= LEVEL_1.getMaxEnergy()) {
|
||||
return LEVEL_1;
|
||||
}
|
||||
return LEVEL_1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查能量是否达到当前等级阈值
|
||||
*/
|
||||
public boolean isReached(Long energy) {
|
||||
return energy >= this.maxEnergy;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,266 @@
|
||||
package com.red.circle.other.domain.rocket;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 火箭状态领域对象
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-15
|
||||
*/
|
||||
@Data
|
||||
public class RocketStatus {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 房间ID
|
||||
*/
|
||||
private Long roomId;
|
||||
|
||||
/**
|
||||
* 日期
|
||||
*/
|
||||
private String date;
|
||||
|
||||
/**
|
||||
* 当前等级
|
||||
*/
|
||||
private RocketLevel level;
|
||||
|
||||
/**
|
||||
* 当前能量值
|
||||
*/
|
||||
private Long currentEnergy;
|
||||
|
||||
/**
|
||||
* 最大能量值
|
||||
*/
|
||||
private Long maxEnergy;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private RocketStatusEnum status;
|
||||
|
||||
/**
|
||||
* 贡献者列表
|
||||
*/
|
||||
private List<RocketContributor> contributors;
|
||||
|
||||
/**
|
||||
* 发射时间
|
||||
*/
|
||||
private LocalDateTime launchTime;
|
||||
|
||||
/**
|
||||
* 领取过期时间
|
||||
*/
|
||||
private LocalDateTime claimExpireTime;
|
||||
|
||||
/**
|
||||
* 已领取人数
|
||||
*/
|
||||
private Integer claimCount;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
/**
|
||||
* 初始化火箭
|
||||
*/
|
||||
public static RocketStatus init(Long roomId) {
|
||||
RocketStatus rocketStatus = new RocketStatus();
|
||||
rocketStatus.setRoomId(roomId);
|
||||
rocketStatus.setDate(LocalDate.now().toString());
|
||||
rocketStatus.setLevel(RocketLevel.LEVEL_1);
|
||||
rocketStatus.setCurrentEnergy(0L);
|
||||
rocketStatus.setMaxEnergy(RocketLevel.LEVEL_1.getMaxEnergy());
|
||||
rocketStatus.setStatus(RocketStatusEnum.CHARGING);
|
||||
rocketStatus.setContributors(new ArrayList<>());
|
||||
rocketStatus.setClaimCount(0);
|
||||
rocketStatus.setCreatedAt(LocalDateTime.now());
|
||||
rocketStatus.setUpdatedAt(LocalDateTime.now());
|
||||
return rocketStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加能量
|
||||
*/
|
||||
public void addEnergy(Long userId, String userName, String userAvatar, 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);
|
||||
this.contributors.add(contributor);
|
||||
}
|
||||
contributor.addEnergy(energyValue);
|
||||
|
||||
// 重新计算贡献率
|
||||
recalculateContributionRate();
|
||||
|
||||
// 检查等级提升
|
||||
checkAndUpgradeLevel();
|
||||
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* 发射火箭
|
||||
*/
|
||||
public void launch() {
|
||||
this.status = RocketStatusEnum.LAUNCHED;
|
||||
this.launchTime = LocalDateTime.now();
|
||||
// 领取过期时间:发射后1分钟
|
||||
this.claimExpireTime = LocalDateTime.now().plusMinutes(1);
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始领取
|
||||
*/
|
||||
public void startClaiming() {
|
||||
this.status = RocketStatusEnum.CLAIMING;
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加领取人数
|
||||
*/
|
||||
public void incrementClaimCount() {
|
||||
if (this.claimCount == null) {
|
||||
this.claimCount = 0;
|
||||
}
|
||||
this.claimCount++;
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置状态
|
||||
*/
|
||||
public void reset() {
|
||||
this.date = LocalDate.now().toString();
|
||||
this.level = RocketLevel.LEVEL_1;
|
||||
this.currentEnergy = 0L;
|
||||
this.maxEnergy = RocketLevel.LEVEL_1.getMaxEnergy();
|
||||
this.status = RocketStatusEnum.CHARGING;
|
||||
this.contributors = new ArrayList<>();
|
||||
this.launchTime = null;
|
||||
this.claimExpireTime = null;
|
||||
this.claimCount = 0;
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否能量已满
|
||||
*/
|
||||
public boolean isFullEnergy() {
|
||||
return this.currentEnergy >= this.maxEnergy;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否可以充能
|
||||
*/
|
||||
public boolean canAddEnergy() {
|
||||
return this.status == RocketStatusEnum.CHARGING;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否可以领取
|
||||
*/
|
||||
public boolean canClaim() {
|
||||
return this.status == RocketStatusEnum.CLAIMING
|
||||
&& this.claimExpireTime != null
|
||||
&& LocalDateTime.now().isBefore(this.claimExpireTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否已过期
|
||||
*/
|
||||
public boolean isExpired() {
|
||||
return this.claimExpireTime != null
|
||||
&& LocalDateTime.now().isAfter(this.claimExpireTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取能量百分比
|
||||
*/
|
||||
public BigDecimal getEnergyPercent() {
|
||||
if (this.maxEnergy == null || this.maxEnergy == 0) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
return BigDecimal.valueOf(this.currentEnergy)
|
||||
.divide(BigDecimal.valueOf(this.maxEnergy), 4, RoundingMode.HALF_UP)
|
||||
.multiply(BigDecimal.valueOf(100));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找贡献者
|
||||
*/
|
||||
private RocketContributor findContributor(Long userId) {
|
||||
if (this.contributors == null) {
|
||||
this.contributors = new ArrayList<>();
|
||||
return null;
|
||||
}
|
||||
return this.contributors.stream()
|
||||
.filter(c -> c.getUserId().equals(userId))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新计算贡献率
|
||||
*/
|
||||
private void recalculateContributionRate() {
|
||||
if (this.currentEnergy == null || this.currentEnergy == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.contributors.forEach(contributor -> {
|
||||
BigDecimal rate = BigDecimal.valueOf(contributor.getEnergy())
|
||||
.divide(BigDecimal.valueOf(this.currentEnergy), 4, RoundingMode.HALF_UP)
|
||||
.multiply(BigDecimal.valueOf(100));
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
package com.red.circle.other.domain.rocket;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 火箭状态枚举
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-15
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum RocketStatusEnum {
|
||||
|
||||
/**
|
||||
* 充能中
|
||||
*/
|
||||
CHARGING(1, "充能中"),
|
||||
|
||||
/**
|
||||
* 已发射
|
||||
*/
|
||||
LAUNCHED(2, "已发射"),
|
||||
|
||||
/**
|
||||
* 领取中
|
||||
*/
|
||||
CLAIMING(3, "领取中");
|
||||
|
||||
/**
|
||||
* 状态码
|
||||
*/
|
||||
private final Integer code;
|
||||
|
||||
/**
|
||||
* 状态描述
|
||||
*/
|
||||
private final String desc;
|
||||
|
||||
/**
|
||||
* 根据状态码获取枚举
|
||||
*/
|
||||
public static RocketStatusEnum of(Integer code) {
|
||||
for (RocketStatusEnum status : values()) {
|
||||
if (status.getCode().equals(code)) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("不支持的火箭状态: " + code);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
package com.red.circle.other.infra.database.cache.key;
|
||||
|
||||
import com.red.circle.component.redis.RedisKeys;
|
||||
|
||||
/**
|
||||
* 火箭系统Redis Key枚举
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-15
|
||||
*/
|
||||
public enum RocketKeys implements RedisKeys {
|
||||
|
||||
/**
|
||||
* 火箭状态缓存
|
||||
* KEY: ROCKET:STATUS:{roomId}
|
||||
* VALUE: JSON(RocketStatusCO)
|
||||
* TTL: 24h
|
||||
*/
|
||||
STATUS,
|
||||
|
||||
/**
|
||||
* 火箭能量进度缓存
|
||||
* KEY: ROCKET:ENERGY:{roomId}
|
||||
* VALUE: String(currentEnergy/maxEnergy)
|
||||
* TTL: 24h
|
||||
*/
|
||||
ENERGY,
|
||||
|
||||
/**
|
||||
* 火箭分布式锁
|
||||
* KEY: ROCKET:LOCK:{roomId}
|
||||
* VALUE: timestamp
|
||||
* TTL: 5s
|
||||
*/
|
||||
LOCK,
|
||||
|
||||
/**
|
||||
* 火箭奖励已领取集合
|
||||
* KEY: ROCKET:CLAIMED:{rocketHistoryId}
|
||||
* VALUE: Set(userId)
|
||||
* TTL: 2min
|
||||
*/
|
||||
CLAIMED,
|
||||
|
||||
/**
|
||||
* 火箭配置缓存
|
||||
* KEY: ROCKET:CONFIG:{level}
|
||||
* VALUE: JSON(RocketConfigCO)
|
||||
* TTL: 1h
|
||||
*/
|
||||
CONFIG;
|
||||
|
||||
@Override
|
||||
public String businessPrefix() {
|
||||
return "ROCKET";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String businessKey() {
|
||||
return this.name();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,109 @@
|
||||
package com.red.circle.other.infra.database.mongo.document;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.index.CompoundIndex;
|
||||
import org.springframework.data.mongodb.core.index.Indexed;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 火箭状态MongoDB文档
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-15
|
||||
*/
|
||||
@Data
|
||||
@Document(collection = "room_rocket_status")
|
||||
@CompoundIndex(name = "uk_room_date", def = "{'room_id': 1, 'date': 1}", unique = true)
|
||||
public class RocketStatusDocument {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 房间ID
|
||||
*/
|
||||
@Indexed
|
||||
private Long roomId;
|
||||
|
||||
/**
|
||||
* 日期 (格式: 2025-01-15)
|
||||
*/
|
||||
@Indexed
|
||||
private String date;
|
||||
|
||||
/**
|
||||
* 等级
|
||||
*/
|
||||
private Integer level;
|
||||
|
||||
/**
|
||||
* 当前能量值
|
||||
*/
|
||||
private Long currentEnergy;
|
||||
|
||||
/**
|
||||
* 最大能量值
|
||||
*/
|
||||
private Long maxEnergy;
|
||||
|
||||
/**
|
||||
* 状态 (1:CHARGING 2:LAUNCHED 3:CLAIMING)
|
||||
*/
|
||||
@Indexed
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 贡献者列表
|
||||
*/
|
||||
private List<ContributorDoc> contributors;
|
||||
|
||||
/**
|
||||
* 发射时间
|
||||
*/
|
||||
private LocalDateTime launchTime;
|
||||
|
||||
/**
|
||||
* 领取过期时间
|
||||
*/
|
||||
@Indexed
|
||||
private LocalDateTime claimExpireTime;
|
||||
|
||||
/**
|
||||
* 已领取人数
|
||||
*/
|
||||
private Integer claimCount;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@Indexed
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@Indexed
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
/**
|
||||
* 贡献者内嵌文档
|
||||
*/
|
||||
@Data
|
||||
public static class ContributorDoc {
|
||||
private Long userId;
|
||||
private String userName;
|
||||
private String userAvatar;
|
||||
private Long energy;
|
||||
private Double contributionRate;
|
||||
private Integer giftCount;
|
||||
private LocalDateTime firstContributeTime;
|
||||
private LocalDateTime lastContributeTime;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package com.red.circle.other.infra.database.mongo.repository;
|
||||
|
||||
import com.red.circle.other.infra.database.mongo.document.RocketStatusDocument;
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 火箭状态Repository
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-15
|
||||
*/
|
||||
@Repository
|
||||
public interface RocketStatusRepository extends MongoRepository<RocketStatusDocument, String> {
|
||||
|
||||
/**
|
||||
* 根据房间ID和日期查询
|
||||
*/
|
||||
Optional<RocketStatusDocument> findByRoomIdAndDate(Long roomId, String date);
|
||||
|
||||
/**
|
||||
* 根据房间ID列表和日期查询
|
||||
*/
|
||||
List<RocketStatusDocument> findByRoomIdInAndDate(List<Long> roomIds, String date);
|
||||
|
||||
/**
|
||||
* 查询所有处于充能状态的火箭
|
||||
*/
|
||||
List<RocketStatusDocument> findByStatusAndDate(Integer status, String date);
|
||||
|
||||
/**
|
||||
* 删除指定日期之前的数据
|
||||
*/
|
||||
void deleteByDateBefore(String date);
|
||||
}
|
||||
@ -0,0 +1,154 @@
|
||||
package com.red.circle.other.infra.gateway;
|
||||
|
||||
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 com.red.circle.other.infra.database.mongo.document.RocketStatusDocument;
|
||||
import com.red.circle.other.infra.database.mongo.repository.RocketStatusRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 火箭状态Gateway实现
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-15
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class RocketStatusGatewayImpl implements RocketStatusGateway {
|
||||
|
||||
private final RocketStatusRepository rocketStatusRepository;
|
||||
|
||||
@Override
|
||||
public RocketStatus findByRoomIdAndDate(Long roomId, String date) {
|
||||
return rocketStatusRepository.findByRoomIdAndDate(roomId, date)
|
||||
.map(this::toDomain)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(RocketStatus rocketStatus) {
|
||||
RocketStatusDocument document = toDocument(rocketStatus);
|
||||
rocketStatusRepository.save(document);
|
||||
// 回填ID
|
||||
rocketStatus.setId(document.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RocketStatus> findByRoomIds(List<Long> roomIds, String date) {
|
||||
List<RocketStatusDocument> documents = rocketStatusRepository.findByRoomIdInAndDate(roomIds, date);
|
||||
return documents.stream()
|
||||
.map(this::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RocketStatus> findAllCharging(String date) {
|
||||
List<RocketStatusDocument> documents = rocketStatusRepository.findByStatusAndDate(
|
||||
RocketStatusEnum.CHARGING.getCode(),
|
||||
date
|
||||
);
|
||||
return documents.stream()
|
||||
.map(this::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String id) {
|
||||
rocketStatusRepository.deleteById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Document转Domain
|
||||
*/
|
||||
private RocketStatus toDomain(RocketStatusDocument doc) {
|
||||
RocketStatus status = new RocketStatus();
|
||||
status.setId(doc.getId());
|
||||
status.setRoomId(doc.getRoomId());
|
||||
status.setDate(doc.getDate());
|
||||
status.setLevel(com.red.circle.other.domain.rocket.RocketLevel.of(doc.getLevel()));
|
||||
status.setCurrentEnergy(doc.getCurrentEnergy());
|
||||
status.setMaxEnergy(doc.getMaxEnergy());
|
||||
status.setStatus(RocketStatusEnum.of(doc.getStatus()));
|
||||
|
||||
// 转换贡献者
|
||||
if (doc.getContributors() != null) {
|
||||
List<com.red.circle.other.domain.rocket.RocketContributor> contributors = doc.getContributors().stream()
|
||||
.map(c -> {
|
||||
com.red.circle.other.domain.rocket.RocketContributor contributor =
|
||||
new com.red.circle.other.domain.rocket.RocketContributor();
|
||||
contributor.setUserId(c.getUserId());
|
||||
contributor.setUserName(c.getUserName());
|
||||
contributor.setUserAvatar(c.getUserAvatar());
|
||||
contributor.setEnergy(c.getEnergy());
|
||||
contributor.setContributionRate(
|
||||
c.getContributionRate() != null ?
|
||||
java.math.BigDecimal.valueOf(c.getContributionRate()) :
|
||||
java.math.BigDecimal.ZERO
|
||||
);
|
||||
contributor.setGiftCount(c.getGiftCount());
|
||||
contributor.setFirstContributeTime(c.getFirstContributeTime());
|
||||
contributor.setLastContributeTime(c.getLastContributeTime());
|
||||
return contributor;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
status.setContributors(contributors);
|
||||
}
|
||||
|
||||
status.setLaunchTime(doc.getLaunchTime());
|
||||
status.setClaimExpireTime(doc.getClaimExpireTime());
|
||||
status.setClaimCount(doc.getClaimCount());
|
||||
status.setCreatedAt(doc.getCreatedAt());
|
||||
status.setUpdatedAt(doc.getUpdatedAt());
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Domain转Document
|
||||
*/
|
||||
private RocketStatusDocument toDocument(RocketStatus status) {
|
||||
RocketStatusDocument doc = new RocketStatusDocument();
|
||||
doc.setId(status.getId());
|
||||
doc.setRoomId(status.getRoomId());
|
||||
doc.setDate(status.getDate() != null ? status.getDate() : LocalDate.now().toString());
|
||||
doc.setLevel(status.getLevel().getLevel());
|
||||
doc.setCurrentEnergy(status.getCurrentEnergy());
|
||||
doc.setMaxEnergy(status.getMaxEnergy());
|
||||
doc.setStatus(status.getStatus().getCode());
|
||||
|
||||
// 转换贡献者
|
||||
if (status.getContributors() != null) {
|
||||
List<RocketStatusDocument.ContributorDoc> contributors = status.getContributors().stream()
|
||||
.map(c -> {
|
||||
RocketStatusDocument.ContributorDoc doc1 = new RocketStatusDocument.ContributorDoc();
|
||||
doc1.setUserId(c.getUserId());
|
||||
doc1.setUserName(c.getUserName());
|
||||
doc1.setUserAvatar(c.getUserAvatar());
|
||||
doc1.setEnergy(c.getEnergy());
|
||||
doc1.setContributionRate(
|
||||
c.getContributionRate() != null ?
|
||||
c.getContributionRate().doubleValue() :
|
||||
0.0
|
||||
);
|
||||
doc1.setGiftCount(c.getGiftCount());
|
||||
doc1.setFirstContributeTime(c.getFirstContributeTime());
|
||||
doc1.setLastContributeTime(c.getLastContributeTime());
|
||||
return doc1;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
doc.setContributors(contributors);
|
||||
}
|
||||
|
||||
doc.setLaunchTime(status.getLaunchTime());
|
||||
doc.setClaimExpireTime(status.getClaimExpireTime());
|
||||
doc.setClaimCount(status.getClaimCount());
|
||||
doc.setCreatedAt(status.getCreatedAt());
|
||||
doc.setUpdatedAt(status.getUpdatedAt());
|
||||
return doc;
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user