package com.ard.work.component.netty; import com.ard.work.component.netty.command.register.HeartbeatCmd; import com.ard.work.component.netty.handler.ClientHandler; import com.ard.work.component.netty.handler.MessageProtocolDecoder; import com.ard.work.component.netty.handler.MessageProtocolEncoder; import com.ard.work.component.netty.cache.TyTcpServerCache; import com.ard.work.domain.entity.ArdServerInfo; import com.ard.work.service.IArdServerInfoService; import io.netty.bootstrap.Bootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.timeout.IdleStateHandler; import io.netty.util.AttributeKey; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.annotation.Resource; import java.util.concurrent.TimeUnit; @Component @Slf4j public class TyTcpClientManager { private final EventLoopGroup eventLoopGroup = new NioEventLoopGroup(); @Resource private IArdServerInfoService ardServerService; // ← 从数据库读取边缘服务器配置 @Resource private ClientHandler clientHandler; /** * 初始化 Bootstrap */ private Bootstrap getBootstrap() { Bootstrap bootstrap = new Bootstrap(); bootstrap.group(eventLoopGroup) .channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .option(ChannelOption.SO_KEEPALIVE, true) .handler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel socketChannel) { ChannelPipeline pipeline = socketChannel.pipeline(); pipeline.addLast("heart", new IdleStateHandler(0, 3, 0, TimeUnit.SECONDS)); pipeline.addLast("encoder", new MessageProtocolEncoder()); pipeline.addLast("decoder", new MessageProtocolDecoder()); pipeline.addLast("handler", clientHandler); } }); return bootstrap; } /** * 新增或重连服务器 */ public void connectServer(ArdServerInfo serverInfo) { String serverId = serverInfo.getId(); String host = serverInfo.getHost(); Integer port = serverInfo.getTcpPort(); // 如果已有旧连接,先断开 disconnectServer(serverId); Bootstrap bootstrap = getBootstrap(); bootstrap.connect(host, port).addListener((ChannelFuture f) -> { if (f.isSuccess()) { log.info("✅ 服务器 [{}] 连接成功", serverId); Channel channel = f.channel(); channel.attr(AttributeKey.valueOf("serverId")).set(serverId); TyTcpServerCache.putTcpServerCache(serverId, true); // 断开服务器,修改状态为: 0 - 在线 ardServerService.updateServerStatus(serverId, "1"); // 成功后再加入 channels TyTcpServerCache.putChannel(serverId, channel); } else { log.warn("❌ 服务器 [{}] 连接失败", serverId); TyTcpServerCache.putTcpServerCache(serverId, false); // 断开服务器,修改状态为: 0 - 离线 ardServerService.updateServerStatus(serverId, "0"); } }); } /** * 断开服务器连接 */ public void disconnectServer(String serverId) { Channel channel = TyTcpServerCache.getChannel(serverId); if (channel != null) { channel.close(); log.info("断开服务器 [{}] 连接", serverId); // 断开服务器,修改状态为: 0 - 离线 ardServerService.updateServerStatus(serverId, "0"); TyTcpServerCache.removeChannel(serverId); } TyTcpServerCache.removeTcpServerCache(serverId); } /** * 启动时从数据库加载所有边缘服务器并建立 TCP 连接 */ @PostConstruct public void initConnectServers() { try { var serverList = ardServerService.selectArdServerInfoList(new ArdServerInfo()); // ← 从数据库查询所有服务器配置 if (serverList == null || serverList.isEmpty()) { log.warn("未在数据库中找到边缘服务器配置,跳过连接初始化"); return; } for (ArdServerInfo serverInfo : serverList) { connectServer(serverInfo); } log.info("共初始化 {} 个边缘服务器连接", serverList.size()); } catch (Exception e) { log.error("创建边缘服务器连接异常:{}", e.getMessage(), e); } } @Scheduled(fixedDelay = 10000) // 每 10 秒检查一次 public void reconnectFailedServers() { TyTcpServerCache.getTcpServerCacheMap().forEach((key, state) -> { if (Boolean.FALSE.equals(state)) { // 连接失败的服务器 ArdServerInfo serverInfo = ardServerService.selectArdServerInfoById(key); if (serverInfo != null) { log.warn("服务器 [{}] 连接失败,尝试重连", key); connectServer(serverInfo); } } }); } /** * 定时向所有已连接的六合服务器发送心跳 */ @Scheduled(fixedRate = 5000) // 每5秒执行一次 public void sendHeartBeat() { long timestamp = System.currentTimeMillis(); TyTcpServerCache.getChannels().stream() // 过滤出仍然活跃的连接 .filter(channel -> channel != null && channel.isActive()) .forEach(channel -> { String serverId = (String) channel.attr(AttributeKey.valueOf("serverId")).get(); HeartbeatCmd heartbeat = new HeartbeatCmd(timestamp); channel.writeAndFlush(heartbeat); log.info("→ 向边缘服务器 [{}]发送心跳,时间戳={}", serverId, timestamp); }); } }