没人的房间缓存

This commit is contained in:
tianfeng 2025-09-16 20:20:16 +08:00
parent 3972f2bfbe
commit 4c448efe22
7 changed files with 396 additions and 9 deletions

View File

@ -1,9 +1,9 @@
package com.red.circle.other.inner.endpoint.live.api;
import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
import com.red.circle.framework.dto.ResultResponse;
import com.red.circle.other.inner.model.cmd.live.AppActiveVoiceRoomQryCmd;
import com.red.circle.other.inner.model.dto.live.ActiveVoiceRoomCO;
import com.red.circle.other.inner.model.dto.live.ActiveVoiceRoomDTO;
import java.util.List;
import org.springframework.web.bind.annotation.GetMapping;
@ -30,4 +30,7 @@ public interface ActiveVoiceRoomClientApi {
@PostMapping("/listOps")
ResultResponse<List<ActiveVoiceRoomDTO>> listOps(@RequestBody AppActiveVoiceRoomQryCmd qryCmd);
@GetMapping("/createNoBody")
ResultResponse<ActiveVoiceRoomCO> createNoBodyRoom(@RequestParam("roomId") Long roomId);
}

View File

@ -0,0 +1,103 @@
package com.red.circle.other.inner.model.dto.live;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.sql.Timestamp;
/**
* 活跃语音房间 copy
*/
@Data
public class ActiveVoiceRoomCO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* id.
*/
@JsonSerialize(using = ToStringSerializer.class)
Long id;
/**
* 时序ID每次创建刷新.
*/
@JsonSerialize(using = ToStringSerializer.class)
Long timeId;
/**
* 归属系统平台.
*/
String sysOrigin;
/**
* 房主id.
*/
@JsonSerialize(using = ToStringSerializer.class)
Long userId;
/**
* 房间account.
*/
String roomAccount;
/**
* SVip等级.
*/
String superVipLevel;
/**
* 热门房间true 不是热门 false 热门.
*/
Boolean hot;
/**
* 置顶房间.
*/
Integer fixedWeights;
/**
* 国家code.
*/
String countryCode;
/**
* 国家名字.
*/
String countryName;
/**
* 区域.
*/
String region;
/**
* 在线数量.
*/
Long onlineQuantity;
/**
* 权重.
*/
Integer weights;
/**
* 过期时间.
*/
Timestamp expiredTime;
/**
* 创建时间.
*/
Timestamp createTime;
/**
* 修改时间.
*/
Timestamp updateTime;
}

View File

@ -1,14 +1,20 @@
package com.red.circle.live.app.command.user;
import com.alibaba.fastjson.JSON;
import com.red.circle.common.business.dto.cmd.app.AppRoomIdCmd;
import com.red.circle.component.redis.service.RedisService;
import com.red.circle.framework.dto.ResultResponse;
import com.red.circle.live.domain.gateway.LiveMicrophoneGateway;
import com.red.circle.live.domain.live.LiveMicrophone;
import com.red.circle.live.domain.live.LiveMusicStatus;
import com.red.circle.live.infra.database.cache.service.LiveMicCacheService;
import com.red.circle.live.infra.database.cache.service.LiveMusicHeartbeatService;
import com.red.circle.other.inner.endpoint.live.ActiveVoiceRoomClient;
import com.red.circle.other.inner.model.dto.live.ActiveVoiceRoomCO;
import com.red.circle.tool.core.collection.CollectionUtils;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
@ -24,6 +30,11 @@ public class LiveQuitRoomExe {
private final LiveMicCacheService liveMicCacheService;
private final LiveMicrophoneGateway liveMicrophoneGateway;
private final LiveMusicHeartbeatService liveMusicHeartbeatService;
private final ActiveVoiceRoomClient activeVoiceRoomClient;
private final RedisService redisService;
private static final String EMPTY_ROOM_CACHE_KEY = "empty_room_cache:";
private static final int CACHE_EXPIRE_MINUTES = 10;
public void execute(AppRoomIdCmd cmd) {
// 移除在线用户
@ -50,6 +61,21 @@ public class LiveQuitRoomExe {
&& Objects.equals(musicStatus.getUserId(), cmd.requiredReqUserId())) {
liveMusicHeartbeatService.removeMickUser(cmd.getRoomId());
}
//将没人的房间存入缓存10分钟
try {
ResultResponse<ActiveVoiceRoomCO> noBodyRoom = activeVoiceRoomClient.createNoBodyRoom(cmd.getRoomId());
ActiveVoiceRoomCO body = noBodyRoom.getBody();
if (Objects.nonNull(body)) {
// 缓存空房间信息设置10分钟过期
String cacheKey = EMPTY_ROOM_CACHE_KEY + cmd.getRoomId();
redisService.setIfAbsent(cacheKey, JSON.toJSONString(body), CACHE_EXPIRE_MINUTES, TimeUnit.MINUTES);
}
} catch (Exception e) {
//ignore
}
}
}

View File

@ -21,6 +21,7 @@ import com.red.circle.other.infra.database.rds.entity.sys.SysCountryCode;
import com.red.circle.other.infra.database.rds.service.sys.SysCountryCodeService;
import com.red.circle.other.infra.database.rds.service.user.device.LatestMobileDeviceService;
import com.red.circle.other.inner.model.cmd.live.AppActiveVoiceRoomQryCmd;
import com.red.circle.other.inner.model.dto.live.ActiveVoiceRoomCO;
import com.red.circle.tool.core.collection.CollectionUtils;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
@ -30,6 +31,8 @@ import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
/**
@ -44,7 +47,6 @@ public class RoomVoiceDiscoverQryExe {
private final UserSVipGateway userSVipGateway;
private final UserRegionGateway userRegionGateway;
private final SysCountryCodeService sysCountryCodeService;
private final RoomVoiceProfileCommon roomVoiceProfileCommon;
private final ActiveVoiceRoomService activeVoiceRoomService;
private final RoomUserBlacklistService roomUserBlacklistService;
@ -52,6 +54,14 @@ public class RoomVoiceDiscoverQryExe {
private final RoomProfileManagerService roomProfileManagerService;
private final RegionRoomCacheService regionRoomCacheService;
private final LiveMicClient liveMicClient;
private final RedisTemplate<String, Object> redisTemplate;
private static final String EMPTY_ROOM_CACHE_KEY = "empty_room_cache:*";
// 区域隔离常量 - 与ActiveVoiceRoomServiceImpl保持一致
private static final Set<String> SA_REGIONS = Set.of("BD", "IN", "PK");
private static final Set<String> AR_REGIONS = Set.of("AE", "BH", "DJ", "DZ", "EG", "EY", "ER", "IL", "IQ", "JO", "KM", "KW", "LB", "LY", "MA", "MR", "OM", "PS", "QA", "SA", "SD", "SO", "SS", "SY", "TD", "TN", "YE");
private static final Set<String> OTHER_REGIONS = Set.of("OTHER", "PH", "ID", "TR", "GH", "IR", "AF", "NG", "DE", "MYS", "TM", "AZ", "NP");
public List<RoomVoiceProfileCO> execute(AppExtCommand cmd) {
String region = cmd.isAllRegion() ? "ALL" : userRegionGateway.getRegionCode(cmd.requiredReqUserId());
@ -64,12 +74,21 @@ public class RoomVoiceDiscoverQryExe {
}
List<ActiveVoiceRoom> activeVoiceRooms = activeVoiceRoomService.listDiscover(cmd.requireReqSysOrigin(), cmd.isAllRegion(), region, 50);
// 查询没人的房间缓存加入进去
List<ActiveVoiceRoom> emptyRooms = getEmptyRoomsFromCache();
if (CollectionUtils.isNotEmpty(emptyRooms)) {
// 按照listDiscover的过滤规则过滤空房间
List<ActiveVoiceRoom> matchedEmptyRooms = filterEmptyRoomsByRegion(emptyRooms, cmd.requireReqSysOrigin(), cmd.isAllRegion(), region);
if (CollectionUtils.isNotEmpty(matchedEmptyRooms)) {
// 创建新的可修改列表合并活跃房间和空房间
List<ActiveVoiceRoom> allRooms = new ArrayList<>(activeVoiceRooms);
allRooms.addAll(matchedEmptyRooms);
activeVoiceRooms = allRooms;
}
}
log.info("activeVoiceRooms:{}", JSON.toJSONString(activeVoiceRooms));
// 处理没有人的房间有缓存不会大量创建线程
List<Long> longs = formatRoomList(activeVoiceRooms);
List<ActiveVoiceRoom> list = activeVoiceRooms.stream().filter(e -> !longs.contains(e.getId())).toList();
List<RoomVoiceProfileCO> roomList = roomVoiceProfileCommon.toListRoomVoiceProfileCO(list);
List<RoomVoiceProfileCO> roomList = roomVoiceProfileCommon.toListRoomVoiceProfileCO(activeVoiceRooms);
if (CollectionUtils.isEmpty(roomList)) {
//roomList = getHomeData(cmd,latestMobileDeviceService.getLatestLanguage(cmd.requiredReqUserId()));
@ -87,7 +106,6 @@ public class RoomVoiceDiscoverQryExe {
room.setUserSVipLevel(svipLevelEnumMap.get(room.getUserId()));
})
.toList();
regionRoomCacheService.saveRegionsRoom(JSON.toJSONString(roomList), key);
return roomList;
}
@ -181,6 +199,149 @@ public class RoomVoiceDiscoverQryExe {
}
}
/**
* 按照listDiscover的过滤规则过滤空房间
*
* @param emptyRooms 空房间列表
* @param sysOrigin 系统来源
* @param isAllRegion 是否显示所有区域
* @param region 当前用户区域
* @return 过滤后的空房间列表
*/
private List<ActiveVoiceRoom> filterEmptyRoomsByRegion(List<ActiveVoiceRoom> emptyRooms, String sysOrigin, boolean isAllRegion, String region) {
List<String> excludeRegions = getRegions(region);
List<String> includeRegions = getSpecialRegions(region);
return emptyRooms.stream()
.filter(room -> Objects.equals(sysOrigin, room.getSysOrigin()))
.filter(room -> {
if (isAllRegion) {
return true; // 如果是所有区域不进行区域过滤
}
String roomRegion = room.getRegion();
if (CollectionUtils.isNotEmpty(includeRegions)) {
// 如果当前用户属于特殊区域组只显示该区域组内的房间
return includeRegions.contains(roomRegion);
}
if (CollectionUtils.isNotEmpty(excludeRegions)) {
// 如果当前用户不属于特殊区域组排除所有特殊区域组的房间
return !excludeRegions.contains(roomRegion);
}
return true;
})
.collect(Collectors.toList());
}
/**
* 获取需要排除的区域列表
*/
private List<String> getRegions(String region) {
List<String> excludeRegions = new ArrayList<>();
if (SA_REGIONS.contains(region)) {
excludeRegions.addAll(AR_REGIONS);
excludeRegions.addAll(OTHER_REGIONS);
} else if (AR_REGIONS.contains(region)) {
excludeRegions.addAll(SA_REGIONS);
excludeRegions.addAll(OTHER_REGIONS);
} else if (OTHER_REGIONS.contains(region)) {
excludeRegions.addAll(SA_REGIONS);
excludeRegions.addAll(AR_REGIONS);
} else {
excludeRegions.addAll(SA_REGIONS);
excludeRegions.addAll(AR_REGIONS);
excludeRegions.addAll(OTHER_REGIONS);
}
return excludeRegions;
}
/**
* 获取特殊区域组列表
*/
private List<String> getSpecialRegions(String region) {
if (SA_REGIONS.contains(region)) {
return new ArrayList<>(SA_REGIONS);
}
if (AR_REGIONS.contains(region)) {
return new ArrayList<>(AR_REGIONS);
}
if (OTHER_REGIONS.contains(region)) {
return new ArrayList<>(OTHER_REGIONS);
}
return new ArrayList<>();
}
/**
* 从缓存中获取没人的房间
*
* @return
*/
private List<ActiveVoiceRoom> getEmptyRoomsFromCache() {
try {
Set<String> keys = redisTemplate.keys(EMPTY_ROOM_CACHE_KEY);
if (CollectionUtils.isEmpty(keys)) {
return new ArrayList<>();
}
List<ActiveVoiceRoom> emptyRooms = new ArrayList<>();
for (String key : keys) {
String cachedRoom = (String) redisTemplate.opsForValue().get(key);
if (cachedRoom != null) {
try {
// 将缓存对象转换为ActiveVoiceRoomCO再转换为ActiveVoiceRoom
ActiveVoiceRoomCO roomCO = JSON.parseObject(cachedRoom, ActiveVoiceRoomCO.class);
if (roomCO != null) {
ActiveVoiceRoom room = convertToActiveVoiceRoom(roomCO);
if (room != null) {
emptyRooms.add(room);
}
}
} catch (Exception e) {
log.warn("转换缓存房间对象失败, key: {}", key, e);
}
}
}
return emptyRooms;
} catch (Exception e) {
log.error("获取空房间缓存失败", e);
return new ArrayList<>();
}
}
/**
* 将ActiveVoiceRoomCO转换为ActiveVoiceRoom
*
* @param roomCO
* @return
*/
private ActiveVoiceRoom convertToActiveVoiceRoom(ActiveVoiceRoomCO roomCO) {
try {
ActiveVoiceRoom room = new ActiveVoiceRoom();
BeanUtils.copyProperties(roomCO, room);
// 设置在线人数为0因为这是没人的房间
room.setOnlineQuantity(0L);
// 设置默认权重如果未设置的话
if (room.getWeights() == null) {
room.setWeights(0);
}
if (room.getFixedWeights() == null) {
room.setFixedWeights(0);
}
return room;
} catch (Exception e) {
log.error("转换ActiveVoiceRoomCO失败", e);
return null;
}
}
/**
* 去除拉黑和被上锁的的房间
*

View File

@ -1,13 +1,16 @@
package com.red.circle.other.app.inner.endpoint.room;
import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
import com.red.circle.framework.dto.ResultResponse;
import com.red.circle.other.app.inner.service.live.ActiveVoiceRoomClientService;
import com.red.circle.other.infra.database.mongo.entity.live.ActiveVoiceRoom;
import com.red.circle.other.inner.model.dto.live.ActiveVoiceRoomCO;
import com.red.circle.other.inner.endpoint.live.ActiveVoiceRoomClient;
import com.red.circle.other.inner.model.cmd.live.AppActiveVoiceRoomQryCmd;
import com.red.circle.other.inner.model.dto.live.ActiveVoiceRoomDTO;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
@ -36,4 +39,12 @@ public class ActiveVoiceRoomClientEndpoint implements ActiveVoiceRoomClient {
return ResultResponse.success(activeVoiceRoomClientService.listOps(qryCmd));
}
@Override
public ResultResponse<ActiveVoiceRoomCO> createNoBodyRoom(Long roomId) {
ActiveVoiceRoomCO activeVoiceRoomCO = new ActiveVoiceRoomCO();
ActiveVoiceRoom nobodyRoom = activeVoiceRoomClientService.createNobodyRoom(roomId);
BeanUtils.copyProperties(nobodyRoom, activeVoiceRoomCO);
return ResultResponse.success(activeVoiceRoomCO);
}
}

View File

@ -1,6 +1,7 @@
package com.red.circle.other.app.inner.service.live;
import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
import com.red.circle.other.infra.database.mongo.entity.live.ActiveVoiceRoom;
import com.red.circle.other.inner.model.cmd.live.AppActiveVoiceRoomQryCmd;
import com.red.circle.other.inner.model.dto.live.ActiveVoiceRoomDTO;
import java.util.List;
@ -22,4 +23,9 @@ public interface ActiveVoiceRoomClientService {
*/
List<ActiveVoiceRoomDTO> listOps(AppActiveVoiceRoomQryCmd qryCmd);
/**
* 创建没人的房间缓存
*/
ActiveVoiceRoom createNobodyRoom(Long roomId);
}

View File

@ -1,12 +1,28 @@
package com.red.circle.other.app.inner.service.live.impl;
import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
import com.red.circle.live.inner.endpoint.LiveMicClient;
import com.red.circle.other.app.inner.service.live.ActiveVoiceRoomClientService;
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.convertor.live.ActiveVoiceInfraConvertor;
import com.red.circle.other.infra.database.cache.service.other.HotRoomCacheService;
import com.red.circle.other.infra.database.cache.service.other.RoomManagerCacheService;
import com.red.circle.other.infra.database.mongo.entity.live.*;
import com.red.circle.other.infra.database.mongo.service.live.ActiveVoiceRoomService;
import com.red.circle.other.infra.database.mongo.service.live.RoomProfileManagerService;
import com.red.circle.other.infra.database.mongo.service.live.RoomTopFixedService;
import com.red.circle.other.inner.model.cmd.live.AppActiveVoiceRoomQryCmd;
import com.red.circle.other.inner.model.dto.live.ActiveVoiceRoomDTO;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import com.red.circle.tool.core.date.TimestampUtils;
import com.red.circle.tool.core.sequence.IdWorkerUtils;
import com.red.circle.tool.core.text.StringUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@ -19,6 +35,14 @@ public class ActiveVoiceRoomClientServiceImpl implements ActiveVoiceRoomClientSe
private final ActiveVoiceRoomService activeVoiceRoomService;
private final ActiveVoiceInfraConvertor activeVoiceInfraConvertor;
private final RoomProfileManagerService roomProfileManagerService;
private final HotRoomCacheService hotRoomCacheService;
private final LiveMicClient liveMicClient;
private final RoomManagerCacheService roomManagerCacheService;
private final UserSVipGateway userSVipGateway;
private final UserProfileGateway userProfileGateway;
private final UserRegionGateway userRegionGateway;
private final RoomTopFixedService roomTopFixedService;
@Override
public Long count(String sysOrigin) {
@ -32,4 +56,57 @@ public class ActiveVoiceRoomClientServiceImpl implements ActiveVoiceRoomClientSe
);
}
@Override
public ActiveVoiceRoom createNobodyRoom(Long roomId) {
RoomProfileManager room = roomProfileManagerService.getById(roomId);
UserProfile userProfile = userProfileGateway.getByUserId(room.getUserId());
String roomOwnRegionCode = userRegionGateway.getRegionCode(room.getUserId());
RoomTopFixed roomTopFixed = roomTopFixedService.getByRoomId(room.getId());
Integer roomTopFixedWeights = Optional.ofNullable(roomTopFixed)
.map(fixed -> Objects.nonNull(fixed.getWeights()) ? fixed.getWeights() : 0)
.orElse(0);
boolean topRoomForeverOnline = Optional.ofNullable(roomTopFixed)
.map(fixed -> Objects.equals(fixed.getForeverOnline(), Boolean.TRUE))
.orElse(Boolean.FALSE);
// 房间上锁.
Integer weights = StringUtils.isNotBlank(Optional.ofNullable(room.getSetting())
.map(RoomSetting::getPassword)
.orElse("")) ? 0 : 1;
return new ActiveVoiceRoom()
.setId(roomId)
.setTimeId(IdWorkerUtils.getId())
.setSysOrigin(room.getSysOrigin())
.setUserId(room.getUserId())
.setRoomAccount(room.getRoomAccount())
.setFixedWeights(roomTopFixedWeights)
.setRunGame(null)
.setHot(
Objects.nonNull(hotRoomCacheService.getAvailable(room.getSysOrigin(), room.getId())))
.setCountryCode(StringUtils.toUpperCase(userProfile.getCountryCode()))
.setCountryName(userProfile.getCountryName())
// ALL所有DEFAULT默认区域其他根据语言分割.
.setRegion(roomOwnRegionCode)
.setExpiredTime(TimestampUtils.nowPlusMinutes(topRoomForeverOnline ? 60 : 3))
.setCreateTime(TimestampUtils.now())
.setUpdateTime(TimestampUtils.now())
.setOnlineQuantity(getAvRoomOnlineMemberNum(room))
.setWeights(weights)
.setSuperVipLevel(Objects.toString(
userSVipGateway.checkSVipIdentity(room.getUserId()))
);
}
private Long getAvRoomOnlineMemberNum(RoomProfile roomProfile) {
Long resultQuantity = liveMicClient.getLiveRoomUserSize(roomProfile.getId()).getBody();
if (Objects.nonNull(resultQuantity)) {
roomManagerCacheService.setNumberPeople(roomProfile.getRoomAccount(), resultQuantity);
return resultQuantity;
}
return 0L;
}
}