Commit a9fd76ea by zhangxingmin

push

parent 5217ecc0
......@@ -12,6 +12,8 @@ import com.yd.communication.service.model.CoSession;
import com.yd.communication.service.service.ICoOperationLogService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RTopic;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
......@@ -124,6 +126,13 @@ public class CoWebSocketServer {
CoWebSocketServer.redisTemplate = redisTemplate;
}
private static RedissonClient redissonClient;
@Autowired
public void setRedissonClient(RedissonClient redissonClient) {
CoWebSocketServer.redissonClient = redissonClient;
}
// ==================== Redis 键名常量 ====================
/**
......@@ -156,73 +165,26 @@ public class CoWebSocketServer {
// ==================== 跨节点订阅(每个节点启动时执行) ====================
/**
* 监听应用启动完成事件,在容器完全就绪后启动 Redis 订阅
* 替代 @PostConstruct,避免 Bean 未完全初始化时连接 Redis
*/
@EventListener(ApplicationReadyEvent.class)
public void startRedisSubscriber() {
log.info("========================================");
log.info("应用已完全启动,开始初始化 Redis 订阅...");
log.info("当前节点ID: {}", NODE_ID);
log.info("RedisTemplate 是否为空: {}", redisTemplate == null ? "是" : "否");
if (redisTemplate != null) {
log.info("RedisTemplate 类型: {}", redisTemplate.getClass().getName());
}
log.info("========================================");
log.info("应用已完全启动,开始初始化 Redisson RTopic 订阅...");
new Thread(() -> {
doSubscribe();
}).start();
}
/**
* 执行 Redis 订阅(支持自动重连)
* 使用 RedisConnection.pSubscribe 进行持久订阅
*/
private void doSubscribe() {
log.info("=== Redis 订阅线程启动 ===");
log.info("当前节点 ID: {}", NODE_ID);
// 订阅全局频道
RTopic topic = redissonClient.getTopic("coordination:channel");
topic.addListener(String.class, (channel, msg) -> {
log.debug("收到跨节点消息: {}", msg);
handleCrossNodeMessage(msg);
});
log.info("Redisson RTopic 订阅已启动,频道: coordination:channel");
// 保持线程存活(监听在后台异步进行,线程无需阻塞,但保留以防止退出)
while (true) {
RedisConnection connection = null;
try {
// 1. 获取 Redis 连接(非回调方式,保持长连接)
connection = redisTemplate.getConnectionFactory().getConnection();
log.info("获取 Redis 连接成功,开始模式订阅 room:channel:* ...");
// 2. 阻塞订阅(模式匹配)
connection.pSubscribe((message, pattern) -> {
// 收到消息时调用
log.debug("收到跨节点消息,频道: {}, 消息长度: {}",
new String(pattern), message.getBody().length);
handleCrossNodeMessage(new String(message.getBody()));
}, "room:channel:*".getBytes());
// 正常情况 pSubscribe 会一直阻塞,执行到这里说明订阅被取消或连接断开
log.warn("Redis 订阅意外结束,准备重连...");
} catch (Exception e) {
log.error("Redis 订阅异常: {}", e.getMessage(), e);
} finally {
// 关闭连接(如果订阅已结束)
if (connection != null) {
try {
connection.close();
} catch (Exception e) {
// ignore
}
}
}
// 3. 重连等待
try {
Thread.sleep(5000);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
Thread.sleep(60000);
} catch (InterruptedException e) {
break;
}
}
}).start();
}
/**
......@@ -644,17 +606,17 @@ public class CoWebSocketServer {
}
}
// 2. 发布到 Redis,通知其他节点广播
String channel = String.format(ROOM_BROADCAST_CHANNEL, roomId);
// 2. 跨节点广播(发布到 Redisson RTopic)
Map<String, String> data = new HashMap<>();
data.put("roomId", roomId); // 房间号
data.put("message", message); // 消息内容
data.put("sourceNodeId", NODE_ID); // 标记消息来源节点
data.put("roomId", roomId);
data.put("message", message);
data.put("sourceNodeId", NODE_ID);
if (exclude != null) {
data.put("excludeSessionId", exclude.getId()); // 需要排除的 sessionId
data.put("excludeSessionId", exclude.getId());
}
// 将数据转为 JSON 并发布到 Redis 频道
redisTemplate.convertAndSend(channel, JSON.toJSONString(data));
String jsonMsg = JSON.toJSONString(data);
RTopic topic = redissonClient.getTopic("coordination:channel");
topic.publish(jsonMsg);
}
/**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment