From 2c28ec51355df7caadbec608be3508dde89f2a96 Mon Sep 17 00:00:00 2001 From: tianfeng <769204422@qq.com> Date: Thu, 18 Dec 2025 14:09:38 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B8=B8=E6=88=8F=E7=8E=8B=E6=B4=BB=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/event/BadgeOperateEvent.java | 12 +- .../model/event/GameKingRewardEvent.java | 62 ++++ .../business/constant/QueueNameConstant.java | 5 + .../producer/UserMqMessageService.java | 34 +- .../business/streams/GameKingRewardSink.java | 15 + .../RankingActivityRestController.java | 22 ++ .../activity/RankingRewardGrantCmdExe.java | 295 ++++++++++++++++++ .../app/listener/BadgeOperateListener.java | 90 +++--- .../app/listener/GameKingRewardListener.java | 117 +++++++ .../activity/RankingActivityServiceImpl.java | 114 ++++++- .../clientobject/activity/RankingUserVO.java | 51 +++ .../dto/cmd/activity/RankingListQueryCmd.java | 7 +- .../activity/RankingActivityService.java | 13 +- .../domain/ranking/RankingActivityType.java | 2 + .../entity/ranking/RankingRewardSnapshot.java | 80 +++++ .../ranking/RankingRewardSnapshotService.java | 53 ++++ 16 files changed, 914 insertions(+), 58 deletions(-) create mode 100644 rc-common-business/common-business-mq/business-mq-model/src/main/java/com/red/circle/mq/business/model/event/GameKingRewardEvent.java create mode 100644 rc-common-business/common-business-mq/business-rocketmq/src/main/java/com/red/circle/mq/rocket/business/streams/GameKingRewardSink.java create mode 100644 rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/RankingRewardGrantCmdExe.java create mode 100644 rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/listener/GameKingRewardListener.java create mode 100644 rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/activity/RankingUserVO.java create mode 100644 rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/ranking/RankingRewardSnapshot.java create mode 100644 rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/ranking/RankingRewardSnapshotService.java diff --git a/rc-common-business/common-business-mq/business-mq-model/src/main/java/com/red/circle/mq/business/model/event/BadgeOperateEvent.java b/rc-common-business/common-business-mq/business-mq-model/src/main/java/com/red/circle/mq/business/model/event/BadgeOperateEvent.java index 3068b827..11a78abe 100644 --- a/rc-common-business/common-business-mq/business-mq-model/src/main/java/com/red/circle/mq/business/model/event/BadgeOperateEvent.java +++ b/rc-common-business/common-business-mq/business-mq-model/src/main/java/com/red/circle/mq/business/model/event/BadgeOperateEvent.java @@ -8,8 +8,6 @@ import lombok.experimental.Accessors; /** * 徽章操作事件消息. - * - * @author system */ @Data @EqualsAndHashCode(callSuper = false) @@ -29,6 +27,16 @@ public class BadgeOperateEvent implements Serializable { */ private String badgeKey; + /** + * 徽章ID. + */ + private Long badgeId; + + /** + * 天数. + */ + private Long days; + /** * 操作类型: WEAR-佩戴, REMOVE-移除. */ diff --git a/rc-common-business/common-business-mq/business-mq-model/src/main/java/com/red/circle/mq/business/model/event/GameKingRewardEvent.java b/rc-common-business/common-business-mq/business-mq-model/src/main/java/com/red/circle/mq/business/model/event/GameKingRewardEvent.java new file mode 100644 index 00000000..fd1f4573 --- /dev/null +++ b/rc-common-business/common-business-mq/business-mq-model/src/main/java/com/red/circle/mq/business/model/event/GameKingRewardEvent.java @@ -0,0 +1,62 @@ +package com.red.circle.mq.business.model.event; + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * 游戏王奖励事件. + */ +@Data +@Accessors(chain = true) +public class GameKingRewardEvent { + + /** + * 活动类型. + */ + private String activityType; + + /** + * 来源系统. + */ + private String origin; + + /** + * 周期Key. + */ + private String cycleKey; + + /** + * 模板ID. + */ + private String templateId; + + /** + * 排名数据. + */ + private List rankingUsers; + + /** + * 排名用户信息. + */ + @Data + @Accessors(chain = true) + public static class RankingUser { + /** + * 用户ID. + */ + private Long userId; + + /** + * 排名. + */ + private Integer rank; + + /** + * 分数. + */ + private Long score; + + } +} diff --git a/rc-common-business/common-business-mq/business-rocketmq/src/main/java/com/red/circle/mq/rocket/business/constant/QueueNameConstant.java b/rc-common-business/common-business-mq/business-rocketmq/src/main/java/com/red/circle/mq/rocket/business/constant/QueueNameConstant.java index f0b36854..65f6b55e 100644 --- a/rc-common-business/common-business-mq/business-rocketmq/src/main/java/com/red/circle/mq/rocket/business/constant/QueueNameConstant.java +++ b/rc-common-business/common-business-mq/business-rocketmq/src/main/java/com/red/circle/mq/rocket/business/constant/QueueNameConstant.java @@ -152,6 +152,11 @@ public interface QueueNameConstant { */ String USER_HEARTBEAT = "USER_HEARTBEAT"; + /** + * 游戏王奖励. + */ + String GAME_KING_REWARD = "GAME_KING_REWARD"; + // =================================================后台管理============================================================= /** * 后台管理审批. diff --git a/rc-common-business/common-business-mq/business-rocketmq/src/main/java/com/red/circle/mq/rocket/business/producer/UserMqMessageService.java b/rc-common-business/common-business-mq/business-rocketmq/src/main/java/com/red/circle/mq/rocket/business/producer/UserMqMessageService.java index d2e2210d..f47bf8fd 100644 --- a/rc-common-business/common-business-mq/business-rocketmq/src/main/java/com/red/circle/mq/rocket/business/producer/UserMqMessageService.java +++ b/rc-common-business/common-business-mq/business-rocketmq/src/main/java/com/red/circle/mq/rocket/business/producer/UserMqMessageService.java @@ -5,6 +5,7 @@ import com.red.circle.component.mq.service.FixedTopic; import com.red.circle.component.mq.service.SenderService; import com.red.circle.mq.business.model.event.BadgeEvent; import com.red.circle.mq.business.model.event.BadgeOperateEvent; +import com.red.circle.mq.business.model.event.GameKingRewardEvent; import com.red.circle.mq.business.model.event.user.AccountBeautifulNumberEvent; import com.red.circle.mq.business.model.event.user.CpApplyEvent; import com.red.circle.mq.business.model.event.user.LoginLoggerEvent; @@ -15,6 +16,7 @@ import com.red.circle.mq.rocket.business.streams.AccountBeautifulNumberSink; import com.red.circle.mq.rocket.business.streams.BadgeCheckSink; import com.red.circle.mq.rocket.business.streams.BadgeOperateSink; import com.red.circle.mq.rocket.business.streams.CpApplySink; +import com.red.circle.mq.rocket.business.streams.GameKingRewardSink; import com.red.circle.mq.rocket.business.streams.LoginRegisterLoggerSink; import com.red.circle.mq.rocket.business.streams.OnlineStatusUploadSink; import com.red.circle.mq.rocket.business.streams.UserCardApplySink; @@ -26,8 +28,6 @@ import org.springframework.stereotype.Component; /** * 用户相关队列消息. - * - * @author pengliang on 2023/5/22 */ @Component @RequiredArgsConstructor @@ -126,11 +126,7 @@ public class UserMqMessageService { } /** - * 发送徽章操作事件. - * - * @param userId 用户ID - * @param badgeKey 徽章Key - * @param operationType 操作类型: WEAR-佩戴, REMOVE-移除 + * 发送徽章操作事件 (旧版本兼容 - 通过badgeKey). */ public void sendBadgeOperateEvent(Long userId, String badgeKey, String operationType) { senderService.sendEventMessage(MessageEvent.builder() @@ -145,4 +141,28 @@ public class UserMqMessageService { .build()); } + /** + * 发送徽章操作事件 (新版本 - 传完整事件对象). + */ + public void sendBadgeOperateEvent(BadgeOperateEvent event) { + senderService.sendEventMessage(MessageEvent.builder() + .consumeId(IdWorkerUtils.getIdStr()) + .topic(FixedTopic.RC_DEFAULT_APP_ORDINARY) + .tag(BadgeOperateSink.TAG) + .body(event) + .build()); + } + + /** + * 发送游戏王奖励事件. + */ + public void sendGameKingRewardEvent(GameKingRewardEvent event) { + senderService.sendEventMessage(MessageEvent.builder() + .consumeId(IdWorkerUtils.getIdStr()) + .topic(FixedTopic.RC_DEFAULT_APP_ORDINARY) + .tag(GameKingRewardSink.TAG) + .body(event) + .build()); + } + } diff --git a/rc-common-business/common-business-mq/business-rocketmq/src/main/java/com/red/circle/mq/rocket/business/streams/GameKingRewardSink.java b/rc-common-business/common-business-mq/business-rocketmq/src/main/java/com/red/circle/mq/rocket/business/streams/GameKingRewardSink.java new file mode 100644 index 00000000..53ec443f --- /dev/null +++ b/rc-common-business/common-business-mq/business-rocketmq/src/main/java/com/red/circle/mq/rocket/business/streams/GameKingRewardSink.java @@ -0,0 +1,15 @@ +package com.red.circle.mq.rocket.business.streams; + +import com.red.circle.mq.rocket.business.constant.QueueNameConstant; + +/** + * 游戏王奖励. + * + * @author system + */ +public interface GameKingRewardSink { + + String INPUT = QueueNameConstant.GAME_KING_REWARD; + String TAG = "game_king_reward_v1"; + +} \ No newline at end of file diff --git a/rc-service/rc-service-other/other-adapter/src/main/java/com/red/circle/other/adapter/app/activity/RankingActivityRestController.java b/rc-service/rc-service-other/other-adapter/src/main/java/com/red/circle/other/adapter/app/activity/RankingActivityRestController.java index 85cfc7e6..474a393f 100644 --- a/rc-service/rc-service-other/other-adapter/src/main/java/com/red/circle/other/adapter/app/activity/RankingActivityRestController.java +++ b/rc-service/rc-service-other/other-adapter/src/main/java/com/red/circle/other/adapter/app/activity/RankingActivityRestController.java @@ -3,18 +3,22 @@ package com.red.circle.other.adapter.app.activity; import com.red.circle.framework.web.controller.BaseController; import com.red.circle.other.app.dto.clientobject.activity.HistoricalTop1ListCO; import com.red.circle.other.app.dto.clientobject.activity.RankingListCO; +import com.red.circle.other.app.dto.clientobject.activity.RankingUserVO; import com.red.circle.other.app.dto.clientobject.activity.UserRankInfoCO; import com.red.circle.other.app.dto.cmd.UserRankQueryCmd; import com.red.circle.other.app.dto.cmd.activity.HistoricalTop1QueryCmd; import com.red.circle.other.app.service.activity.RankingActivityService; import com.red.circle.other.app.dto.cmd.activity.RankingDataUpdateCmd; import com.red.circle.other.app.dto.cmd.activity.RankingListQueryCmd; +import com.red.circle.other.domain.ranking.RankingActivityType; import com.red.circle.tool.core.date.ZonedDateTimeAsiaRiyadhUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import java.util.List; + /** * 排行榜活动控制器 */ @@ -67,4 +71,22 @@ public class RankingActivityRestController extends BaseController { public HistoricalTop1ListCO getHistoricalTop1List(@RequestBody @Validated HistoricalTop1QueryCmd cmd) { return rankingActivityService.getHistoricalTop1List(cmd); } + + /** + * 查询排行榜前四名(带缓存和奖励发送) + * + * 用途:获取指定活动的前四名排行榜,数据缓存五分钟,查询DB时发送游戏王奖励消息 + * + * @param cmd 查询命令 + * @return 排行榜前四名 + */ + @PostMapping("/top-four-with-reward") + public List getTopFourRankingWithReward(@RequestBody RankingListQueryCmd cmd) { + if (cmd.getCycleType() == null) { + cmd.setCycleKey(ZonedDateTimeAsiaRiyadhUtils.nowWeekMondayToInt().toString()); + } + cmd.setActivityType(RankingActivityType.KING_GAMES.getCode()); + cmd.setTemplateId("2001207026499137537"); + return rankingActivityService.getTopFourRankingWithReward(cmd); + } } \ No newline at end of file diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/RankingRewardGrantCmdExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/RankingRewardGrantCmdExe.java new file mode 100644 index 00000000..84e218dc --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/RankingRewardGrantCmdExe.java @@ -0,0 +1,295 @@ +package com.red.circle.other.app.command.activity; + +import com.google.common.collect.Sets; +import com.red.circle.common.business.enums.SendPropsOrigin; +import com.red.circle.mq.business.model.event.BadgeOperateEvent; +import com.red.circle.mq.rocket.business.producer.UserMqMessageService; +import com.red.circle.other.app.dto.clientobject.sys.ActivityConfigVO; +import com.red.circle.other.infra.database.mongo.entity.ranking.RankingRewardSnapshot; +import com.red.circle.other.infra.database.mongo.service.ranking.RankingRewardSnapshotService; +import com.red.circle.other.infra.database.rds.entity.badge.BadgeConfig; +import com.red.circle.other.infra.database.rds.service.badge.BadgeConfigService; +import com.red.circle.other.inner.endpoint.material.props.PropsBackpackClient; +import com.red.circle.other.inner.enums.material.PropsCommodityType; +import com.red.circle.other.inner.model.cmd.material.DeletePropsCmd; +import com.red.circle.other.inner.model.cmd.material.GivePropsBackpackCmd; +import com.red.circle.other.inner.model.dto.material.PropsActivityRewardConfigInfoDTO; +import com.red.circle.tool.core.collection.CollectionUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 排名奖励发放执行器. + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class RankingRewardGrantCmdExe { + + private final RankingRewardSnapshotService snapshotService; + private final PropsBackpackClient propsBackpackClient; + private final UserMqMessageService userMqMessageService; + private final BadgeConfigService badgeConfigService; + + /** + * 处理排名奖励变化. + */ + public void execute(String activityType, String origin, String cycleKey, + String templateId, List currentRankingUsers, + ActivityConfigVO activityConfig) { + + RankingRewardSnapshot lastSnapshot = snapshotService.getLatestSnapshot(activityType, origin, cycleKey); + + Map currentRankMap = currentRankingUsers.stream() + .collect(Collectors.toMap(RankingUserData::getRank, u -> u)); + + Map lastUserMap = new HashMap<>(); + if (lastSnapshot != null && CollectionUtils.isNotEmpty(lastSnapshot.getRankingUsers())) { + lastUserMap = lastSnapshot.getRankingUsers().stream() + .collect(Collectors.toMap(RankingRewardSnapshot.RankingUserSnapshot::getUserId, u -> u)); + } + + processRewardChanges(currentRankMap, lastUserMap, activityConfig); + + saveNewSnapshot(activityType, origin, cycleKey, templateId, currentRankingUsers, activityConfig); + } + + /** + * 处理奖励变化. + */ + private void processRewardChanges(Map currentRankMap, + Map lastUserMap, + ActivityConfigVO activityConfig) { + + Set currentUserIds = currentRankMap.values().stream() + .map(RankingUserData::getUserId) + .collect(Collectors.toSet()); + + for (RankingRewardSnapshot.RankingUserSnapshot lastUser : lastUserMap.values()) { + if (!currentUserIds.contains(lastUser.getUserId())) { + log.info("用户失去排名,收回奖励, userId: {}, rank: {}", lastUser.getUserId(), lastUser.getRank()); + revokeRewards(lastUser.getUserId(), lastUser.getRewards()); + } + } + + for (RankingUserData currentUser : currentRankMap.values()) { + Long userId = currentUser.getUserId(); + Integer currentRank = currentUser.getRank(); + + RankingRewardSnapshot.RankingUserSnapshot lastUser = lastUserMap.get(userId); + + if (lastUser == null) { + log.info("用户新获得排名,发放奖励, userId: {}, rank: {}", userId, currentRank); + List rewards = getRewardsForRank(currentRank, activityConfig); + grantRewards(userId, rewards); + + } else if (!Objects.equals(lastUser.getRank(), currentRank)) { + log.info("用户排名变化, userId: {}, 旧排名: {}, 新排名: {}", + userId, lastUser.getRank(), currentRank); + revokeRewards(userId, lastUser.getRewards()); + List rewards = getRewardsForRank(currentRank, activityConfig); + grantRewards(userId, rewards); + } + } + } + + /** + * 发放奖励. + */ + private void grantRewards(Long userId, List rewards) { + if (CollectionUtils.isEmpty(rewards)) { + return; + } + + for (RankingRewardSnapshot.RewardItem reward : rewards) { + try { + if ("PROPS".equals(reward.getType())) { + grantProps(userId, reward); + } else if ("BADGE".equals(reward.getType())) { + grantBadge(userId, reward); + } + } catch (Exception e) { + log.error("发放奖励失败, userId: {}, reward: {}", userId, reward, e); + } + } + } + + /** + * 收回奖励. + */ + private void revokeRewards(Long userId, List rewards) { + if (CollectionUtils.isEmpty(rewards)) { + return; + } + + for (RankingRewardSnapshot.RewardItem reward : rewards) { + try { + if ("BADGE".equals(reward.getType())) { + revokeBadge(userId, reward); + } else if ("PROPS".equals(reward.getType())) { + revokeProps(userId, reward); + } + } catch (Exception e) { + log.error("收回奖励失败, userId: {}, reward: {}", userId, reward, e); + } + } + } + + /** + * 发放道具. + */ + private void grantProps(Long userId, RankingRewardSnapshot.RewardItem reward) { + GivePropsBackpackCmd cmd = new GivePropsBackpackCmd() + .setAcceptUserId(userId) + .setPropsId(Long.valueOf(reward.getContent())) + .setType(reward.getDetailType()) + .setOrigin(SendPropsOrigin.ACTIVITY_REWARD.name()) + .setOriginDesc("游戏王排名奖励") + .setDays(reward.getQuantity()) + .setUseProps(Boolean.TRUE) + .setAllowGive(Boolean.FALSE) + .setOpUser(0L); + + propsBackpackClient.giveProps(cmd); + propsBackpackClient.givePropsNotify(userId, reward.getDetailType(), + reward.getQuantity(), Long.valueOf(reward.getContent())); + } + + /** + * 收回道具. + */ + private void revokeProps(Long userId, RankingRewardSnapshot.RewardItem reward) { + DeletePropsCmd cmd = new DeletePropsCmd() + .setUserIds(Sets.newHashSet(userId)) + .setPropsIds(Sets.newHashSet(Long.valueOf(reward.getContent()))) + .setPropsType(PropsCommodityType.valueOf(reward.getDetailType())); + + propsBackpackClient.deleteProps(cmd); + } + + /** + * 发放徽章. + */ + private void grantBadge(Long userId, RankingRewardSnapshot.RewardItem reward) { + BadgeOperateEvent event = new BadgeOperateEvent() + .setUserId(userId) + .setBadgeId(Long.valueOf(reward.getContent())) + .setDays(1L) + .setOperationType(BadgeOperateEvent.OperationType.WEAR.name()); + + userMqMessageService.sendBadgeOperateEvent(event); + } + + /** + * 收回徽章. + */ + private void revokeBadge(Long userId, RankingRewardSnapshot.RewardItem reward) { + BadgeOperateEvent event = new BadgeOperateEvent() + .setUserId(userId) + .setBadgeId(Long.valueOf(reward.getContent())) + .setDays(1L) + .setOperationType(BadgeOperateEvent.OperationType.REMOVE.name()); + + userMqMessageService.sendBadgeOperateEvent(event); + } + + /** + * 获取指定排名的奖励列表. + */ + private List getRewardsForRank(Integer rank, ActivityConfigVO activityConfig) { + if (activityConfig == null || CollectionUtils.isEmpty(activityConfig.getButOneRewards())) { + return Collections.emptyList(); + } + + String rankRange = getRankRange(rank); + + return activityConfig.getButOneRewards().stream() + .filter(r -> rankRange.equals(r.getRankRange())) + .flatMap(r -> { + if (CollectionUtils.isEmpty(r.getRewards())) { + return java.util.stream.Stream.empty(); + } + return r.getRewards().stream() + .filter(this::isSupportedRewardType) + .map(this::convertToRewardItem); + }) + .collect(Collectors.toList()); + } + + /** + * 判断是否为支持的奖励类型. + */ + private boolean isSupportedRewardType(PropsActivityRewardConfigInfoDTO dto) { + return ("PROPS".equals(dto.getType()) && "AVATAR_FRAME".equals(dto.getDetailType())) || + ("BADGE".equals(dto.getType()) && "HONOR_ACTIVITY".equals(dto.getDetailType())); + } + + /** + * 转换为奖励项. + */ + private RankingRewardSnapshot.RewardItem convertToRewardItem(PropsActivityRewardConfigInfoDTO dto) { + return new RankingRewardSnapshot.RewardItem() + .setType(dto.getType()) + .setDetailType(dto.getDetailType()) + .setContent(dto.getContent()) + .setQuantity(dto.getQuantity()) + .setBadgeName(dto.getBadgeName()); + } + + /** + * 获取排名范围. + */ + private String getRankRange(Integer rank) { + if (rank == 1) return "1"; + if (rank == 2) return "2"; + if (rank == 3) return "3"; + if (rank <= 10) return "4-10"; + return "other"; + } + + /** + * 保存新快照. + */ + private void saveNewSnapshot(String activityType, String origin, String cycleKey, + String templateId, List currentRankingUsers, + ActivityConfigVO activityConfig) { + List snapshotUsers = currentRankingUsers.stream() + .map(user -> { + String rankRange = getRankRange(user.getRank()); + List rewards = getRewardsForRank(user.getRank(), activityConfig); + + return new RankingRewardSnapshot.RankingUserSnapshot() + .setUserId(user.getUserId()) + .setRank(user.getRank()) + .setRankRange(rankRange) + .setScore(user.getScore()) + .setRewards(rewards); + }) + .collect(Collectors.toList()); + + RankingRewardSnapshot snapshot = new RankingRewardSnapshot() + .setActivityType(activityType) + .setOrigin(origin) + .setCycleKey(cycleKey) + .setTemplateId(templateId) + .setSnapshotTime(LocalDateTime.now()) + .setRankingUsers(snapshotUsers); + + snapshotService.save(snapshot); + } + + /** + * 排名用户数据. + */ + @lombok.Data + public static class RankingUserData { + private Long userId; + private Integer rank; + private Long score; + } +} diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/listener/BadgeOperateListener.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/listener/BadgeOperateListener.java index 5cba52ac..7fa44dba 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/listener/BadgeOperateListener.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/listener/BadgeOperateListener.java @@ -31,8 +31,6 @@ import lombok.extern.slf4j.Slf4j; /** * 徽章操作处理. - * - * @author system */ @Slf4j @RocketMqMessageListener(groupId = BadgeOperateSink.INPUT, tag = BadgeOperateSink.TAG) @@ -63,28 +61,35 @@ public class BadgeOperateListener implements MessageListener { private void handleBadgeOperate(BadgeOperateEvent event) { Long userId = event.getUserId(); String badgeKey = event.getBadgeKey(); + Long badgeId = event.getBadgeId(); String operationType = event.getOperationType(); - if (userId == null || badgeKey == null || operationType == null) { - log.warn("徽章操作事件参数不完整, userId: {}, badgeKey: {}, operationType: {}", - userId, badgeKey, operationType); + if (userId == null || operationType == null) { + log.warn("徽章操作事件参数不完整, userId: {}, operationType: {}", userId, operationType); return; } - // 根据badgeKey查询徽章配置 - BadgeConfig badgeConfig = badgeConfigService.getByBadgeKey(badgeKey); - if (badgeConfig == null) { - log.warn("徽章配置不存在, badgeKey: {}", badgeKey); - return; + Long finalBadgeId = badgeId; + + // 如果传了badgeId则使用badgeId,否则通过badgeKey查询 + if (finalBadgeId == null) { + if (badgeKey == null) { + log.warn("徽章操作事件缺少badgeKey和badgeId, userId: {}", userId); + return; + } + BadgeConfig badgeConfig = badgeConfigService.getByBadgeKey(badgeKey); + if (badgeConfig == null) { + log.warn("徽章配置不存在, badgeKey: {}", badgeKey); + return; + } + finalBadgeId = badgeConfig.getId(); } - Long badgeId = badgeConfig.getId(); - // 根据操作类型执行对应逻辑 if (BadgeOperateEvent.OperationType.WEAR.name().equals(operationType)) { - handleWearBadge(userId, badgeId); + handleWearBadge(userId, finalBadgeId, event.getDays()); } else if (BadgeOperateEvent.OperationType.REMOVE.name().equals(operationType)) { - handleRemoveBadge(userId, badgeId); + handleRemoveBadge(userId, finalBadgeId); } else { log.warn("未知的操作类型, operationType: {}", operationType); } @@ -93,11 +98,10 @@ public class BadgeOperateListener implements MessageListener { /** * 佩戴徽章. */ - private void handleWearBadge(Long userId, Long badgeId) { + private void handleWearBadge(Long userId, Long badgeId, Long days) { String lockKey = "badge:operate:wear:" + userId + ":" + badgeId; distributedLockUtil.executeWithLock(lockKey, () -> { - // 检查用户是否已经拥有该徽章(幂等处理) BadgeBackpack existBadge = badgeBackpackService.query() .eq(BadgeBackpack::getUserId, userId) .eq(BadgeBackpack::getBadgeId, badgeId) @@ -110,7 +114,7 @@ public class BadgeOperateListener implements MessageListener { BadgeBackpack badgeBackpack = badgeBackpackService.getByUserIdByBadgeId(userId, badgeId); if (Objects.isNull(badgeBackpack)) { - badgeBackpackService.save(buildSaveUserBadgeBackpack(badgeId, userId)); + badgeBackpackService.save(buildSaveUserBadgeBackpack(badgeId, userId, days)); userProfileGateway.removeCacheAll(userId); } return null; @@ -121,33 +125,37 @@ public class BadgeOperateListener implements MessageListener { * 移除徽章. */ private void handleRemoveBadge(Long userId, Long badgeId) { - badgeBackpackService.deleteBadges(userId, Sets.newHashSet(badgeId)); - userProfileGateway.removeCache(userId); - UserProfile userProfile = userProfileGateway.getByUserId(userId); - if (Objects.isNull(userProfile)) { - return; - } - List wearBadge = userProfile.getWearBadge(); - List honorBadge = userProfile.getWearHonor(); - wearBadge.addAll(honorBadge); - if (CollectionUtils.isEmpty(wearBadge)) { - return; - } - //过滤需要删除的 wearBadge id - Set saveWear = wearBadge.stream().map(UserUseBadgeDTO::getId) - .filter(id -> !badgeId.equals(id)).collect(Collectors.toSet()); - userProfileGateway.updateUserWearBadge(userId, userProfile.getOriginSys(),saveWear); + badgeBackpackService.deleteBadges(userId, Sets.newHashSet(badgeId)); + userProfileGateway.removeCache(userId); + UserProfile userProfile = userProfileGateway.getByUserId(userId); + if (Objects.isNull(userProfile)) { + return; + } + List wearBadge = userProfile.getWearBadge(); + List honorBadge = userProfile.getWearHonor(); + wearBadge.addAll(honorBadge); + if (CollectionUtils.isEmpty(wearBadge)) { + return; + } + Set saveWear = wearBadge.stream().map(UserUseBadgeDTO::getId) + .filter(id -> !badgeId.equals(id)).collect(Collectors.toSet()); + userProfileGateway.updateUserWearBadge(userId, userProfile.getOriginSys(), saveWear); } + private BadgeBackpack buildSaveUserBadgeBackpack(Long badgeId, Long userId, Long days) { + BadgeBackpack badgeBackpack = new BadgeBackpack() + .setUserId(userId) + .setBadgeId(badgeId) + .setUseProps(Boolean.TRUE); - private BadgeBackpack buildSaveUserBadgeBackpack(Long badgeId, Long userId) { - return new BadgeBackpack() - .setUserId(userId) - .setBadgeId(badgeId) - .setExpireTime(TimestampUtils.now()) - .setExpireType(BadgeBackpackExpireTypeEnum.PERMANENT.name()) - .setUseProps(Boolean.TRUE); + if (days != null && days > 0) { + badgeBackpack.setExpireTime(TimestampUtils.nowPlusDays(days.intValue())); + badgeBackpack.setExpireType(BadgeBackpackExpireTypeEnum.TEMPORARY.name()); + } else { + badgeBackpack.setExpireTime(TimestampUtils.now()); + badgeBackpack.setExpireType(BadgeBackpackExpireTypeEnum.PERMANENT.name()); } - + return badgeBackpack; + } } diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/listener/GameKingRewardListener.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/listener/GameKingRewardListener.java new file mode 100644 index 00000000..691aff17 --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/listener/GameKingRewardListener.java @@ -0,0 +1,117 @@ +package com.red.circle.other.app.listener; + +import com.google.common.collect.Sets; +import com.red.circle.common.business.dto.cmd.AppExtCommand; +import com.red.circle.component.mq.MessageEventProcess; +import com.red.circle.component.mq.MessageEventProcessDescribe; +import com.red.circle.component.mq.config.RocketMqMessageListener; +import com.red.circle.component.mq.service.Action; +import com.red.circle.component.mq.service.ConsumerMessage; +import com.red.circle.component.mq.service.MessageListener; +import com.red.circle.framework.core.dto.ReqSysOrigin; +import com.red.circle.mq.business.model.event.GameKingRewardEvent; +import com.red.circle.mq.rocket.business.streams.GameKingRewardSink; +import com.red.circle.other.app.command.activity.RankingRewardGrantCmdExe; +import com.red.circle.other.app.dto.clientobject.sys.ActivityConfigVO; +import com.red.circle.other.app.service.activity.WeekKingQueenService; +import com.red.circle.other.app.util.DistributedLockUtil; +import com.red.circle.tool.core.collection.CollectionUtils; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * 游戏王奖励监听器. + */ +@Slf4j +@RocketMqMessageListener(groupId = GameKingRewardSink.INPUT, tag = GameKingRewardSink.TAG) +@RequiredArgsConstructor +public class GameKingRewardListener implements MessageListener { + + private final MessageEventProcess messageEventProcess; + private final DistributedLockUtil distributedLockUtil; + private final WeekKingQueenService weekKingQueenService; + private final RankingRewardGrantCmdExe rankingRewardGrantCmdExe; + + @Override + public Action consume(ConsumerMessage message) { + return messageEventProcess.consume( + MessageEventProcessDescribe.builder() + .logTag("游戏王奖励处理") + .consumeMsgTimeoutMinute(60) + .repeatConsumeMinute(1) + .repeatConsumeTag("GameKingReward") + .checkConsumeTag(GameKingRewardSink.TAG) + .message(message) + .build(), + GameKingRewardEvent.class, + this::handleGameKingReward); + } + + /** + * 处理游戏王奖励事件. + */ + private void handleGameKingReward(GameKingRewardEvent event) { + String activityType = event.getActivityType(); + String origin = event.getOrigin(); + String cycleKey = event.getCycleKey(); + String templateId = event.getTemplateId() == null ? "-1" : event.getTemplateId(); + + if (activityType == null || origin == null || cycleKey == null) { + log.warn("游戏王奖励事件参数不完整, activityType: {}, origin: {}, cycleKey: {}", + activityType, origin, cycleKey); + return; + } + + String lockKey = "game_king_reward:" + activityType + ":" + origin + ":" + cycleKey; + + distributedLockUtil.executeWithLock(lockKey, () -> { + try { + log.info("开始处理游戏王奖励, activityType: {}, origin: {}, cycleKey: {}, templateId: {}, rankingUsers: {}", + activityType, origin, cycleKey, templateId, + event.getRankingUsers() != null ? event.getRankingUsers().size() : 0); + + if (CollectionUtils.isEmpty(event.getRankingUsers())) { + log.warn("排名用户列表为空,跳过处理"); + return null; + } + + // 获取活动配置 + AppExtCommand cmd = new AppExtCommand(); + cmd.setReqSysOrigin(ReqSysOrigin.of("LIKEI")); + ActivityConfigVO activityConfig = weekKingQueenService.getEffectiveActivity(cmd, templateId); + if (activityConfig == null || activityConfig.getConfig() == null) { + log.warn("活动配置不存在, templateId: {}", templateId); + return null; + } + + // 转换排名数据 + List rankingUsers = event.getRankingUsers().stream() + .map(user -> { + RankingRewardGrantCmdExe.RankingUserData data = new RankingRewardGrantCmdExe.RankingUserData(); + data.setUserId(user.getUserId()); + data.setRank(user.getRank()); + data.setScore(user.getScore()); + return data; + }) + .collect(Collectors.toList()); + + // 执行奖励发放逻辑 + rankingRewardGrantCmdExe.execute(activityType, origin, cycleKey, templateId, + rankingUsers, activityConfig); + + log.info("游戏王奖励处理完成, activityType: {}, origin: {}, cycleKey: {}", + activityType, origin, cycleKey); + + } catch (Exception e) { + log.error("处理游戏王奖励失败, activityType: {}, origin: {}, cycleKey: {}", + activityType, origin, cycleKey, e); + throw e; + } + return null; + }); + } +} diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/activity/RankingActivityServiceImpl.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/activity/RankingActivityServiceImpl.java index eea363c2..87ee48d3 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/activity/RankingActivityServiceImpl.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/activity/RankingActivityServiceImpl.java @@ -1,23 +1,36 @@ package com.red.circle.other.app.service.activity; +import com.alibaba.fastjson.JSON; +import com.red.circle.component.redis.service.RedisService; +import com.red.circle.mq.business.model.event.GameKingRewardEvent; import com.red.circle.other.app.command.activity.query.HistoricalTop1QryExe; import com.red.circle.other.app.command.activity.query.RankingDataAccumulateCmdExe; import com.red.circle.other.app.command.activity.query.RankingListQryExe; import com.red.circle.other.app.command.activity.query.UserRankQryExe; -import com.red.circle.other.app.dto.clientobject.activity.HistoricalTop1ListCO; -import com.red.circle.other.app.dto.clientobject.activity.RankingListCO; -import com.red.circle.other.app.dto.clientobject.activity.UserRankInfoCO; +import com.red.circle.other.app.dto.clientobject.activity.*; import com.red.circle.other.app.dto.cmd.UserRankQueryCmd; import com.red.circle.other.app.dto.cmd.activity.HistoricalTop1QueryCmd; import com.red.circle.other.app.dto.cmd.activity.RankingDataUpdateCmd; import com.red.circle.other.app.dto.cmd.activity.RankingListQueryCmd; +import com.red.circle.mq.rocket.business.producer.UserMqMessageService; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import com.red.circle.other.domain.gateway.user.UserProfileGateway; +import com.red.circle.other.domain.model.user.UserProfile; +import com.red.circle.other.domain.ranking.RankingActivityType; +import com.red.circle.other.domain.ranking.RankingCycleType; +import com.red.circle.tool.core.collection.CollectionUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; /** - * 排行榜活动服务实现 - * + * 排行榜活动服务实现. */ @Slf4j @Service @@ -28,6 +41,12 @@ public class RankingActivityServiceImpl implements RankingActivityService { private final RankingListQryExe rankingListQryExe; private final UserRankQryExe userRankQryExe; private final HistoricalTop1QryExe historicalTop1QryExe; + private final RedisService redisService; + private final UserMqMessageService userMqMessageService; + private final UserProfileGateway userProfileGateway; + + private static final String RANKING_TOP4_CACHE_KEY_PREFIX = "ranking_top4:"; + private static final int CACHE_EXPIRE_MINUTES = 5; @Override public Boolean accumulateRankingData(RankingDataUpdateCmd cmd) { @@ -48,4 +67,87 @@ public class RankingActivityServiceImpl implements RankingActivityService { public HistoricalTop1ListCO getHistoricalTop1List(HistoricalTop1QueryCmd cmd) { return historicalTop1QryExe.execute(cmd); } -} \ No newline at end of file + + @Override + public List getTopFourRankingWithReward(RankingListQueryCmd cmd) { + String cacheKey = buildCacheKey(cmd); + + String cachedJson = redisService.getString(cacheKey); + if (cachedJson != null) { + return JSON.parseArray(cachedJson, RankingUserVO.class); + } + + RankingListQueryCmd top4Cmd = new RankingListQueryCmd(); + top4Cmd.setActivityType(cmd.getActivityType()); + top4Cmd.setCycleKey(cmd.getCycleKey()); + top4Cmd.setCycleType(RankingCycleType.WEEKLY.getCode()); + top4Cmd.setReqUserId(cmd.getReqUserId()); + top4Cmd.setReqSysOrigin(cmd.getReqSysOrigin()); + top4Cmd.setTopN(10); + + RankingListCO result = rankingListQryExe.execute(top4Cmd); + + List rankingUsers = null; + if (result != null && CollectionUtils.isNotEmpty(result.getRankingList())) { + Set userIds = result.getRankingList().stream() + .map(RankingActivityRecordCO::getUserId) + .collect(Collectors.toSet()); + + Map userProfileMap = userProfileGateway.mapByUserIds(userIds); + + rankingUsers = result.getRankingList().stream() + .limit(4) + .map(user -> { + UserProfile profile = userProfileMap.get(user.getUserId()); + RankingUserVO vo = new RankingUserVO(); + vo.setUserId(user.getUserId()); + vo.setRank(user.getRank()); + vo.setQuantity(String.valueOf(user.getQuantity())); + + if (profile != null) { + vo.setAccount(profile.getAccount()); + vo.setNickname(profile.getUserNickname()); + vo.setAvatar(profile.getUserAvatar()); + } + + return vo; + }) + .collect(Collectors.toList()); + + redisService.setString(cacheKey, JSON.toJSONString(rankingUsers), CACHE_EXPIRE_MINUTES, TimeUnit.MINUTES); + + sendGameKingRewardEvent(cmd, result); + } + + return rankingUsers; + } + + /** + * 发送游戏王奖励消息. + */ + private void sendGameKingRewardEvent(RankingListQueryCmd cmd, RankingListCO result) { + if (CollectionUtils.isEmpty(result.getRankingList())) { + return; + } + + GameKingRewardEvent event = new GameKingRewardEvent() + .setActivityType(RankingActivityType.getByCode(cmd.getActivityType()).name()) + .setOrigin(cmd.getReqSysOrigin().getOrigin()) + .setCycleKey(cmd.getCycleKey()) + .setTemplateId(cmd.getTemplateId()) + .setRankingUsers(result.getRankingList().stream() + .map(user -> new GameKingRewardEvent.RankingUser() + .setUserId(user.getUserId()) + .setRank(user.getRank())) + .collect(Collectors.toList())); + + userMqMessageService.sendGameKingRewardEvent(event); + } + + /** + * 构建缓存Key. + */ + private String buildCacheKey(RankingListQueryCmd cmd) { + return RANKING_TOP4_CACHE_KEY_PREFIX + RankingActivityType.getByCode(cmd.getActivityType()).name() + ":" + cmd.getCycleKey(); + } +} diff --git a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/activity/RankingUserVO.java b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/activity/RankingUserVO.java new file mode 100644 index 00000000..a89efe7d --- /dev/null +++ b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/activity/RankingUserVO.java @@ -0,0 +1,51 @@ +package com.red.circle.other.app.dto.clientobject.activity; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 排名用户VO. + */ +@Data +@Accessors(chain = true) +public class RankingUserVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 用户ID. + */ + @JsonSerialize(using = ToStringSerializer.class) + private Long userId; + + /** + * 账号. + */ + private String account; + + /** + * 昵称. + */ + private String nickname; + + /** + * 头像. + */ + private String avatar; + + /** + * 排名. + */ + private Integer rank; + + /** + * 数量/分数. + */ + private String quantity; +} diff --git a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/cmd/activity/RankingListQueryCmd.java b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/cmd/activity/RankingListQueryCmd.java index 75eebb8c..cf431dde 100644 --- a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/cmd/activity/RankingListQueryCmd.java +++ b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/cmd/activity/RankingListQueryCmd.java @@ -57,4 +57,9 @@ public class RankingListQueryCmd extends AppExtCommand { * 是否包含当前用户排名 */ private Boolean includeCurrentUser = true; -} \ No newline at end of file + + /** + * 模板ID + */ + private String templateId; +} diff --git a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/service/activity/RankingActivityService.java b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/service/activity/RankingActivityService.java index 92383017..6c21b553 100644 --- a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/service/activity/RankingActivityService.java +++ b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/service/activity/RankingActivityService.java @@ -2,12 +2,15 @@ package com.red.circle.other.app.service.activity; import com.red.circle.other.app.dto.clientobject.activity.HistoricalTop1ListCO; import com.red.circle.other.app.dto.clientobject.activity.RankingListCO; +import com.red.circle.other.app.dto.clientobject.activity.RankingUserVO; import com.red.circle.other.app.dto.clientobject.activity.UserRankInfoCO; import com.red.circle.other.app.dto.cmd.UserRankQueryCmd; import com.red.circle.other.app.dto.cmd.activity.HistoricalTop1QueryCmd; import com.red.circle.other.app.dto.cmd.activity.RankingDataUpdateCmd; import com.red.circle.other.app.dto.cmd.activity.RankingListQueryCmd; +import java.util.List; + /** * 排行榜活动服务 */ @@ -44,4 +47,12 @@ public interface RankingActivityService { * @return 历史Top1列表(最近10周) */ HistoricalTop1ListCO getHistoricalTop1List(HistoricalTop1QueryCmd cmd); -} \ No newline at end of file + + /** + * 获取排行榜前四名(带缓存和奖励发送) + * + * @param cmd 查询命令 + * @return 排行榜前四名 + */ + List getTopFourRankingWithReward(RankingListQueryCmd cmd); +} diff --git a/rc-service/rc-service-other/other-domain/src/main/java/com/red/circle/other/domain/ranking/RankingActivityType.java b/rc-service/rc-service-other/other-domain/src/main/java/com/red/circle/other/domain/ranking/RankingActivityType.java index e78dc5a5..6292def5 100644 --- a/rc-service/rc-service-other/other-domain/src/main/java/com/red/circle/other/domain/ranking/RankingActivityType.java +++ b/rc-service/rc-service-other/other-domain/src/main/java/com/red/circle/other/domain/ranking/RankingActivityType.java @@ -44,6 +44,8 @@ public enum RankingActivityType { GREEDY_GAME(1068, "GreedyLion", "游戏中奖排行"), LUCK_GIFT(9999, "LuckGift", "统计送幸运礼物排行"), + + KING_GAMES(10, "游戏王活动", "统计游戏数据排行"), ; /** diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/ranking/RankingRewardSnapshot.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/ranking/RankingRewardSnapshot.java new file mode 100644 index 00000000..ec546341 --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/ranking/RankingRewardSnapshot.java @@ -0,0 +1,80 @@ +package com.red.circle.other.infra.database.mongo.entity.ranking; + +import lombok.Data; +import lombok.experimental.Accessors; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.CompoundIndex; +import org.springframework.data.mongodb.core.index.CompoundIndexes; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 排名奖励快照. + */ +@Data +@Accessors(chain = true) +@Document(collection = "ranking_reward_snapshot") +@CompoundIndexes({ + @CompoundIndex(name = "idx_activity_cycle", def = "{'activityType': 1, 'cycleKey': 1, 'origin': 1}"), + @CompoundIndex(name = "idx_snapshot_time", def = "{'snapshotTime': -1}") +}) +public class RankingRewardSnapshot implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Id + private String id; + + private String activityType; + + private String origin; + + private String cycleKey; + + private String templateId; + + private LocalDateTime snapshotTime; + + private List rankingUsers; + + @Data + @Accessors(chain = true) + public static class RankingUserSnapshot implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private Long userId; + + private Integer rank; + + private String rankRange; + + private Long score; + + private List rewards; + } + + @Data + @Accessors(chain = true) + public static class RewardItem implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private String type; + + private String detailType; + + private String content; + + private Integer quantity; + + private String badgeName; + } +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/ranking/RankingRewardSnapshotService.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/ranking/RankingRewardSnapshotService.java new file mode 100644 index 00000000..3c9ab7c1 --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/ranking/RankingRewardSnapshotService.java @@ -0,0 +1,53 @@ +package com.red.circle.other.infra.database.mongo.service.ranking; + +import com.red.circle.other.infra.database.mongo.entity.ranking.RankingRewardSnapshot; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; + +/** + * 排名奖励快照服务. + */ +@Service +@RequiredArgsConstructor +public class RankingRewardSnapshotService { + + private final MongoTemplate mongoTemplate; + + /** + * 保存快照. + */ + public void save(RankingRewardSnapshot snapshot) { + mongoTemplate.save(snapshot); + } + + /** + * 获取最新快照. + */ + public RankingRewardSnapshot getLatestSnapshot(String activityType, String origin, String cycleKey) { + Query query = new Query(); + query.addCriteria(Criteria.where("activityType").is(activityType) + .and("origin").is(origin) + .and("cycleKey").is(cycleKey)); + query.with(org.springframework.data.domain.Sort.by( + org.springframework.data.domain.Sort.Direction.DESC, "snapshotTime")); + query.limit(1); + + return mongoTemplate.findOne(query, RankingRewardSnapshot.class); + } + + /** + * 删除旧快照. + */ + public void deleteOldSnapshots(String activityType, String origin, String cycleKey) { + Query query = new Query(); + query.addCriteria(Criteria.where("activityType").is(activityType) + .and("origin").is(origin) + .and("cycleKey").is(cycleKey)); + + mongoTemplate.remove(query, RankingRewardSnapshot.class); + } +}