新增发送cp榜单奖励定时任务

This commit is contained in:
tianfeng 2026-01-14 16:30:52 +08:00
parent 96bcc0cf7b
commit 1ab0d93bd5
5 changed files with 478 additions and 0 deletions

View File

@ -276,6 +276,11 @@ public enum SendPropsOrigin {
LUCK_DOLLAR_REWARD("Luck Dollar Rewards"),
/**
* 每赛季CP相互赠送礼物榜单.
*/
SEASON_CP_GIFT(""),
;
private final String desc;

View File

@ -742,6 +742,11 @@ public enum GoldOrigin {
* CP告白信封
*/
CP_LOVE_LETTER("CP love letter"),
/**
* cp榜单(周榜和季度榜)
*/
CP_RANKING_REWARD("CP ranking reward"),
;
private final String desc;

View File

@ -0,0 +1,290 @@
package com.red.circle.other.app.manager.activity.award;
import com.google.common.collect.Lists;
import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
import com.red.circle.common.business.enums.SendPropsOrigin;
import com.red.circle.other.infra.common.activity.PropsActivitySendCommon;
import com.red.circle.other.infra.common.activity.send.SendRewardGroup;
import com.red.circle.other.infra.database.mongo.entity.user.count.WeekCpValueCount;
import com.red.circle.other.infra.database.mongo.service.user.count.WeekCpValueCountService;
import com.red.circle.other.infra.database.rds.entity.activity.PropsActivityRuleConfig;
import com.red.circle.other.infra.database.rds.service.activity.PropsActivityRuleConfigService;
import com.red.circle.other.infra.utils.SeasonUtils;
import com.red.circle.other.inner.enums.activity.PropsActivityTypeEnum;
import com.red.circle.tool.core.collection.CollectionUtils;
import com.red.circle.tool.core.date.DateFormatConstant;
import com.red.circle.tool.core.date.ZonedDateTimeAsiaRiyadhUtils;
import com.red.circle.tool.core.date.ZonedDateTimeUtils;
//import com.red.circle.wallet.client.gold.WalletGoldClient;
//import com.red.circle.wallet.inner.cmd.GoldReceiptCmd;
//import com.red.circle.wallet.inner.enums.GoldOrigin;
//import com.red.circle.wallet.inner.model.PennyAmount;
import com.red.circle.tool.core.tuple.PennyAmount;
import com.red.circle.wallet.inner.endpoint.wallet.WalletGoldClient;
import com.red.circle.wallet.inner.model.cmd.GoldReceiptCmd;
import com.red.circle.wallet.inner.model.enums.GoldOrigin;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* CP排行榜奖励发送管理器.
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class CpRankingRewardManager {
private final WeekCpValueCountService weekCpValueCountService;
private final PropsActivityRuleConfigService propsActivityRuleConfigService;
private final PropsActivitySendCommon sendPropsManager;
private final WalletGoldClient walletGoldClient;
private final StringRedisTemplate stringRedisTemplate;
/**
* 发送上周周榜前三名奖励
*/
@Transactional(rollbackFor = Exception.class)
public void sendLastWeekTopReward(SysOriginPlatformEnum sysOrigin) {
String lastWeekDate = getLastWeekDate();
log.info("[CP周榜] 开始发送上周周榜前三奖励, 系统平台: {}, 上周日期: {}",
sysOrigin.name(), lastWeekDate);
try {
// 1. 获取上周前三名
List<WeekCpValueCount> lastWeekTop3 = weekCpValueCountService
.listLastWeekTop(sysOrigin.name(), 3);
if (CollectionUtils.isEmpty(lastWeekTop3)) {
log.warn("[CP周榜] 上周前三不存在, 系统平台: {}", sysOrigin.name());
return;
}
// 2. 获取周榜奖励配置
List<PropsActivityRuleConfig> rewardConfigs = propsActivityRuleConfigService
.listPlatformByActivityType(sysOrigin.getSysOrigin(), PropsActivityTypeEnum.WEEK_CP_GIFT);
if (CollectionUtils.isEmpty(rewardConfigs) || rewardConfigs.size() < 3) {
log.error("[CP周榜] 奖励配置不足3个, 系统平台: {}, 实际数量: {}",
sysOrigin.name(), rewardConfigs == null ? 0 : rewardConfigs.size());
return;
}
// 3. 发放奖励
for (int rank = 0; rank < Math.min(lastWeekTop3.size(), 3); rank++) {
WeekCpValueCount cpCount = lastWeekTop3.get(rank);
PropsActivityRuleConfig rewardConfig = rewardConfigs.get(rank);
// 发送奖励
sendWeekCpReward(sysOrigin, cpCount, rewardConfig, rank + 1, lastWeekDate);
}
log.info("[CP周榜] 上周周榜前三奖励发放完成, 系统平台: {}", sysOrigin.name());
} catch (Exception e) {
log.error("[CP周榜] 发送上周周榜奖励异常, 系统平台: {}", sysOrigin.name(), e);
throw e;
}
}
/**
* 发送上赛季前三名奖励
*/
@Transactional(rollbackFor = Exception.class)
public void sendLastSeasonTopReward(SysOriginPlatformEnum sysOrigin) {
String lastSeasonCycleKey = SeasonUtils.getLastSeasonCycleKey();
log.info("[CP赛季榜] 开始发送上赛季前三奖励, 系统平台: {}, 上赛季: {}",
sysOrigin.name(), lastSeasonCycleKey);
try {
// 1. 获取上赛季前三名
List<WeekCpValueCount> lastSeasonTop3 = weekCpValueCountService
.listLastSeasonTop(sysOrigin.name(), 3);
if (CollectionUtils.isEmpty(lastSeasonTop3)) {
log.warn("[CP赛季榜] 上赛季前三不存在, 系统平台: {}", sysOrigin.name());
return;
}
// 2. 获取赛季榜奖励配置
List<PropsActivityRuleConfig> rewardConfigs = propsActivityRuleConfigService
.listPlatformByActivityType(sysOrigin.getSysOrigin(), PropsActivityTypeEnum.SEASON_CP_GIFT);
if (CollectionUtils.isEmpty(rewardConfigs) || rewardConfigs.size() < 3) {
log.error("[CP赛季榜] 奖励配置不足3个, 系统平台: {}, 实际数量: {}",
sysOrigin.name(), rewardConfigs == null ? 0 : rewardConfigs.size());
return;
}
// 3. 发放奖励
for (int rank = 0; rank < Math.min(lastSeasonTop3.size(), 3); rank++) {
WeekCpValueCount cpCount = lastSeasonTop3.get(rank);
PropsActivityRuleConfig rewardConfig = rewardConfigs.get(rank);
// 发送奖励
sendSeasonCpReward(sysOrigin, cpCount, rewardConfig, rank + 1, lastSeasonCycleKey);
}
log.info("[CP赛季榜] 上赛季前三奖励发放完成, 系统平台: {}", sysOrigin.name());
} catch (Exception e) {
log.error("[CP赛季榜] 发送上赛季奖励异常, 系统平台: {}", sysOrigin.name(), e);
throw e;
}
}
/**
* 发送周榜CP奖励
*/
private void sendWeekCpReward(SysOriginPlatformEnum sysOrigin,
WeekCpValueCount cpCount,
PropsActivityRuleConfig rewardConfig,
int rank,
String weekDate) {
ImmutablePair<Long, Long> userPair = weekCpValueCountService.parseUserIdPair(cpCount.getId());
Long userOne = userPair.getLeft();
Long userTwo = userPair.getRight();
// 计算金币奖励
double[] percentages = {0.04, 0.03, 0.02};
double percentage = percentages[rank - 1];
long goldReward = (long) (cpCount.getQuantity() * percentage);
// 发送给用户1
String eventIdOne = String.format("WK_CP_RW_%s_%d_%d", weekDate, userOne, rank);
if (checkAndSetIdempotent(eventIdOne, 15)) {
sendGoldAndProps(sysOrigin, userOne, goldReward, rewardConfig.getResourceGroupId(),
rewardConfig.getId(), SendPropsOrigin.WEEK_CP_GIFT, eventIdOne);
log.info("[CP周榜] 排名 {} 用户 {} 奖励发放成功, 金币: {}, 道具组: {}",
rank, userOne, goldReward, rewardConfig.getResourceGroupId());
}
// 发送给用户2
String eventIdTwo = String.format("WK_CP_RW_%s_%d_%d", weekDate, userTwo, rank);
if (checkAndSetIdempotent(eventIdTwo, 15)) {
sendGoldAndProps(sysOrigin, userTwo, goldReward, rewardConfig.getResourceGroupId(),
rewardConfig.getId(), SendPropsOrigin.WEEK_CP_GIFT, eventIdTwo);
log.info("[CP周榜] 排名 {} 用户 {} 奖励发放成功, 金币: {}, 道具组: {}",
rank, userTwo, goldReward, rewardConfig.getResourceGroupId());
}
}
/**
* 发送赛季榜CP奖励
*/
private void sendSeasonCpReward(SysOriginPlatformEnum sysOrigin,
WeekCpValueCount cpCount,
PropsActivityRuleConfig rewardConfig,
int rank,
String seasonCycleKey) {
ImmutablePair<Long, Long> userPair = weekCpValueCountService.parseUserIdPair(cpCount.getId());
Long userOne = userPair.getLeft();
Long userTwo = userPair.getRight();
// 赛季榜直接发送resourceGroupId奖励不计算金币
// 发送给用户1
String eventIdOne = String.format("SN_CP_RW_%s_%d_%d", seasonCycleKey, userOne, rank);
if (checkAndSetIdempotent(eventIdOne, 60)) {
sendProps(sysOrigin, userOne, rewardConfig.getResourceGroupId(),
rewardConfig.getId(), SendPropsOrigin.SEASON_CP_GIFT);
log.info("[CP赛季榜] 排名 {} 用户 {} 奖励发放成功, 道具组: {}",
rank, userOne, rewardConfig.getResourceGroupId());
}
// 发送给用户2
String eventIdTwo = String.format("SN_CP_RW_%s_%d_%d", seasonCycleKey, userTwo, rank);
if (checkAndSetIdempotent(eventIdTwo, 60)) {
sendProps(sysOrigin, userTwo, rewardConfig.getResourceGroupId(),
rewardConfig.getId(), SendPropsOrigin.SEASON_CP_GIFT);
log.info("[CP赛季榜] 排名 {} 用户 {} 奖励发放成功, 道具组: {}",
rank, userTwo, rewardConfig.getResourceGroupId());
}
}
/**
* 发送金币和道具
*/
private void sendGoldAndProps(SysOriginPlatformEnum sysOrigin,
Long userId,
long goldAmount,
Long resourceGroupId,
Long trackId,
SendPropsOrigin origin,
String eventId) {
// 发送金币
addUserGold(userId, sysOrigin.name(), goldAmount, eventId);
// 发送道具
sendProps(sysOrigin, userId, resourceGroupId, trackId, origin);
}
/**
* 发送道具
*/
private void sendProps(SysOriginPlatformEnum sysOrigin,
Long userId,
Long resourceGroupId,
Long trackId,
SendPropsOrigin origin) {
sendPropsManager.sendActivityGroup(SendRewardGroup.builder()
.trackId(trackId)
.sysOrigin(sysOrigin)
.acceptUserId(userId)
.resourceGroupId(resourceGroupId)
.origin(origin)
.build());
}
/**
* 增加用户金币
*/
private void addUserGold(Long userId, String sysOrigin, Long amount, String eventId) {
GoldReceiptCmd build = GoldReceiptCmd.builder()
.appIncome()
.userId(userId)
.amount(PennyAmount.ofDollar(amount))
.eventId(eventId)
.sysOrigin(sysOrigin)
.origin(GoldOrigin.CP_RANKING_REWARD)
.build();
try {
walletGoldClient.changeBalance(build);
} catch (Exception e) {
log.error("增加金币失败 userId={} amount={} eventId={}", userId, amount, eventId, e);
throw new RuntimeException("Failed to increase gold coins");
}
}
/**
* 幂等性检查
*/
private boolean checkAndSetIdempotent(String eventId, int expireDays) {
String key = "CP_REWARD_IDEMPOTENT:" + eventId;
Boolean result = stringRedisTemplate.opsForValue()
.setIfAbsent(key, "1", expireDays, TimeUnit.DAYS);
if (Boolean.FALSE.equals(result)) {
log.warn("奖励已发放, 跳过重复发送, eventId={}", eventId);
return false;
}
return true;
}
/**
* 获取上周一日期
*/
private String getLastWeekDate() {
return ZonedDateTimeUtils.format(
ZonedDateTimeAsiaRiyadhUtils.getLastWeekMonday(),
DateFormatConstant.yyyyMMdd
);
}
}

View File

@ -0,0 +1,119 @@
package com.red.circle.other.app.scheduler;
import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
import com.red.circle.component.redis.annotation.TaskCacheLock;
import com.red.circle.other.app.manager.activity.award.CpRankingRewardManager;
import com.red.circle.other.infra.utils.SeasonUtils;
import com.red.circle.tool.core.date.LocalDateTimeUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* CP排行榜奖励发放定时任务.
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class CpRankingRewardTask {
private final CpRankingRewardManager cpRankingRewardManager;
private final StringRedisTemplate stringRedisTemplate;
private static final String LAST_SEASON_KEY = "CP_REWARD:LAST_SEASON_CYCLE";
/**
* 每周一 0点10秒执行 - 发送上周周榜前三奖励
*/
@Scheduled(cron = "10 0 0 ? * MON", zone = "Asia/Riyadh")
@TaskCacheLock(key = "CP_WEEK_RANKING_REWARD_TASK", expireSecond = 86400)
public void sendWeekRankingReward() {
long startTime = System.currentTimeMillis();
log.warn("========== CP周榜奖励发放定时任务开始 ==========");
try {
List<SysOriginPlatformEnum> platforms = List.of(SysOriginPlatformEnum.LIKEI);
int successCount = 0;
int errorCount = 0;
for (SysOriginPlatformEnum platform : platforms) {
try {
cpRankingRewardManager.sendLastWeekTopReward(platform);
successCount++;
log.info("[CP周榜] 平台 {} 奖励发放成功", platform.name());
} catch (Exception e) {
errorCount++;
log.error("[CP周榜] 平台 {} 奖励发放失败", platform.name(), e);
}
}
log.warn("========== CP周榜奖励发放定时任务完成, 耗时: {} ms, 成功: {}, 失败: {} ==========",
System.currentTimeMillis() - startTime, successCount, errorCount);
} catch (Exception e) {
log.error("========== CP周榜奖励发放定时任务异常 ==========", e);
}
}
/**
* 每天 0点0秒执行 - 检查赛季是否切换如果切换则发送上赛季前三奖励
*/
@Scheduled(cron = "0 0 0 * * ?", zone = "Asia/Riyadh")
@TaskCacheLock(key = "CP_SEASON_RANKING_REWARD_TASK", expireSecond = 86400)
public void sendSeasonRankingReward() {
long startTime = System.currentTimeMillis();
log.info("========== CP赛季榜奖励发放检查开始 ==========");
try {
// 获取当前赛季cycleKey
String currentSeasonCycleKey = SeasonUtils.getCurrentSeasonCycleKey();
// 获取上次执行时的赛季cycleKey
String lastSeasonCycleKey = stringRedisTemplate.opsForValue().get(LAST_SEASON_KEY);
log.info("[CP赛季榜] 当前赛季: {}, 上次记录赛季: {}", currentSeasonCycleKey, lastSeasonCycleKey);
// 如果赛季切换了发放上赛季奖励
if (lastSeasonCycleKey != null && !lastSeasonCycleKey.equals(currentSeasonCycleKey)) {
log.warn("========== 检测到赛季切换,开始发放上赛季奖励 ==========");
log.warn("[CP赛季榜] 赛季切换: {} -> {}", lastSeasonCycleKey, currentSeasonCycleKey);
List<SysOriginPlatformEnum> platforms = List.of(SysOriginPlatformEnum.LIKEI);
int successCount = 0;
int errorCount = 0;
for (SysOriginPlatformEnum platform : platforms) {
try {
cpRankingRewardManager.sendLastSeasonTopReward(platform);
successCount++;
log.info("[CP赛季榜] 平台 {} 奖励发放成功", platform.name());
} catch (Exception e) {
errorCount++;
log.error("[CP赛季榜] 平台 {} 奖励发放失败", platform.name(), e);
}
}
log.warn("========== CP赛季榜奖励发放完成, 耗时: {} ms, 成功: {}, 失败: {} ==========",
System.currentTimeMillis() - startTime, successCount, errorCount);
} else {
log.info("[CP赛季榜] 赛季未切换,无需发放奖励");
}
// 更新缓存的赛季cycleKey无论是否切换都更新确保下次能正确判断
stringRedisTemplate.opsForValue().set(LAST_SEASON_KEY, currentSeasonCycleKey, 90, TimeUnit.DAYS);
log.info("========== CP赛季榜奖励发放检查完成, 总耗时: {} ms ==========",
System.currentTimeMillis() - startTime);
} catch (Exception e) {
log.error("========== CP赛季榜奖励发放检查异常 ==========", e);
}
}
}

View File

@ -0,0 +1,59 @@
package activity;
import com.red.circle.OtherServiceApplication;
import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
import com.red.circle.other.app.manager.activity.award.CpRankingRewardManager;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* CP排行榜奖励测试类.
*/
@Slf4j
@SpringBootTest(classes = OtherServiceApplication.class)
@RunWith(SpringRunner.class)
public class CpRankingRewardTest {
@Autowired
private CpRankingRewardManager cpRankingRewardManager;
/**
* 测试发送上周周榜前三奖励
*/
@Test
public void testSendLastWeekTopReward() {
log.info("========== 开始测试发送上周周榜前三奖励 ==========");
try {
SysOriginPlatformEnum platform = SysOriginPlatformEnum.LIKEI;
cpRankingRewardManager.sendLastWeekTopReward(platform);
log.info("========== 上周周榜前三奖励发送测试完成 ==========");
} catch (Exception e) {
log.error("========== 上周周榜前三奖励发送测试异常 ==========", e);
}
}
/**
* 测试发送上赛季前三奖励
*/
@Test
public void testSendLastSeasonTopReward() {
log.info("========== 开始测试发送上赛季前三奖励 ==========");
try {
SysOriginPlatformEnum platform = SysOriginPlatformEnum.LIKEI;
cpRankingRewardManager.sendLastSeasonTopReward(platform);
log.info("========== 上赛季前三奖励发送测试完成 ==========");
} catch (Exception e) {
log.error("========== 上赛季前三奖励发送测试异常 ==========", e);
}
}
}