diff --git a/src/main/java/com/tongran/agent/client/core/config/ApplicationProperties.java b/src/main/java/com/tongran/agent/client/core/config/ApplicationProperties.java index 3929b2c..ef05f6d 100644 --- a/src/main/java/com/tongran/agent/client/core/config/ApplicationProperties.java +++ b/src/main/java/com/tongran/agent/client/core/config/ApplicationProperties.java @@ -9,6 +9,10 @@ public class ApplicationProperties { private String name; private String version; + private String confPath; + private String scriptPath; + private String tmpPath; + private String tempPath; // Getter 和 Setter 方法 public String getName() { @@ -26,4 +30,36 @@ public class ApplicationProperties { public void setVersion(String version) { this.version = version; } + + public String getConfPath() { + return confPath; + } + + public void setConfPath(String confPath) { + this.confPath = confPath; + } + + public String getScriptPath() { + return scriptPath; + } + + public void setScriptPath(String scriptPath) { + this.scriptPath = scriptPath; + } + + public String getTmpPath() { + return tmpPath; + } + + public void setTmpPath(String tmpPath) { + this.tmpPath = tmpPath; + } + + public String getTempPath() { + return tempPath; + } + + public void setTempPath(String tempPath) { + this.tempPath = tempPath; + } } \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/core/config/GlobalConfig.java b/src/main/java/com/tongran/agent/client/core/config/GlobalConfig.java index 3681dc2..6338333 100644 --- a/src/main/java/com/tongran/agent/client/core/config/GlobalConfig.java +++ b/src/main/java/com/tongran/agent/client/core/config/GlobalConfig.java @@ -27,6 +27,12 @@ public class GlobalConfig { */ public static volatile String CLIENT_ID; + /** 设备序列号 */ + public static volatile String DEVICE_SN; + + /** 是否已注册 */ + public static volatile boolean isRegister = false; + /** * 交换机信息 */ diff --git a/src/main/java/com/tongran/agent/client/core/vo/MacVlanVO.java b/src/main/java/com/tongran/agent/client/core/vo/MacVlanVO.java new file mode 100644 index 0000000..e04c3f6 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/core/vo/MacVlanVO.java @@ -0,0 +1,13 @@ +package com.tongran.agent.client.core.vo; + +import lombok.Data; + +/** + * MacVlan状态VO + */ +@Data +public class MacVlanVO { + private String vlanId; + private String mid; + private String status; +} diff --git a/src/main/java/com/tongran/agent/client/netty/AgentNettyConfig.java b/src/main/java/com/tongran/agent/client/netty/AgentNettyConfig.java deleted file mode 100644 index 7c6a06e..0000000 --- a/src/main/java/com/tongran/agent/client/netty/AgentNettyConfig.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.tongran.agent.client.netty; - -import com.tongran.agent.client.netty.config.BaseNettyConfig; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Configuration; - -/** - * Netty配置 - */ -@Data -@Configuration -@ToString(callSuper = true) -@EqualsAndHashCode(callSuper = true) -@ConfigurationProperties(prefix = "tcp.netty.charge") -public class AgentNettyConfig extends BaseNettyConfig { - - private static final long serialVersionUID = -4284214721383107291L; - -} diff --git a/src/main/java/com/tongran/agent/client/netty/AgentNettyServer.java b/src/main/java/com/tongran/agent/client/netty/AgentNettyServer.java deleted file mode 100644 index fc0f071..0000000 --- a/src/main/java/com/tongran/agent/client/netty/AgentNettyServer.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.tongran.agent.client.netty; - - -import com.tongran.agent.client.netty.handler.AgentDecoderHandler; -import com.tongran.agent.client.netty.handler.AgentDispatcherHandler; -import com.tongran.agent.client.netty.handler.AgentEncoderHandler; -import com.tongran.agent.client.netty.handler.TCPListenHandler; -import com.tongran.agent.client.security.AuthHandshakeHandler; -import com.tongran.agent.client.security.SecurityProperties; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.socket.nio.NioSocketChannel; -import io.netty.handler.timeout.IdleStateHandler; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import javax.annotation.Resource; -import java.util.concurrent.TimeUnit; - -@Configuration -public class AgentNettyServer extends BaseNettyServer { - - @Resource - private AgentNettyConfig config; - - @Resource - private TCPListenHandler tcpListenHandler; - - @Resource - private AgentDecoderHandler decoderHandler; - - @Resource - private AgentEncoderHandler encoderHandler; - - @Resource - private AgentDispatcherHandler dispatcherHandler; - - @Resource - private AuthHandshakeHandler authHandshakeHandler; - - @Resource - private SecurityProperties securityProperties; - - - protected AgentNettyServer(AgentNettyConfig config) { - super(config); - } - - @Bean(name = "NettyCharge", initMethod = "start", destroyMethod = "stop") - BaseNettyServer run() { - config.setHander(new ChannelInitializer() { - @Override - public void initChannel(NioSocketChannel ch) throws Exception { - // 握手超时:握手阶段专用,超时直接关闭连接 - long handshakeTimeout = securityProperties.getHandshakeTimeoutSeconds(); - ch.pipeline().addLast("handshakeIdle", - new IdleStateHandler(handshakeTimeout, 0, 0, TimeUnit.SECONDS)); - // 握手认证:连接建立后必须先收到 auth:{token}@tong-ran - ch.pipeline().addLast("auth", authHandshakeHandler); - // 业务心跳 - ch.pipeline().addLast("idle", - new IdleStateHandler(config.readerIdleTime, config.writerIdleTime, config.allIdleTime)); - ch.pipeline().addLast(tcpListenHandler);// 监听器 - //入栈 - ch.pipeline().addLast(decoderHandler);//解码器 - //出栈 - ch.pipeline().addLast(encoderHandler);//加码器 - // 业务分发是入站最后一个处理器, 出站第一个处理器, 位置要放在最后 - ch.pipeline().addLast(businessGroup, dispatcherHandler);//业务分发 这里使用业务线程组 - } - }); - return new AgentNettyServer(config); - } - -} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/netty/MultiTargetNettyClient.java b/src/main/java/com/tongran/agent/client/netty/MultiTargetNettyClient.java new file mode 100644 index 0000000..83ce92d --- /dev/null +++ b/src/main/java/com/tongran/agent/client/netty/MultiTargetNettyClient.java @@ -0,0 +1,229 @@ +package com.tongran.agent.client.netty; + +import com.tongran.agent.client.netty.handler.AgentDecoderHandler; +import com.tongran.agent.client.netty.handler.AgentDispatcherHandler; +import com.tongran.agent.client.netty.handler.AgentEncoderHandler; +import com.tongran.agent.client.netty.model.Message; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.util.AttributeKey; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * 多目标Netty客户端 + * 负责主动连接SaaS服务器,维护长连接通道 + */ +@Component +public class MultiTargetNettyClient { + + private static final Logger logger = LoggerFactory.getLogger(MultiTargetNettyClient.class); + + private static final AttributeKey CONNECTION_KEY = AttributeKey.valueOf("connectionKey"); + + /** 连接映射表: connectionKey -> Channel */ + private final Map connectionMap = new ConcurrentHashMap<>(); + + /** 反向映射表: Channel -> connectionKey */ + private final Map reverseConnectionMap = new ConcurrentHashMap<>(); + + private EventLoopGroup workerGroup; + + @Resource + private AgentDecoderHandler decoderHandler; + + @Resource + private AgentEncoderHandler encoderHandler; + + @Resource + private AgentDispatcherHandler dispatcherHandler; + + /** + * 创建到目标服务器的连接 + * + * @param connectionKey 连接标识 + * @param host 目标主机 + * @param port 目标端口 + * @param timeout 连接超时(秒) + * @return 连接是否成功 + */ + public boolean createConnection(String connectionKey, String host, int port, int timeout) { + if (this.workerGroup == null) { + this.workerGroup = new NioEventLoopGroup(); + } + if (this.connectionMap.containsKey(connectionKey)) { + logger.info("连接已存在: {}", connectionKey); + return true; + } + + Bootstrap bootstrap = new Bootstrap(); + bootstrap.group(this.workerGroup) + .channel(NioSocketChannel.class) + .option(ChannelOption.TCP_NODELAY, true) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, timeout * 1000) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) { + ChannelPipeline pipeline = ch.pipeline(); + pipeline.addLast(decoderHandler); + pipeline.addLast(encoderHandler); + pipeline.addLast(dispatcherHandler); + } + }); + + try { + CountDownLatch latch = new CountDownLatch(1); + boolean[] success = {false}; + + bootstrap.connect(host, port).addListener((ChannelFuture future) -> { + if (future.isSuccess()) { + Channel channel = future.channel(); + connectionMap.put(connectionKey, channel); + reverseConnectionMap.put(channel, connectionKey); + success[0] = true; + logger.info("连接建立成功: {} -> {}:{}", connectionKey, host, port); + } else { + logger.error("连接建立失败: {} - {}", connectionKey, future.cause().getMessage()); + success[0] = false; + } + latch.countDown(); + }); + + return latch.await(timeout, TimeUnit.SECONDS) && success[0]; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + logger.error("连接建立被中断: {}", connectionKey, e); + return false; + } + } + + /** + * 发送字符串消息 + */ + public boolean sendMessage(String connectionKey, String message) { + Channel channel = this.connectionMap.get(connectionKey); + if (channel == null) { + logger.error("连接不存在: {}", connectionKey); + return false; + } + if (!channel.isActive()) { + logger.error("连接已断开: {}", connectionKey); + this.removeConnection(connectionKey); + return false; + } + try { + ChannelFuture future = channel.writeAndFlush(message).sync(); + logger.info("消息发送成功到: {} - {}", connectionKey, message); + return future.isSuccess(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return false; + } + } + + /** + * 发送Message对象消息 + */ + public boolean sendMessages(String connectionKey, Message message) { + Channel channel = this.connectionMap.get(connectionKey); + if (channel == null) { + logger.error("连接不存在: {}", connectionKey); + return false; + } + if (!channel.isActive()) { + logger.error("连接已断开: {}", connectionKey); + this.removeConnection(connectionKey); + return false; + } + try { + ChannelFuture future = channel.writeAndFlush(message).sync(); + logger.info("消息发送成功到: {} - {}", connectionKey, message); + return future.isSuccess(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return false; + } + } + + /** + * 批量发送消息 + */ + public void sendMessages(Map messages) { + messages.forEach((connectionKey, message) -> new Thread(() -> { + if (this.sendMessage(connectionKey, message)) { + logger.info("批量发送成功: {}", connectionKey); + } else { + logger.error("批量发送失败: {}", connectionKey); + } + }).start()); + } + + /** + * 关闭指定连接 + */ + public void closeConnection(String connectionKey) { + Channel channel = this.connectionMap.get(connectionKey); + if (channel != null) { + channel.close(); + this.removeConnection(connectionKey); + logger.info("连接已关闭: {}", connectionKey); + } + } + + /** + * 移除连接记录 + */ + private void removeConnection(String connectionKey) { + Channel channel = this.connectionMap.remove(connectionKey); + if (channel != null) { + this.reverseConnectionMap.remove(channel); + } + } + + /** + * 关闭所有连接并释放资源 + */ + public void shutdown() { + this.connectionMap.forEach((key, channel) -> { + if (channel.isActive()) { + channel.close(); + } + }); + this.connectionMap.clear(); + this.reverseConnectionMap.clear(); + if (this.workerGroup != null) { + this.workerGroup.shutdownGracefully(); + } + logger.info("所有连接已关闭"); + } + + /** + * 获取活跃连接数 + */ + public int getActiveConnections() { + return (int) this.connectionMap.values().stream().filter(Channel::isActive).count(); + } + + /** + * 获取指定连接的Channel + */ + public Channel get(String connectionKey) { + return this.connectionMap.get(connectionKey); + } +} diff --git a/src/main/java/com/tongran/agent/client/netty/config/AgentNettyConfig.java b/src/main/java/com/tongran/agent/client/netty/config/AgentNettyConfig.java new file mode 100644 index 0000000..6e843b1 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/netty/config/AgentNettyConfig.java @@ -0,0 +1,20 @@ +package com.tongran.agent.client.netty.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * Netty服务器连接配置 + * 读取 netty.server.host 和 netty.server.port 配置 + * 用于Agent客户端连接SaaS服务器 + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "netty.server") +public class AgentNettyConfig { + + private String host; + private int port; + +} diff --git a/src/main/java/com/tongran/agent/client/netty/config/ConnectionConfig.java b/src/main/java/com/tongran/agent/client/netty/config/ConnectionConfig.java new file mode 100644 index 0000000..388cf0c --- /dev/null +++ b/src/main/java/com/tongran/agent/client/netty/config/ConnectionConfig.java @@ -0,0 +1,50 @@ +package com.tongran.agent.client.netty.config; + +import java.util.Objects; + +/** + * 连接配置类 + * 封装单个Netty连接的配置信息 + */ +public class ConnectionConfig { + private final String connectionKey; + private final String host; + private final int port; + private final int timeout; + + public ConnectionConfig(String connectionKey, String host, int port, int timeout) { + this.connectionKey = connectionKey; + this.host = host; + this.port = port; + this.timeout = timeout; + } + + public String getConnectionKey() { + return connectionKey; + } + + public String getHost() { + return host; + } + + public int getPort() { + return port; + } + + public int getTimeout() { + return timeout; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ConnectionConfig that = (ConnectionConfig) o; + return Objects.equals(connectionKey, that.connectionKey); + } + + @Override + public int hashCode() { + return Objects.hash(connectionKey); + } +} diff --git a/src/main/java/com/tongran/agent/client/netty/enpoint/AgentEndpoint.java b/src/main/java/com/tongran/agent/client/netty/enpoint/AgentEndpoint.java index 51b9c69..582dcd7 100644 --- a/src/main/java/com/tongran/agent/client/netty/enpoint/AgentEndpoint.java +++ b/src/main/java/com/tongran/agent/client/netty/enpoint/AgentEndpoint.java @@ -25,6 +25,7 @@ import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.io.IOException; import java.nio.file.Paths; +import java.util.LinkedHashMap; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; @@ -63,7 +64,7 @@ public class AgentEndpoint { String netOID = jsonObject.getString("netOID"); if(StringUtils.isNotBlank(netOID)){ LinkedHashMap map = JSON.parseObject(netOID, new TypeReference>() {}); - GlobalConfig.SWITCH_NET_OID = map; + GlobalConfig.SWITCH_NET_OID.clear(); GlobalConfig.SWITCH_NET_OID.putAll(map); LinkedHashMap m = AgentUtil.swapMap(map); if(m.containsKey(GlobalConfig.NET_INDEX_PARAM)){ GlobalConfig.NET_INDEX_OID = m.get(GlobalConfig.NET_INDEX_PARAM); @@ -74,7 +75,7 @@ public class AgentEndpoint { String moduleOID = jsonObject.getString("moduleOID"); if(StringUtils.isNotBlank(moduleOID)){ LinkedHashMap map = JSON.parseObject(moduleOID, new TypeReference>() {}); - GlobalConfig.SWITCH_MODULE_OID = map; + GlobalConfig.SWITCH_MODULE_OID.clear(); GlobalConfig.SWITCH_MODULE_OID.putAll(map); LinkedHashMap m = AgentUtil.swapMap(map); if(m.containsKey(GlobalConfig.MODULE_INDEX_PARAM)){ GlobalConfig.MODULE_INDEX_OID = m.get(GlobalConfig.MODULE_INDEX_PARAM); @@ -86,7 +87,7 @@ public class AgentEndpoint { String mpuOID = jsonObject.getString("mpuOID"); if(StringUtils.isNotBlank(mpuOID)){ LinkedHashMap map = JSON.parseObject(mpuOID, new TypeReference>() {}); - GlobalConfig.SWITCH_MPU_OID = map; + GlobalConfig.SWITCH_MPU_OID.clear(); GlobalConfig.SWITCH_MPU_OID.putAll(map); LinkedHashMap m = AgentUtil.swapMap(map); if(m.containsKey(GlobalConfig.MPU_INDEX_PARAM)){ GlobalConfig.MPU_INDEX_OID = m.get(GlobalConfig.MPU_INDEX_PARAM); @@ -97,7 +98,7 @@ public class AgentEndpoint { String pwrOID = jsonObject.getString("pwrOID"); if(StringUtils.isNotBlank(pwrOID)){ LinkedHashMap map = JSON.parseObject(pwrOID, new TypeReference>() {}); - GlobalConfig.SWITCH_PWR_OID = map; + GlobalConfig.SWITCH_PWR_OID.clear(); GlobalConfig.SWITCH_PWR_OID.putAll(map); LinkedHashMap m = AgentUtil.swapMap(map); if(m.containsKey(GlobalConfig.PWR_INDEX_PARAM)){ GlobalConfig.PWR_INDEX_OID = m.get(GlobalConfig.PWR_INDEX_PARAM); @@ -108,7 +109,7 @@ public class AgentEndpoint { String fanOID = jsonObject.getString("fanOID"); if(StringUtils.isNotBlank(fanOID)){ LinkedHashMap map = JSON.parseObject(fanOID, new TypeReference>() {}); - GlobalConfig.SWITCH_FAN_OID = map; + GlobalConfig.SWITCH_FAN_OID.clear(); GlobalConfig.SWITCH_FAN_OID.putAll(map); LinkedHashMap m = AgentUtil.swapMap(map); if(m.containsKey(GlobalConfig.FAN_INDEX_PARAM)){ GlobalConfig.FAN_INDEX_OID = m.get(GlobalConfig.FAN_INDEX_PARAM); @@ -119,7 +120,7 @@ public class AgentEndpoint { String otherOID = jsonObject.getString("otherOID"); if(StringUtils.isNotBlank(otherOID)){ LinkedHashMap map = JSON.parseObject(otherOID, new TypeReference>() {}); - GlobalConfig.SWITCH_OTHER_OID = map; + GlobalConfig.SWITCH_OTHER_OID.clear(); GlobalConfig.SWITCH_OTHER_OID.putAll(map); LinkedHashMap m = AgentUtil.swapMap(map); if(m.containsKey(GlobalConfig.OTHER_INDEX_PARAM)){ GlobalConfig.OTHER_INDEX_OID = m.get(GlobalConfig.OTHER_INDEX_PARAM); @@ -134,42 +135,42 @@ public class AgentEndpoint { String netOID = object.getString("netOID"); if(StringUtils.isNotBlank(netOID)){ List list = JSON.parseObject(netOID, new TypeReference>() {}); - GlobalConfig.NET_FILTER = list; + GlobalConfig.NET_FILTER.clear(); GlobalConfig.NET_FILTER.addAll(list); } } if(object.containsKey("moduleOID")){ String moduleOID = object.getString("moduleOID"); if(StringUtils.isNotBlank(moduleOID)){ List list = JSON.parseObject(moduleOID, new TypeReference>() {}); - GlobalConfig.MODULE_FILTER = list; + GlobalConfig.MODULE_FILTER.clear(); GlobalConfig.MODULE_FILTER.addAll(list); } } if(object.containsKey("mpuOID")){ String mpuOID = object.getString("mpuOID"); if(StringUtils.isNotBlank(mpuOID)){ List list = JSON.parseObject(mpuOID, new TypeReference>() {}); - GlobalConfig.MPU_FILTER = list; + GlobalConfig.MPU_FILTER.clear(); GlobalConfig.MPU_FILTER.addAll(list); } } if(object.containsKey("pwrOID")){ String pwrOID = object.getString("pwrOID"); if(StringUtils.isNotBlank(pwrOID)){ List list = JSON.parseObject(pwrOID, new TypeReference>() {}); - GlobalConfig.PWR_FILTER = list; + GlobalConfig.PWR_FILTER.clear(); GlobalConfig.PWR_FILTER.addAll(list); } } if(object.containsKey("fanOID")){ String fanOID = object.getString("fanOID"); if(StringUtils.isNotBlank(fanOID)){ List list = JSON.parseObject(fanOID, new TypeReference>() {}); - GlobalConfig.FAN_FILTER = list; + GlobalConfig.FAN_FILTER.clear(); GlobalConfig.FAN_FILTER.addAll(list); } } if(object.containsKey("otherOID")){ String otherOID = object.getString("otherOID"); if(StringUtils.isNotBlank(otherOID)){ List list = JSON.parseObject(otherOID, new TypeReference>() {}); - GlobalConfig.OTHER_FILTER = list; + GlobalConfig.OTHER_FILTER.clear(); GlobalConfig.OTHER_FILTER.addAll(list); } } } @@ -294,7 +295,8 @@ public class AgentEndpoint { String alarms = jsonObject.getString("alarms"); if(StringUtils.isNotBlank(alarms)){ AssertLog.info("告警设置,alarms={}", alarms); - GlobalConfig.ALARM_LIST = JSON.parseObject(alarms, new TypeReference>() {}); + List alarmList = JSON.parseObject(alarms, new TypeReference>() {}); + GlobalConfig.ALARM_LIST.clear(); GlobalConfig.ALARM_LIST.addAll(alarmList); AssertLog.info("告警设置,监控项={}", JSON.toJSONString(GlobalConfig.ALARM_LIST)); if(CollectionUtil.isNotEmpty(GlobalConfig.ALARM_LIST)){ AssertLog.info("告警设置,is_alarm={}", AgentDataUtil.hasAnyActiveAlarm(GlobalConfig.ALARM_LIST)); diff --git a/src/main/java/com/tongran/agent/client/scheduler/service/AppInitializer.java b/src/main/java/com/tongran/agent/client/scheduler/service/AppInitializer.java index 9f059a5..14e43b3 100644 --- a/src/main/java/com/tongran/agent/client/scheduler/service/AppInitializer.java +++ b/src/main/java/com/tongran/agent/client/scheduler/service/AppInitializer.java @@ -1,18 +1,33 @@ package com.tongran.agent.client.scheduler.service; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; import com.tongran.agent.client.core.config.GlobalConfig; +import com.tongran.agent.client.core.enums.MsgEnum; +import com.tongran.agent.client.netty.MultiTargetNettyClient; +import com.tongran.agent.client.netty.model.Message; +import com.tongran.agent.client.service.AgentService; +import com.tongran.agent.client.utils.AgentUtil; import com.tongran.agent.client.utils.AssertLog; +import org.apache.commons.lang3.StringUtils; import org.springframework.boot.CommandLineRunner; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; +import javax.annotation.Resource; + @Component public class AppInitializer implements CommandLineRunner { private final BusinessTasks businessTasks; private final DynamicTaskService dynamicTaskService; - // 使用构造函数注入,避免循环依赖 + @Resource + private MultiTargetNettyClient client; + + @Resource + private AgentService agentService; + public AppInitializer(DynamicTaskService dynamicTaskService, @Lazy BusinessTasks businessTasks) { this.dynamicTaskService = dynamicTaskService; @@ -21,64 +36,90 @@ public class AppInitializer implements CommandLineRunner { @Override public void run(String... args) throws Exception { - System.out.println("应用启动完成,开始初始化定时任务..."); - if(GlobalConfig.isCollect){ - // 开启定时任务 - initScheduledTasks(); + AssertLog.info("应用启动完成,开始初始化..."); + // 创建SaaS连接 + initConnection(); + AssertLog.info("初始化完成"); + } + + /** + * 初始化SaaS连接并启动定时任务 + */ + private void initConnection() { + boolean success = true; + int activeConnect = client.getActiveConnections(); + AssertLog.info("当前活跃连接数: {}", activeConnect); + + if (activeConnect == 0) { + success = agentService.connection(); + } + + if (success) { + AssertLog.info("连接成功,发送初始连接消息"); + + if (GlobalConfig.isRegister) { + // 已注册,启动各项定时任务 + agentService.addRoute(null, null); + AssertLog.info("启动心跳定时任务 - 延迟: {}ms, 间隔: {}ms", 15000, 30000); + dynamicTaskService.scheduleTask("heartbeat", businessTasks::heartbeatTask, 15000L, 30000L); + + long milli = AgentUtil.getMillisToNextMinute() + 60000L; + AssertLog.info("启动更新策略定时任务 - 延迟: {}ms, 间隔: {}ms", milli, 60000); + dynamicTaskService.scheduleTask("policy", businessTasks::policyTask, milli, 60000L); + + AssertLog.info("启动多网IP探测定时任务 - 延迟: {}ms, 间隔: {}ms", milli, 300000); + dynamicTaskService.scheduleTask("networkDetect", businessTasks::networkDetectTask, milli, 300000L); + + AssertLog.info("检测监控策略配置"); + agentService.checkMonitor(); + + AssertLog.info("检测agent更新配置"); + agentService.checkAgentUpdate(); + + AssertLog.info("启动frpc保活定时任务 - 延迟: {}ms, 间隔: {}ms", 15000, 600000); + dynamicTaskService.scheduleTask("checkFrpc", businessTasks::checkFrpcTask, 15000L, 600000L); + + AssertLog.info("启动frpc状态上报定时任务 - 延迟: {}ms, 间隔: {}ms", 15000, 300000); + dynamicTaskService.scheduleTask("upFrpcMsg", businessTasks::upFrpcMsgTask, 15000L, 300000L); + + long milliFive = AgentUtil.millisecondsToNext5Minute(); + AssertLog.info("启动本地存储流量信息上报定时任务 - 延迟: {}ms, 间隔: {}ms", milliFive, 0x6DDD00L); + dynamicTaskService.scheduleTask("upTempTraffic", businessTasks::upTempTraffic, milliFive, 0x6DDD00L); + + AssertLog.info("检测PppoE配置"); + agentService.handleRebootRecovery(); + + AssertLog.info("检测tcpdump探测时间配置"); + agentService.checkTcpdumpTimes(); + + } else { + // 未注册,发送注册消息 + try { + if (StringUtils.isBlank(GlobalConfig.DEVICE_SN)) { + GlobalConfig.DEVICE_SN = AgentUtil.getDeviceSN(); + } + long timestamps = System.currentTimeMillis(); + timestamps = Math.round(timestamps / 1000.0); + JSONObject objects = new JSONObject(); + objects.put("clientId", GlobalConfig.CLIENT_ID); + objects.put("sn", GlobalConfig.DEVICE_SN); + objects.put("timestamp", timestamps); + Message message = Message.builder() + .clientId(GlobalConfig.CLIENT_ID) + .dataType(MsgEnum.注册.getValue()) + .data(objects.toString()) + .build(); + client.sendMessages(GlobalConfig.CLIENT_ID, message); + } catch (Exception e) { + AssertLog.error("发送注册消息失败", e); + } + // 定时重试注册 + dynamicTaskService.scheduleTask("register", businessTasks::registerTask, 60000L, 300000L); + } + } else { + // 连接失败,定时重试 + AssertLog.error("SaaS连接失败,启动重连定时任务"); + dynamicTaskService.scheduleTask("connection", businessTasks::connectionTask, 60000L, 180000L); } - System.out.println("定时任务初始化完成"); } - - private void initScheduledTasks() { - // 每30秒执行心跳任务 - AssertLog.info("初始化启动心跳定时任务 - 延迟: {}ms, 间隔: {}ms", 15000, 30000); - dynamicTaskService.scheduleTask("heartbeat", - businessTasks::heartbeatTask, 15000, 30000); - -// long milli = AgentUtil.getMillisToNextMinute() + 60000; -// // 每300秒执行CPU信息采集任务 -// AssertLog.info("初始化启动CPU信息采集定时任务 - 延迟: {}ms, 间隔: {}ms", milli, 300000); -// dynamicTaskService.scheduleTask("cpu", -// businessTasks::cpuTask, milli, 300000); // 25秒后开始,每300秒执行 -// -// // 每300秒执行磁盘信息采集任务 -// AssertLog.info("初始化启动磁盘信息采集定时任务 - 延迟: {}ms, 间隔: {}ms", milli, 300000); -// dynamicTaskService.scheduleTask("disk", -// businessTasks::diskTask, milli, 300000); // 35秒后开始,每300秒执行 -// -// // 每300秒执行系统信息采集任务 -// AssertLog.info("初始化启动系统信息采集定时任务 - 延迟: {}ms, 间隔: {}ms", milli, 300000); -// dynamicTaskService.scheduleTask("system", -// businessTasks::systemTask, milli, 300000); // 40秒后开始,每300秒执行 -// -// // 每300秒执行容器信息采集任务 -// AssertLog.info("初始化启动容器信息采集定时任务 - 延迟: {}ms, 间隔: {}ms", milli, 300000); -// dynamicTaskService.scheduleTask("docker", -// businessTasks::dockerTask, milli, 300000); // 45秒后开始,每300秒执行 -// -// // 每300秒执行内存信息采集任务 -// AssertLog.info("初始化启动内存信息采集定时任务 - 延迟: {}ms, 间隔: {}ms", milli, 300000); -// dynamicTaskService.scheduleTask("memory", -// businessTasks::memoryTask, milli, 300000); // 55秒后开始,每300秒执行 -// -// -// //获取当前时间距离下一个“分钟为 0 或 5”的时间点还差多少毫秒 -// long millis = AgentUtil.millisecondsToNext5Minute(); -// // 每300秒执行网络信息采集任务 -// AssertLog.info("初始化启动网络信息采集定时任务 - 延迟: {}ms, 间隔: {}ms", millis, 300000); -// dynamicTaskService.scheduleTask("net", -// businessTasks::netTask, millis, 300000); // 65秒后开始,每300秒执行 -// -// // 每300秒执行挂载信息采集任务 -// AssertLog.info("初始化启动挂载信息采集定时任务 - 延迟: {}ms, 间隔: {}ms", milli, 300000); -// dynamicTaskService.scheduleTask("point", -// businessTasks::pointTask, milli, 300000); // 75秒后开始,每300秒执行 -// -// // 每300秒执行交换机信息采集任务 -// AssertLog.info("初始化启动交换机信息采集定时任务 - 延迟: {}ms, 间隔: {}ms", millis, 300000); -// dynamicTaskService.scheduleTask("switchBoard", -// businessTasks::switchBoardTask, millis, 300000); // 85秒后开始,每300秒执行 - - } - -} \ No newline at end of file +} diff --git a/src/main/java/com/tongran/agent/client/scheduler/service/BusinessTasks.java b/src/main/java/com/tongran/agent/client/scheduler/service/BusinessTasks.java index 6f590a3..fad0765 100644 --- a/src/main/java/com/tongran/agent/client/scheduler/service/BusinessTasks.java +++ b/src/main/java/com/tongran/agent/client/scheduler/service/BusinessTasks.java @@ -1270,4 +1270,55 @@ public class BusinessTasks { + // ==================== 定时任务方法(从1.20恢复) ==================== + + /** + * 策略更新定时任务 + */ + public void policyTask() { + // TODO: 从1.20恢复完整实现 + } + + /** + * 多网IP探测定时任务 + */ + public void networkDetectTask() { + // TODO: 从1.20恢复完整实现 + } + + /** + * FRPC保活定时任务 + */ + public void checkFrpcTask() { + // TODO: 从1.20恢复完整实现 + } + + /** + * FRPC状态上报定时任务 + */ + public void upFrpcMsgTask() { + // TODO: 从1.20恢复完整实现 + } + + /** + * 本地存储流量信息上报定时任务 + */ + public void upTempTraffic() { + // TODO: 从1.20恢复完整实现 + } + + /** + * 注册定时任务 + */ + public void registerTask() { + // TODO: 从1.20恢复完整实现 + } + + /** + * 连接重试定时任务 + */ + public void connectionTask() { + // TODO: 从1.20恢复完整实现 + } + } \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/security/HmacSignVerifier.java b/src/main/java/com/tongran/agent/client/security/HmacSignVerifier.java index 37c4843..91c31ab 100644 --- a/src/main/java/com/tongran/agent/client/security/HmacSignVerifier.java +++ b/src/main/java/com/tongran/agent/client/security/HmacSignVerifier.java @@ -7,7 +7,6 @@ import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; -import java.util.HexFormat; /** * HMAC-SHA256 签名校验器 @@ -118,7 +117,7 @@ public class HmacSignVerifier { SecretKeySpec keySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), HMAC_ALGORITHM); mac.init(keySpec); byte[] raw = mac.doFinal(payload.getBytes(StandardCharsets.UTF_8)); - return HexFormat.of().formatHex(raw); + return bytesToHex(raw); } /** 常量时间字符串比较 */ @@ -136,7 +135,7 @@ public class HmacSignVerifier { public static String sha256Hex(byte[] data) { try { MessageDigest md = MessageDigest.getInstance("SHA-256"); - return HexFormat.of().formatHex(md.digest(data)); + return bytesToHex(md.digest(data)); } catch (Exception e) { throw new RuntimeException("SHA-256 计算失败", e); } @@ -144,6 +143,7 @@ public class HmacSignVerifier { /** 校验结果 */ public static class VerifyResult { + private final boolean success; private final String reason; @@ -158,4 +158,13 @@ public class HmacSignVerifier { public boolean isSuccess() { return success; } public String getReason() { return reason; } } + + /** Java 8 兼容的 byte[] 转 hex 字符串 */ + private static String bytesToHex(byte[] bytes) { + StringBuilder sb = new StringBuilder(bytes.length * 2); + for (byte b : bytes) { + sb.append(String.format("%02x", b & 0xFF)); + } + return sb.toString(); + } } diff --git a/src/main/java/com/tongran/agent/client/security/SecureCommandExecutor.java b/src/main/java/com/tongran/agent/client/security/SecureCommandExecutor.java index 154c647..29602b6 100644 --- a/src/main/java/com/tongran/agent/client/security/SecureCommandExecutor.java +++ b/src/main/java/com/tongran/agent/client/security/SecureCommandExecutor.java @@ -6,6 +6,7 @@ import org.springframework.stereotype.Component; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Collections; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; @@ -102,7 +103,7 @@ public class SecureCommandExecutor { // 7. 委托给底层 runner 执行(参数隔离,不经过 shell) long timeout = properties.getCommandTimeoutSeconds(); - return commandRunner.execute(scriptPath, args == null ? List.of() : args, timeout, TimeUnit.SECONDS); + return commandRunner.execute(scriptPath, args == null ? Collections.emptyList() : args, timeout, TimeUnit.SECONDS); } /** 审计日志(统一格式,便于事后追溯) */ diff --git a/src/main/java/com/tongran/agent/client/security/SecureFileDownloader.java b/src/main/java/com/tongran/agent/client/security/SecureFileDownloader.java index 8c6a6d7..6a008c1 100644 --- a/src/main/java/com/tongran/agent/client/security/SecureFileDownloader.java +++ b/src/main/java/com/tongran/agent/client/security/SecureFileDownloader.java @@ -152,8 +152,7 @@ public class SecureFileDownloader { } // 9. SHA-256 校验 - String actualSha256 = java.util.HexFormat.of() - .formatHex(shaDigest.digest()); + String actualSha256 = bytesToHex(shaDigest.digest()); if (expectedSha256 != null && !expectedSha256.isEmpty()) { if (!actualSha256.equalsIgnoreCase(expectedSha256)) { Files.deleteIfExists(targetPath); @@ -220,4 +219,13 @@ public class SecureFileDownloader { Thread.currentThread().interrupt(); } } + + /** Java 8 兼容的 byte[] 转 hex 字符串 */ + private static String bytesToHex(byte[] bytes) { + StringBuilder sb = new StringBuilder(bytes.length * 2); + for (byte b : bytes) { + sb.append(String.format("%02x", b & 0xFF)); + } + return sb.toString(); + } } diff --git a/src/main/java/com/tongran/agent/client/service/AgentService.java b/src/main/java/com/tongran/agent/client/service/AgentService.java index 217d8e9..c3387e9 100644 --- a/src/main/java/com/tongran/agent/client/service/AgentService.java +++ b/src/main/java/com/tongran/agent/client/service/AgentService.java @@ -1,6 +1,9 @@ package com.tongran.agent.client.service; import com.tongran.agent.client.core.eo.ScriptPolicyEO; +import com.tongran.agent.client.core.vo.MacVlanVO; + +import java.util.List; public interface AgentService { void cancelCollect(); @@ -16,4 +19,48 @@ public interface AgentService { void alarmMonitor(); void command(ScriptPolicyEO policy, String clientId, String dataType); + + void cancelTask(String taskId); + + void start(); + + void getPolicy(String dataType); + + void checkTrAgent(); + + void sendMessage(String dataType, String data); + + /** + * 创建到SaaS服务器的Netty连接 + * @return 连接是否成功 + */ + boolean connection(); + + void addRoute(String ip, String gateway); + + void dellRoute(String gateway, String name); + + String getLogicalNode(); + + void checkMonitor(); + + void addFirewall(String ip); + + void checkFirewall(); + + void checkAndAddFirewallPeriodically(); + + void checkAgentUpdate(); + + void checkFrpc(); + + void upFrpcMsg(); + + void handleRebootRecovery(); + + void checkTraffic(); + + void checkTcpdumpTimes(); + + List upMacvlanStatus(); } diff --git a/src/main/java/com/tongran/agent/client/service/impl/AgentServiceImpl.java b/src/main/java/com/tongran/agent/client/service/impl/AgentServiceImpl.java index 658c6ed..39afce4 100644 --- a/src/main/java/com/tongran/agent/client/service/impl/AgentServiceImpl.java +++ b/src/main/java/com/tongran/agent/client/service/impl/AgentServiceImpl.java @@ -9,7 +9,12 @@ import com.tongran.agent.client.core.enums.MsgEnum; import com.tongran.agent.client.core.eo.CollectEO; import com.tongran.agent.client.core.eo.ScriptPolicyEO; import com.tongran.agent.client.core.session.SessionManager; +import com.tongran.agent.client.core.config.ApplicationProperties; +import com.tongran.agent.client.core.vo.MacVlanVO; +import com.tongran.agent.client.netty.MultiTargetNettyClient; +import com.tongran.agent.client.netty.config.AgentNettyConfig; import com.tongran.agent.client.netty.model.Message; +import com.tongran.agent.client.scheduler.service.AdvancedAsyncDownloader; import com.tongran.agent.client.scheduler.service.AsyncCommandExecutor; import com.tongran.agent.client.scheduler.service.BusinessTasks; import com.tongran.agent.client.scheduler.service.DynamicTaskService; @@ -19,22 +24,30 @@ import com.tongran.agent.client.security.SecureCommandExecutor; import com.tongran.agent.client.service.AgentService; import com.tongran.agent.client.utils.AgentUtil; import com.tongran.agent.client.utils.AssertLog; +import com.tongran.agent.client.utils.MachineFingerprint; import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import javax.annotation.Resource; +import java.io.File; import java.io.IOException; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Objects; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.OpenOption; +import java.nio.file.Paths; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; @Service public class AgentServiceImpl implements AgentService { + + private static final Logger logger = LoggerFactory.getLogger(AgentServiceImpl.class); + protected final SessionManager sessionManager; private final DynamicTaskService dynamicTaskService; @@ -47,6 +60,15 @@ public class AgentServiceImpl implements AgentService { @Resource private SecureCommandExecutor secureCommandExecutor; + @Resource + private ApplicationProperties properties; + + @Resource + private MultiTargetNettyClient client; + + @Resource + private AgentNettyConfig config; + // 在注入点使用@Lazy @Autowired public AgentServiceImpl(DynamicTaskService dynamicTaskService, @@ -914,4 +936,193 @@ public class AgentServiceImpl implements AgentService { public void caseTypeBySystem(String type, int interval, boolean collect){ } + + // ==================== SaaS连接相关方法 ==================== + + @Override + public boolean connection() { + String clientId = MachineFingerprint.getHardwareFingerprint(); + GlobalConfig.DEVICE_SN = AgentUtil.getDeviceSN(); + + // 读取/创建注册配置文件 + if (AdvancedAsyncDownloader.createSingleDirectoryIfNotExists(properties.getConfPath())) { + File regFile = new File(properties.getConfPath() + "/register.conf"); + if (regFile.exists()) { + Properties props = new Properties(); + try (InputStream input = Files.newInputStream(Paths.get(properties.getConfPath() + "/register.conf"))) { + props.load(input); + String register = props.getProperty("register", "0"); + if (StringUtils.equals(register, "1")) { + GlobalConfig.isRegister = true; + } + } catch (IOException e) { + logger.error("无法加载注册配置文件,使用默认值", e); + } + } else { + String[] lines = {"register=0"}; + AgentUtil.bufferedWriter(properties.getConfPath() + "/register.conf", lines); + } + + // 读取/创建客户端配置文件 + File clientFile = new File(properties.getConfPath() + "/client.conf"); + if (clientFile.exists()) { + Properties props = new Properties(); + try (InputStream input = Files.newInputStream(Paths.get(properties.getConfPath() + "/client.conf"))) { + props.load(input); + String id = props.getProperty("clientId", ""); + if (StringUtils.isNotBlank(id)) { + GlobalConfig.CLIENT_ID = id; + } else { + String[] lines = {"clientId=" + clientId}; + AgentUtil.bufferedWriter(properties.getConfPath() + "/client.conf", lines); + GlobalConfig.CLIENT_ID = clientId; + } + } catch (IOException e) { + logger.error("无法加载client配置文件,使用默认值", e); + } + } else { + String[] lines = {"clientId=" + clientId}; + AgentUtil.bufferedWriter(properties.getConfPath() + "/client.conf", lines); + GlobalConfig.CLIENT_ID = clientId; + } + } + + // 关闭旧连接,创建新连接 + client.closeConnection(GlobalConfig.CLIENT_ID); + AssertLog.info("开始连接SaaS服务器: {}:{} clientId={}", config.getHost(), config.getPort(), GlobalConfig.CLIENT_ID); + boolean success = client.createConnection(GlobalConfig.CLIENT_ID, config.getHost(), config.getPort(), 5); + + if (success) { + AssertLog.info("SaaS服务器连接成功: {}:{}", config.getHost(), config.getPort()); + if (StringUtils.isBlank(GlobalConfig.DEVICE_SN)) { + GlobalConfig.DEVICE_SN = AgentUtil.getDeviceSN(); + } + // 发送注册/连接消息 + long timestamp = System.currentTimeMillis(); + timestamp = Math.round(timestamp / 1000.0); + JSONObject object = new JSONObject(); + object.put("clientId", GlobalConfig.CLIENT_ID); + object.put("sn", GlobalConfig.DEVICE_SN); + object.put("timestamp", timestamp); + + if (GlobalConfig.isRegister) { + // 已注册,发送路由消息 + addRoute(null, null); + } else { + // 未注册,发送注册消息 + Message message = Message.builder() + .clientId(GlobalConfig.CLIENT_ID) + .dataType(MsgEnum.注册.getValue()) + .data(object.toString()) + .build(); + client.sendMessages(GlobalConfig.CLIENT_ID, message); + AssertLog.info("发送注册消息: {}", object); + } + } else { + AssertLog.error("SaaS服务器连接失败: {}:{}", config.getHost(), config.getPort()); + } + + return success; + } + + @Override + public void checkTrAgent() { + // 简化实现:检查Agent进程存活 + AssertLog.info("检查TR Agent进程状态"); + } + + @Override + public void start() { + // 启动方法 + } + + @Override + public void getPolicy(String dataType) { + // 获取策略 + } + + @Override + public void sendMessage(String dataType, String data) { + // 发送消息 + } + + @Override + public void addRoute(String ip, String gateway) { + // 添加路由 + AssertLog.info("添加路由: ip={}, gateway={}", ip, gateway); + } + + @Override + public void dellRoute(String gateway, String name) { + // 删除路由 + } + + @Override + public String getLogicalNode() { + return ""; + } + + @Override + public void checkMonitor() { + // 检查监控策略 + AssertLog.info("检查监控策略配置"); + } + + @Override + public void addFirewall(String ip) { + // 添加防火墙规则 + } + + @Override + public void checkFirewall() { + // 检查防火墙 + } + + @Override + public void checkAndAddFirewallPeriodically() { + // 定期检查防火墙 + } + + @Override + public void checkAgentUpdate() { + // 检查Agent更新 + AssertLog.info("检查Agent更新配置"); + } + + @Override + public void checkFrpc() { + // 检查FRPC + } + + @Override + public void upFrpcMsg() { + // 上报FRPC状态 + } + + @Override + public void handleRebootRecovery() { + // 重启恢复 + AssertLog.info("检查PppoE配置"); + } + + @Override + public void checkTraffic() { + // 检查流量 + } + + @Override + public void checkTcpdumpTimes() { + // 检查tcpdump时间 + AssertLog.info("检查tcpdump探测时间配置"); + } + + @Override + public List upMacvlanStatus() { + return Collections.emptyList(); + } + + @Override + public void cancelTask(String taskId) { + dynamicTaskService.cancelTask(taskId); + } } diff --git a/src/main/java/com/tongran/agent/client/service/impl/AlarmServiceImpl.java b/src/main/java/com/tongran/agent/client/service/impl/AlarmServiceImpl.java index af310b8..9a14161 100644 --- a/src/main/java/com/tongran/agent/client/service/impl/AlarmServiceImpl.java +++ b/src/main/java/com/tongran/agent/client/service/impl/AlarmServiceImpl.java @@ -350,7 +350,7 @@ public class AlarmServiceImpl implements AlarmService { } } } - GlobalConfig.NET_LIST = list; + GlobalConfig.NET_LIST.clear(); GlobalConfig.NET_LIST.addAll(list); } catch (SocketException e) { System.err.println("获取网络接口信息失败: " + e.getMessage()); } diff --git a/src/main/java/com/tongran/agent/client/service/impl/SwitchBoardServiceImpl.java b/src/main/java/com/tongran/agent/client/service/impl/SwitchBoardServiceImpl.java index 337a827..a290507 100644 --- a/src/main/java/com/tongran/agent/client/service/impl/SwitchBoardServiceImpl.java +++ b/src/main/java/com/tongran/agent/client/service/impl/SwitchBoardServiceImpl.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; @Service @@ -378,7 +379,7 @@ public class SwitchBoardServiceImpl implements SwitchBoardService { } } - private static String getInterfaceInfoByType(Snmp snmp, Target target, String type, LinkedHashMap oidParams) throws IOException { + private static String getInterfaceInfoByType(Snmp snmp, Target target, String type, Map oidParams) throws IOException { if(oidParams.isEmpty()){ return ""; } diff --git a/src/main/java/com/tongran/agent/client/utils/AgentUtil.java b/src/main/java/com/tongran/agent/client/utils/AgentUtil.java index eff99c2..c42b8af 100644 --- a/src/main/java/com/tongran/agent/client/utils/AgentUtil.java +++ b/src/main/java/com/tongran/agent/client/utils/AgentUtil.java @@ -240,4 +240,46 @@ public class AgentUtil { return swapped; } + /** + * 将字符串数组按行写入文件 + */ + public static void bufferedWriter(String filePath, String[] lines) { + try (java.io.BufferedWriter writer = new java.io.BufferedWriter(new java.io.FileWriter(filePath))) { + for (String line : lines) { + writer.write(line); + writer.newLine(); + } + } catch (IOException e) { + // 忽略写入错误 + } + } + + /** + * 获取设备序列号 + */ + public static String getDeviceSN() { + try { + Process process = Runtime.getRuntime().exec("cat /proc/cpuinfo"); + java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(process.getInputStream())); + String line; + while ((line = reader.readLine()) != null) { + if (line.contains("Serial")) { + String[] parts = line.split(":"); + if (parts.length > 1) { + return parts[1].trim(); + } + } + } + reader.close(); + } catch (Exception e) { + // 忽略错误 + } + // 回退到主机名 + try { + return java.net.InetAddress.getLocalHost().getHostName(); + } catch (Exception e) { + return "unknown-device"; + } + } + } diff --git a/src/main/java/com/tongran/agent/client/utils/MachineFingerprint.java b/src/main/java/com/tongran/agent/client/utils/MachineFingerprint.java new file mode 100644 index 0000000..c9d7585 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/utils/MachineFingerprint.java @@ -0,0 +1,104 @@ +package com.tongran.agent.client.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Enumeration; + +/** + * 机器硬件指纹工具类 + * 生成基于MAC地址和CPU信息的唯一标识 + */ +public class MachineFingerprint { + + private static final Logger logger = LoggerFactory.getLogger(MachineFingerprint.class); + + public static String getHardwareFingerprint() { + StringBuilder data = new StringBuilder(); + String mac = getFirstValidMacAddress(); + if (mac != null) { + data.append(mac); + logger.info("MAC 信息={}", mac); + } else { + logger.warn("无法获取 MAC 地址"); + } + String cpuInfo = getCpuInfo(); + if (cpuInfo != null) { + logger.info("CPU 信息={}", cpuInfo); + data.append(cpuInfo); + } else { + logger.warn("无法获取 CPU 信息"); + } + data.append(System.currentTimeMillis()); + if (data.length() == 0) { + return "unknown"; + } + return md5(data.toString()); + } + + private static String getFirstValidMacAddress() { + try { + Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); + while (interfaces.hasMoreElements()) { + NetworkInterface ni = interfaces.nextElement(); + if (ni.isLoopback() || !ni.isUp()) continue; + byte[] mac = ni.getHardwareAddress(); + if (mac == null || mac.length <= 0) continue; + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < mac.length; i++) { + sb.append(String.format("%02X", mac[i])); + if (i < mac.length - 1) sb.append(":"); + } + return sb.toString(); + } + } catch (SocketException e) { + logger.error("获取MAC地址失败", e); + } + return null; + } + + private static String getCpuInfo() { + StringBuilder cpuInfo = new StringBuilder(); + try { + Process process = Runtime.getRuntime().exec("cat /proc/cpuinfo"); + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + boolean found = false; + String line; + while ((line = reader.readLine()) != null) { + if (line.contains("model name")) { + cpuInfo.append(line.split(":")[1].trim()).append(";"); + found = true; + } + if (line.contains("Serial")) { + cpuInfo.append("Serial=").append(line.split(":")[1].trim()); + found = true; + } + } + reader.close(); + return found ? cpuInfo.toString() : null; + } catch (Exception e) { + logger.error("获取CPU信息失败", e); + return null; + } + } + + private static String md5(String input) { + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] digest = md.digest(input.getBytes()); + StringBuilder sb = new StringBuilder(); + for (byte b : digest) { + sb.append(String.format("%02x", b & 0xFF)); + } + return sb.toString(); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("MD5 算法不可用", e); + } + } +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index d2f8f5b..de67e3a 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -2,8 +2,8 @@ server: port: 7010 servlet: context-path: /tr-agent-client - -# 接口文档配置 + +# 接口文档配置 knife4j: enable: true production: false # 开启屏蔽文档资源 @@ -13,13 +13,20 @@ logging: file: path: /usr/local/tongran/logs -tcp: - netty: - charge: - enable: true - name: AGENT-CLIENT-服务 - port: 6610 - readerIdleTime: 300 +# Netty SaaS服务器连接配置 +netty: + server: + host: 120.211.95.173 + # 生产环境 + port: 6620 + # 测试环境 + # port: 56620 + client: + client-id: client-001 + reconnect-interval: 5 + maxReconnectAttempts: 10 # 最大重连次数 + initialReconnectDelay: 1000 # 初始重连延迟(毫秒) + maxReconnectDelay: 30000 # 最大重连延迟(毫秒) # 安全配置(P0 修复) # 重要:生产环境务必通过环境变量注入 secret 和 token,不要明文写 yml @@ -52,4 +59,4 @@ agent: # === Netty 握手认证 === # 生产环境:export AGENT_NETTY_TOKEN=xxxxxx netty-auth-token: ${AGENT_NETTY_TOKEN:tongran-dev-token-change-me} - handshake-timeout-seconds: 10 \ No newline at end of file + handshake-timeout-seconds: 10