diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/room/query/RoomVoiceDiscoverQryExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/room/query/RoomVoiceDiscoverQryExe.java index a0c05d32..010dc437 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/room/query/RoomVoiceDiscoverQryExe.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/room/query/RoomVoiceDiscoverQryExe.java @@ -10,10 +10,12 @@ import com.red.circle.other.app.common.room.RoomVoiceProfileCommon; import com.red.circle.other.app.dto.clientobject.room.RoomVoiceProfileCO; import com.red.circle.other.app.service.game.GameLudoService; import com.red.circle.other.app.service.room.RocketStatusCacheService; +import com.red.circle.other.app.dto.clientobject.room.OnlineUserCO; import com.red.circle.other.domain.gateway.user.UserProfileGateway; import com.red.circle.other.domain.gateway.user.ability.UserRegionGateway; import com.red.circle.other.domain.gateway.user.ability.UserSVipGateway; import com.red.circle.other.domain.model.user.UserProfile; +import com.red.circle.other.infra.database.cache.service.other.MicUserCacheService; import com.red.circle.other.domain.rocket.RocketStatus; import com.red.circle.other.infra.database.cache.service.user.RegionRoomCacheService; import com.red.circle.other.infra.database.mongo.entity.live.ActiveVoiceRoom; @@ -63,6 +65,7 @@ public class RoomVoiceDiscoverQryExe { private final RocketStatusCacheService rocketStatusCacheService; private final GameLudoService gameLudoService; private final UserProfileGateway userProfileGateway; + private final MicUserCacheService micUserCacheService; private static final String EMPTY_ROOM_CACHE_KEY = "empty_room_cache:*"; @@ -119,6 +122,8 @@ public class RoomVoiceDiscoverQryExe { fillRocketStatus(roomList); // 填充游戏图标 fillGameIcon(roomList); + // 填充在线用户 + fillOnlineUsers(roomList); return roomList; } @@ -132,6 +137,39 @@ public class RoomVoiceDiscoverQryExe { roomList.forEach(room -> room.setRoomGameIcon(coverMap.get(room.getId()))); } + private void fillOnlineUsers(List roomList) { + if (CollectionUtils.isEmpty(roomList)) { + return; + } + List roomIds = roomList.stream().map(RoomVoiceProfileCO::getId).toList(); + + // 每个房间取在线用户 userId(纯 Redis,无 RPC) + Map> roomOnlineUserIdsMap = roomIds.stream() + .collect(Collectors.toMap(roomId -> roomId, micUserCacheService::listUser)); + + // 汇总所有 userId,单次 RPC 批量拉取用户资料 + Set allUserIds = roomOnlineUserIdsMap.values().stream() + .flatMap(List::stream) + .collect(Collectors.toSet()); + Map userProfileMap = CollectionUtils.isEmpty(allUserIds) + ? Map.of() + : userProfileGateway.mapByUserIds(allUserIds); + + roomList.forEach(room -> { + List onlineUsers = roomOnlineUserIdsMap + .getOrDefault(room.getId(), List.of()) + .stream() + .map(userProfileMap::get) + .filter(Objects::nonNull) + .map(p -> new OnlineUserCO() + .setId(p.getId()) + .setAccount(p.getAccount()) + .setUserAvatar(p.getUserAvatar())) + .collect(Collectors.toList()); + room.setOnlineUsers(onlineUsers); + }); + } + /** * 填充房间的火箭状态信息 * diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/room/query/UserFollowRoomsQryExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/room/query/UserFollowRoomsQryExe.java index 4b0dea62..aa12d6ac 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/room/query/UserFollowRoomsQryExe.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/room/query/UserFollowRoomsQryExe.java @@ -3,15 +3,20 @@ package com.red.circle.other.app.command.room.query; import com.google.common.collect.Lists; import com.red.circle.common.business.dto.cmd.app.AppFlowCmd; import com.red.circle.other.app.common.room.RoomVoiceProfileCommon; +import com.red.circle.other.app.dto.clientobject.room.OnlineUserCO; import com.red.circle.other.app.dto.clientobject.room.RoomBrowseRecordsV2CO; import com.red.circle.other.app.dto.clientobject.room.RoomVoiceProfileCO; import com.red.circle.other.app.service.game.GameLudoService; +import com.red.circle.other.domain.gateway.user.UserProfileGateway; +import com.red.circle.other.domain.model.user.UserProfile; +import com.red.circle.other.infra.database.cache.service.other.MicUserCacheService; import com.red.circle.other.infra.database.rds.entity.live.RoomSubscription; import com.red.circle.other.infra.database.rds.service.live.RoomSubscriptionService; import com.red.circle.tool.core.collection.CollectionUtils; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -28,6 +33,8 @@ public class UserFollowRoomsQryExe { private final RoomVoiceProfileCommon roomVoiceProfileCommon; private final RoomSubscriptionService roomSubscriptionService; private final GameLudoService gameLudoService; + private final UserProfileGateway userProfileGateway; + private final MicUserCacheService micUserCacheService; public List execute(AppFlowCmd cmd) { List roomSubscriptions = roomSubscriptionService @@ -44,19 +51,43 @@ public class UserFollowRoomsQryExe { roomSubscriptions.stream().map(RoomSubscription::getRoomId).collect(Collectors.toSet())); // 填充游戏图标 - List list = roomSubscriptions.stream().map(RoomSubscription::getRoomId).toList(); - Map coverMap = gameLudoService.listGameCoverByRoomIds(list); + List roomIds = roomSubscriptions.stream().map(RoomSubscription::getRoomId).toList(); + Map coverMap = gameLudoService.listGameCoverByRoomIds(roomIds); + + // 每个房间取在线用户 userId(纯 Redis,无 RPC) + Map> roomOnlineUserIdsMap = roomIds.stream() + .collect(Collectors.toMap(roomId -> roomId, micUserCacheService::listUser)); + + // 汇总所有 userId,单次 RPC 批量拉取用户资料 + Set allUserIds = roomOnlineUserIdsMap.values().stream() + .flatMap(List::stream) + .collect(Collectors.toSet()); + Map userProfileMap = CollectionUtils.isEmpty(allUserIds) + ? Map.of() + : userProfileGateway.mapByUserIds(allUserIds); return roomSubscriptions.stream().map(roomMember -> { - RoomVoiceProfileCO profileCO = roomProfileMap.get(roomMember.getRoomId()); - if (profileCO != null) { - profileCO.setRoomGameIcon(coverMap.get(roomMember.getRoomId())); - } - return new RoomBrowseRecordsV2CO() - .setId(roomMember.getId()) - .setRoomProfile(profileCO); - }) - .filter(sub -> Objects.nonNull(sub.getRoomProfile())).collect(Collectors.toList()); + RoomVoiceProfileCO profileCO = roomProfileMap.get(roomMember.getRoomId()); + if (profileCO != null) { + profileCO.setRoomGameIcon(coverMap.get(roomMember.getRoomId())); + } + List onlineUsers = roomOnlineUserIdsMap + .getOrDefault(roomMember.getRoomId(), List.of()) + .stream() + .map(userProfileMap::get) + .filter(Objects::nonNull) + .map(p -> new OnlineUserCO() + .setId(p.getId()) + .setAccount(p.getAccount()) + .setUserAvatar(p.getUserAvatar())) + .collect(Collectors.toList()); + return new RoomBrowseRecordsV2CO() + .setId(roomMember.getId()) + .setRoomProfile(profileCO) + .setOnlineUsers(onlineUsers); + }) + .filter(sub -> Objects.nonNull(sub.getRoomProfile())) + .collect(Collectors.toList()); } } diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/listener/gift/strategy/GiftAnchorCountStrategy.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/listener/gift/strategy/GiftAnchorCountStrategy.java index f35c436a..eab8e0a1 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/listener/gift/strategy/GiftAnchorCountStrategy.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/listener/gift/strategy/GiftAnchorCountStrategy.java @@ -165,7 +165,6 @@ public class GiftAnchorCountStrategy implements GiftStrategy { } //yoloSalaryDiamondCount(runningWater, giftAcceptUsers.get(0)); - log.warn("发送通知,更新代理工资数据,团队Ids:{}", teamIds); // 发送通知,更新代理工资数据 if (CollectionUtils.isNotEmpty(teamIds)) { // teamSalaryMqMessage.sendSalaryCount(teamIds); diff --git a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/room/OnlineUserCO.java b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/room/OnlineUserCO.java new file mode 100644 index 00000000..acc58efe --- /dev/null +++ b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/room/OnlineUserCO.java @@ -0,0 +1,31 @@ +package com.red.circle.other.app.dto.clientobject.room; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import com.red.circle.framework.dto.ClientObject; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 房间在线用户简要信息. + */ +@Data +@Accessors(chain = true) +public class OnlineUserCO extends ClientObject { + + /** + * 用户 id. + */ + @JsonSerialize(using = ToStringSerializer.class) + private Long id; + + /** + * 账号. + */ + private String account; + + /** + * 头像. + */ + private String userAvatar; +} diff --git a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/room/RoomBrowseRecordsV2CO.java b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/room/RoomBrowseRecordsV2CO.java index 66037979..963d99cb 100644 --- a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/room/RoomBrowseRecordsV2CO.java +++ b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/room/RoomBrowseRecordsV2CO.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import com.red.circle.framework.dto.ClientObject; +import java.util.List; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; @@ -30,4 +31,9 @@ public class RoomBrowseRecordsV2CO extends ClientObject { */ private RoomVoiceProfileCO roomProfile; + /** + * 房间在线用户列表. + */ + private List onlineUsers; + } diff --git a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/room/RoomVoiceProfileCO.java b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/room/RoomVoiceProfileCO.java index 38a1cf2b..022b986d 100644 --- a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/room/RoomVoiceProfileCO.java +++ b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/room/RoomVoiceProfileCO.java @@ -146,6 +146,11 @@ public class RoomVoiceProfileCO extends ClientObject { */ private Long rocketEnergy; + /** + * 房间在线用户列表. + */ + private List onlineUsers; + /** * 设置房间成员数量. */ diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/cache/key/MicUserKey.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/cache/key/MicUserKey.java new file mode 100644 index 00000000..ccae9ca6 --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/cache/key/MicUserKey.java @@ -0,0 +1,25 @@ +package com.red.circle.other.infra.database.cache.key; + +import com.red.circle.component.redis.RedisKeys; + +/** + * 麦克风在线用户缓存 key(与 live 服务共用同一 Redis hash). + */ +public enum MicUserKey implements RedisKeys { + + /** + * 房间用户. + */ + ROOM; + + @Override + public String businessPrefix() { + // 必须与 live 服务 LiveMicUserKey.businessPrefix() 保持一致 + return "LMUser"; + } + + @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/cache/service/other/MicUserCacheService.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/cache/service/other/MicUserCacheService.java new file mode 100644 index 00000000..ca385fea --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/cache/service/other/MicUserCacheService.java @@ -0,0 +1,14 @@ +package com.red.circle.other.infra.database.cache.service.other; + +import java.util.List; + +/** + * 房间在线用户缓存服务(只读,数据由 live 服务写入). + */ +public interface MicUserCacheService { + + /** + * 获取房间在线用户 userId 列表. + */ + List listUser(Long roomId); +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/cache/service/other/impl/MicUserCacheServiceImpl.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/cache/service/other/impl/MicUserCacheServiceImpl.java new file mode 100644 index 00000000..3971907a --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/cache/service/other/impl/MicUserCacheServiceImpl.java @@ -0,0 +1,35 @@ +package com.red.circle.other.infra.database.cache.service.other.impl; + +import com.red.circle.component.redis.service.RedisService; +import com.red.circle.other.infra.database.cache.key.MicUserKey; +import com.red.circle.other.infra.database.cache.service.other.MicUserCacheService; +import java.util.List; +import java.util.Set; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +/** + * 房间在线用户缓存服务实现. + * + *

live 服务以 userId 字符串为 hash field 写入,直接取 field 名集合即可, + * 无需引入 live-domain 依赖,也无需反序列化。 + */ +@Service +@RequiredArgsConstructor +public class MicUserCacheServiceImpl implements MicUserCacheService { + + private final RedisService redisService; + + @Override + public List listUser(Long roomId) { + Set fields = redisService.hashKeys(MicUserKey.ROOM.getKey(roomId)); + if (fields == null || fields.isEmpty()) { + return List.of(); + } + return fields.stream() + .filter(StringUtils::isNotBlank) + .map(Long::valueOf) + .toList(); + } +} diff --git a/rc-service/rc-service-other/other-start/src/test/java/BdSettlementTest.java b/rc-service/rc-service-other/other-start/src/test/java/BdSettlementTest.java index f0a7711c..720e68bd 100644 --- a/rc-service/rc-service-other/other-start/src/test/java/BdSettlementTest.java +++ b/rc-service/rc-service-other/other-start/src/test/java/BdSettlementTest.java @@ -1,5 +1,6 @@ +import com.alibaba.fastjson.JSON; import com.red.circle.OtherServiceApplication; -import com.red.circle.common.business.core.enums.SysOriginPlatformEnum; +import com.red.circle.other.app.listener.team.TeamBillSettleListener; import com.red.circle.other.app.scheduler.AdminSalarySettlementTask; import com.red.circle.other.app.scheduler.BdLeaderSalarySettlementTask; import com.red.circle.other.app.scheduler.BdSalarySettlementTask; @@ -42,86 +43,52 @@ public class BdSettlementTest { private MongoTemplate mongoTemplate; @Autowired private UserSalaryAccountClient userSalaryAccountClient; - + @Autowired + private TeamBillSettleListener teamBillSettleListener; /** - * BD 工资结算 + * 批量团队结算 */ @Test - public void testBdSettlement() { - Integer billBelong = TeamBillCycleUtils.getCalcBillBelong(); - - bdSalarySettlementTask.processBdSalarySettlementTest(billBelong); - + public void processTeamBillSettle() { + String json = "['2043970266712944642', '2042485790634393602']"; + List teamList = JSON.parseArray(json, String.class); + for (String teamId : teamList) { + Long id = Long.parseLong(teamId); + teamBillSettleListener.processBillTest(id); + } } /** - * BDLEADER 工资结算 + * BD 工资结算(指定用户) */ @Test - public void testBdLeaderSettlement() { + public void testBdSettlementByUserId() { + String json = "['1963790037740466178', '1963790095911268354']"; Integer billBelong = TeamBillCycleUtils.getCalcBillBelong(); + List teamList = JSON.parseArray(json, String.class); - bdLeaderSalarySettlementTask.processBdLeaderSalarySettlementTest(billBelong); - - } - - @Test - public void testAdminSettlement() { - adminSalarySettlementTask.processAdminSalarySettlementTest(TeamBillCycleUtils.getCalcBillBelong()); - } - - @Test - public void testIssueSalary() { -// Long bdUserId = 1963790037740466178L; -// BdSalarySettlementRecord bdSalarySettlementRecord = new BdSalarySettlementRecord(); -// bdSalarySettlementRecord.setBillBelong(20251116); -// bdSalarySettlementRecord.setSysOrigin(SysOriginPlatformEnum.ATYOU.name()); -// bdSalarySettlementRecord.setSettlementSalary(new BigDecimal(111)); -// bdSalarySettlementRecord.setId(IdWorkerUtils.getIdStr()); -// issueSalary(bdUserId, bdSalarySettlementRecord); - - - Long bdLeaderUserId = 1963790037740466178L; - BdLeaderSalarySettlementRecord bdLeaderSalarySettlementRecord = new BdLeaderSalarySettlementRecord(); - bdLeaderSalarySettlementRecord.setBillBelong(20251116); - bdLeaderSalarySettlementRecord.setSysOrigin(SysOriginPlatformEnum.ATYOU.name()); - bdLeaderSalarySettlementRecord.setSettlementSalary(new BigDecimal(2)); - bdLeaderSalarySettlementRecord.setId(IdWorkerUtils.getIdStr()); - issueSalary(bdLeaderUserId, bdLeaderSalarySettlementRecord); - } - - /** - * 发放BD工资 - */ - private void issueSalary(Long bdUserId, BdSalarySettlementRecord record) { - SalaryIssueInnerCmd innerCmd = new SalaryIssueInnerCmd(); - innerCmd.setBillBelong(record.getBillBelong()); - innerCmd.setUserId(bdUserId); - innerCmd.setSysOrigin(record.getSysOrigin()); - innerCmd.setSalaryType("BD_SALARY"); - innerCmd.setEventDesc("SALARY_BD"); - innerCmd.setAmount(record.getSettlementSalary()); - innerCmd.setTrackId(record.getId()); - innerCmd.setBdId(bdUserId); - userSalaryAccountClient.issueSalary(innerCmd); + for (String userIdStr : teamList) { + Long userId = Long.parseLong(userIdStr); +// bdSalarySettlementTask.processBdSalarySettlementTestByUserId(userId, billBelong); } + } - /** - * 发放BDLEADER工资 - */ - private void issueSalary(Long bdLeaderUserId, BdLeaderSalarySettlementRecord record) { - SalaryIssueInnerCmd innerCmd = new SalaryIssueInnerCmd(); - innerCmd.setBillBelong(record.getBillBelong()); - innerCmd.setUserId(bdLeaderUserId); - innerCmd.setSysOrigin(record.getSysOrigin()); - innerCmd.setSalaryType("BD_LEADER_SALARY"); - innerCmd.setEventDesc("SALARY_BD_LEADER"); - innerCmd.setAmount(record.getSettlementSalary()); - innerCmd.setTrackId(record.getId()); - innerCmd.setBdId(bdLeaderUserId); - userSalaryAccountClient.issueSalary(innerCmd); + + /** + * BDLEADER 工资结算(指定用户) + */ + @Test + public void testBdLeaderSettlementByUserId() { + String json = "['1963790037740466178', '1963790095911268354']"; + Integer billBelong = TeamBillCycleUtils.getCalcBillBelong(); + List teamList = JSON.parseArray(json, String.class); + + for (String userIdStr : teamList) { + Long userId = Long.parseLong(userIdStr); +// bdLeaderSalarySettlementTask.processBdLeaderSalarySettlementTestByUserId(userId, billBelong); } + } }