房间列表新增在线用户字段

This commit is contained in:
tianfeng 2026-04-14 18:14:48 +08:00
parent 931835a4b5
commit 69f77f1193
10 changed files with 230 additions and 79 deletions

View File

@ -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<RoomVoiceProfileCO> roomList) {
if (CollectionUtils.isEmpty(roomList)) {
return;
}
List<Long> roomIds = roomList.stream().map(RoomVoiceProfileCO::getId).toList();
// 每个房间取在线用户 userId Redis RPC
Map<Long, List<Long>> roomOnlineUserIdsMap = roomIds.stream()
.collect(Collectors.toMap(roomId -> roomId, micUserCacheService::listUser));
// 汇总所有 userId单次 RPC 批量拉取用户资料
Set<Long> allUserIds = roomOnlineUserIdsMap.values().stream()
.flatMap(List::stream)
.collect(Collectors.toSet());
Map<Long, UserProfile> userProfileMap = CollectionUtils.isEmpty(allUserIds)
? Map.of()
: userProfileGateway.mapByUserIds(allUserIds);
roomList.forEach(room -> {
List<OnlineUserCO> 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);
});
}
/**
* 填充房间的火箭状态信息
*

View File

@ -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<RoomBrowseRecordsV2CO> execute(AppFlowCmd cmd) {
List<RoomSubscription> roomSubscriptions = roomSubscriptionService
@ -44,19 +51,43 @@ public class UserFollowRoomsQryExe {
roomSubscriptions.stream().map(RoomSubscription::getRoomId).collect(Collectors.toSet()));
// 填充游戏图标
List<Long> list = roomSubscriptions.stream().map(RoomSubscription::getRoomId).toList();
Map<Long, String> coverMap = gameLudoService.listGameCoverByRoomIds(list);
List<Long> roomIds = roomSubscriptions.stream().map(RoomSubscription::getRoomId).toList();
Map<Long, String> coverMap = gameLudoService.listGameCoverByRoomIds(roomIds);
// 每个房间取在线用户 userId Redis RPC
Map<Long, List<Long>> roomOnlineUserIdsMap = roomIds.stream()
.collect(Collectors.toMap(roomId -> roomId, micUserCacheService::listUser));
// 汇总所有 userId单次 RPC 批量拉取用户资料
Set<Long> allUserIds = roomOnlineUserIdsMap.values().stream()
.flatMap(List::stream)
.collect(Collectors.toSet());
Map<Long, UserProfile> 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<OnlineUserCO> 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());
}
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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<OnlineUserCO> onlineUsers;
}

View File

@ -146,6 +146,11 @@ public class RoomVoiceProfileCO extends ClientObject {
*/
private Long rocketEnergy;
/**
* 房间在线用户列表.
*/
private List<OnlineUserCO> onlineUsers;
/**
* 设置房间成员数量.
*/

View File

@ -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();
}
}

View File

@ -0,0 +1,14 @@
package com.red.circle.other.infra.database.cache.service.other;
import java.util.List;
/**
* 房间在线用户缓存服务只读数据由 live 服务写入.
*/
public interface MicUserCacheService {
/**
* 获取房间在线用户 userId 列表.
*/
List<Long> listUser(Long roomId);
}

View File

@ -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;
/**
* 房间在线用户缓存服务实现.
*
* <p>live 服务以 userId 字符串为 hash field 写入直接取 field 名集合即可
* 无需引入 live-domain 依赖也无需反序列化
*/
@Service
@RequiredArgsConstructor
public class MicUserCacheServiceImpl implements MicUserCacheService {
private final RedisService redisService;
@Override
public List<Long> listUser(Long roomId) {
Set<String> 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();
}
}

View File

@ -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<String> 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<String> 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<String> teamList = JSON.parseArray(json, String.class);
for (String userIdStr : teamList) {
Long userId = Long.parseLong(userIdStr);
// bdLeaderSalarySettlementTask.processBdLeaderSalarySettlementTestByUserId(userId, billBelong);
}
}
}