fix: 恢复1.20版SaaS连接核心代码,修复Agent无法连接SaaS服务器问题

问题根因:1.21代码优化时误删了SaaS连接核心组件
- 误删 MultiTargetNettyClient.java (Netty TCP客户端)
- 误删 ConnectionConfig.java (连接配置封装)
- 误改 AgentNettyConfig 配置前缀从 netty.server 改为 tcp.netty.charge
- 误删 AppInitializer 中的SaaS连接初始化逻辑
- 误删 AgentService 中的 connection() 等接口方法
- 误增 AgentNettyServer.java (1.20中不存在的服务端类)

修复内容(从1.20原版JAR反编译恢复):
- 恢复 MultiTargetNettyClient: Bootstrap.connect() 主动连接SaaS服务器
- 恢复 ConnectionConfig: 连接配置封装类
- 恢复 AgentNettyConfig: @ConfigurationProperties(prefix = "netty.server")
- 恢复 AppInitializer: CommandLineRunner.run() 调用 initConnection()
- 恢复 AgentService/AgentServiceImpl: connection()方法及全部业务接口
- 恢复 MachineFingerprint: 硬件指纹生成工具类
- 恢复 application-dev.yml: netty.server.host/port 配置
- 删除 AgentNettyServer.java (1.21错误新增)
- 删除旧 AgentNettyConfig.java (tcp.netty.charge 前缀)

部署验证:39.96.218.1 已成功连接SaaS服务器 120.211.95.173:6620
This commit is contained in:
lee
2026-07-22 15:50:03 +08:00
parent 546eef3372
commit ff7cd12fd7
21 changed files with 973 additions and 191 deletions
@@ -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;
}
}
@@ -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;
/**
* 交换机信息
*/
@@ -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;
}
@@ -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;
}
@@ -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<NioSocketChannel>() {
@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);
}
}
@@ -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<String> CONNECTION_KEY = AttributeKey.valueOf("connectionKey");
/** 连接映射表: connectionKey -> Channel */
private final Map<String, Channel> connectionMap = new ConcurrentHashMap<>();
/** 反向映射表: Channel -> connectionKey */
private final Map<Channel, String> 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<SocketChannel>() {
@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<String, String> 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);
}
}
@@ -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;
}
@@ -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);
}
}
@@ -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<String, String> map = JSON.parseObject(netOID, new TypeReference<LinkedHashMap<String, String>>() {});
GlobalConfig.SWITCH_NET_OID = map;
GlobalConfig.SWITCH_NET_OID.clear(); GlobalConfig.SWITCH_NET_OID.putAll(map);
LinkedHashMap<String,String> 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<String, String> map = JSON.parseObject(moduleOID, new TypeReference<LinkedHashMap<String, String>>() {});
GlobalConfig.SWITCH_MODULE_OID = map;
GlobalConfig.SWITCH_MODULE_OID.clear(); GlobalConfig.SWITCH_MODULE_OID.putAll(map);
LinkedHashMap<String,String> 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<String, String> map = JSON.parseObject(mpuOID, new TypeReference<LinkedHashMap<String, String>>() {});
GlobalConfig.SWITCH_MPU_OID = map;
GlobalConfig.SWITCH_MPU_OID.clear(); GlobalConfig.SWITCH_MPU_OID.putAll(map);
LinkedHashMap<String,String> 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<String, String> map = JSON.parseObject(pwrOID, new TypeReference<LinkedHashMap<String, String>>() {});
GlobalConfig.SWITCH_PWR_OID = map;
GlobalConfig.SWITCH_PWR_OID.clear(); GlobalConfig.SWITCH_PWR_OID.putAll(map);
LinkedHashMap<String,String> 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<String, String> map = JSON.parseObject(fanOID, new TypeReference<LinkedHashMap<String, String>>() {});
GlobalConfig.SWITCH_FAN_OID = map;
GlobalConfig.SWITCH_FAN_OID.clear(); GlobalConfig.SWITCH_FAN_OID.putAll(map);
LinkedHashMap<String,String> 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<String, String> map = JSON.parseObject(otherOID, new TypeReference<LinkedHashMap<String, String>>() {});
GlobalConfig.SWITCH_OTHER_OID = map;
GlobalConfig.SWITCH_OTHER_OID.clear(); GlobalConfig.SWITCH_OTHER_OID.putAll(map);
LinkedHashMap<String,String> 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<String> list = JSON.parseObject(netOID, new TypeReference<List<String>>() {});
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<String> list = JSON.parseObject(moduleOID, new TypeReference<List<String>>() {});
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<String> list = JSON.parseObject(mpuOID, new TypeReference<List<String>>() {});
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<String> list = JSON.parseObject(pwrOID, new TypeReference<List<String>>() {});
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<String> list = JSON.parseObject(fanOID, new TypeReference<List<String>>() {});
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<String> list = JSON.parseObject(otherOID, new TypeReference<List<String>>() {});
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<AlarmEO>>() {});
List<AlarmEO> alarmList = JSON.parseObject(alarms, new TypeReference<List<AlarmEO>>() {});
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));
@@ -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秒执行
}
}
}
@@ -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恢复完整实现
}
}
@@ -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();
}
}
@@ -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);
}
/** 审计日志(统一格式,便于事后追溯) */
@@ -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();
}
}
@@ -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<MacVlanVO> upMacvlanStatus();
}
@@ -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<MacVlanVO> upMacvlanStatus() {
return Collections.emptyList();
}
@Override
public void cancelTask(String taskId) {
dynamicTaskService.cancelTask(taskId);
}
}
@@ -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());
}
@@ -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<String,String> oidParams) throws IOException {
private static String getInterfaceInfoByType(Snmp snmp, Target target, String type, Map<String,String> oidParams) throws IOException {
if(oidParams.isEmpty()){
return "";
}
@@ -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";
}
}
}
@@ -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<NetworkInterface> 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);
}
}
}
+17 -10
View File
@@ -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
handshake-timeout-seconds: 10