From 4d859fc2448d7a5cc55daa3ab444fbf24c0d7ad4 Mon Sep 17 00:00:00 2001 From: zhx Date: Mon, 6 Jul 2026 19:29:59 +0800 Subject: [PATCH] fix: send weekly room rewards to owners --- .../RoomContributionWeeklyRewardTask.java | 115 +++++++++++------- .../RoomContributionWeeklyRewardTaskTest.java | 112 +++++++++++++++++ .../RoomContributionActivityCount.java | 30 +++++ .../RoomContributionActivityCountService.java | 22 ++++ ...mContributionActivityCountServiceImpl.java | 72 +++++++++++ 5 files changed, 304 insertions(+), 47 deletions(-) create mode 100644 rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/scheduler/RoomContributionWeeklyRewardTaskTest.java diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/RoomContributionWeeklyRewardTask.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/RoomContributionWeeklyRewardTask.java index 9dc1e25d..76563b97 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/RoomContributionWeeklyRewardTask.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/RoomContributionWeeklyRewardTask.java @@ -2,15 +2,18 @@ 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.framework.core.dto.ReqSysOrigin; -import com.red.circle.other.app.command.redpacket.SendRoomRedPacketCmdExe; -import com.red.circle.other.app.dto.cmd.SendRoomRedPacketCmd; +import com.red.circle.framework.dto.ResultResponse; import com.red.circle.other.app.enums.RoomRewardLevelEnum; import com.red.circle.other.infra.database.mongo.entity.activity.RoomContributionActivityCount; import com.red.circle.other.infra.database.mongo.entity.live.RoomProfileManager; import com.red.circle.other.infra.database.mongo.service.activity.RoomContributionActivityCountService; import com.red.circle.other.infra.database.mongo.service.live.RoomProfileManagerService; import com.red.circle.tool.core.collection.CollectionUtils; +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.dto.WalletReceiptResDTO; +import com.red.circle.wallet.inner.model.enums.GoldOrigin; import java.math.BigDecimal; import java.util.Arrays; import java.util.Collections; @@ -35,21 +38,14 @@ import org.springframework.stereotype.Component; matchIfMissing = true) public class RoomContributionWeeklyRewardTask { - private static final Integer DEFAULT_EXPIRE_MINUTES = 10; - private static final Integer DEFAULT_TOTAL_COUNT = 10; + private static final String OWNER_REWARD_EVENT_PREFIX = "ROOM_CONTRIBUTION_WEEKLY_OWNER_REWARD:"; private static final BigDecimal DEFAULT_MIN_CONTRIBUTION = BigDecimal.valueOf(100000); private static final BigDecimal TARAB_MIN_CONTRIBUTION = BigDecimal.valueOf(150000); - private final SendRoomRedPacketCmdExe sendRoomRedPacketCmdExe; + private final WalletGoldClient walletGoldClient; private final RoomProfileManagerService roomProfileManagerService; private final RoomContributionActivityCountService roomContributionActivityCountService; - @Value("${activity.room-contribution.weekly-reward.expire-minutes:10}") - private Integer expireMinutes; - - @Value("${activity.room-contribution.weekly-reward.total-count:10}") - private Integer totalCount; - @Value("${activity.room-contribution.weekly-reward.repair-enabled:true}") private Boolean repairEnabled; @@ -80,12 +76,12 @@ public class RoomContributionWeeklyRewardTask { } private void sendLastWeekRoomContributionRewards(String trigger) { - log.warn("[RoomContributionWeeklyReward] start trigger={}", trigger); + log.warn("[RoomContributionWeeklyOwnerReward] start trigger={}", trigger); List list = - roomContributionActivityCountService.listLastWeekRewardUnsentData(); + roomContributionActivityCountService.listLastWeekOwnerRewardUnsentData(); if (CollectionUtils.isEmpty(list)) { - log.warn("[RoomContributionWeeklyReward] no unsent reward data trigger={}", trigger); + log.warn("[RoomContributionWeeklyOwnerReward] no unsent reward data trigger={}", trigger); return; } @@ -98,12 +94,12 @@ public class RoomContributionWeeklyRewardTask { } } catch (Exception e) { fail++; - log.error("[RoomContributionWeeklyReward] send failed roomId={} dataId={} amount={}", + log.error("[RoomContributionWeeklyOwnerReward] send failed roomId={} dataId={} amount={}", data.getRoomId(), data.getId(), data.getContributionValue(), e); } } - log.warn("[RoomContributionWeeklyReward] end trigger={} total={} success={} fail={}", + log.warn("[RoomContributionWeeklyOwnerReward] end trigger={} total={} success={} fail={}", trigger, list.size(), success, fail); } @@ -115,20 +111,20 @@ public class RoomContributionWeeklyRewardTask { RoomProfileManager room = roomProfileManagerService.getById(data.getRoomId()); SysOriginPlatformEnum sysOrigin = getSysOrigin(room); - if (Objects.isNull(room) || Objects.isNull(sysOrigin)) { - log.warn("[RoomContributionWeeklyReward] skip invalid room roomId={} dataId={}", + if (Objects.isNull(room) || Objects.isNull(room.getUserId()) || Objects.isNull(sysOrigin)) { + log.warn("[RoomContributionWeeklyOwnerReward] skip invalid room roomId={} dataId={}", data.getRoomId(), data.getId()); return false; } if (!getEnabledSysOrigins().contains(sysOrigin)) { - log.warn("[RoomContributionWeeklyReward] skip disabled sysOrigin roomId={} dataId={} sysOrigin={}", + log.warn("[RoomContributionWeeklyOwnerReward] skip disabled sysOrigin roomId={} dataId={} sysOrigin={}", data.getRoomId(), data.getId(), sysOrigin); return false; } if (!meetsPlatformThreshold(sysOrigin, data.getContributionValue())) { - log.warn("[RoomContributionWeeklyReward] skip threshold roomId={} dataId={} sysOrigin={} amount={}", + log.warn("[RoomContributionWeeklyOwnerReward] skip threshold roomId={} dataId={} sysOrigin={} amount={}", data.getRoomId(), data.getId(), sysOrigin, data.getContributionValue()); return false; } @@ -139,22 +135,62 @@ public class RoomContributionWeeklyRewardTask { return false; } - SendRoomRedPacketCmd cmd = new SendRoomRedPacketCmd(); - cmd.setRoomId(data.getRoomId()); - cmd.setTotalAmount(level.getRewardCoins()); - cmd.setTotalCount(getTotalCount(level.getRewardCoins())); - cmd.setExpireMinutes(getExpireMinutes()); - cmd.setReqUserId(room.getUserId()); - cmd.setReqSysOrigin(ReqSysOrigin.of(sysOrigin.name())); - - if (Objects.isNull(sendRoomRedPacketCmdExe.executeRoomContributionReward(cmd, data.getId()))) { + String eventId = buildOwnerRewardEventId(data.getId()); + if (!roomContributionActivityCountService.markOwnerRewardCoinsSending(data.getId(), eventId)) { + log.warn("[RoomContributionWeeklyOwnerReward] skip sending occupied roomId={} dataId={} eventId={}", + data.getRoomId(), data.getId(), eventId); return false; } - log.info("[RoomContributionWeeklyReward] sent roomId={} dataId={} level={} rewardCoins={}", - data.getRoomId(), data.getId(), level.getLevel(), level.getRewardCoins()); + + try { + if (!ownerRewardEventExists(eventId)) { + addOwnerGold(room.getUserId(), sysOrigin, level.getRewardCoins(), eventId); + } + if (!roomContributionActivityCountService.markOwnerRewardCoinsSent(data.getId(), eventId)) { + log.warn("[RoomContributionWeeklyOwnerReward] mark sent skipped roomId={} ownerId={} dataId={} eventId={}", + data.getRoomId(), room.getUserId(), data.getId(), eventId); + } + } catch (Exception e) { + roomContributionActivityCountService.resetOwnerRewardCoinsSending(data.getId(), eventId, + e.getMessage()); + throw e; + } + + log.info("[RoomContributionWeeklyOwnerReward] sent roomId={} ownerId={} dataId={} level={} rewardCoins={} eventId={}", + data.getRoomId(), room.getUserId(), data.getId(), level.getLevel(), level.getRewardCoins(), + eventId); return true; } + private String buildOwnerRewardEventId(String dataId) { + return OWNER_REWARD_EVENT_PREFIX + dataId; + } + + private boolean ownerRewardEventExists(String eventId) { + ResultResponse response = walletGoldClient.existsEventId(eventId); + if (Objects.isNull(response) || !response.checkSuccess()) { + throw new IllegalStateException("check owner reward event failed: " + eventId); + } + return Boolean.TRUE.equals(response.getBody()); + } + + private void addOwnerGold(Long userId, SysOriginPlatformEnum sysOrigin, Long rewardCoins, + String eventId) { + GoldReceiptCmd cmd = GoldReceiptCmd.builder() + .appIncome() + .userId(userId) + .amount(PennyAmount.ofDollar(rewardCoins)) + .eventId(eventId) + .sysOrigin(sysOrigin) + .origin(GoldOrigin.ROOM_CONTRIBUTION_REWARD) + .build(); + + ResultResponse response = walletGoldClient.changeBalance(cmd); + if (Objects.isNull(response) || !response.checkSuccess()) { + throw new IllegalStateException("send owner reward gold failed: " + eventId); + } + } + private SysOriginPlatformEnum getSysOrigin(RoomProfileManager room) { if (Objects.isNull(room) || Objects.isNull(room.getSysOrigin())) { return null; @@ -184,19 +220,4 @@ public class RoomContributionWeeklyRewardTask { .collect(Collectors.toSet()); } - private Integer getExpireMinutes() { - if (Objects.isNull(expireMinutes) || !List.of(1, 3, 5, 10).contains(expireMinutes)) { - return DEFAULT_EXPIRE_MINUTES; - } - return expireMinutes; - } - - private Integer getTotalCount(Long rewardCoins) { - int count = Objects.isNull(totalCount) ? DEFAULT_TOTAL_COUNT : totalCount; - count = Math.max(1, Math.min(count, 100)); - if (Objects.nonNull(rewardCoins)) { - count = Math.min(count, rewardCoins.intValue()); - } - return count; - } } diff --git a/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/scheduler/RoomContributionWeeklyRewardTaskTest.java b/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/scheduler/RoomContributionWeeklyRewardTaskTest.java new file mode 100644 index 00000000..788492e9 --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/scheduler/RoomContributionWeeklyRewardTaskTest.java @@ -0,0 +1,112 @@ +package com.red.circle.other.app.scheduler; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.red.circle.common.business.core.enums.SysOriginPlatformEnum; +import com.red.circle.framework.dto.ResultResponse; +import com.red.circle.other.infra.database.mongo.entity.activity.RoomContributionActivityCount; +import com.red.circle.other.infra.database.mongo.entity.live.RoomProfileManager; +import com.red.circle.other.infra.database.mongo.service.activity.RoomContributionActivityCountService; +import com.red.circle.other.infra.database.mongo.service.live.RoomProfileManagerService; +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.dto.WalletReceiptResDTO; +import com.red.circle.wallet.inner.model.enums.GoldOrigin; +import java.math.BigDecimal; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; + +@ExtendWith(MockitoExtension.class) +class RoomContributionWeeklyRewardTaskTest { + + private static final String DATA_ID = "2071532202565136385_20260629"; + private static final String EVENT_ID = "ROOM_CONTRIBUTION_WEEKLY_OWNER_REWARD:" + DATA_ID; + private static final Long ROOM_ID = 2071532202565136385L; + private static final Long OWNER_ID = 2071520380357021697L; + + @Mock + private WalletGoldClient walletGoldClient; + @Mock + private RoomProfileManagerService roomProfileManagerService; + @Mock + private RoomContributionActivityCountService roomContributionActivityCountService; + + private RoomContributionWeeklyRewardTask task; + + @BeforeEach + void setUp() { + task = new RoomContributionWeeklyRewardTask(walletGoldClient, roomProfileManagerService, + roomContributionActivityCountService); + ReflectionTestUtils.setField(task, "enabledSysOrigins", "ATYOU"); + } + + @Test + void shouldSendWeeklyRewardToRoomOwnerDirectly() { + when(roomContributionActivityCountService.listLastWeekOwnerRewardUnsentData()) + .thenReturn(List.of(activityCount())); + when(roomProfileManagerService.getById(ROOM_ID)).thenReturn(room()); + when(roomContributionActivityCountService.markOwnerRewardCoinsSending(DATA_ID, EVENT_ID)) + .thenReturn(true); + when(walletGoldClient.existsEventId(EVENT_ID)).thenReturn(ResultResponse.success(false)); + when(walletGoldClient.changeBalance(any(GoldReceiptCmd.class))) + .thenReturn(ResultResponse.success(new WalletReceiptResDTO())); + when(roomContributionActivityCountService.markOwnerRewardCoinsSent(DATA_ID, EVENT_ID)) + .thenReturn(true); + + task.sendLastWeekRoomContributionRewards(); + + ArgumentCaptor captor = ArgumentCaptor.forClass(GoldReceiptCmd.class); + verify(walletGoldClient).changeBalance(captor.capture()); + GoldReceiptCmd cmd = captor.getValue(); + assertEquals(OWNER_ID, cmd.getUserId()); + assertEquals(SysOriginPlatformEnum.ATYOU, cmd.getSysOrigin()); + assertEquals(EVENT_ID, cmd.getEventId()); + assertEquals(GoldOrigin.ROOM_CONTRIBUTION_REWARD, cmd.getOrigin()); + assertEquals(25000000L, cmd.getAmount().getPennyAmount()); + verify(roomContributionActivityCountService).markOwnerRewardCoinsSent(DATA_ID, EVENT_ID); + } + + @Test + void shouldOnlyMarkSentWhenWalletEventAlreadyExists() { + when(roomContributionActivityCountService.listLastWeekOwnerRewardUnsentData()) + .thenReturn(List.of(activityCount())); + when(roomProfileManagerService.getById(ROOM_ID)).thenReturn(room()); + when(roomContributionActivityCountService.markOwnerRewardCoinsSending(DATA_ID, EVENT_ID)) + .thenReturn(true); + when(walletGoldClient.existsEventId(EVENT_ID)).thenReturn(ResultResponse.success(true)); + when(roomContributionActivityCountService.markOwnerRewardCoinsSent(DATA_ID, EVENT_ID)) + .thenReturn(true); + + task.sendLastWeekRoomContributionRewards(); + + verify(walletGoldClient, never()).changeBalance(any(GoldReceiptCmd.class)); + verify(roomContributionActivityCountService).markOwnerRewardCoinsSent(DATA_ID, EVENT_ID); + } + + private RoomContributionActivityCount activityCount() { + RoomContributionActivityCount count = new RoomContributionActivityCount(); + count.setId(DATA_ID); + count.setRoomId(ROOM_ID); + count.setContributionValue(new BigDecimal("20777706")); + count.setRewardCoinsSent(Boolean.TRUE); + return count; + } + + private RoomProfileManager room() { + RoomProfileManager room = new RoomProfileManager(); + room.setId(ROOM_ID); + room.setUserId(OWNER_ID); + room.setSysOrigin(SysOriginPlatformEnum.ATYOU.name()); + return room; + } +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/activity/RoomContributionActivityCount.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/activity/RoomContributionActivityCount.java index ff4512af..4d88ad95 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/activity/RoomContributionActivityCount.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/activity/RoomContributionActivityCount.java @@ -87,6 +87,36 @@ public class RoomContributionActivityCount extends Convert implements Serializab */ String rewardCoinsSendError; + /** + * 房主奖励金币是否已直发. + */ + Boolean ownerRewardCoinsSent; + + /** + * 房主奖励金币是否直发中. + */ + Boolean ownerRewardCoinsSending; + + /** + * 房主奖励金币直发事件ID. + */ + String ownerRewardCoinsEventId; + + /** + * 房主奖励金币直发时间. + */ + Timestamp ownerRewardCoinsSentTime; + + /** + * 房主奖励金币直发中时间. + */ + Timestamp ownerRewardCoinsSendingTime; + + /** + * 房主奖励金币直发失败原因. + */ + String ownerRewardCoinsSendError; + /** * 比例. */ diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/activity/RoomContributionActivityCountService.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/activity/RoomContributionActivityCountService.java index ba97fa68..55688741 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/activity/RoomContributionActivityCountService.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/activity/RoomContributionActivityCountService.java @@ -126,6 +126,28 @@ public interface RoomContributionActivityCountService { */ List listLastWeekRewardUnsentData(); + /** + * 获取所有房间上周贡献数据(房主奖励未直发状态). + * + * @return 所有房间上周贡献数据列表 + */ + List listLastWeekOwnerRewardUnsentData(); + + /** + * 抢占房主奖励金币直发记录. + */ + boolean markOwnerRewardCoinsSending(String id, String eventId); + + /** + * 标记房主奖励金币已直发. + */ + boolean markOwnerRewardCoinsSent(String id, String eventId); + + /** + * 释放房主奖励金币直发中状态,等待重试. + */ + void resetOwnerRewardCoinsSending(String id, String eventId, String reason); + /** * 根据房间ID和日期查询数据 * diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/activity/impl/RoomContributionActivityCountServiceImpl.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/activity/impl/RoomContributionActivityCountServiceImpl.java index 78ade878..8e0b9e4d 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/activity/impl/RoomContributionActivityCountServiceImpl.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/activity/impl/RoomContributionActivityCountServiceImpl.java @@ -359,6 +359,62 @@ public class RoomContributionActivityCountServiceImpl implements return mongoTemplate.find(Query.query(criteria), RoomContributionActivityCount.class); } + @Override + public List listLastWeekOwnerRewardUnsentData() { + Criteria criteria = new Criteria().andOperator( + Criteria.where("dateNumber").is(getLastWeekDateNumber()), + Criteria.where("contributionValue").gte(integralMin), + unsentOwnerRewardCriteria(), + ownerRewardNotSendingOrExpiredCriteria() + ); + + return mongoTemplate.find(Query.query(criteria), RoomContributionActivityCount.class); + } + + @Override + public boolean markOwnerRewardCoinsSending(String id, String eventId) { + Query query = Query.query(new Criteria().andOperator( + Criteria.where("_id").is(id), + unsentOwnerRewardCriteria(), + ownerRewardNotSendingOrExpiredCriteria() + )); + Update update = new Update() + .set("ownerRewardCoinsSending", Boolean.TRUE) + .set("ownerRewardCoinsEventId", eventId) + .set("ownerRewardCoinsSendingTime", TimestampUtils.now()) + .unset("ownerRewardCoinsSendError"); + UpdateResult result = mongoTemplate.updateFirst(query, update, + RoomContributionActivityCount.class); + return result.getModifiedCount() > 0; + } + + @Override + public boolean markOwnerRewardCoinsSent(String id, String eventId) { + Query query = Query.query(Criteria.where("_id").is(id) + .and("ownerRewardCoinsEventId").is(eventId)); + Update update = new Update() + .set("ownerRewardCoinsSent", Boolean.TRUE) + .set("ownerRewardCoinsSending", Boolean.FALSE) + .set("ownerRewardCoinsSentTime", TimestampUtils.now()) + .unset("ownerRewardCoinsSendError"); + UpdateResult result = mongoTemplate.updateFirst(query, update, + RoomContributionActivityCount.class); + return result.getModifiedCount() > 0; + } + + @Override + public void resetOwnerRewardCoinsSending(String id, String eventId, String reason) { + Query query = Query.query(new Criteria().andOperator( + Criteria.where("_id").is(id), + Criteria.where("ownerRewardCoinsEventId").is(eventId), + unsentOwnerRewardCriteria() + )); + Update update = new Update() + .set("ownerRewardCoinsSending", Boolean.FALSE) + .set("ownerRewardCoinsSendError", limitErrorReason(reason)); + mongoTemplate.updateFirst(query, update, RoomContributionActivityCount.class); + } + @Override public Optional findByRoomIdAndDateNumber(Long roomId, Integer dateNumber) { Criteria criteria = Criteria.where("roomId").is(roomId) @@ -393,6 +449,22 @@ public class RoomContributionActivityCountServiceImpl implements ); } + private Criteria unsentOwnerRewardCriteria() { + return new Criteria().orOperator( + Criteria.where("ownerRewardCoinsSent").exists(false), + Criteria.where("ownerRewardCoinsSent").ne(Boolean.TRUE) + ); + } + + private Criteria ownerRewardNotSendingOrExpiredCriteria() { + return new Criteria().orOperator( + Criteria.where("ownerRewardCoinsSending").exists(false), + Criteria.where("ownerRewardCoinsSending").ne(Boolean.TRUE), + Criteria.where("ownerRewardCoinsSendingTime") + .lt(new java.sql.Timestamp(System.currentTimeMillis() - REWARD_SENDING_EXPIRE_MILLIS)) + ); + } + private String limitErrorReason(String reason) { if (StringUtils.isBlank(reason) || reason.length() <= 500) { return reason;