diff --git a/rc-service/rc-service-other/other-adapter/src/main/java/com/red/circle/other/adapter/app/RocketRestController.java b/rc-service/rc-service-other/other-adapter/src/main/java/com/red/circle/other/adapter/app/RocketRestController.java new file mode 100644 index 00000000..0aa3103a --- /dev/null +++ b/rc-service/rc-service-other/other-adapter/src/main/java/com/red/circle/other/adapter/app/RocketRestController.java @@ -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); + } +} diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/rocket/RocketEnergyAddCmdExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/rocket/RocketEnergyAddCmdExe.java new file mode 100644 index 00000000..0cba6686 --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/rocket/RocketEnergyAddCmdExe.java @@ -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; + } +} diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/rocket/RocketLaunchCmdExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/rocket/RocketLaunchCmdExe.java new file mode 100644 index 00000000..6106469d --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/rocket/RocketLaunchCmdExe.java @@ -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. 保存历史记录 + } +} diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/rocket/RocketRewardClaimCmdExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/rocket/RocketRewardClaimCmdExe.java new file mode 100644 index 00000000..d714379f --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/rocket/RocketRewardClaimCmdExe.java @@ -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); + } +} diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/rocket/RocketStatusQryExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/rocket/RocketStatusQryExe.java new file mode 100644 index 00000000..79dd9a8f --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/rocket/RocketStatusQryExe.java @@ -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 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()); + }); + } + } +} diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/RocketClaimExpireTask.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/RocketClaimExpireTask.java new file mode 100644 index 00000000..a5c2e0cb --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/RocketClaimExpireTask.java @@ -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 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); + } + } +} diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/RocketDailyResetTask.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/RocketDailyResetTask.java new file mode 100644 index 00000000..a09850ef --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/RocketDailyResetTask.java @@ -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 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); + } + } +} diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/RocketServiceImpl.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/RocketServiceImpl.java new file mode 100644 index 00000000..54e96730 --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/RocketServiceImpl.java @@ -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); + } +} diff --git a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/RocketContributorCO.java b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/RocketContributorCO.java new file mode 100644 index 00000000..4d51fb37 --- /dev/null +++ b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/RocketContributorCO.java @@ -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; +} diff --git a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/RocketRewardCO.java b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/RocketRewardCO.java new file mode 100644 index 00000000..51e87649 --- /dev/null +++ b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/RocketRewardCO.java @@ -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; +} diff --git a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/RocketStatusCO.java b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/RocketStatusCO.java new file mode 100644 index 00000000..f1e27211 --- /dev/null +++ b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/RocketStatusCO.java @@ -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 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; +} diff --git a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/cmd/RocketEnergyAddCmd.java b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/cmd/RocketEnergyAddCmd.java new file mode 100644 index 00000000..a8f9acad --- /dev/null +++ b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/cmd/RocketEnergyAddCmd.java @@ -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; +} diff --git a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/cmd/RocketRewardClaimCmd.java b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/cmd/RocketRewardClaimCmd.java new file mode 100644 index 00000000..5914c434 --- /dev/null +++ b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/cmd/RocketRewardClaimCmd.java @@ -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; +} diff --git a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/cmd/RocketStatusQueryCmd.java b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/cmd/RocketStatusQueryCmd.java new file mode 100644 index 00000000..154a2bfc --- /dev/null +++ b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/cmd/RocketStatusQueryCmd.java @@ -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; +} diff --git a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/service/RocketService.java b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/service/RocketService.java new file mode 100644 index 00000000..0831d21f --- /dev/null +++ b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/service/RocketService.java @@ -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); +} diff --git a/rc-service/rc-service-other/other-domain/src/main/java/com/red/circle/other/domain/gateway/RocketStatusGateway.java b/rc-service/rc-service-other/other-domain/src/main/java/com/red/circle/other/domain/gateway/RocketStatusGateway.java new file mode 100644 index 00000000..340e98f1 --- /dev/null +++ b/rc-service/rc-service-other/other-domain/src/main/java/com/red/circle/other/domain/gateway/RocketStatusGateway.java @@ -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 findByRoomIds(List roomIds, String date); + + /** + * 查询所有处于充能状态的火箭 + */ + List findAllCharging(String date); + + /** + * 删除火箭状态 + */ + void delete(String id); +} diff --git a/rc-service/rc-service-other/other-domain/src/main/java/com/red/circle/other/domain/rocket/RocketContributor.java b/rc-service/rc-service-other/other-domain/src/main/java/com/red/circle/other/domain/rocket/RocketContributor.java new file mode 100644 index 00000000..ae967585 --- /dev/null +++ b/rc-service/rc-service-other/other-domain/src/main/java/com/red/circle/other/domain/rocket/RocketContributor.java @@ -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(); + } + } +} diff --git a/rc-service/rc-service-other/other-domain/src/main/java/com/red/circle/other/domain/rocket/RocketLevel.java b/rc-service/rc-service-other/other-domain/src/main/java/com/red/circle/other/domain/rocket/RocketLevel.java new file mode 100644 index 00000000..585dbf34 --- /dev/null +++ b/rc-service/rc-service-other/other-domain/src/main/java/com/red/circle/other/domain/rocket/RocketLevel.java @@ -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; + } +} diff --git a/rc-service/rc-service-other/other-domain/src/main/java/com/red/circle/other/domain/rocket/RocketStatus.java b/rc-service/rc-service-other/other-domain/src/main/java/com/red/circle/other/domain/rocket/RocketStatus.java new file mode 100644 index 00000000..68c68cb9 --- /dev/null +++ b/rc-service/rc-service-other/other-domain/src/main/java/com/red/circle/other/domain/rocket/RocketStatus.java @@ -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 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(); + } + } +} diff --git a/rc-service/rc-service-other/other-domain/src/main/java/com/red/circle/other/domain/rocket/RocketStatusEnum.java b/rc-service/rc-service-other/other-domain/src/main/java/com/red/circle/other/domain/rocket/RocketStatusEnum.java new file mode 100644 index 00000000..29ffdc53 --- /dev/null +++ b/rc-service/rc-service-other/other-domain/src/main/java/com/red/circle/other/domain/rocket/RocketStatusEnum.java @@ -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); + } +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/cache/key/RocketKeys.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/cache/key/RocketKeys.java new file mode 100644 index 00000000..0391d665 --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/cache/key/RocketKeys.java @@ -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(); + } +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/document/RocketStatusDocument.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/document/RocketStatusDocument.java new file mode 100644 index 00000000..2b5d30a6 --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/document/RocketStatusDocument.java @@ -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 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; + } +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/repository/RocketStatusRepository.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/repository/RocketStatusRepository.java new file mode 100644 index 00000000..ea3e5971 --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/repository/RocketStatusRepository.java @@ -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 { + + /** + * 根据房间ID和日期查询 + */ + Optional findByRoomIdAndDate(Long roomId, String date); + + /** + * 根据房间ID列表和日期查询 + */ + List findByRoomIdInAndDate(List roomIds, String date); + + /** + * 查询所有处于充能状态的火箭 + */ + List findByStatusAndDate(Integer status, String date); + + /** + * 删除指定日期之前的数据 + */ + void deleteByDateBefore(String date); +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/gateway/RocketStatusGatewayImpl.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/gateway/RocketStatusGatewayImpl.java new file mode 100644 index 00000000..3cc45284 --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/gateway/RocketStatusGatewayImpl.java @@ -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 findByRoomIds(List roomIds, String date) { + List documents = rocketStatusRepository.findByRoomIdInAndDate(roomIds, date); + return documents.stream() + .map(this::toDomain) + .collect(Collectors.toList()); + } + + @Override + public List findAllCharging(String date) { + List 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 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 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; + } +}