Compare commits

63 Commits

Author SHA1 Message Date
gaoyutao e145974aad 区分配置文件 2026-03-30 18:00:10 +08:00
gaoyutao 5e4ebf5c9c 1、优化iptables规则增删逻辑。 2026-03-20 17:55:02 +08:00
gaoyutao e786599310 1、优化出省流量统计采集。
2、优化业务流量数据统计。
2026-03-19 17:55:25 +08:00
gaoyutao 30d44561d1 cgroup+iptables获取业务流量方法优化为区分多个进程模式。 2026-03-17 18:00:03 +08:00
gaoyutao 2ba1134c82 优化内存使用 2026-03-12 18:04:06 +08:00
gaoyutao e60f14ab11 增加流量最高的进程流量采集,优化ping缓存。 2026-03-10 17:49:25 +08:00
gaoyutao 26d8ef298a 增加采集进程流量方法 2026-03-05 17:54:15 +08:00
gaoyutao 630558f4d6 历史流量信息最后一个文件延迟1分钟处理 2026-02-28 18:00:00 +08:00
gaoyutao f453f17903 优化ip公网探测降级逻辑。
优化网络流量重试个别文件发送失败逻辑。
2026-02-27 17:49:01 +08:00
gaoyutao 3ff73519f9 增加采集内存详情、健康状态方法 2026-02-25 17:52:50 +08:00
gaoyutao 95922cac09 优化磁盘健康信息采集 2026-02-09 18:05:02 +08:00
gaoyutao 01fd405e0b 增加nvme类型健康分数计算 2026-02-06 17:11:30 +08:00
gaoyutao 3e7c9ec5cf tcpdump探测ip剔除223.5.5.5。
磁盘增加磁盘健康状态采集。
2026-02-05 18:15:43 +08:00
gaoyutao 04a51bc201 tcpdump策略增加立即执行、优化tcpdump探测 2026-01-30 19:18:40 +08:00
gaoyutao c5353aaafd 优化磁盘数据采集。 2026-01-28 18:57:53 +08:00
gaoyutao 057c879b59 增加tcpdump探测 2026-01-22 17:57:23 +08:00
gaoyutao 421b7f1073 调试agent本地存储流量功能 2026-01-21 18:12:35 +08:00
gaoyutao 3820d11353 1、开发agent本地存储流量信息到文件功能。
2、开发agent每隔2小时上报本地存储文件功能。
2026-01-20 18:05:24 +08:00
gaoyutao 6c5010119f 增加ping丢包率采集 2026-01-19 18:18:16 +08:00
gaoyutao 158fddd0b2 增加cpu温度采集 2026-01-16 18:10:00 +08:00
gaoyutao f5c3ae500b 1、开发agent采集磁盘类型、磁盘已用空间方法。
2、开发磁盘测试IOPS方法。
3、开发磁盘分区卸载方法
2026-01-15 18:16:34 +08:00
gaoyutao f9a4de8c97 多网卡ip探测增加网卡创建时间字段上报 2026-01-12 18:28:32 +08:00
gaoyutao b4155ea9c8 优化agent客户端网关探测 2026-01-09 19:18:44 +08:00
gaoyutao bb87f80593 优化pppoe数据处理。 2026-01-08 18:24:48 +08:00
gaoyutao fc97e66dc2 优化PppoE配置逻辑
增加非saas平台拨号数据处理
2026-01-07 19:17:24 +08:00
gaoyutao b9afa99623 优化agent多网卡ip探测功能 2026-01-06 19:31:07 +08:00
gaoyutao 8bcc017d22 优化公网ip解析。 2026-01-04 18:11:58 +08:00
gaoyutao 0f649a2cfc pppoe功能增加 2025-12-30 18:21:12 +08:00
gaoyutao 2ad6ea2ad5 优化注册重试 2025-12-26 18:32:03 +08:00
gaoyutao 13968d60aa 优化frpc保活 2025-12-25 18:06:23 +08:00
gaoyutao cebcf56adf 优化frpc保活 2025-12-24 19:57:09 +08:00
gaoyutao 6dbee99194 增加frpc保活、状态上报方法 2025-12-23 18:56:36 +08:00
gaoyutao ed801a05c9 增加frpc配置文件处理 2025-12-22 18:53:49 +08:00
gaoyutao 1c1cdad5e0 agent网卡探测方法修改。
网卡流量探测兼容没配IP的
2025-12-17 18:20:41 +08:00
gaoyutao a9ca7f7ca6 agent网卡探测方法修改。
网卡流量探测兼容没配IP的
2025-12-16 20:58:53 +08:00
gaoyutao 154e57a0a6 增加子网卡机器注册兼容逻辑。 2025-12-15 19:51:18 +08:00
gaoyutao eebc2de645 1、优化agent注册解析公网ip超时问题。
2、优化agent注册网卡上报逻辑。
2025-12-12 16:05:55 +08:00
gaoyutao 196961ff11 优化防火墙策略定时任务检查 2025-12-05 18:56:12 +08:00
gaoyutao 5f67018d35 agent定时更新优化、脚本策略定时执行优化
优化ipv4、ipv6采集
2025-12-04 18:28:59 +08:00
gaoyutao 45840e00e3 开发防火墙策略区分ipv4和ipv6。 2025-12-03 19:19:02 +08:00
gaoyutao 1b3ec66803 sn获取方式改变,ipv4临时路由添加异常处理完善 2025-12-01 11:03:56 +08:00
gaoyutao 620c97ee5b 解决server端断联之后,client端重启导致一直建立连接重试且不读取旧配置文件问题 2025-11-26 11:48:40 +08:00
qiminbao 73cfc796d4 增加日志打印输出 2025-11-07 18:48:11 +08:00
qiminbao 9c3db28249 网卡采集,接收、发送带宽数据调整为总字节 2025-11-05 17:20:50 +08:00
qiminbao b8b85d0e76 操作系统、架构 数据返回调整 2025-11-05 10:21:03 +08:00
qiminbao 504cf8f65b 保活定时任务调整 2025-11-04 20:05:51 +08:00
qiminbao eb7fba8529 多网IP探测优化 2025-10-31 18:19:10 +08:00
qiminbao 8175f939f8 多网IP探测优化 2025-10-31 17:18:30 +08:00
qiminbao b433c8480e 多网IP探测-过滤无网关网卡 2025-10-31 16:07:26 +08:00
qiminbao c12efbf410 内存利用率数据优化 2025-10-31 14:24:45 +08:00
qiminbao b2d6f2f655 内存利用率数据优化 2025-10-31 14:16:45 +08:00
qiminbao 4669573373 内存利用率数据优化 2025-10-31 13:16:32 +08:00
qiminbao ae5c50e5b4 1.监控策略配置文件格式优化 2025-10-30 19:31:12 +08:00
qiminbao c4bb76049a 1.启动增加检查监控策略机制;
2.cpu上报数据单位优化
2025-10-30 17:41:33 +08:00
qiminbao 85ab66fe6e 1.启动增加检查监控策略机制;
2.cpu上报数据单位优化
2025-10-30 16:43:45 +08:00
qiminbao 9c08f97347 1.启动增加检查监控策略机制;
2.cpu上报数据单位优化
2025-10-30 16:37:08 +08:00
qiminbao 8237833624 日志时间UTC增加时区 2025-10-30 11:31:00 +08:00
qiminbao 1a58e37e7c 中文乱码 2025-10-28 17:34:59 +08:00
qiminbao 72af737163 增加断开重连 2025-10-28 17:07:51 +08:00
qiminbao a2e3e296b5 增加多网IP探测上报;
优化公网运营商信息;
逻辑标识改为读取外部文件;
增加定时任务清理日志临时文件;
2025-10-28 14:24:19 +08:00
qiminbao 1485d5d46c v1.1初始化 2025-10-27 10:27:48 +08:00
qiminbao 62c8736728 v1.1初始化 2025-10-21 14:16:25 +08:00
qiminbao 36ff2dbb48 v1.0.2初始化 2025-10-10 10:20:25 +08:00
60 changed files with 9443 additions and 2754 deletions
+46
View File
@@ -0,0 +1,46 @@
######################################################################
# Build Tools
.gradle
/build/
!gradle/wrapper/gradle-wrapper.jar
target/
!.mvn/wrapper/maven-wrapper.jar
######################################################################
# IDE
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### JRebel ###
rebel.xml
### NetBeans ###
nbproject/private/
build/*
nbbuild/
dist/
nbdist/
.nb-gradle/
######################################################################
# Others
*.log
*.xml.versionsBackup
*.swp
!*/build/*.java
!*/build/*.html
!*/build/*.xml
+9
View File
@@ -143,6 +143,15 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- 打包跳过测试 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
</build>
@@ -3,9 +3,13 @@ package com.tongran.agent.client;
import com.tongran.agent.client.core.config.GlobalConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
@ComponentScan(basePackages = {"com.tongran.agent.client", "cn.hutool.extra.spring"})
public class TrAgentClientApplication {
public static void main(String[] args) {
@@ -1,29 +1,22 @@
package com.tongran.agent.client.core.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "spring.application")
@Data
public class ApplicationProperties {
private String name;
private String version;
// Getter 和 Setter 方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
private String confPath;
private String scriptPath;
private String tmpPath;
private String tempPath;
private String frpPath;
private String poePath;
private String tempTrafficPath;
private String tempPcapPath;
}
@@ -3,6 +3,7 @@ package com.tongran.agent.client.core.config;
import com.tongran.agent.client.core.eo.AlarmEO;
import com.tongran.agent.client.core.eo.NativeNetworkInterfaceEO;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
@@ -14,51 +15,58 @@ import java.util.concurrent.ConcurrentHashMap;
*/
public class GlobalConfig {
/**
* pppoe配置方式(1静态ip配置,2非saas平台配置)
*/
public static String PPPOEMODE = "";
/**
* 业务网卡名称
*/
public static String NETNAME = "";
/**
* tcpdump探测时间
*/
public static String TCPDUMP_TIMES = "";
/**
* 服务启动时间
*/
public static long startupTime;
/**
* 注册标识
*/
public static boolean isRegister = false;
/**
* 客户端ID - 对应平台唯一SN
* 客户端ID
*/
public static String CLIENT_ID;
/**
* 交换机信息
* 设备SN
*/
public static String SWITCH_COMMUNITY; //团名
public static String SWITCH_IP; //交换机IP
public static int SWITCH_PORT; //交换机端口
public static LinkedHashMap<String, String> SWITCH_NET_OID = new LinkedHashMap<>(); //交换机网络端口发现OID
public static LinkedHashMap<String, String> SWITCH_MODULE_OID = new LinkedHashMap<>(); //交换机光模块发现OID
public static LinkedHashMap<String, String> SWITCH_MPU_OID = new LinkedHashMap<>(); //交换机MPU发现OID
public static LinkedHashMap<String, String> SWITCH_PWR_OID = new LinkedHashMap<>(); //交换机电源发现OID
public static LinkedHashMap<String, String> SWITCH_FAN_OID = new LinkedHashMap<>(); //交换机风扇发现OID
public static LinkedHashMap<String, String> SWITCH_OTHER_OID = new LinkedHashMap<>(); //交换机系统其他OID
public static String OTHER_INDEX_PARAM = "entIndex"; //交换机其他监控项索引参数
public static String OTHER_INDEX_OID = "";
public static List<String> OTHER_FILTER = new ArrayList<>(); //过滤值
public static String NET_INDEX_PARAM = "ifIndex"; //交换机网络端口索引参数
public static String NET_INDEX_OID = "";
public static List<String> NET_FILTER = new ArrayList<>(); //过滤值
public static String MODULE_INDEX_PARAM = "fiberEntIndex"; //交换机光模块端口索引参数
public static String MODULE_INDEX_OID = "";
public static List<String> MODULE_FILTER = new ArrayList<>(); //过滤值
public static String MPU_INDEX_PARAM = "mpuEntIndex"; //交换机MPU索引参数
public static String MPU_INDEX_OID = "";
public static List<String> MPU_FILTER = new ArrayList<>(); //过滤值
public static String PWR_INDEX_PARAM = "pwrEntIndex"; //交换机电源索引参数
public static String PWR_INDEX_OID = "";
public static List<String> PWR_FILTER = new ArrayList<>(); //过滤值
public static String FAN_INDEX_PARAM = "fanEntIndex"; //交换机风扇索引参数
public static String FAN_INDEX_OID = "";
public static List<String> FAN_FILTER = new ArrayList<>(); //过滤值
public static String DEVICE_SN;
/**
* 系统 HZ (每秒tick数)
*/
public static Integer systemHz = null;
/**
* 逻辑标识
*/
public static LocalDateTime LOGICAL_NODE_LAST_TIME;
public static String LOGICAL_NODE;
/**
* 最新策略信息
*/
public static long MONITOR_TIME = 0L;
public static long SCRIPT_TIME = 0L;
public static long VERSION_TIME = 0L;
public static long ROUTE_TIME = 0L;
/**
* 采集标识
*/
@@ -114,51 +122,6 @@ public class GlobalConfig {
public static long systemUptimeInterval = 300; //系统正常运行时间采集间隔
public static boolean procNumCollect = false; //进程数采集
public static long procNumInterval = 300; //进程数采集间隔
/**
* 采集交换机监控
*/
public static boolean switchNetCollect = false; //交换机网络采集
public static long switchNetInterval = 300; //交换机网络采集间隔
public static boolean switchModuleCollect = false; //光模块采集
public static long switchModuleInterval = 300; //光模块采集间隔
public static boolean switchMpuCollect = false; //MPU采集
public static long switchMpuInterval = 300; //MPU采集间隔
public static boolean switchPwrCollect = false; //电源采集
public static long switchPwrInterval = 300; //电源采集间隔
public static boolean switchFanCollect = false; //风扇采集
public static long switchFanInterval = 300; //风扇采集间隔
/**
* 采集交换机其他监控
*/
public static boolean switchSysDescrCollect = false; //系统描述采集
public static long switchSysDescrInterval = 300; //系统描述采集间隔
public static boolean switchSysObjectIDCollect = false; //系统Object ID采集
public static long switchSysObjectIDInterval = 300; //系统Object ID采集间隔
public static boolean switchSysUpTimeCollect = false; //系统运行时间采集
public static long switchSysUpTimeInterval = 300; //系统运行时间采集间隔
public static boolean switchSysContactCollect = false; //系统联系信息采集
public static long switchSysContactInterval = 300; //系统联系信息采集间隔
public static boolean switchSysNameCollect = false; //系统名称采集
public static long switchSysNameInterval = 300; //系统名称采集间隔
public static boolean switchSysLocationCollect = false; //系统位置采集
public static long switchSysLocationInterval = 300; //系统位置采集间隔
public static boolean switchHwStackSystemMacCollect = false; //系统MAC地址采集
public static long switchHwStackSystemMacInterval = 300; //系统MAC地址采集间隔
public static boolean switchEntIndexCollect = false; //设备索引采集
public static long switchEntIndexInterval = 300; //设备索引采集间隔
public static boolean switchEntPhysicalNameCollect = false; //设备名称采集
public static long switchEntPhysicalNameInterval = 300; //设备名称采集间隔
public static boolean switchEntPhysicalSoftwareRevCollect = false; //设备软件版本采集
public static long switchEntPhysicalSoftwareRevInterval = 300; //设备软件版本采集间隔
public static boolean switchHwEntityCpuUsageCollect = false; //设备CPU使用率(%)采集
public static long switchHwEntityCpuUsageInterval = 300; //设备CPU使用率(%)采集间隔
public static boolean switchHwEntityMemUsageCollect = false; //设备内存使用率(%)采集
public static long switchHwEntityMemUsageInterval = 300; //设备内存使用率(%)采集间隔
public static boolean switchHwAveragePowerCollect = false; //系统平均功率(mW)采集
public static long switchHwAveragePowerInterval = 300; //系统平均功率(mW)采集间隔
public static boolean switchHwCurrentPowerCollect = false; //系统实时功率(mW)采集
public static long switchHwCurrentPowerInterval = 300; //系统实时功率(mW)采集间隔
/**
* 告警
@@ -166,18 +129,6 @@ public class GlobalConfig {
public static boolean IS_ALARM = false;
public static long ALARM_INTERVAL = 60;
public static List<AlarmEO> ALARM_LIST = new ArrayList<>(); //告警设置信息
/**
* 告警监控
*/
public static boolean systemCpuUti = false; //CPU使用率
public static boolean memoryUtilization = false; //内存利用率
public static boolean systemSwapSizePercent = false; //可用交换空间百分比
public static boolean systemUsersNum = false; //登录用户数
public static boolean vfsFsUtil = false; //挂载点的空间利用率
public static boolean netIfStatus = false; //网络运行状态UP变down
public static boolean netUtil = false; //网络带宽使用率
public static boolean containerMemUtil = false; //容器内存使用率
public static boolean extraPorts = false; //多余端口
/**
* 所有网络接口
@@ -11,11 +11,18 @@ import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
public enum MsgEnum {
建立连接("CONNECT"),
建立连接应答("CONNECT_RSP"),
注册("REGISTER"),
注册应答("REGISTER_RSP"),
获取最新策略("GET_POLICY"),
获取最新策略应答("GET_POLICY_RSP"),
断开("DISCONNECT"),
断开应答("DISCONNECT_RSP"),
@@ -32,6 +39,10 @@ public enum MsgEnum {
网络上报("NET"),
网络上报重试("NET_RECOVER"),
业务网络上报("BUSINESS_NET"),
挂载上报("POINT"),
交换机上报("SWITCHBOARD"),
@@ -48,14 +59,6 @@ public enum MsgEnum {
关闭所有系统采集应答("SYSTEM_COLLECT_STOP_RSP"),
开启或更新交换机采集("SWITCH_COLLECT_START"),
开启或更新交换机采集应答("SWITCH_COLLECT_START_RSP"),
关闭所有交换机采集("SWITCH_COLLECT_STOP"),
关闭所有交换机采集应答("SWITCH_COLLECT_STOP_RSP"),
告警设置("ALARM_SET"),
告警设置应答("ALARM_SET_RSP"),
@@ -66,7 +69,19 @@ public enum MsgEnum {
Agent版本更新("AGENT_VERSION_UPDATE"),
Agent版本更新应答("AGENT_VERSION_UPDATE_RSP");
Agent版本更新应答("AGENT_VERSION_UPDATE_RSP"),
修改frp配置文件应答("UPDATE_FRP_RSP"),
macvlan状态上报("MACVLAN_STATUS_RSP"),
iops结果上报("IOPS_RESULT"),
tcpdump结果上报("TCPDUMP_RESULT"),
内存详情上报("MEMORY_DETAILS"),
多网IP探测上报("NETWORK_DETECT");
private String value;
@@ -5,8 +5,6 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@@ -16,11 +14,8 @@ public class AgentVersionUpdateEO {
//文件地址,外网HTTPS)地址
private String fileUrl;
//保存文件地址
private String filePath;
//命令
private List<String> commands;
//文件MD5
private String fileMd5;
//执行方式:0、立即执行;1、定时执行;
private int method;
@@ -0,0 +1,15 @@
package com.tongran.agent.client.core.eo;
import lombok.Data;
@Data
public class FrpMsgEO {
/** 远程端口 */
private Integer remotePort;
/** 服务器端口 */
private String serverPort;
/** 服务器地址 */
private String serverAddr;
}
@@ -0,0 +1,21 @@
package com.tongran.agent.client.core.eo;
import lombok.Data;
@Data
public class PppoeConfigSubEO {
/** 序号 */
private Long serialNumber;
/** VLANID */
private Long vlanId;
/** IPv4地址 */
private String ipv4Address;
/** IPv4掩码位数 */
private Long ipv4MaskBits;
/** IPv4网关 */
private String ipv4Gateway;
}
@@ -0,0 +1,19 @@
package com.tongran.agent.client.core.eo;
import lombok.Data;
import java.util.List;
@Data
public class PppoeEO {
/** 是否启用PPPoE(0-否,1-是) */
private Long pppoeEnabled;
/** PPPoE配置方式 */
private String pppoeConfigMode;
/** PPPoE网卡名称 */
private String pppoeInterface;
/** pppoe子表集合 */
private List<PppoeConfigSubEO> subList;
}
@@ -6,7 +6,6 @@ import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
@Data
@Builder
@@ -16,17 +15,15 @@ public class ScriptPolicyEO implements Serializable {
private static final long serialVersionUID = -1267013167162440612L;
private String scriptId;
private String policyName;
/**
* 文件
*/
private List<ScriptFile> files;
//目标路径地址
private String filePath;
//文件类型为1、外网HTTPS)时必传
private String fileUrl;
//命令
private List<String> commands;
//脚本参数
private String commandParams;
//执行方式:0、立即执行;1、定时执行;
private int method;
@@ -34,25 +31,4 @@ public class ScriptPolicyEO implements Serializable {
//执行方式为1、定时执行时必传-指定时间
private long policyTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class ScriptFile{
//文件类型:0、平台文件地址;1、外网HTTP(S)
private int fileType;
//文件类型为1、外网HTTP(S)时必传
private String fileUrl;
//文件类型为0、平台文件地址时必传(文件名)
private String fileName;
//文件类型为0、平台文件地址时必传(文件流)
private String fileData;
}
}
@@ -0,0 +1,17 @@
package com.tongran.agent.client.core.eo;
import lombok.Data;
import java.util.List;
@Data
public class TcpdumpEO {
/** 开启探测(0:否,1:是) */
private Integer detectFlag;
/** 探测频率1 每天 2立即执行 */
private String frequency;
/** 探测时间列表(多个时间用,分隔) */
private String detectTimes;
}
@@ -25,8 +25,8 @@ public class CpuVO implements Serializable {
@Schema(description = "CPU15分钟负载")
private double avg15;
@Schema(description = "CPU硬件中断提供服务时间")
private long interrupt;
@Schema(description = "CPU硬件中断提供服务时间:秒")
private double interrupt;
@Schema(description = "CPU使用率%")
private double uti;
@@ -34,23 +34,29 @@ public class CpuVO implements Serializable {
@Schema(description = "CPU数量")
private int num;
@Schema(description = "CPU正常运行时间/秒")
@Schema(description = "CPU核数")
private int cores;
@Schema(description = "CPU正常运行时间:秒")
private long normal;
@Schema(description = "CPU空闲时间")
private long idle;
@Schema(description = "CPU空闲时间:秒")
private double idle;
@Schema(description = "CPU等待响应时间")
@Schema(description = "CPU等待响应时间:秒")
private double iowait;
@Schema(description = "CPU系统时间")
private long system;
@Schema(description = "CPU系统时间:秒")
private double system;
@Schema(description = "CPU软件无响应时间")
private double noresp;
@Schema(description = "CPU用户进程所花费的时间")
private long user;
@Schema(description = "CPU用户进程所花费的时间:秒")
private double user;
@Schema(description = "cpu温度")
private double temperature;
@Schema(description = "时间戳")
private long timestamp;
@@ -45,6 +45,15 @@ public class DiskVO implements Serializable {
@Schema(description = "时间戳")
private long timestamp;
@Schema(description = "磁盘类型")
private String type;
@Schema(description = "已用空间")
private long usedSpace;
/** 健康状态 0 不健康,1健康 */
@Schema(description = "健康状态")
private Long healthStatus;
@Schema(description = "健康分数")
private Integer healthScore;
}
@@ -0,0 +1,13 @@
package com.tongran.agent.client.core.vo;
import lombok.Data;
@Data
public class MacVlanVO {
/** 虚拟网卡id */
private String vlanId;
/** 编号 */
private String mid;
/** 状态 */
private String status;
}
@@ -0,0 +1,20 @@
package com.tongran.agent.client.core.vo;
import lombok.Data;
@Data
public class MemoryDetailsVO {
private String name; // 名称(如 DIMM000
private String location; // 位置(mainboard
private String manufacturer; // 厂商(Samsung
private Long capacity; // 容量(MB,如 32768
private Integer frequency; // 主频(MHz,如 2133
private String serialNumber; // 序列号(如 0x32A1EA07
private String type; // 类型(DDR4
private Integer minVoltage; // 最小电压(mV,如 1200
private String rank; // RANK(列)(如 4 rank
private String bitWidth; // 位宽(如 72 bit
private String technology; // 技术(Synchronous|Registered (B N/A
private String partNumber; // 部件编码(如 M386A4G40DM0-CPB
private String healthStatus; // 健康状态(1正常/0配置错误)
}
@@ -0,0 +1,42 @@
package com.tongran.agent.client.core.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import java.io.Serializable;
@Data
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "网络业务信息")
public class NetBusinessVO implements Serializable {
private static final long serialVersionUID = 9L;
private String name; // 网卡名称
private String mac; // mac地址
private int pid; // 进程ID
private String processName; // 进程名
private Long inSpeed; // 累计接收字节数
private Long outSpeed; // 累计发送字节数
/** IPv4接收流量 */
private Long ipv4InSpeed;
/** IPv4发送流量 */
private Long ipv4OutSpeed;
/** IPv6接收流量 */
private Long ipv6InSpeed;
/** IPv6发送流量 */
private Long ipv6OutSpeed;
private int connectionCount; // 当前连接数
private long timestamp; // 采集时间戳
}
@@ -31,18 +31,36 @@ public class NetVO implements Serializable {
@Schema(description = "IPv4")
private String ipV4;
@Schema(description = "IPv6")
private String ipV6;
@Schema(description = "入站丢包")
private long inDropped;
@Schema(description = "出站丢包")
private long outDropped;
@Schema(description = "发送流量")
@Schema(description = "ping丢包率")
private Double pingDropped;
@Schema(description = "发送流量(发送总字节)")
private long outSpeed;
@Schema(description = "接收流量")
@Schema(description = "接收流量(接收总字节)")
private long inSpeed;
@Schema(description = "IPv4接收流量")
private Long ipv4InSpeed;
@Schema(description = "IPv4发送流量")
private Long ipv4OutSpeed;
@Schema(description = "IPv6接收流量")
private Long ipv6InSpeed;
@Schema(description = "IPv6发送流量")
private Long ipv6OutSpeed;
@Schema(description = "协商速度")
private String speed;
@@ -51,5 +69,7 @@ public class NetVO implements Serializable {
@Schema(description = "时间戳")
private long timestamp;
/** 是否最后一次 */
private boolean lastTrafficFlag;
}
}
@@ -0,0 +1,36 @@
package com.tongran.agent.client.core.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import java.util.List;
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "网卡信息")
public class NetworkInterfaceInfo {
String name; // 接口名称:eth0, enp3s0
String mac; // MAC 地址
String type; // 接口类型:Ethernet
String ipv4; // IPv4 地址
String gateway; // 网关
String publicIp; // 公网 IP
String carrier; // 运营商
String province; // 省
String city; // 市
String ipv6; // 市
String status; // 与外网连通状态
// 如果是子接口,存储父接口名称
private String parentInterface;
/** 网卡创建时间 */
private Long netCreateTime;
// 如果是父接口,存储子接口列表
private List<NetworkInterfaceInfo> subInterfaces;
}
@@ -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,57 +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 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;
@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;
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 {
ch.pipeline().addLast(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,301 @@
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.*;
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.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;
@Component
public class MultiTargetNettyClient {
// 连接标识属性键
private static final AttributeKey<String> CONNECTION_KEY = AttributeKey.valueOf("connectionKey");
// 连接管理器
private final Map<String, Channel> connectionMap = new ConcurrentHashMap<>();
private final Map<Channel, String> reverseConnectionMap = new ConcurrentHashMap<>();
private EventLoopGroup workerGroup;
@Resource
private AgentDecoderHandler decoderHandler;
@Resource
private AgentEncoderHandler encoderHandler;
@Resource
private AgentDispatcherHandler dispatcherHandler;
/**
* 动态创建TCP连接
* @param connectionKey 连接唯一标识(格式:ip:port 或自定义)
* @param host 目标主机
* @param port 目标端口
* @param timeout 超时时间(秒)
* @return 是否创建成功
*/
public boolean createConnection(String connectionKey, String host, int port, int timeout) {
if (workerGroup == null) {
workerGroup = new NioEventLoopGroup();
}
if (connectionMap.containsKey(connectionKey)) {
System.out.println("连接已存在: " + connectionKey);
return true;
}
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(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(new IdleStateHandler(0, 30, 0, TimeUnit.SECONDS));
// 添加自定义处理器
pipeline.addLast(dispatcherHandler);
}
});
try {
CountDownLatch latch = new CountDownLatch(1);
final 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;
System.out.println("连接建立成功: " + connectionKey + " -> " + host + ":" + port);
} else {
System.err.println("连接建立失败: " + connectionKey + " - " + future.cause().getMessage());
success[0] = false;
}
latch.countDown();
});
// 等待连接结果
return latch.await(timeout, TimeUnit.SECONDS) && success[0];
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
/**
* 向指定连接发送消息
* @param connectionKey 连接标识
* @param message 消息内容
* @return 是否发送成功
*/
public boolean sendMessage(String connectionKey, String message) {
Channel channel = connectionMap.get(connectionKey);
if (channel == null) {
System.err.println("连接不存在: " + connectionKey);
return false;
}
if (!channel.isActive()) {
System.err.println("连接已断开: " + connectionKey);
removeConnection(connectionKey);
return false;
}
try {
ChannelFuture future = channel.writeAndFlush(message).sync();
System.out.println("消息发送成功到: " + connectionKey + " - " + message);
return future.isSuccess();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
public boolean sendMessages(String connectionKey, Message message) {
Channel channel = connectionMap.get(connectionKey);
if (channel == null) {
System.err.println("连接不存在: " + connectionKey);
return false;
}
if (!channel.isActive()) {
System.err.println("连接已断开: " + connectionKey);
removeConnection(connectionKey);
return false;
}
try {
ChannelFuture future = channel.writeAndFlush(message).sync();
System.out.println("消息发送成功到: " + connectionKey + " - " + message);
return future.isSuccess();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
/**
* 批量发送消息到不同连接
* @param messages Map<连接标识, 消息内容>
*/
public void sendMessages(Map<String, String> messages) {
messages.forEach((connectionKey, message) -> {
new Thread(() -> {
if (sendMessage(connectionKey, message)) {
System.out.println("批量发送成功: " + connectionKey);
} else {
System.err.println("批量发送失败: " + connectionKey);
}
}).start();
});
}
/**
* 关闭指定连接
* @param connectionKey 连接标识
*/
public void closeConnection(String connectionKey) {
Channel channel = connectionMap.get(connectionKey);
if (channel != null) {
channel.close();
removeConnection(connectionKey);
System.out.println("连接已关闭: " + connectionKey);
}
}
/**
* 移除连接记录
* @param connectionKey 连接标识
*/
private void removeConnection(String connectionKey) {
Channel channel = connectionMap.remove(connectionKey);
if (channel != null) {
reverseConnectionMap.remove(channel);
}
}
/**
* 关闭所有连接
*/
public void shutdown() {
// 关闭所有连接
connectionMap.forEach((key, channel) -> {
if (channel.isActive()) {
channel.close();
}
});
connectionMap.clear();
reverseConnectionMap.clear();
// 关闭EventLoopGroup
if (workerGroup != null) {
workerGroup.shutdownGracefully();
}
System.out.println("所有连接已关闭");
}
/**
* 获取连接状态
* @return 当前活跃连接数
*/
public int getActiveConnections() {
return (int) connectionMap.values().stream()
.filter(Channel::isActive)
.count();
}
// /**
// * 客户端处理器
// */
// private class ClientHandler extends SimpleChannelInboundHandler<String> {
// @Override
// protected void channelRead0(ChannelHandlerContext ctx, String msg) {
// String connectionKey = ctx.channel().attr(CONNECTION_KEY).get();
// System.out.println("收到来自 " + connectionKey + " 的消息: " + msg);
// }
//
// @Override
// public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// String connectionKey = ctx.channel().attr(CONNECTION_KEY).get();
// System.err.println("连接 " + connectionKey + " 发生异常: " + cause.getMessage());
// ctx.close();
// }
//
// @Override
// public void channelInactive(ChannelHandlerContext ctx) {
// String connectionKey = ctx.channel().attr(CONNECTION_KEY).get();
// System.out.println("连接断开: " + connectionKey);
// removeConnection(connectionKey);
// }
// }
/**
* 使用示例
*/
public static void main(String[] args) throws InterruptedException {
MultiTargetNettyClient client = new MultiTargetNettyClient();
try {
// 动态创建多个不同IP和端口的连接
boolean success1 = client.createConnection("server1", "127.0.0.1", 8080, 5);
boolean success2 = client.createConnection("server2", "192.168.1.100", 8081, 5);
boolean success3 = client.createConnection("server3", "10.0.0.50", 8082, 5);
// 等待连接建立
Thread.sleep(2000);
if (success1) {
client.sendMessage("server1", "Hello Server 1 from 8080");
}
if (success2) {
client.sendMessage("server2", "Hello Server 2 from 8081");
}
if (success3) {
client.sendMessage("server3", "Hello Server 3 from 8082");
}
// // 批量发送示例
// Map<String, String> batchMessages = Map.of(
// "server1", "Batch message to server 1",
// "server2", "Batch message to server 2",
// "server3", "Batch message to server 3"
// );
//
// client.sendMessages(batchMessages);
// 保持运行一段时间
Thread.sleep(5000);
System.out.println("当前活跃连接数: " + client.getActiveConnections());
} finally {
client.shutdown();
}
}
public Channel get(String connectionKey){
Channel channel = connectionMap.get(connectionKey);
return channel;
}
}
@@ -0,0 +1,16 @@
package com.tongran.agent.client.netty.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "netty.server")
public class AgentNettyConfig {
private String host;
private int port;
}
@@ -0,0 +1,39 @@
package com.tongran.agent.client.netty.config;
import java.util.Objects;
/**
* 连接配置信息
*/
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;
}
// Getter方法
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);
}
}
@@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.TypeReference;
import com.tongran.agent.client.core.config.ApplicationProperties;
import com.tongran.agent.client.core.config.GlobalConfig;
import com.tongran.agent.client.core.enums.MsgEnum;
import com.tongran.agent.client.core.eo.AgentVersionUpdateEO;
@@ -13,8 +14,6 @@ import com.tongran.agent.client.netty.annotation.AgentDispatcher;
import com.tongran.agent.client.netty.basics.AgentHandler;
import com.tongran.agent.client.netty.model.UpMsgResponse;
import com.tongran.agent.client.scheduler.service.AdvancedAsyncDownloader;
import com.tongran.agent.client.scheduler.service.AppInitializer;
import com.tongran.agent.client.scheduler.task.SpecificTimeTaskService;
import com.tongran.agent.client.service.AgentService;
import com.tongran.agent.client.utils.AgentDataUtil;
import com.tongran.agent.client.utils.AgentUtil;
@@ -24,262 +23,89 @@ import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.List;
@Component
public class AgentEndpoint {
@Resource
private AppInitializer appInitializer;
@Resource
private SpecificTimeTaskService taskService;
@Resource
private AgentService agentService;
@Resource
private ApplicationProperties properties;
@AgentDispatcher(msgId = MsgEnum.注册)
@AgentDispatcher(msgId = MsgEnum.建立连接应答)
public class ConnectHandler implements AgentHandler {
@Override
public UpMsgResponse upHandle(String data, String clientId) {
JSONObject json = new JSONObject();
json.put("resCode",1);
return UpMsgResponse.builder().clientId(clientId).dataType(MsgEnum.建立连接应答.getValue())
.content(json.toString()).build();
}
}
@AgentDispatcher(msgId = MsgEnum.注册应答)
public class RegisterHandler implements AgentHandler {
@Override
public UpMsgResponse upHandle(String data, String clientId) {
//更改全局变量
JSONObject jsonObject = JSONObject.parseObject(data);
if(jsonObject.containsKey("switchBoard")){
String switchBoard = jsonObject.getString("switchBoard");
jsonObject = JSONObject.parseObject(switchBoard);
if(jsonObject.containsKey("community")){
GlobalConfig.SWITCH_COMMUNITY = jsonObject.getString("community");
}
if(jsonObject.containsKey("ip")){
GlobalConfig.SWITCH_IP = jsonObject.getString("ip");
}
if(jsonObject.containsKey("port")){
GlobalConfig.SWITCH_PORT = jsonObject.getInteger("port");
}
if(jsonObject.containsKey("netOID")){
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;
LinkedHashMap<String,String> m = AgentUtil.swapMap(map);
if(m.containsKey(GlobalConfig.NET_INDEX_PARAM)){
GlobalConfig.NET_INDEX_OID = m.get(GlobalConfig.NET_INDEX_PARAM);
JSONObject object = JSONObject.parseObject(data);
int resCode = 0;
if(object.containsKey("resCode")){
resCode = object.getInteger("resCode");
}
//注册成功,更新外置文件注册标识
if(resCode == 1){
String addRoute = "";
if(object.containsKey("addRoute")) {
addRoute = object.getString("addRoute");
AssertLog.info("注册成功,路由addRoute={}",addRoute);
if(StringUtils.isNotBlank(addRoute)){
JSONObject addRouteJson = JSONObject.parseObject(addRoute);
String name = "";
String gateway = "";
if(addRouteJson.containsKey("name")){
name = addRouteJson.getString("name");
}
if(addRouteJson.containsKey("gateway")){
gateway = addRouteJson.getString("gateway");
}
if(StringUtils.isNotBlank(name) && StringUtils.isNotBlank(gateway)){
agentService.addRoute(name,gateway);
}
}
}
if(jsonObject.containsKey("moduleOID")){
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;
LinkedHashMap<String,String> m = AgentUtil.swapMap(map);
if(m.containsKey(GlobalConfig.MODULE_INDEX_PARAM)){
GlobalConfig.MODULE_INDEX_OID = m.get(GlobalConfig.MODULE_INDEX_PARAM);
}
}
}
if(jsonObject.containsKey("mpuOID")){
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;
LinkedHashMap<String,String> m = AgentUtil.swapMap(map);
if(m.containsKey(GlobalConfig.MPU_INDEX_PARAM)){
GlobalConfig.MPU_INDEX_OID = m.get(GlobalConfig.MPU_INDEX_PARAM);
}
if(object.containsKey("netName")){
String netName = object.getString("netName");;
AssertLog.info("注册成功,业务网卡={}",netName);
if(StringUtils.isNotBlank(netName)){
// 添加ipv4 ipv6防火墙策略
agentService.addFirewall(netName);
}
}
if(jsonObject.containsKey("pwrOID")){
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;
LinkedHashMap<String,String> m = AgentUtil.swapMap(map);
if(m.containsKey(GlobalConfig.PWR_INDEX_PARAM)){
GlobalConfig.PWR_INDEX_OID = m.get(GlobalConfig.PWR_INDEX_PARAM);
}
}
}
if(jsonObject.containsKey("fanOID")){
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;
LinkedHashMap<String,String> m = AgentUtil.swapMap(map);
if(m.containsKey(GlobalConfig.FAN_INDEX_PARAM)){
GlobalConfig.FAN_INDEX_OID = m.get(GlobalConfig.FAN_INDEX_PARAM);
}
}
}
if(jsonObject.containsKey("otherOID")){
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;
LinkedHashMap<String,String> m = AgentUtil.swapMap(map);
if(m.containsKey(GlobalConfig.OTHER_INDEX_PARAM)){
GlobalConfig.OTHER_INDEX_OID = m.get(GlobalConfig.OTHER_INDEX_PARAM);
}
}
}
if(jsonObject.containsKey("filters")){
String filters = jsonObject.getString("filters");
if(StringUtils.isNotBlank(filters)){
JSONObject object = JSONObject.parseObject(filters);
if(object.containsKey("netOID")){
String netOID = object.getString("netOID");
if(StringUtils.isNotBlank(netOID)){
List<String> list = JSON.parseObject(netOID, new TypeReference<List<String>>() {});
GlobalConfig.NET_FILTER = 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;
}
}
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;
}
}
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;
}
}
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;
}
}
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;
}
}
}
//检查外置目录是否存在
if(AdvancedAsyncDownloader.createSingleDirectoryIfNotExists(properties.getConfPath())){
String[] lines = {
"register=1"
};
AgentUtil.bufferedWriter(properties.getConfPath()+"/register.conf",lines);
}
GlobalConfig.isRegister = true;
//取消注册定时任务
agentService.cancelTask("register");
agentService.start();
}
GlobalConfig.isCollect = true;
GlobalConfig.CLIENT_ID = clientId;
//调用采集任务
try {
appInitializer.run();
} catch (Exception e) {
e.printStackTrace();
}
AssertLog.info("SWITCH_NET_OID={}",JSON.toJSONString(GlobalConfig.SWITCH_NET_OID));
AssertLog.info("SWITCH_MODULE_OID={}",JSON.toJSONString(GlobalConfig.SWITCH_MODULE_OID));
AssertLog.info("SWITCH_MPU_OID={}",JSON.toJSONString(GlobalConfig.SWITCH_MPU_OID));
AssertLog.info("SWITCH_PWR_OID={}",JSON.toJSONString(GlobalConfig.SWITCH_PWR_OID));
AssertLog.info("SWITCH_FAN_OID={}",JSON.toJSONString(GlobalConfig.SWITCH_FAN_OID));
AssertLog.info("SWITCH_OTHER_OID={}",JSON.toJSONString(GlobalConfig.SWITCH_OTHER_OID));
long timestamp = System.currentTimeMillis();
timestamp = Math.round(timestamp / 1000.0);
JSONObject json = new JSONObject();
json.put("resCode",1);
json.put("resMag","");
json.put("timestamp",timestamp);
return UpMsgResponse.builder().clientId(clientId).dataType(MsgEnum.注册应答.getValue())
.content(json.toString()).build();
return null;
}
}
@AgentDispatcher(msgId = MsgEnum.断开)
public class DisconnectHandler implements AgentHandler {
@AgentDispatcher(msgId = MsgEnum.获取最新策略应答)
public class GetPolicyRspHandler implements AgentHandler {
@Override
public UpMsgResponse upHandle(String data, String clientId) {
//更改全局变量
GlobalConfig.isCollect = false;
//调用采集任务
agentService.cancelCollect();
long timestamp = System.currentTimeMillis();
timestamp = Math.round(timestamp / 1000.0);
JSONObject json = new JSONObject();
json.put("resCode",1);
json.put("resMag","");
json.put("timestamp",timestamp);
return UpMsgResponse.builder().clientId(clientId).dataType(MsgEnum.断开应答.getValue())
.content(json.toString()).build();
}
}
@AgentDispatcher(msgId = MsgEnum.开启或更新系统采集)
public class SysCollectStartHandler implements AgentHandler {
@Override
public UpMsgResponse upHandle(String data, String clientId) {
//更改全局变量
agentService.systemCollectStart(data);
long timestamp = System.currentTimeMillis();
timestamp = Math.round(timestamp / 1000.0);
JSONObject json = new JSONObject();
json.put("resCode",1);
json.put("resMag","");
json.put("timestamp",timestamp);
return UpMsgResponse.builder().clientId(clientId).dataType(MsgEnum.开启或更新系统采集应答.getValue()).content(json.toString()).build();
}
}
@AgentDispatcher(msgId = MsgEnum.关闭所有系统采集)
public class SysCollectStopHandler implements AgentHandler {
@Override
public UpMsgResponse upHandle(String data, String clientId) {
//更改全局变量
agentService.systemCollectStop();
long timestamp = System.currentTimeMillis();
timestamp = Math.round(timestamp / 1000.0);
JSONObject json = new JSONObject();
json.put("resCode",1);
json.put("resMag","");
json.put("timestamp",timestamp);
return UpMsgResponse.builder().clientId(clientId).dataType(MsgEnum.关闭所有系统采集应答.getValue()).content(json.toString()).build();
}
}
@AgentDispatcher(msgId = MsgEnum.开启或更新交换机采集)
public class SwitchCollectStartHandler implements AgentHandler {
@Override
public UpMsgResponse upHandle(String data, String clientId) {
//更改全局变量
agentService.switchCollectStart(data);
long timestamp = System.currentTimeMillis();
timestamp = Math.round(timestamp / 1000.0);
JSONObject json = new JSONObject();
json.put("resCode",1);
json.put("resMag","");
json.put("timestamp",timestamp);
return UpMsgResponse.builder().clientId(clientId).dataType(MsgEnum.开启或更新交换机采集应答.getValue()).content(json.toString()).build();
}
}
@AgentDispatcher(msgId = MsgEnum.关闭所有交换机采集)
public class SwitchCollectStopHandler implements AgentHandler {
@Override
public UpMsgResponse upHandle(String data, String clientId) {
//更改全局变量
agentService.switchCollectStop();
long timestamp = System.currentTimeMillis();
timestamp = Math.round(timestamp / 1000.0);
JSONObject json = new JSONObject();
json.put("resCode",1);
json.put("resMag","");
json.put("timestamp",timestamp);
return UpMsgResponse.builder().clientId(clientId).dataType(MsgEnum.关闭所有交换机采集应答.getValue()).content(json.toString()).build();
agentService.getPolicy(data);
return null;
}
}
@@ -323,18 +149,11 @@ public class AgentEndpoint {
if(jsonObject.containsKey("policyName")) {
policy.setPolicyName(jsonObject.getString("policyName"));
}
if(jsonObject.containsKey("files")) {
String files = jsonObject.getString("files");
List<ScriptPolicyEO.ScriptFile> fileList = JSON.parseObject(files, new TypeReference<List<ScriptPolicyEO.ScriptFile>>() {});
policy.setFiles(fileList);
if(jsonObject.containsKey("fileUrl")) {
policy.setFileUrl(jsonObject.getString("fileUrl"));
}
if(jsonObject.containsKey("filePath")) {
policy.setFilePath(jsonObject.getString("filePath"));
}
if(jsonObject.containsKey("commands")) {
String commands = jsonObject.getString("commands");
List<String> commandList = JSON.parseObject(commands, new TypeReference<List<String>>() {});
policy.setCommands(commandList);
if(jsonObject.containsKey("commandParams")) {
policy.setCommandParams(jsonObject.getString("commandParams"));
}
if(jsonObject.containsKey("method")) {
policy.setMethod(jsonObject.getInteger("method"));
@@ -343,81 +162,30 @@ public class AgentEndpoint {
policy.setPolicyTime(jsonObject.getLong("policyTime"));
}
boolean isFalse = false;
if(StringUtils.isNotBlank(policy.getFilePath())){
if(AdvancedAsyncDownloader.createSingleDirectoryIfNotExists(policy.getFilePath())){
if(CollectionUtil.isNotEmpty(policy.getFiles())){
isFalse = true;
GlobalConfig.DOWN_FILES.put(policy.getPolicyName(), policy.getFiles().size());
for (ScriptPolicyEO.ScriptFile f : policy.getFiles()) {
String finalSaveDir = policy.getFilePath();
//文件类型:0、平台文件地址;1、外网HTTP(S)
if(f.getFileType() == 0){
String filePath = policy.getFilePath() + "/" + f.getFileName();
// 移除Base64 URL前缀(如果存在)
// if (f.getFileData().contains(",")) {
// f.setFileData(f.getFileData().split(",")[1]);
// }
try {
AgentUtil.base64ToFile(f.getFileData(), filePath);
System.out.println("文件保存成功: " + filePath);
isFalse = true;
try {
AgentDataUtil.chmod(filePath,"+x");
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
} catch (IOException e) {
isFalse = false;
System.err.println("文件保存失败: " + e.getMessage());
}
}else{
AdvancedAsyncDownloader.downloadWithProgress(f.getFileUrl(), finalSaveDir, f.getFileName(), progress -> {
System.out.printf("下载进度: %.1f%%\n", progress);
}).thenAccept(filePath -> {
System.out.println("下载完成: " + filePath);
try {
AgentDataUtil.chmod(finalSaveDir+"/"+f.getFileName(),"775");
int count = GlobalConfig.DOWN_FILES.get(policy.getPolicyName());
if(count > 1){
GlobalConfig.DOWN_FILES.put(policy.getPolicyName(), count--);
}else{
//所有文件下载完成,执行脚本命令
agentService.command(policy, clientId,MsgEnum.执行脚本策略应答.getValue());
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).exceptionally(ex -> {
System.err.println("下载错误: " + ex.getMessage());
return null;
});
// Java8FileDownloader.DownloadResult result = Java8FileDownloader.downloadFile(f.getFileUrl(), finalSaveDir, f.getFileName());
// // 验证下载是否完成
// if (result.isSuccess()) {
// boolean isComplete = Java8FileDownloader.verifyFileCompletion(
// result.getFilePath(), result.getExpectedSize());
// if(isComplete){
// isFalse = true;
// System.out.println("下载完成: " + finalSaveDir+"/"+f.getFileName());
// try {
// AgentDataUtil.chmod(finalSaveDir+"/"+f.getFileName(),"775");
// } catch (IOException e) {
// e.printStackTrace();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }else{
// isFalse = false;
// System.err.println("下载失败");
// }
// }
}
if(AdvancedAsyncDownloader.createSingleDirectoryIfNotExists(properties.getTmpPath()+"/script")){
if(StringUtils.isNotBlank(policy.getFileUrl())){
isFalse = true;
String fileName = policy.getFileUrl().substring(policy.getFileUrl().lastIndexOf('/') + 1);
//异步下载脚本
AdvancedAsyncDownloader.downloadWithProgress(policy.getFileUrl(), properties.getTmpPath()+"/script", fileName, progress -> {
System.out.printf("下载进度: %.1f%%\n", progress);
}).thenAccept(filePath -> {
System.out.println("下载完成: " + filePath);
try {
AgentDataUtil.chmod(properties.getTmpPath()+"/script/"+fileName,"775");
String params = properties.getTmpPath()+"/script/"+fileName+" " + policy.getCommandParams();
policy.setCommandParams(params);
//所有文件下载完成,执行脚本命令
agentService.command(policy, GlobalConfig.CLIENT_ID,MsgEnum.执行脚本策略应答.getValue());
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).exceptionally(ex -> {
System.err.println("下载错误: " + ex.getMessage());
return null;
});
}
}
//判断是否需要保存文件
@@ -440,48 +208,61 @@ public class AgentEndpoint {
AgentVersionUpdateEO versionUpdateEO = JSON.parseObject(data, AgentVersionUpdateEO.class);
ScriptPolicyEO policy = ScriptPolicyEO.builder()
.method(versionUpdateEO.getMethod())
.commandParams("systemctl restart tragent")
.policyTime(versionUpdateEO.getPolicyTime())
.build();
JSONObject jsonObject = JSONObject.parseObject(data);
if(jsonObject.containsKey("commands")) {
String commands = jsonObject.getString("commands");
List<String> commandList = JSON.parseObject(commands, new TypeReference<List<String>>() {});
policy.setCommands(commandList);
}
boolean isFalse = false;
if(StringUtils.isNotBlank(versionUpdateEO.getFileUrl())){
isFalse = true;
String finalSaveDir = versionUpdateEO.getFilePath();
AdvancedAsyncDownloader.downloadWithProgress(versionUpdateEO.getFileUrl(), finalSaveDir, null, progress -> {
String fileName = versionUpdateEO.getFileUrl().substring(versionUpdateEO.getFileUrl().lastIndexOf('/') + 1);
AdvancedAsyncDownloader.downloadWithProgress(versionUpdateEO.getFileUrl(), properties.getTempPath(), fileName, progress -> {
System.out.printf("下载进度: %.1f%%\n", progress);
}).thenAccept(filePath -> {
System.out.println("下载完成: " + filePath);
try {
//更改全局变量
GlobalConfig.isCollect = false;
//调用采集任务
agentService.cancelCollect();
//所有文件下载完成,执行脚本命令
agentService.command(policy, clientId,MsgEnum.Agent版本更新应答.getValue());
//验证文件MD5
String md5 = AgentUtil.getFileMD5(properties.getTempPath()+"/"+fileName);
if(StringUtils.isNotBlank(md5) && StringUtils.isNotBlank(versionUpdateEO.getFileMd5())
&& StringUtils.equals(md5,versionUpdateEO.getFileMd5())){
//更改全局变量
// GlobalConfig.isCollect = false;
//调用采集任务
// agentService.cancelCollect();
//所有文件下载完成,执行脚本命令
agentService.command(policy, clientId,MsgEnum.Agent版本更新应答.getValue());
}else{
//发送更新失败
long timestamp = System.currentTimeMillis();
timestamp = Math.round(timestamp / 1000.0);
JSONObject json = new JSONObject();
json.put("resCode",0);
json.put("resMsg", "版本策略MD5验证失败");
json.put("timestamp",timestamp);
agentService.sendMessage(MsgEnum.Agent版本更新应答.getValue(), json.toString());
}
} catch (Exception e) {
e.printStackTrace();
}
}).exceptionally(ex -> {
System.err.println("下载错误: " + ex.getMessage());
long timestamp = System.currentTimeMillis();
timestamp = Math.round(timestamp / 1000.0);
JSONObject json = new JSONObject();
json.put("resCode",0);
json.put("resMsg", "版本策略文件下载失败");
json.put("timestamp",timestamp);
agentService.sendMessage(MsgEnum.Agent版本更新应答.getValue(), json.toString());
return null;
});
}
//判断是否需要保存文件
if(!isFalse){
agentService.command(policy, clientId,MsgEnum.Agent版本更新应答.getValue());
return null;
}else{
if(isFalse){
JSONObject json = new JSONObject();
json.put("resCode",1);
json.put("resMsg", "Agent版本更新文件保存中");
return UpMsgResponse.builder().clientId(clientId).dataType(MsgEnum.Agent版本更新应答.getValue()).content(json.toString()).build();
}
return null;
}
}
@@ -143,7 +143,7 @@ public class AgentDecoderHandler extends ChannelInboundHandlerAdapter {
}
byteBuf.release(); // 释放 ByteBuf 资源(重要!)
} else {
System.out.println("Unexpected message type: " + msg.getClass());
AssertLog.error("Unexpected message type: " + msg.getClass());
}
}
@@ -1,84 +0,0 @@
package com.tongran.agent.client.netty.handler;
import com.tongran.agent.client.core.config.GlobalConfig;
import com.tongran.agent.client.core.session.SessionManager;
import com.tongran.agent.client.scheduler.service.AppInitializer;
import com.tongran.agent.client.service.AgentService;
import com.tongran.agent.client.utils.AssertLog;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.io.IOException;
/**
* @Description: TCP消息适配器
*/
@Component
@ChannelHandler.Sharable
public class TCPListenHandler extends ChannelInboundHandlerAdapter {
@Resource
private AppInitializer appInitializer;
private final SessionManager sessionManager;
@Resource
private AgentService agentService;
public TCPListenHandler() {
this.sessionManager = SessionManager.getInstance();
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
AssertLog.info("<<<<<<[终端连接]{}", ctx.channel().remoteAddress());
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
AssertLog.info(">>>>>>[断开连接]{}", sessionManager.client(ctx));
sessionManager.remove(ctx.channel());
try {
GlobalConfig.isCollect = false;
agentService.cancelCollect();
appInitializer.run();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) {
if (e instanceof IOException) {
AssertLog.info("<<<<<<[终端断开连接]{} {}", sessionManager.client(ctx), e.getMessage());
agentService.cancelCollect();
} else {
AssertLog.info(">>>>>>[消息处理异常]" + sessionManager.client(ctx), e);
}
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
IdleState state = event.state();
if (state == IdleState.READER_IDLE || state == IdleState.WRITER_IDLE || state == IdleState.ALL_IDLE) {
AssertLog.warn(">>>>>>[终端心跳超时]{} {}", state, sessionManager.client(ctx));
sessionManager.remove(ctx.channel());
ctx.close();
try {
GlobalConfig.isCollect = false;
appInitializer.run();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
@@ -1,24 +0,0 @@
package com.tongran.agent.client.netty.handler;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.socket.DatagramPacket;
import org.springframework.stereotype.Component;
/**
* @Description: UDP消息适配器
*/
@Component
@ChannelHandler.Sharable
public class UDPListenHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
DatagramPacket packet = (DatagramPacket) msg;
ByteBuf buf = packet.content();
buf.clear();// 暂未实现
}
}
@@ -52,4 +52,34 @@ public class SchedulerConfig {
executor.initialize(); // 重要:必须调用initialize()
return executor;
}
// 在SchedulerConfig中添加专用TCPDUMP线程池
@Bean("tcpdumpTaskExecutor")
public ThreadPoolTaskExecutor tcpdumpTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2); // 核心线程数
executor.setMaxPoolSize(10); // 最大线程数(根据网卡数量调整)
executor.setQueueCapacity(20); // 队列容量
executor.setKeepAliveSeconds(300);// 空闲线程存活时间
executor.setThreadNamePrefix("tcpdump-worker-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
// 新增:心跳专用线程池
@Bean("heartbeatExecutor")
public ThreadPoolTaskExecutor heartbeatExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(4);
executor.setQueueCapacity(0); // 重要:设为0,不要队列!
executor.setKeepAliveSeconds(30);
executor.setThreadNamePrefix("heartbeat-");
// 重要:使用CallerRunsPolicy,确保心跳一定执行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
@@ -0,0 +1,45 @@
package com.tongran.agent.client.scheduler.job;
import com.tongran.agent.client.core.config.ApplicationProperties;
import com.tongran.agent.client.utils.AssertLog;
import com.tongran.agent.client.utils.FileCleaner;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.time.Duration;
import java.time.LocalDateTime;
@Slf4j
@Component
public class DailyJob {
// @Value("${spring.profiles.active}")
// private String active;
@Resource
private ApplicationProperties properties;
@Scheduled(cron = "0 0 2 * * ?")
void dailyTask() {
// if (!StringUtils.equals(active, "prod")) {
// return;
// }
LocalDateTime start = LocalDateTime.now();
AssertLog.info("临时文件定时清理作业开始:{}", LocalDateTime.now());
String[] paths = new String[]{properties.getTmpPath(), properties.getTempPath()};
for (String path : paths) {
if(StringUtils.isNotBlank(path)){
FileCleaner.cleanOldFiles(path,30);
}
}
LocalDateTime end = LocalDateTime.now();
AssertLog.info("临时文件定时清理作业结束:{},耗时={}s", end, Duration.between(start, end).getSeconds());
}
}
@@ -1,17 +1,36 @@
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.core.vo.NetworkInterfaceInfo;
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 com.tongran.agent.client.utils.NetworkInterfaceUtil;
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;
import java.util.List;
@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) {
@@ -21,64 +40,105 @@ public class AppInitializer implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("应用启动完成,开始初始化定时任务...");
if(GlobalConfig.isCollect){
// 开启定时任务
initScheduledTasks();
}
System.out.println("定时任务初始化完成");
AssertLog.info("创建连接");
// 建立连接
initConnection();
// 检查保活脚本
agentService.checkTrAgent();
AssertLog.info("创建连接完成");
}
private void initScheduledTasks() {
// 每30秒执行心跳任务
AssertLog.info("初始化启动心跳定时任务 - 延迟: {}ms, 间隔: {}ms", 15000, 30000);
dynamicTaskService.scheduleTask("heartbeat",
businessTasks::heartbeatTask, 15000, 30000);
private void initConnection() {
boolean success = true;
int activeConnect = client.getActiveConnections();
AssertLog.info("activeConnect={}",activeConnect);
if(activeConnect == 0){
success = agentService.connection();
}
if(success){
AssertLog.info("连接成功,发送初始连接消息");
//连接成功,发送初始连接消息
// 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);
// Message msg = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.建立连接.getValue()).data(object.toString()).build();
// // 将对象转为 JSON 字符串
// client.sendMessages(GlobalConfig.CLIENT_ID, msg);
// // 等待3秒
// try {
// TimeUnit.SECONDS.sleep(3);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
//判断是否发送注册信息
if(GlobalConfig.isRegister){
//添加路由
agentService.addRoute(null,null);
//已注册,发送心跳
// 创建心跳定时任务
AssertLog.info("启动心跳定时任务 - 延迟: {}ms, 间隔: {}ms", 15000, 30000);
dynamicTaskService.scheduleTask("heartbeat", businessTasks::heartbeatTask, 15000, 30000);
// 创建获取最新策略定时任务
long milli = AgentUtil.getMillisToNextMinute() + 60000;
AssertLog.info("启动更新策略定时任务 - 延迟: {}ms, 间隔: {}ms", milli, 60000);
dynamicTaskService.scheduleTask("policy", businessTasks::policyTask, milli, 60000);
// 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秒执行
// 创建多网IP探测上报
AssertLog.info("启动多网IP探测定时任务 - 延迟: {}ms, 间隔: {}ms", milli, 300000);
dynamicTaskService.scheduleTask("networkDetect", businessTasks::networkDetectTask, milli, 300000);
// 检测监控策略配置
AssertLog.info("检测监控策略配置");
agentService.checkMonitor();
// 检测防火墙策略配置
AssertLog.info("检测防火墙策略配置");
agentService.checkFirewall();
AssertLog.info("启动检查防火墙策略定时任务 - 延迟: {}ms, 间隔: {}ms", 15000, 600000);
dynamicTaskService.scheduleTask("checkFirewall", businessTasks::checkFirewallTask, 15000, 600000);
AssertLog.info("检测agent更新配置");
agentService.checkAgentUpdate();
AssertLog.info("启动frpc保活定时任务 - 延迟: {}ms, 间隔: {}ms", 15000, 600000);
dynamicTaskService.scheduleTask("checkFrpc", businessTasks::checkFrpcTask, 15000, 600000);
AssertLog.info("启动frpc状态上报定时任务 - 延迟: {}ms, 间隔: {}ms", 15000, 300000);
dynamicTaskService.scheduleTask("upFrpcMsg", businessTasks::upFrpcMsgTask, 15000, 300000);
long milliFive = AgentUtil.millisecondsToNext5Minute();
AssertLog.info("启动本地存储流量信息上报定时任务 - 延迟: {}ms, 间隔: {}ms", milliFive, 7200000);
dynamicTaskService.scheduleTask("upTempTraffic", businessTasks::upTempTraffic, milliFive, 7200000);
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);
// List<NetworkInterfaceInfo> infos = AgentUtil.collectNetworkInfo();
List<NetworkInterfaceInfo> infos = NetworkInterfaceUtil.collectNetworkInfo();
JSONObject objects = new JSONObject();
objects.put("clientId", GlobalConfig.CLIENT_ID);
objects.put("sn", GlobalConfig.DEVICE_SN);
objects.put("networkInfo", JSON.toJSONString(infos));
objects.put("timestamp", timestamps);
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.注册.getValue()).data(objects.toString()).build();
// 将对象转为 JSON 字符串
client.sendMessages(GlobalConfig.CLIENT_ID, message);
} catch (Exception e) {
e.printStackTrace();
}
//设置注册定时任务,收注册成功后取消该定时任务
dynamicTaskService.scheduleTask("register", businessTasks::registerTask, 60000, 300000);
}
}else{
//连接建立失败,3分钟后重试
dynamicTaskService.scheduleTask("connection", businessTasks::connectionTask, 60000, 180000);
}
}
}
@@ -9,19 +9,22 @@ import com.tongran.agent.client.core.config.GlobalConfig;
import com.tongran.agent.client.core.enums.MsgEnum;
import com.tongran.agent.client.core.session.SessionManager;
import com.tongran.agent.client.core.vo.*;
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.service.*;
import com.tongran.agent.client.utils.AgentUtil;
import com.tongran.agent.client.utils.AssertLog;
import com.tongran.agent.client.utils.NetworkInterfaceUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
@Component
@@ -53,26 +56,15 @@ public class BusinessTasks {
private final AtomicInteger systemUptimeTask = new AtomicInteger(0);
private final AtomicInteger procNumTask = new AtomicInteger(0);
private final AtomicInteger switchNetTask = new AtomicInteger(0);
private final AtomicInteger switchModuleTask = new AtomicInteger(0);
private final AtomicInteger switchMpuTask = new AtomicInteger(0);
private final AtomicInteger switchPwrTask = new AtomicInteger(0);
private final AtomicInteger switchFanTask = new AtomicInteger(0);
private final AtomicInteger switchSysDescrTask = new AtomicInteger(0);
private final AtomicInteger switchSysObjectIDTask = new AtomicInteger(0);
private final AtomicInteger switchSysUpTimeTask = new AtomicInteger(0);
private final AtomicInteger switchSysContactTask = new AtomicInteger(0);
private final AtomicInteger switchSysNameTask = new AtomicInteger(0);
private final AtomicInteger switchSysLocationTask = new AtomicInteger(0);
private final AtomicInteger switchHwStackSystemMacTask = new AtomicInteger(0);
private final AtomicInteger switchEntIndexTask = new AtomicInteger(0);
private final AtomicInteger switchEntPhysicalNameTask = new AtomicInteger(0);
private final AtomicInteger switchEntPhysicalSoftwareRevTask = new AtomicInteger(0);
private final AtomicInteger switchHwEntityCpuUsageTask = new AtomicInteger(0);
private final AtomicInteger switchHwEntityMemUsageTask = new AtomicInteger(0);
private final AtomicInteger switchHwAveragePowerTask = new AtomicInteger(0);
private final AtomicInteger switchHwCurrentPowerTask = new AtomicInteger(0);
private final AtomicInteger policyTask = new AtomicInteger(0);
private final AtomicInteger registerTask = new AtomicInteger(0);
private final AtomicInteger connectionTask = new AtomicInteger(0);
private final AtomicInteger networkDetectTask = new AtomicInteger(0);
private final AtomicInteger checkFirewallTask = new AtomicInteger(0);
private final AtomicInteger checkFrpcTask = new AtomicInteger(0);
private final AtomicInteger upFrpcMsgTask = new AtomicInteger(0);
private final AtomicInteger upMacvlanStatusTask = new AtomicInteger(0);
private final AtomicInteger upTempTrafficTask = new AtomicInteger(0);
@@ -103,9 +95,8 @@ public class BusinessTasks {
@Resource
private NetService netService;
@Resource
private SwitchBoardService switchBoardService;
private NetBusinessService netBusinessService;
@Resource
private SystemService systemService;
@@ -116,28 +107,52 @@ public class BusinessTasks {
@Resource
private ApplicationProperties properties;
@Resource
private MultiTargetNettyClient client;
@Resource
private AgentNettyConfig config;
@Resource
private AgentService agentService;
/**
* 任务1:心跳上报任务
*/
@Async("taskExecutor")
@Async("heartbeatExecutor")
public void heartbeatTask() {
long timestamp = System.currentTimeMillis();
timestamp = Math.round(timestamp / 1000.0);
int count = heartbeatTask.incrementAndGet();
AssertLog.info("心跳定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
// 业务处理
// 判定客户端与服务端是否连接
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
// 发送心跳包
JSONObject object = new JSONObject();
object.put("strength","31");
object.put("name", properties.getName());
object.put("version", properties.getVersion());
object.put("startupTime", GlobalConfig.startupTime);
object.put("timestamp",timestamp);
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.心跳上报.getValue()).data(object.toString()).build();
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message);
AssertLog.info("发送心跳包={}",JSON.toJSONString(message));
boolean success = true;
int activeConnect = client.getActiveConnections();
if(activeConnect == 0){
success = agentService.connection();
}
if(success){
// 业务处理
// 判定客户端与服务端是否连接
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
if(StringUtils.isBlank(GlobalConfig.DEVICE_SN)){
GlobalConfig.DEVICE_SN = AgentUtil.getDeviceSN();
}
// 发送心跳包
JSONObject object = new JSONObject();
object.put("clientId", GlobalConfig.CLIENT_ID);
object.put("logicalNode", agentService.getLogicalNode());
object.put("sn", GlobalConfig.DEVICE_SN);
object.put("strength","31");
object.put("name", properties.getName());
object.put("version", properties.getVersion());
object.put("startupTime", GlobalConfig.startupTime);
object.put("timestamp",timestamp);
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.心跳上报.getValue()).data(object.toString()).build();
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message);
AssertLog.info("发送心跳包={}",JSON.toJSONString(message));
}
}else{
AssertLog.info("心跳定时任务执行失败-连接断开");
}
AssertLog.info("心跳定时任务执行 - task #{} completed", count);
}
@@ -246,13 +261,31 @@ public class BusinessTasks {
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
// 发送网卡信息包
String data = "";
String businessData = "";
List<NetVO> list = netService.netList(timestamp);
if(CollectionUtil.isNotEmpty(list)){
List<NetBusinessVO> businessNetList = netBusinessService.netList(timestamp);
// 2. 优化MAC地址赋值(使用HashMap,避免重复分割)
if (CollectionUtil.isNotEmpty(list)) {
data = JSONArray.toJSONString(list);
}
if (CollectionUtil.isNotEmpty(businessNetList)) {
businessData = JSONArray.toJSONString(businessNetList);
}
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.网络上报.getValue()).data(data).build();
// 将本次流量存储到本地文件
String[] lines = {
"traffic=" + data,
};
// 检查目录并写入配置文件
if (AdvancedAsyncDownloader.createSingleDirectoryIfNotExists(properties.getTempTrafficPath())) {
AgentUtil.bufferedWriter(properties.getTempTrafficPath() + "/" + timestamp+".conf", lines);
}
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message);
AssertLog.info("发送网络信息包={}",JSON.toJSONString(message));
Message businessMessage = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.业务网络上报.getValue()).data(businessData).build();
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), businessMessage);
AssertLog.info("发送业务网络信息包={}",JSON.toJSONString(businessMessage));
}
AssertLog.info("网络信息采集定时任务执行 - task #{} completed", count);
}
@@ -354,8 +387,22 @@ public class BusinessTasks {
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.系统其他上报.getValue()).data(jsonObject.toString()).build();
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message);
AssertLog.info("发送系统其他信息-内存利用率信息包={}",JSON.toJSONString(message));
// 执行内存详情包
List<MemoryDetailsVO> memDetailsList = memoryService.getMemoryDetails();
Integer total = memoryService.getMemorySlotsNum();
JSONObject object = new JSONObject();
if(memDetailsList != null && !memDetailsList.isEmpty()){
object.put("details", memDetailsList);
if(total != null){
object.put("total", total);
}
}
object.put("timestamp", timestamp);
Message memDetails = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.内存详情上报.getValue()).data(object.toString()).build();
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), memDetails);
AssertLog.info("发送内存详情信息包={}",JSON.toJSONString(memDetails));
}
AssertLog.info("系统其他信息-内存利用率采集定时任务执行 - task #{} completed", count);
AssertLog.info("系统其他信息-内存利用率采集/内存详情定时任务执行 - task #{} completed", count);
}
/**
@@ -734,480 +781,286 @@ public class BusinessTasks {
}
/**
* 任务26交换机网络采集
* 任务26获取最新策略
*/
@Async("taskExecutor")
public void switchNetTask() {
public void policyTask() {
long timestamp = AgentUtil.roundMinutes();
int count = switchNetTask.incrementAndGet();
AssertLog.info("交换机网络采集定时任务执行 - 时间: {}task #{}", LocalDateTime.now(), count);
int count = policyTask.incrementAndGet();
AssertLog.info("获取最新策略定时任务执行 - 时间: {}task #{}", LocalDateTime.now(), count);
// 判定客户端与服务端是否连接
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
// 发送交换机信息包
JSONObject jsonObject = new JSONObject();
String data = switchBoardService.getSwitchDataByType("switchNetCollect");
if(StringUtils.isNotBlank(data)){
jsonObject = JSONObject.parseObject(data);
if(StringUtils.isBlank(GlobalConfig.DEVICE_SN)){
GlobalConfig.DEVICE_SN = AgentUtil.getDeviceSN();
}
jsonObject.put("timestamp", timestamp);
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.交换机上报.getValue()).data(jsonObject.toString()).build();
// 发送更新策略
JSONObject object = new JSONObject();
object.put("clientId", GlobalConfig.CLIENT_ID);
object.put("sn",GlobalConfig.DEVICE_SN);
object.put("timestamp",timestamp);
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.获取最新策略.getValue()).data(object.toString()).build();
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message);
AssertLog.info("发送交换机网络信息包={}",JSON.toJSONString(message));
AssertLog.info("发送获取最新策略信息包={}",JSON.toJSONString(message));
}
AssertLog.info("交换机网络采集定时任务执行 - task #{} completed", count);
AssertLog.info("获取最新策略定时任务执行 - task #{} completed", count);
}
/**
* 任务27交换机光模块采集
* 任务27注册重试
*/
@Async("taskExecutor")
public void switchModuleTask() {
long timestamp = AgentUtil.roundMinutes();
int count = switchModuleTask.incrementAndGet();
AssertLog.info("交换机光模块采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
// 判定客户端与服务端是否连接
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
// 发送交换机信息包
JSONObject jsonObject = new JSONObject();
String data = switchBoardService.getSwitchDataByType("switchModuleCollect");
if(StringUtils.isNotBlank(data)){
jsonObject = JSONObject.parseObject(data);
}
jsonObject.put("timestamp", timestamp);
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.交换机上报.getValue()).data(jsonObject.toString()).build();
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message);
AssertLog.info("发送交换机光模块信息包={}",JSON.toJSONString(message));
}
AssertLog.info("交换机光模块采集定时任务执行 - task #{} completed", count);
}
/**
* 任务28:交换机MPU采集
*/
@Async("taskExecutor")
public void switchMpuTask() {
public void registerTask() {
long timestamp = System.currentTimeMillis();
timestamp = Math.round(timestamp / 1000.0);
int count = switchMpuTask.incrementAndGet();
AssertLog.info("交换机MPU采集定时任务执行 - 时间: {}task #{}", LocalDateTime.now(), count);
// 判定客户端与服务端是否连接
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
// 发送交换机信息包
JSONObject jsonObject = new JSONObject();
String data = switchBoardService.getSwitchDataByType("switchMpuCollect");
if(StringUtils.isNotBlank(data)){
jsonObject = JSONObject.parseObject(data);
}
jsonObject.put("timestamp", timestamp);
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.交换机上报.getValue()).data(jsonObject.toString()).build();
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message);
AssertLog.info("发送交换机MPU信息包={}",JSON.toJSONString(message));
int count = registerTask.incrementAndGet();
AssertLog.info("注册重试定时任务执行 - 时间: {}task #{}", LocalDateTime.now(), count);
boolean success = true;
int activeConnect = client.getActiveConnections();
AssertLog.info("注册重试定时任务执行 - 时间: {}activeConnect={}", LocalDateTime.now(), activeConnect);
if(activeConnect == 0 || count > 1){
success = agentService.connection();
}
AssertLog.info("交换机MPU采集定时任务执行 - task #{} completed", count);
if(success){
AssertLog.info("连接成功,发送初始连接消息");
try {
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
if(StringUtils.isBlank(GlobalConfig.DEVICE_SN)){
GlobalConfig.DEVICE_SN = AgentUtil.getDeviceSN();
}
// List<NetworkInterfaceInfo> infos = AgentUtil.collectNetworkInfo();
List<NetworkInterfaceInfo> infos = NetworkInterfaceUtil.collectNetworkInfo();
JSONObject object = new JSONObject();
object.put("clientId", GlobalConfig.CLIENT_ID);
object.put("sn", GlobalConfig.DEVICE_SN);
object.put("networkInfo", JSON.toJSONString(infos));
object.put("timestamp", timestamp);
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.注册.getValue()).data(object.toString()).build();
// 将对象转为 JSON 字符串
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message);
AssertLog.info("发送注册重试={}",JSON.toJSONString(message));
}
} catch (Exception e) {
e.printStackTrace();
}
}else{
AssertLog.info("注册重试定时任务执行 - 时间: {},建立连接失败", LocalDateTime.now());
}
AssertLog.info("注册重试定时任务执行 - task #{} completed", count);
}
/**
* 任务29:交换机电源采集
* 任务28:建立连接重试
*/
@Async("taskExecutor")
public void switchPwrTask() {
long timestamp = System.currentTimeMillis();
timestamp = Math.round(timestamp / 1000.0);
int count = switchPwrTask.incrementAndGet();
AssertLog.info("交换机电源采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
// 判定客户端与服务端是否连接
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
// 发送交换机信息包
JSONObject jsonObject = new JSONObject();
String data = switchBoardService.getSwitchDataByType("switchPwrCollect");
if(StringUtils.isNotBlank(data)){
jsonObject = JSONObject.parseObject(data);
public void connectionTask() {
int count = connectionTask.incrementAndGet();
AssertLog.info("建立连接重试定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
boolean success = client.createConnection(GlobalConfig.CLIENT_ID, config.getHost(), config.getPort(), 5);
if(success){
if(StringUtils.isBlank(GlobalConfig.DEVICE_SN)){
GlobalConfig.DEVICE_SN = AgentUtil.getDeviceSN();
}
jsonObject.put("timestamp", timestamp);
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.交换机上报.getValue()).data(jsonObject.toString()).build();
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message);
AssertLog.info("发送交换机电源信息包={}",JSON.toJSONString(message));
//连接成功,发送初始连接消息
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);
Message msg = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.建立连接.getValue()).data(object.toString()).build();
// 将对象转为 JSON 字符串
client.sendMessages(GlobalConfig.CLIENT_ID, msg);
//判断是否发送注册信息
if(GlobalConfig.isRegister){
//已注册,发送心跳
// 创建心跳定时任务
AssertLog.info("启动心跳定时任务 - 延迟: {}ms, 间隔: {}ms", 15000, 30000);
dynamicTaskService.scheduleTask("heartbeat", businessTasks::heartbeatTask, 15000, 30000);
// 创建获取最新策略定时任务
long milli = AgentUtil.getMillisToNextMinute() + 60000;
AssertLog.info("启动更新策略定时任务 - 延迟: {}ms, 间隔: {}ms", milli, 60000);
dynamicTaskService.scheduleTask("policy", businessTasks::policyTask, milli, 60000);
// 创建多网IP探测上报
AssertLog.info("启动多网IP探测定时任务Retry - 延迟: {}ms, 间隔: {}ms", milli, 300000);
dynamicTaskService.scheduleTask("networkDetect", businessTasks::networkDetectTask, milli, 300000);
// 检测监控策略配置
AssertLog.info("检测监控策略配置Retry");
agentService.checkMonitor();
// 检测防火墙策略配置
AssertLog.info("检测防火墙策略配置");
agentService.checkFirewall();
AssertLog.info("启动检查防火墙策略定时任务 - 延迟: {}ms, 间隔: {}ms", 15000, 600000);
dynamicTaskService.scheduleTask("checkFirewall", businessTasks::checkFirewallTask, 15000, 600000);
AssertLog.info("检测agent更新配置");
agentService.checkAgentUpdate();
AssertLog.info("启动frpc保活定时任务 - 延迟: {}ms, 间隔: {}ms", 15000, 600000);
dynamicTaskService.scheduleTask("checkFrpc", businessTasks::checkFrpcTask, 15000, 600000);
AssertLog.info("启动frpc状态上报定时任务 - 延迟: {}ms, 间隔: {}ms", 15000, 300000);
dynamicTaskService.scheduleTask("upFrpcMsg", businessTasks::upFrpcMsgTask, 15000, 300000);
long milliFive = AgentUtil.millisecondsToNext5Minute();
AssertLog.info("启动本地存储流量信息上报定时任务 - 延迟: {}ms, 间隔: {}ms", milliFive, 7200000);
dynamicTaskService.scheduleTask("upTempTraffic", businessTasks::upTempTraffic, milliFive, 7200000);
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);
// List<NetworkInterfaceInfo> infos = AgentUtil.collectNetworkInfo();
List<NetworkInterfaceInfo> infos = NetworkInterfaceUtil.collectNetworkInfo();
JSONObject objects = new JSONObject();
objects.put("clientId", GlobalConfig.CLIENT_ID);
objects.put("sn", GlobalConfig.DEVICE_SN);
objects.put("networkInfo", JSON.toJSONString(infos));
objects.put("timestamp", timestamps);
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.注册.getValue()).data(objects.toString()).build();
// 将对象转为 JSON 字符串
client.sendMessages(GlobalConfig.CLIENT_ID, message);
} catch (Exception e) {
e.printStackTrace();
}
//设置注册定时任务,收注册成功后取消该定时任务
dynamicTaskService.scheduleTask("register", businessTasks::registerTask, 0, 300000);
}
dynamicTaskService.cancelTask("connection");
}
AssertLog.info("交换机电源采集定时任务执行 - task #{} completed", count);
AssertLog.info("建立连接重试定时任务执行 - task #{} completed", count);
}
/**
* 任务30:交换机风扇采集
* 任务29:多网IP探测
*/
@Async("taskExecutor")
public void switchFanTask() {
long timestamp = System.currentTimeMillis();
timestamp = Math.round(timestamp / 1000.0);
int count = switchFanTask.incrementAndGet();
AssertLog.info("交换机风扇采集采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
public void networkDetectTask() {
int count = networkDetectTask.incrementAndGet();
AssertLog.info("多网IP探测定时任务执行 - 时间: {}task #{}", LocalDateTime.now(), count);
// 判定客户端与服务端是否连接
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
// 发送交换机信息包
JSONObject jsonObject = new JSONObject();
String data = switchBoardService.getSwitchDataByType("switchFanCollect");
if(StringUtils.isNotBlank(data)){
jsonObject = JSONObject.parseObject(data);
if(StringUtils.isBlank(GlobalConfig.DEVICE_SN)){
GlobalConfig.DEVICE_SN = AgentUtil.getDeviceSN();
}
jsonObject.put("timestamp", timestamp);
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.交换机上报.getValue()).data(jsonObject.toString()).build();
// 发送多网IP探测
long timestamps = System.currentTimeMillis();
timestamps = Math.round(timestamps / 1000.0);
List<NetworkInterfaceInfo> infos = null;
try {
// infos = AgentUtil.collectNetworkInfo();
infos = NetworkInterfaceUtil.collectNetworkInfo();
} catch (Exception e) {
e.printStackTrace();
}
JSONObject objects = new JSONObject();
objects.put("clientId", GlobalConfig.CLIENT_ID);
objects.put("sn", GlobalConfig.DEVICE_SN);
objects.put("networkInfo", JSON.toJSONString(infos));
objects.put("timestamp", timestamps);
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.多网IP探测上报.getValue()).data(objects.toString()).build();
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message);
AssertLog.info("发送交换机风扇信息包={}",JSON.toJSONString(message));
AssertLog.info("发送多网IP探测信息包={}",JSON.toJSONString(message));
}
AssertLog.info("发送多网IP探测定时任务执行 - task #{} completed", count);
}
/**
* 任务30:监测防火墙策略
*/
@Async("taskExecutor")
public void checkFirewallTask() {
int count = checkFirewallTask.incrementAndGet();
AssertLog.info("监测防火墙策略定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
// 判定客户端与服务端是否连接
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
// 检测防火墙策略是否存在
agentService.checkAndAddFirewallPeriodically();
}else{
AssertLog.info("断开连接--监测防火墙策略定时任务执行 - task #{} completed", count);
}
}
/**
* 任务31frpc保活机制
*/
@Async("taskExecutor")
public void checkFrpcTask() {
int count = checkFrpcTask.incrementAndGet();
AssertLog.info("frpc保活机制定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
// 判定客户端与服务端是否连接
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
// frpc保活机制
agentService.checkFrpc();
}else{
AssertLog.info("断开连接--frpc保活机制定时任务执行 - task #{} completed", count);
}
}
/**
* 任务32frpc状态上报
*/
@Async("taskExecutor")
public void upFrpcMsgTask() {
int count = upFrpcMsgTask.incrementAndGet();
AssertLog.info("frpc状态上报定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
// 判定客户端与服务端是否连接
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
// frpc状态上报
agentService.upFrpcMsg();
}else{
AssertLog.info("断开连接--frpc状态上报定时任务执行 - task #{} completed", count);
}
AssertLog.info("交换机风扇采集定时任务执行 - task #{} completed", count);
}
/**
* 任务31:交换机其他信息-系统描述采集
* 任务33macvlan状态上报
*/
@Async("taskExecutor")
public void switchSysDescrTask() {
long timestamp = System.currentTimeMillis();
timestamp = Math.round(timestamp / 1000.0);
int count = switchSysDescrTask.incrementAndGet();
AssertLog.info("交换机其他信息-系统描述采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
public void upMacvlanStatus() {
int count = upMacvlanStatusTask.incrementAndGet();
AssertLog.info("macvlan状态上报定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
// 判定客户端与服务端是否连接
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
// 发送交换机信息包
JSONObject jsonObject = new JSONObject();
String data = switchBoardService.getSwitchDataByType("switchSysDescrCollect");
if(StringUtils.isNotBlank(data)){
jsonObject = JSONObject.parseObject(data);
if(StringUtils.isBlank(GlobalConfig.DEVICE_SN)){
GlobalConfig.DEVICE_SN = AgentUtil.getDeviceSN();
}
long timestamps = System.currentTimeMillis();
timestamps = Math.round(timestamps / 1000.0);
List<MacVlanVO> macVlanVOList = null;
try {
macVlanVOList = agentService.upMacvlanStatus();
} catch (Exception e) {
e.printStackTrace();
}
if(macVlanVOList != null && !macVlanVOList.isEmpty()){
JSONObject objects = new JSONObject();
objects.put("clientId", GlobalConfig.CLIENT_ID);
objects.put("macVlans", macVlanVOList);
objects.put("timestamp", timestamps);
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.macvlan状态上报.getValue()).data(objects.toString()).build();
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message);
AssertLog.info("发送macvlan状态上报信息包={}",JSON.toJSONString(message));
}else {
AssertLog.info("未检测到虚拟网卡,不进行macvlan状态上报");
}
jsonObject.put("timestamp", timestamp);
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.交换机上报.getValue()).data(jsonObject.toString()).build();
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message);
AssertLog.info("发送交换机其他信息-系统描述信息包={}",JSON.toJSONString(message));
}
AssertLog.info("交换机其他信息-系统描述采集定时任务执行 - task #{} completed", count);
AssertLog.info("发送macvlan状态上报定时任务执行 - task #{} completed", count);
}
/**
* 任务32:交换机其他信息-系统ObjectID采集
* 任务34:本地存储流量信息上报
*/
@Async("taskExecutor")
public void switchSysObjectIDTask() {
long timestamp = System.currentTimeMillis();
timestamp = Math.round(timestamp / 1000.0);
int count = switchSysObjectIDTask.incrementAndGet();
AssertLog.info("交换机其他信息-系统ObjectID采集定时任务执行 - 时间: {}task #{}", LocalDateTime.now(), count);
public void upTempTraffic() {
int count = upTempTrafficTask.incrementAndGet();
AssertLog.info("本地存储流量信息上报定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
// 判定客户端与服务端是否连接
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
// 发送交换机信息包
JSONObject jsonObject = new JSONObject();
String data = switchBoardService.getSwitchDataByType("switchSysObjectIDCollect");
if(StringUtils.isNotBlank(data)){
jsonObject = JSONObject.parseObject(data);
}
jsonObject.put("timestamp", timestamp);
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.交换机上报.getValue()).data(jsonObject.toString()).build();
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message);
AssertLog.info("发送交换机其他信息-系统ObjectID信息包={}",JSON.toJSONString(message));
agentService.checkTraffic();
}
AssertLog.info("交换机其他信息-系统ObjectID采集定时任务执行 - task #{} completed", count);
AssertLog.info("本地存储流量信息上报定时任务执行 - task #{} completed", count);
}
/**
* 任务33:交换机其他信息-系统运行时间采集
*/
@Async("taskExecutor")
public void switchSysUpTimeTask() {
long timestamp = System.currentTimeMillis();
timestamp = Math.round(timestamp / 1000.0);
int count = switchSysUpTimeTask.incrementAndGet();
AssertLog.info("交换机其他信息-系统运行时间采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
// 判定客户端与服务端是否连接
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
// 发送交换机信息包
JSONObject jsonObject = new JSONObject();
String data = switchBoardService.getSwitchDataByType("switchSysUpTimeCollect");
if(StringUtils.isNotBlank(data)){
jsonObject = JSONObject.parseObject(data);
}
jsonObject.put("timestamp", timestamp);
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.交换机上报.getValue()).data(jsonObject.toString()).build();
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message);
AssertLog.info("发送交换机其他信息-系统运行时间信息包={}",JSON.toJSONString(message));
}
AssertLog.info("交换机其他信息-系统运行时间采集定时任务执行 - task #{} completed", count);
}
/**
* 任务34:交换机其他信息-系统联系信息采集
*/
@Async("taskExecutor")
public void switchSysContactTask() {
long timestamp = System.currentTimeMillis();
timestamp = Math.round(timestamp / 1000.0);
int count = switchSysContactTask.incrementAndGet();
AssertLog.info("交换机其他信息-系统联系信息采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
// 判定客户端与服务端是否连接
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
// 发送交换机信息包
JSONObject jsonObject = new JSONObject();
String data = switchBoardService.getSwitchDataByType("switchSysContactCollect");
if(StringUtils.isNotBlank(data)){
jsonObject = JSONObject.parseObject(data);
}
jsonObject.put("timestamp", timestamp);
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.交换机上报.getValue()).data(jsonObject.toString()).build();
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message);
AssertLog.info("发送交换机其他信息-系统联系信息包={}",JSON.toJSONString(message));
}
AssertLog.info("交换机其他信息-系统联系信息采集定时任务执行 - task #{} completed", count);
}
/**
* 任务35:交换机其他信息-系统名称采集
*/
@Async("taskExecutor")
public void switchSysNameTask() {
long timestamp = System.currentTimeMillis();
timestamp = Math.round(timestamp / 1000.0);
int count = switchSysNameTask.incrementAndGet();
AssertLog.info("交换机其他信息-系统名称采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
// 判定客户端与服务端是否连接
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
// 发送交换机信息包
JSONObject jsonObject = new JSONObject();
String data = switchBoardService.getSwitchDataByType("switchSysNameCollect");
if(StringUtils.isNotBlank(data)){
jsonObject = JSONObject.parseObject(data);
}
jsonObject.put("timestamp", timestamp);
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.交换机上报.getValue()).data(jsonObject.toString()).build();
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message);
AssertLog.info("发送交换机其他信息-系统名称信息包={}",JSON.toJSONString(message));
}
AssertLog.info("交换机其他信息-系统名称信息采集定时任务执行 - task #{} completed", count);
}
/**
* 任务36:交换机其他信息-系统位置采集
*/
@Async("taskExecutor")
public void switchSysLocationTask() {
long timestamp = System.currentTimeMillis();
timestamp = Math.round(timestamp / 1000.0);
int count = switchSysLocationTask.incrementAndGet();
AssertLog.info("交换机其他信息-系统位置采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
// 判定客户端与服务端是否连接
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
// 发送交换机信息包
JSONObject jsonObject = new JSONObject();
String data = switchBoardService.getSwitchDataByType("switchSysLocationCollect");
if(StringUtils.isNotBlank(data)){
jsonObject = JSONObject.parseObject(data);
}
jsonObject.put("timestamp", timestamp);
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.交换机上报.getValue()).data(jsonObject.toString()).build();
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message);
AssertLog.info("发送交换机其他信息-系统位置信息包={}",JSON.toJSONString(message));
}
AssertLog.info("交换机其他信息-系统位置采集定时任务执行 - task #{} completed", count);
}
/**
* 任务37:交换机其他信息-系统MAC地址采集
*/
@Async("taskExecutor")
public void switchHwStackSystemMacTask() {
long timestamp = System.currentTimeMillis();
timestamp = Math.round(timestamp / 1000.0);
int count = switchHwStackSystemMacTask.incrementAndGet();
AssertLog.info("交换机其他信息-系统MAC地址采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
// 判定客户端与服务端是否连接
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
// 发送交换机信息包
JSONObject jsonObject = new JSONObject();
String data = switchBoardService.getSwitchDataByType("switchHwStackSystemMacCollect");
if(StringUtils.isNotBlank(data)){
jsonObject = JSONObject.parseObject(data);
}
jsonObject.put("timestamp", timestamp);
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.交换机上报.getValue()).data(jsonObject.toString()).build();
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message);
AssertLog.info("发送交换机其他信息-系统MAC地址信息包={}",JSON.toJSONString(message));
}
AssertLog.info("交换机其他信息-系统MAC地址采集定时任务执行 - task #{} completed", count);
}
/**
* 任务38:交换机其他信息-设备索引采集
*/
@Async("taskExecutor")
public void switchEntIndexTask() {
long timestamp = System.currentTimeMillis();
timestamp = Math.round(timestamp / 1000.0);
int count = switchEntIndexTask.incrementAndGet();
AssertLog.info("交换机其他信息-设备索引采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
// 判定客户端与服务端是否连接
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
// 发送交换机信息包
JSONObject jsonObject = new JSONObject();
String data = switchBoardService.getSwitchDataByType("switchEntIndexCollect");
if(StringUtils.isNotBlank(data)){
jsonObject = JSONObject.parseObject(data);
}
jsonObject.put("timestamp", timestamp);
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.交换机上报.getValue()).data(jsonObject.toString()).build();
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message);
AssertLog.info("发送交换机其他信息-设备索引信息包={}",JSON.toJSONString(message));
}
AssertLog.info("交换机其他信息-设备索引采集定时任务执行 - task #{} completed", count);
}
/**
* 任务39:交换机其他信息-设备名称采集
*/
@Async("taskExecutor")
public void switchEntPhysicalNameTask() {
long timestamp = System.currentTimeMillis();
timestamp = Math.round(timestamp / 1000.0);
int count = switchEntPhysicalNameTask.incrementAndGet();
AssertLog.info("交换机其他信息-设备名称采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
// 判定客户端与服务端是否连接
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
// 发送交换机信息包
JSONObject jsonObject = new JSONObject();
String data = switchBoardService.getSwitchDataByType("switchEntPhysicalNameCollect");
if(StringUtils.isNotBlank(data)){
jsonObject = JSONObject.parseObject(data);
}
jsonObject.put("timestamp", timestamp);
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.交换机上报.getValue()).data(jsonObject.toString()).build();
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message);
AssertLog.info("发送交换机其他信息-设备名称信息包={}",JSON.toJSONString(message));
}
AssertLog.info("交换机其他信息-设备名称采集定时任务执行 - task #{} completed", count);
}
/**
* 任务40:交换机其他信息-设备软件版本采集
*/
@Async("taskExecutor")
public void switchEntPhysicalSoftwareRevTask() {
long timestamp = System.currentTimeMillis();
timestamp = Math.round(timestamp / 1000.0);
int count = switchEntPhysicalSoftwareRevTask.incrementAndGet();
AssertLog.info("交换机其他信息-设备软件版本采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
// 判定客户端与服务端是否连接
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
// 发送交换机信息包
JSONObject jsonObject = new JSONObject();
String data = switchBoardService.getSwitchDataByType("switchEntPhysicalSoftwareRevCollect");
if(StringUtils.isNotBlank(data)){
jsonObject = JSONObject.parseObject(data);
}
jsonObject.put("timestamp", timestamp);
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.交换机上报.getValue()).data(jsonObject.toString()).build();
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message);
AssertLog.info("发送交换机其他信息-设备软件版本信息包={}",JSON.toJSONString(message));
}
AssertLog.info("交换机其他信息-设备软件版本采集定时任务执行 - task #{} completed", count);
}
/**
* 任务41:交换机其他信息-设备CPU使用率采集
*/
@Async("taskExecutor")
public void switchHwEntityCpuUsageTask() {
long timestamp = System.currentTimeMillis();
timestamp = Math.round(timestamp / 1000.0);
int count = switchHwEntityCpuUsageTask.incrementAndGet();
AssertLog.info("交换机其他信息-设备CPU使用率采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
// 判定客户端与服务端是否连接
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
// 发送交换机信息包
JSONObject jsonObject = new JSONObject();
String data = switchBoardService.getSwitchDataByType("switchHwEntityCpuUsageCollect");
if(StringUtils.isNotBlank(data)){
jsonObject = JSONObject.parseObject(data);
}
jsonObject.put("timestamp", timestamp);
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.交换机上报.getValue()).data(jsonObject.toString()).build();
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message);
AssertLog.info("发送交换机其他信息-设备CPU使用率信息包={}",JSON.toJSONString(message));
}
AssertLog.info("交换机其他信息-设备CPU使用率采集定时任务执行 - task #{} completed", count);
}
/**
* 任务42:交换机其他信息-设备内存使用率采集
*/
@Async("taskExecutor")
public void switchHwEntityMemUsageTask() {
long timestamp = System.currentTimeMillis();
timestamp = Math.round(timestamp / 1000.0);
int count = switchHwEntityMemUsageTask.incrementAndGet();
AssertLog.info("交换机其他信息-设备内存使用率采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
// 判定客户端与服务端是否连接
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
// 发送交换机信息包
JSONObject jsonObject = new JSONObject();
String data = switchBoardService.getSwitchDataByType("switchHwEntityMemUsageCollect");
if(StringUtils.isNotBlank(data)){
jsonObject = JSONObject.parseObject(data);
}
jsonObject.put("timestamp", timestamp);
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.交换机上报.getValue()).data(jsonObject.toString()).build();
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message);
AssertLog.info("发送交换机其他信息-设备内存使用率信息包={}",JSON.toJSONString(message));
}
AssertLog.info("交换机其他信息-设备内存使用率采集定时任务执行 - task #{} completed", count);
}
/**
* 任务43:交换机其他信息-系统平均功率采集
*/
@Async("taskExecutor")
public void switchHwAveragePowerTask() {
long timestamp = System.currentTimeMillis();
timestamp = Math.round(timestamp / 1000.0);
int count = switchHwAveragePowerTask.incrementAndGet();
AssertLog.info("交换机其他信息-系统平均功率采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
// 判定客户端与服务端是否连接
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
// 发送交换机信息包
JSONObject jsonObject = new JSONObject();
String data = switchBoardService.getSwitchDataByType("switchHwAveragePowerCollect");
if(StringUtils.isNotBlank(data)){
jsonObject = JSONObject.parseObject(data);
}
jsonObject.put("timestamp", timestamp);
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.交换机上报.getValue()).data(jsonObject.toString()).build();
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message);
AssertLog.info("发送交换机其他信息-系统平均功率信息包={}",JSON.toJSONString(message));
}
AssertLog.info("交换机其他信息-系统平均功率采集定时任务执行 - task #{} completed", count);
}
/**
* 任务44:交换机其他信息-系统实时功率采集
*/
@Async("taskExecutor")
public void switchHwCurrentPowerTask() {
long timestamp = System.currentTimeMillis();
timestamp = Math.round(timestamp / 1000.0);
int count = switchHwCurrentPowerTask.incrementAndGet();
AssertLog.info("交换机其他信息-系统实时功率采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
// 判定客户端与服务端是否连接
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
// 发送交换机信息包
JSONObject jsonObject = new JSONObject();
String data = switchBoardService.getSwitchDataByType("switchHwCurrentPowerCollect");
if(StringUtils.isNotBlank(data)){
jsonObject = JSONObject.parseObject(data);
}
jsonObject.put("timestamp", timestamp);
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.交换机上报.getValue()).data(jsonObject.toString()).build();
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message);
AssertLog.info("发送交换机其他信息-系统实时功率信息包={}",JSON.toJSONString(message));
}
AssertLog.info("交换机其他信息-系统实时功率采集定时任务执行 - task #{} completed", count);
}
//
// @Async("taskExecutor")
@@ -6,7 +6,6 @@ import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.List;
@Data
@Builder
@@ -15,8 +14,9 @@ import java.util.List;
public class SpecificTimeRequest {
private String taskId;
private String taskName;
private List<String> taskData;
private String taskData;
private String scriptId;
private String clientId;
private String dataType;
@@ -1,8 +1,12 @@
package com.tongran.agent.client.scheduler.task;
import com.tongran.agent.client.utils.AssertLog;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.config.CronTask;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
@@ -20,6 +24,9 @@ public class SpecificTimeTaskConfig implements SchedulingConfigurer {
private ScheduledTaskRegistrar taskRegistrar;
private final Map<String, ScheduledFuture<?>> taskFutures = new ConcurrentHashMap<>();
private final Map<String, Object> taskMap = new ConcurrentHashMap<>();
@Autowired
@Qualifier("tcpdumpTaskExecutor")
private ThreadPoolTaskExecutor tcpdumpExecutor;
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
@@ -31,13 +38,36 @@ public class SpecificTimeTaskConfig implements SchedulingConfigurer {
*/
public void addCronTask(String taskId, Runnable task, String cronExpression) {
removeTaskIfExists(taskId);
CronTask cronTask = new CronTask(task, cronExpression);
ScheduledFuture<?> future = taskRegistrar.getScheduler().schedule(
cronTask.getRunnable(),
cronTask.getRunnable(),
cronTask.getTrigger()
);
taskMap.put(taskId, cronTask);
taskFutures.put(taskId, future);
}
public void addTcpdumpCronTask(String taskId, Runnable task, String cronExpression) {
removeTaskIfExists(taskId);
// 使用轻量级调度,实际任务交给专用线程池
CronTask cronTask = new CronTask(() -> {
// 异步执行,不阻塞定时任务线程
tcpdumpExecutor.execute(() -> {
try {
task.run();
} catch (Exception e) {
AssertLog.error("任务执行异常 {}: {}", taskId, e.getMessage());
}
});
}, cronExpression);
ScheduledFuture<?> future = taskRegistrar.getScheduler().schedule(
cronTask.getRunnable(),
cronTask.getTrigger()
);
taskMap.put(taskId, cronTask);
taskFutures.put(taskId, future);
}
@@ -80,6 +110,19 @@ public class SpecificTimeTaskConfig implements SchedulingConfigurer {
String cronExpression = String.format("%d %d %d * * ?", second, minute, hour);
addCronTask(taskId, task, cronExpression);
}
public void addDailyTimeTcpdumpTask(String taskId, Runnable task, String time) {
String[] timeParts = time.split(":");
if (timeParts.length < 2) {
throw new IllegalArgumentException("时间格式应为 HH:mm 或 HH:mm:ss");
}
int hour = Integer.parseInt(timeParts[0]);
int minute = Integer.parseInt(timeParts[1]);
int second = timeParts.length > 2 ? Integer.parseInt(timeParts[2]) : 0;
String cronExpression = String.format("%d %d %d * * ?", second, minute, hour);
addTcpdumpCronTask(taskId, task, cronExpression);
}
/**
* 添加延迟任务
@@ -1,34 +1,90 @@
package com.tongran.agent.client.scheduler.task;
import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.tongran.agent.client.core.config.ApplicationProperties;
import com.tongran.agent.client.core.config.GlobalConfig;
import com.tongran.agent.client.core.enums.MsgEnum;
import com.tongran.agent.client.core.session.SessionManager;
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.service.AgentService;
import com.tongran.agent.client.utils.AgentDataUtil;
import com.tongran.agent.client.utils.AgentUtil;
import com.tongran.agent.client.utils.AssertLog;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
@Service
public class SpecificTimeTaskService {
protected final SessionManager sessionManager;
@Resource
private ApplicationProperties properties;
@Resource
private AgentService agentService;
@Resource
private SpecificTimeTaskConfig taskConfig;
@Resource
@Qualifier("tcpdumpTaskExecutor")
private ThreadPoolTaskExecutor tcpdumpExecutor;
public SpecificTimeTaskService() {
this.sessionManager = SessionManager.getInstance();
}
public boolean createTcpdumpSpecificTimeTask(SpecificTimeRequest request){
String taskId = request.getTaskId();
Runnable task = createTcpdumpTaskRunnable(request);
try {
if (request.getTime() != null) {
taskConfig.addDailyTimeTcpdumpTask(taskId, task, request.getTime());
} else {
throw new IllegalArgumentException("必须指定一种时间方式");
}
return true;
} catch (Exception e) {
throw new RuntimeException("创建任务失败: " + e.getMessage(), e);
}
}
public CompletableFuture<Boolean> executeTcpdumpImmediatelyWithResult(SpecificTimeRequest request) {
String taskId = request.getTaskId() != null ?
request.getTaskId() : "tcpdump-immediate-" + System.currentTimeMillis();
return CompletableFuture.supplyAsync(() -> {
try {
AssertLog.info("立即执行TCPDUMP任务开始: {}", taskId);
executeTcpdump(request);
AssertLog.info("立即执行TCPDUMP任务完成: {}", taskId);
return true;
} catch (Exception e) {
AssertLog.error("立即执行TCPDUMP任务异常 {}: {}", taskId, e.getMessage(), e);
return false;
}
}, tcpdumpExecutor); // 使用专用线程池
}
/**
* 创建指定时间任务
*/
@@ -55,14 +111,351 @@ public class SpecificTimeTaskService {
throw new RuntimeException("创建任务失败: " + e.getMessage(), e);
}
}
private Runnable createTcpdumpTaskRunnable(SpecificTimeRequest request) {
return () -> {
// 直接执行,因为已经在专用线程池中了
try {
System.out.println("执行定时任务: " + request.getTaskName());
System.out.println("执行时间: " + LocalDateTime.now());
System.out.println("-----------------------------------");
executeTcpdump(request);
} catch (Exception e) {
AssertLog.error("TCPDUMP任务执行异常: {}", e.getMessage(), e);
}
};
}
private void executeTcpdump(SpecificTimeRequest request) {
try {
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
List<IpCount> allIpCountList = new ArrayList<>();
// 获取业务网卡列表
String businessNetName = GlobalConfig.NETNAME;
if (businessNetName == null || businessNetName.trim().isEmpty()) {
AssertLog.error("No business network interfaces configured");
return;
}
String[] businessNetNameArr = businessNetName.split(";");
// 遍历所有业务网卡进行抓包
for (String interfaceName : businessNetNameArr) {
if (interfaceName.trim().isEmpty()) {
continue;
}
String cleanInterfaceName = interfaceName.trim();
AssertLog.info("Starting tcpdump on interface: {}", cleanInterfaceName);
try {
List<IpCount> interfaceResults = captureAndAnalyzeInterface(request, timestamp, cleanInterfaceName);
allIpCountList.addAll(interfaceResults);
} catch (Exception e) {
AssertLog.error("Failed to capture interface {}: {}", cleanInterfaceName, e.getMessage());
// 继续处理其他网卡,不中断整个流程
}
}
// 合并所有网卡的统计结果(按IP聚合)
List<IpCount> mergedResults = mergeIpCounts(allIpCountList);
// 保存合并后的结果
saveTcpdumpResult(request, timestamp + "_merged", mergedResults);
} catch (Exception e) {
AssertLog.error("Error executing tcpdump task: {}", request.getTaskId(), e);
}
}
/**
* 对单个网卡进行抓包和分析
*/
private List<IpCount> captureAndAnalyzeInterface(SpecificTimeRequest request,
String timestamp, String interfaceName) {
if (!AdvancedAsyncDownloader.createSingleDirectoryIfNotExists(properties.getTempPcapPath())) {
return new ArrayList<>();
}
String pcapFileName = properties.getTempPcapPath() + timestamp + "_" + interfaceName + ".pcap";
List<IpCount> ipCountList = new ArrayList<>();
try {
// 第一步:执行tcpdump抓包(带超时控制)
CompletableFuture<Boolean> captureFuture = CompletableFuture.supplyAsync(() -> {
try {
ProcessBuilder captureBuilder = new ProcessBuilder();
// 设置抓包时间,根据实际需求调整
captureBuilder.command("timeout", "10", "tcpdump",
"-i", interfaceName, "-nn", "-n", "-w", pcapFileName);
Process captureProcess = captureBuilder.start();
int captureExitCode = captureProcess.waitFor();
if (captureExitCode != 0 && captureExitCode != 124) {
AssertLog.error("Tcpdump抓包失败,网卡: {},退出码: {}", interfaceName, captureExitCode);
return false;
}
return true;
} catch (Exception e) {
AssertLog.error("抓包过程异常 {}: {}", interfaceName, e.getMessage());
return false;
}
});
// 等待抓包完成(最长7分钟)
boolean captureSuccess = captureFuture.get(20, TimeUnit.SECONDS);
if (!captureSuccess) {
return ipCountList;
}
// 第二步:分析PCAP文件
CompletableFuture<List<IpCount>> analyzeFuture = CompletableFuture.supplyAsync(() -> {
return analyzePcapFile(pcapFileName, interfaceName);
});
// 分析阶段超时时间(2分钟应该足够)
ipCountList = analyzeFuture.get(2, TimeUnit.MINUTES);
} catch (TimeoutException e) {
AssertLog.warn("网卡 {} 抓包或分析超时,跳过处理", interfaceName);
} catch (Exception e) {
AssertLog.error("处理网卡 {} 时异常: {}", interfaceName, e.getMessage());
} finally {
// 确保清理临时文件
cleanupTempFile(pcapFileName);
}
return ipCountList;
}
/**
* 分析PCAP文件并统计IP信息
*/
private List<IpCount> analyzePcapFile(String pcapFileName, String interfaceName) {
List<IpCount> ipCountList = new ArrayList<>();
File pcapFile = new File(pcapFileName);
if (!pcapFile.exists() || pcapFile.length() == 0) {
AssertLog.warn("PCAP文件不存在或为空: {}", pcapFileName);
return ipCountList;
}
try {
ProcessBuilder analyzeBuilder = new ProcessBuilder();
// 使用更可靠的命令分析PCAP文件
analyzeBuilder.command("bash", "-c",
"tcpdump -r " + pcapFileName + " -nn 2>/dev/null | " +
"awk '/^[0-9]/ && ($2 == \"IP\" || $2 == \"IP6\") { " +
" dest=$5; " +
" if(match(dest,/^([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)/)) { " +
" dest=substr(dest,RSTART,RLENGTH) " +
" } else if(match(dest,/([0-9a-fA-F:]+)/)) { " +
" dest=substr(dest,RSTART,RLENGTH); gsub(/:[0-9]+$/,\"\",dest) " +
" } " +
" print dest " +
"}' | " +
"sort | uniq -c | sort -rn");
Process analyzeProcess = analyzeBuilder.start();
// 读取分析结果
StringBuilder result = new StringBuilder();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(analyzeProcess.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
result.append(line).append("\n");
}
}
int analyzeExitCode = analyzeProcess.waitFor();
if (analyzeExitCode == 0) {
AssertLog.debug("PCAP分析完成: {},文件大小: {} bytes", interfaceName, pcapFile.length());
ipCountList = parseTcpdumpOutput(result.toString());
} else {
AssertLog.error("PCAP分析失败: {},退出码: {}", interfaceName, analyzeExitCode);
}
} catch (Exception e) {
AssertLog.error("分析PCAP文件异常 {}: {}", interfaceName, e.getMessage());
}
return ipCountList;
}
/**
* 清理临时文件
*/
private void cleanupTempFile(String fileName) {
try {
File file = new File(fileName);
if (file.exists()) {
boolean deleted = file.delete();
if (!deleted) {
AssertLog.warn("无法删除临时文件: {}", fileName);
} else {
AssertLog.debug("已清理临时文件: {}", fileName);
}
}
} catch (Exception e) {
AssertLog.warn("清理临时文件异常: {}", e.getMessage());
}
}
/**
* 合并多个网卡的IP统计结果(相同IP的数量相加)
*/
private List<IpCount> mergeIpCounts(List<IpCount> allIpCountList) {
Map<String, Double> ipCountMap = new HashMap<>();
final String excludedIp = "223.5.5.5";
for (IpCount ipCount : allIpCountList) {
String ip = ipCount.getIp();
// 跳过要排除的IP
if (excludedIp.equals(ip)) {
continue;
}
Double count = ipCount.getCount();
if (ipCountMap.containsKey(ip)) {
ipCountMap.put(ip, ipCountMap.get(ip) + count);
} else {
ipCountMap.put(ip, count);
}
}
// 转换为列表并按数量降序排序
return ipCountMap.entrySet().stream()
.map(entry -> new IpCount(entry.getKey(), entry.getValue()))
.sorted((a, b) -> Double.compare(b.getCount(), a.getCount())) // 降序
.collect(Collectors.toList());
}
/**
* 保存合并后的结果
*/
private void saveTcpdumpResult(SpecificTimeRequest request, String fileName, List<IpCount> ipCountList) {
try {
AssertLog.info("Saving merged tcpdump result - Task: {}, File: {}, Total unique IPs: {}",
request.getTaskId(), fileName, ipCountList.size());
String data = "";
if (CollectionUtil.isNotEmpty(ipCountList)) {
data = JSONArray.toJSONString(ipCountList);
}
Message message = Message.builder()
.clientId(request.getClientId())
.dataType(MsgEnum.tcpdump结果上报.getValue())
.data(data)
.build();
if (Objects.nonNull(sessionManager.getSessionById(request.getClientId()))) {
sessionManager.writeAndFlush(sessionManager.getSessionById(request.getClientId()).getChannel(), message);
AssertLog.info("发送合并后的tcpdump包统计,共{}个IP", ipCountList.size());
}
} catch (Exception e) {
AssertLog.error("Failed to save merged tcpdump result for task: {}", request.getTaskId(), e);
}
}
/**
* 解析tcpdump输出为IP统计列表(优化版本)
*/
private List<IpCount> parseTcpdumpOutput(String output) {
List<IpCount> ipCountList = new ArrayList<>();
if (output == null || output.trim().isEmpty()) {
return ipCountList;
}
String[] lines = output.split("\n");
for (String line : lines) {
line = line.trim();
// 跳过空行和 tcpdump 信息行
if (line.isEmpty() || line.contains("reading from file") || line.contains("link-type")) {
continue;
}
// 修改:匹配 "数字 + IP" 的格式,IP 可以是 IPv4 或 IPv6
String[] parts = line.trim().split("\\s+", 2);
if (parts.length == 2) {
String countStr = parts[0].trim();
String ipStr = parts[1].trim();
// 验证 count 部分是否是数字
try {
Double count = Double.valueOf(countStr);
// 验证 IP 格式(IPv4 或 IPv6
if (isValidIP(ipStr)) {
ipCountList.add(new IpCount(ipStr, count));
} else {
AssertLog.debug("Invalid IP format: {}", ipStr);
}
} catch (NumberFormatException e) {
AssertLog.debug("Failed to parse count from line: {}", line);
}
}
}
return ipCountList;
}
// 添加 IP 验证方法
private boolean isValidIP(String ip) {
// IPv4 验证
if (ip.contains(".")) {
String ipv4Pattern =
"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$";
return ip.matches(ipv4Pattern);
}
// IPv6 验证
else if (ip.contains(":")) {
// 简化的 IPv6 验证(支持标准格式和压缩格式)
String ipv6Pattern =
"^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|" + // 完整格式
"^([0-9a-fA-F]{1,4}:){1,7}:$|" + // 结尾压缩
"^:(:[0-9a-fA-F]{1,4}){1,7}$|" + // 开头压缩
"^([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}$|" + // 中间压缩
"^([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}$|" +
"^([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}$|" +
"^([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}$|" +
"^([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}$|" +
"^[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})$|" +
"^:((:[0-9a-fA-F]{1,4}){1,7}|:)$";
return ip.matches(ipv6Pattern);
}
return false;
}
/**
* IP统计实体类
*/
@Data
@AllArgsConstructor
public static class IpCount {
private String ip;
private Double count;
}
private Runnable createTaskRunnable(SpecificTimeRequest request) {
return () -> {
System.out.println("执行定时任务: " + request.getTaskName());
System.out.println("任务数据: " + request.getTaskData());
System.out.println("执行时间: " + LocalDateTime.now());
System.out.println("-----------------------------------");
// 具体的业务逻辑
executeBusinessLogic(request);
};
@@ -73,56 +466,85 @@ public class SpecificTimeTaskService {
try {
System.out.println("处理业务: " + request.getTaskData());
String key = request.getTaskName()+"-"+System.currentTimeMillis();
for (String command : request.getTaskData()) {
if(StringUtils.equals(request.getDataType(), MsgEnum.Agent版本更新应答.getValue())){
try {
System.out.println("重启进程已启动,当前服务退出");
System.out.println("command="+command);
ProcessBuilder pb = new ProcessBuilder("/bin/sh", "-c",
command);
pb.start();
System.exit(0);
} catch (IOException e) {
e.printStackTrace();
}
}else{
List<String> cmd = Arrays.asList(command.split("\\s+"));
CompletableFuture<AsyncCommandExecutor.CommandResult> future =
AsyncCommandExecutor.executeCommandAsync(
cmd,
100, TimeUnit.SECONDS);
future.thenAccept(result -> {
JSONObject jsonObject = new JSONObject();
jsonObject.put("command", command);
jsonObject.put("resOut", result.getOutput());
System.out.println("JSON: " + jsonObject.toJSONString()); // 注意:toJSONString()
JSONObject json = new JSONObject();
json.put("resCode",1);
json.put("resMsg", "");
json.put("result", jsonObject.toJSONString());
Message message = Message.builder().clientId(request.getClientId()).dataType(request.getDataType()).data(jsonObject.toJSONString()).build();
if (Objects.nonNull(sessionManager.getSessionById(request.getClientId()))) {
System.out.println("发送执行结果: " + json.toJSONString()); // 注意:toJSONString()
sessionManager.writeAndFlush(sessionManager.getSessionById(request.getClientId()).getChannel(), message);
}
}).exceptionally(ex -> {
System.err.println("执行失败: " + ex.getMessage());
JSONObject json = new JSONObject();
json.put("resCode",0);
json.put("resMsg", "执行失败:Policy execute filed");
json.put("result", "");
Message message = Message.builder().clientId(request.getClientId()).dataType(request.getDataType()).data(json.toJSONString()).build();
if (Objects.nonNull(sessionManager.getSessionById(request.getClientId()))) {
sessionManager.writeAndFlush(sessionManager.getSessionById(request.getClientId()).getChannel(), message);
}
return null;
});
if(StringUtils.equals(request.getDataType(), MsgEnum.Agent版本更新应答.getValue())){
try {
AssertLog.info("定时更新启动...暂停心跳和采集任务");
//更改全局变量
GlobalConfig.isCollect = false;
//调用采集任务
agentService.cancelCollect();
//设置检查回滚任务
String SCRIPT_PATH = properties.getScriptPath()+"/rollback-tragent.sh";
AgentDataUtil.chmod(SCRIPT_PATH,"775");
AgentUtil.scheduleScriptIn3Minutes(SCRIPT_PATH);
System.out.println("重启进程已启动,当前服务退出");
System.out.println("command="+request.getTaskData());
ProcessBuilder pb = new ProcessBuilder("/bin/sh", "-c",
request.getTaskData());
pb.start();
System.exit(0);
} catch (IOException e) {
e.printStackTrace();
}
}else{
List<String> cmd = Arrays.asList(request.getTaskData().split("\\s+"));
CompletableFuture<AsyncCommandExecutor.CommandResult> future =
AsyncCommandExecutor.executeCommandAsync(
cmd,
100, TimeUnit.SECONDS);
future.thenAccept(result -> {
JSONObject jsonObject = new JSONObject();
jsonObject.put("scriptId", request.getScriptId());
jsonObject.put("command", request.getTaskData());
jsonObject.put("resOut", result.getOutput());
System.out.println("JSON: " + jsonObject.toJSONString()); // 注意:toJSONString()
long timestamps = System.currentTimeMillis();
timestamps = Math.round(timestamps / 1000.0);
JSONObject json = new JSONObject();
json.put("resCode",1);
json.put("resMsg", "");
json.put("result", jsonObject.toJSONString());
json.put("timestamp",timestamps);
Message message = Message.builder().clientId(request.getClientId()).dataType(request.getDataType()).data(json.toJSONString()).build();
if (Objects.nonNull(sessionManager.getSessionById(request.getClientId()))) {
System.out.println("发送执行结果: " + json.toJSONString()); // 注意:toJSONString()
sessionManager.writeAndFlush(sessionManager.getSessionById(request.getClientId()).getChannel(), message);
}
}).exceptionally(ex -> {
System.err.println("执行失败: " + ex.getMessage());
JSONObject rse = new JSONObject();
rse.put("scriptId", request.getScriptId());
rse.put("command", request.getTaskData());
rse.put("resOut", "脚本执行失败");
long timestamps = System.currentTimeMillis();
timestamps = Math.round(timestamps / 1000.0);
JSONObject json = new JSONObject();
json.put("resCode",0);
json.put("resMsg", "执行失败:Policy execute filed");
json.put("result", rse.toString());
json.put("timestamp",timestamps);
Message message = Message.builder().clientId(request.getClientId()).dataType(request.getDataType()).data(json.toJSONString()).build();
if (Objects.nonNull(sessionManager.getSessionById(request.getClientId()))) {
sessionManager.writeAndFlush(sessionManager.getSessionById(request.getClientId()).getChannel(), message);
}
return null;
});
}
// 调用其他服务等
} catch (Exception e) {
System.err.println("任务执行异常: " + e.getMessage());
}
}
public void cancleTcpdumpSpecificTimeTask() {
// 获取所有任务ID
Set<String> taskIds = taskConfig.getTaskMap().keySet();
// 遍历并关闭所有以"tcpdump"开头的任务
for (String taskId : taskIds) {
if (taskId.startsWith("tcpdump")) {
taskConfig.removeTask(taskId);
}
}
}
}
@@ -1,19 +1,54 @@
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();
void systemCollectStart(String data);
void systemCollectStop();
void switchCollectStart(String data);
void switchCollectStop();
void alarmMonitor();
void command(ScriptPolicyEO policy, String clientId, String dataType);
void cancelTask(String taskId);
void start();
void getPolicy(String data);
void checkTrAgent();
void sendMessage(String type, String data);
boolean connection();
void addRoute(String name, String gateway);
void dellRoute(String name, String gateway);
String getLogicalNode();
void checkMonitor();
void addFirewall(String netName);
void checkFirewall();
void checkAndAddFirewallPeriodically();
void checkAgentUpdate();
void checkFrpc();
void upFrpcMsg();
void handleRebootRecovery();
void checkTraffic();
void checkTcpdumpTimes();
List<MacVlanVO> upMacvlanStatus();
}
@@ -1,7 +1,15 @@
package com.tongran.agent.client.service;
import com.tongran.agent.client.core.vo.MemoryDetailsVO;
import com.tongran.agent.client.core.vo.MemoryVO;
import java.io.IOException;
import java.util.List;
public interface MemoryService {
MemoryVO get();
List<MemoryDetailsVO> getMemoryDetails();
Integer getMemorySlotsNum();
}
@@ -0,0 +1,10 @@
package com.tongran.agent.client.service;
import com.tongran.agent.client.core.vo.NetBusinessVO;
import com.tongran.agent.client.core.vo.NetVO;
import java.util.List;
public interface NetBusinessService {
List<NetBusinessVO> netList(long timestamp);
}
@@ -5,7 +5,4 @@ import com.tongran.agent.client.core.vo.SwitchBoardVO;
import java.util.List;
public interface SwitchBoardService {
List<SwitchBoardVO> switchBoardList(long timestamp);
String getSwitchDataByType(String type);
}
File diff suppressed because it is too large Load Diff
@@ -2,11 +2,14 @@ package com.tongran.agent.client.service.impl;
import com.tongran.agent.client.core.vo.CpuVO;
import com.tongran.agent.client.service.CPUService;
import com.tongran.agent.client.utils.AgentUtil;
import com.tongran.agent.client.utils.AssertLog;
import com.tongran.agent.client.utils.CpuUsageFromProcStat;
import org.springframework.stereotype.Service;
import oshi.SystemInfo;
import oshi.hardware.CentralProcessor;
import oshi.hardware.HardwareAbstractionLayer;
import oshi.hardware.Sensors;
import oshi.software.os.OperatingSystem;
import java.util.concurrent.TimeUnit;
@@ -14,39 +17,78 @@ import java.util.concurrent.TimeUnit;
@Service
public class CPUServiceImpl implements CPUService {
// 改为单例只初始化一次
private static final SystemInfo SI = new SystemInfo();
private final HardwareAbstractionLayer hal = SI.getHardware();
private final OperatingSystem os = SI.getOperatingSystem();
@Override
public CpuVO get() {
CpuVO cpuVO = CpuVO.builder().build();
try {
SystemInfo si = new SystemInfo();
HardwareAbstractionLayer hal = si.getHardware();
CentralProcessor processor = hal.getProcessor();
OperatingSystem os = si.getOperatingSystem();
System.out.println("=========================================================");
// 1. CPU基本信息
cpuVO.setNum(processor.getPhysicalProcessorCount()); // CUP数量
cpuVO.setCores(processor.getLogicalProcessorCount()); // CUP核数
System.out.println("=== CPU基本信息 ===");
System.out.println("CPU型号: " + processor.getProcessorIdentifier().getName());
System.out.println("物理核心数: " + processor.getPhysicalProcessorCount());
System.out.println("逻辑核心数: " + processor.getLogicalProcessorCount());
System.out.println("最大频率: " + processor.getMaxFreq() / 1_000_000.0 + " GHz");
// 1.1 CPU温度信息
Sensors sensors = hal.getSensors();
double cpuTemperature = sensors.getCpuTemperature();
cpuVO.setTemperature(cpuTemperature);
System.out.println("CPU温度: " + (cpuTemperature > 0 ? String.format("%.1f°C", cpuTemperature) : "N/A"));
// 2. CPU负载信息
System.out.println("\n=== CPU负载信息 ===");
double[] loadAverage = processor.getSystemLoadAverage(3);
cpuVO.setAvg1(loadAverage[0]);// CUP1分钟负载
cpuVO.setAvg1(loadAverage[1]);// CUP5分钟负载
cpuVO.setAvg1(loadAverage[2]);// CUP15分钟负载
cpuVO.setAvg5(loadAverage[1]);// CUP5分钟负载
cpuVO.setAvg15(loadAverage[2]);// CUP15分钟负载
System.out.println("1分钟平均负载: " + loadAverage[0]);
System.out.println("5分钟平均负载: " + loadAverage[1]);
System.out.println("15分钟平均负载: " + loadAverage[2]);
// 3. CPU使用率需要两次采样
System.out.println("\n=== CPU使用率 ===");
AssertLog.info("\n=== CPU使用率 ===");
// 预热第一次调用可能不准
processor.getSystemCpuLoadBetweenTicks(processor.getSystemCpuLoadTicks());
TimeUnit.SECONDS.sleep(1);
// 正式采样
long[] prevTicks = processor.getSystemCpuLoadTicks();
TimeUnit.SECONDS.sleep(1); // 等待1秒
TimeUnit.SECONDS.sleep(1); // 更长间隔减少抖动
// 计算使用率
double cpuUsage = processor.getSystemCpuLoadBetweenTicks(prevTicks);
cpuVO.setUti(cpuUsage * 100); // CPU使用率
System.out.printf("CPU 使用率: %.2f%%\n", cpuUsage * 100);
if(cpuUsage < 1.0){
cpuVO.setUti(cpuUsage * 100); // CPU使用率
}else {
cpuUsage = -1;
try {
// 预热
CpuUsageFromProcStat.getCpuUsage();
Thread.sleep(1000);
// 正式采集
for (int i = 0; i < 5; i++) {
cpuUsage = CpuUsageFromProcStat.getCpuUsage();
System.out.printf("CPU 使用率: %.2f%%\n", cpuUsage * 100);
Thread.sleep(2000);
}
} catch (Exception e) {
e.printStackTrace();
}
if(cpuUsage > 0){
cpuVO.setUti(cpuUsage * 100); // CPU使用率
}else{
cpuVO.setUti(cpuUsage); // CPU使用率
}
}
AssertLog.info("CPU 使用率: {}%",cpuUsage * 100);
long[] ticks = processor.getSystemCpuLoadTicks();
long user = ticks[CentralProcessor.TickType.USER.getIndex()] -
prevTicks[CentralProcessor.TickType.USER.getIndex()];
@@ -65,34 +107,35 @@ public class CPUServiceImpl implements CPUService {
long steal = ticks[CentralProcessor.TickType.STEAL.getIndex()] -
prevTicks[CentralProcessor.TickType.STEAL.getIndex()];
long total = user + nice + sys + idle + iowait + irq + softirq + steal;
// 4. CPU时间累计值
System.out.println("\n=== CPU时间累计值 ===");
long[] allTicks = processor.getSystemCpuLoadTicks();
System.out.println("中断累计时间: " +
(allTicks[CentralProcessor.TickType.IRQ.getIndex()] +
allTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()]));
System.out.println("空闲累计时间: " + allTicks[CentralProcessor.TickType.IDLE.getIndex()]);
System.out.printf("I/O等待时间(CPU等待响应时间): %.2f%%\n", 100d * iowait / total);
System.out.println("系统累计时间: " + allTicks[CentralProcessor.TickType.SYSTEM.getIndex()]);
System.out.println("中断累计时间: " + AgentUtil.ticksToSeconds((allTicks[CentralProcessor.TickType.IRQ.getIndex()] +
allTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()])));
System.out.println("空闲累计时间: " + AgentUtil.ticksToSeconds(allTicks[CentralProcessor.TickType.IDLE.getIndex()]));
System.out.println("I/O等待时间(CPU等待响应时间): " + AgentUtil.ticksToSeconds(iowait));
System.out.println("系统累计时间: " + AgentUtil.ticksToSeconds(allTicks[CentralProcessor.TickType.SYSTEM.getIndex()]));
long softwareTime = sys - irq - softirq;
System.out.printf("软件相关时间(近似无响应时间): %.2f%%\n", 100d * softwareTime / total);
System.out.println("用户进程累计时间: " + allTicks[CentralProcessor.TickType.USER.getIndex()]);
System.out.println("软件相关时间(近似无响应时间): " + AgentUtil.ticksToSeconds(softwareTime));
System.out.println("用户进程累计时间: " + AgentUtil.ticksToSeconds(allTicks[CentralProcessor.TickType.USER.getIndex()]));
cpuVO.setInterrupt((allTicks[CentralProcessor.TickType.IRQ.getIndex()] +
allTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()])); // CPU硬件中断提供服务时间
cpuVO.setIdle(allTicks[CentralProcessor.TickType.IDLE.getIndex()]);// CPU空闲时间
cpuVO.setIowait(100d * iowait / total);// CPU等待响应时间
cpuVO.setSystem(allTicks[CentralProcessor.TickType.SYSTEM.getIndex()]);// CPU系统时间
cpuVO.setNoresp(100d * softwareTime / total);// CPU软件无响应时间
cpuVO.setUser(allTicks[CentralProcessor.TickType.USER.getIndex()]);// CPU用户进程所花费的时间
cpuVO.setInterrupt(AgentUtil.ticksToSeconds((allTicks[CentralProcessor.TickType.IRQ.getIndex()] +
allTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()]))); // CPU硬件中断提供服务时间
cpuVO.setIdle(AgentUtil.ticksToSeconds(allTicks[CentralProcessor.TickType.IDLE.getIndex()]));// CPU空闲时间
cpuVO.setIowait(AgentUtil.ticksToSeconds(iowait));// CPU等待响应时间
cpuVO.setSystem(AgentUtil.ticksToSeconds(allTicks[CentralProcessor.TickType.SYSTEM.getIndex()]));// CPU系统时间
cpuVO.setNoresp(AgentUtil.ticksToSeconds(softwareTime));// CPU软件无响应时间
cpuVO.setUser(AgentUtil.ticksToSeconds(allTicks[CentralProcessor.TickType.USER.getIndex()]));// CPU用户进程所花费的时间
// 5. 系统运行时间和CPU空闲时间
long uptime = os.getSystemUptime();
cpuVO.setNormal(uptime);// CPU正常运行时间
cpuVO.setNormal(uptime);// CPU正常运行时间
System.out.println("CPU正常运行时间: " + cpuVO.getNormal());
} catch (InterruptedException e) {
e.printStackTrace();
}
return cpuVO;
}
}
}
@@ -7,41 +7,90 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import oshi.SystemInfo;
import oshi.hardware.HWDiskStore;
import oshi.hardware.HardwareAbstractionLayer;
import oshi.software.os.OSFileStore;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.*;
@Service
public class DiskServiceImpl implements DiskService {
// 改为单例只初始化一次
private static final SystemInfo si = new SystemInfo();
// 定义需要排除的虚拟设备前缀列表
private static final String[] VIRTUAL_DISK_PREFIXES = {
"/dev/dm", // device-mapper (LVM/加密/RAID)
"/dev/nbd", // 网络块设备
"/dev/loop", // 回环设备
"/dev/ram", // RAM磁盘
"/dev/drbd", // DRBD
"/dev/vd", // VirtIO (KVM)
"/dev/xvd", // Xen
"/dev/bcache"
};
@Override
public List<DiskVO> diskList(long timestamp) {
List<DiskVO> tempList = new ArrayList<>();
List<DiskVO> resultList = new ArrayList<>();
SystemInfo si = new SystemInfo();
Map<String, String> typeMap = getDiskTypeMap();
Map<String, Long> usedSpaceMap = getDiskUsedSpaceMap();
System.out.println("=========================================================");
// 获取磁盘IO信息
System.out.println("\n=== 磁盘IO信息 ===");
for (HWDiskStore disk : si.getHardware().getDiskStores()) {
DiskVO diskVO = DiskVO.builder().timestamp(timestamp).build();
diskVO.setName(disk.getName());//磁盘名称
diskVO.setSerial(disk.getSerial());//序列号
diskVO.setTotal(disk.getSize());//磁盘大小
diskVO.setWriteTimes(disk.getWrites());//磁盘写入次数
diskVO.setReadTimes(disk.getReads());//磁盘读取次数
diskVO.setWriteBytes(disk.getReadBytes());//磁盘写入字节
diskVO.setReadBytes(disk.getWriteBytes());//磁盘读取字节
// 处理磁盘名称去掉/dev/前缀
String diskFullName = disk.getName();
// 获取磁盘是否健康
Long isHealth = isDiskHealthy(diskFullName);
if (diskFullName.startsWith("/dev/nvme")) {
int score = getNvmeHealthScore(diskFullName);
diskVO.setHealthScore(score);
}
// 在判断中使用
boolean isVirtualDisk = false;
for (String prefix : VIRTUAL_DISK_PREFIXES) {
if (diskFullName.startsWith(prefix)) {
isVirtualDisk = true;
break;
}
}
if (!isVirtualDisk) {
diskVO.setHealthStatus(isHealth);
}
String diskName = diskFullName.replace("/dev/", "");
diskVO.setName(diskName); // 保持原样存储
diskVO.setSerial(disk.getSerial()); // 序列号
diskVO.setTotal(disk.getSize()); // 磁盘大小
diskVO.setWriteTimes(disk.getWrites()); // 磁盘写入次数
diskVO.setReadTimes(disk.getReads()); // 磁盘读取次数
diskVO.setWriteBytes(disk.getWriteBytes()); // 磁盘写入字节
diskVO.setReadBytes(disk.getReadBytes()); // 磁盘读取字节
// 获取磁盘类型和已用空间使用不带/dev/前缀的名称
String type = typeMap.get(diskName);
Long usedSpace = usedSpaceMap.get(diskName);
diskVO.setType(type != null ? type : "UNKNOWN");
diskVO.setUsedSpace(usedSpace != null ? usedSpace : 0L);
tempList.add(diskVO);
System.out.println("磁盘名称: " + diskVO.getName());
System.out.println("序列号: " + diskVO.getSerial());
System.out.println("磁盘类型: " + diskVO.getType());
System.out.println("已用空间: " + diskVO.getUsedSpace());
System.out.println("磁盘大小: " + diskVO.getTotal());
System.out.println("磁盘写入次数: " + diskVO.getWriteTimes());
System.out.println("磁盘读取次数: " + diskVO.getReadTimes());
System.out.println("磁盘写入字节: " + diskVO.getWriteBytes());
System.out.println("磁盘读取字节: " + diskVO.getReadBytes());
}
try{
// 第一次采样
List<HWDiskStore> disks1 = si.getHardware().getDiskStores();
@@ -59,11 +108,14 @@ public class DiskServiceImpl implements DiskService {
long readDiff = disks2.get(i).getReadBytes() - readBytes1[i];
long writeDiff = disks2.get(i).getWriteBytes() - writeBytes1[i];
String serial = disks2.get(i).getSerial();
DiskVO diskVO = tempList.stream().filter(d -> StringUtils.equals(d.getSerial(),serial)).findFirst().orElse(null);
String name = disks2.get(i).getName().replace("/dev/", "");
DiskVO diskVO = tempList.stream().filter(d -> StringUtils.equals(d.getName(),name)).findFirst().orElse(null);
if(Objects.nonNull(diskVO)){
diskVO.setWriteSpeed(readDiff);//磁盘写入速率
diskVO.setReadSpeed(writeDiff);//磁盘读取速率
diskVO.setWriteSpeed(writeDiff);//磁盘写入速率
diskVO.setReadSpeed(readDiff);//磁盘读取速率
System.out.println("磁盘名称: " + diskVO.getName());
System.out.println("磁盘类型: " + diskVO.getType());
System.out.println("已用空间: " + diskVO.getUsedSpace());
System.out.println("磁盘写入速率: " + diskVO.getWriteSpeed());
System.out.println("磁盘读取速率: " + diskVO.getReadSpeed());
}
@@ -75,10 +127,376 @@ public class DiskServiceImpl implements DiskService {
return resultList;
}
/**
* 获取磁盘类型映射设备名 -> 类型
*/
public Map<String, String> getDiskTypeMap() {
Map<String, String> diskTypeMap = new HashMap<>();
try {
ProcessBuilder pb = new ProcessBuilder("lsblk", "-d", "-o", "name,rota");
Process process = pb.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
reader.readLine(); // 跳过标题行
while ((line = reader.readLine()) != null) {
String[] parts = line.trim().split("\\s+");
if (parts.length >= 2) {
String diskName = parts[0];
String rota = parts[1];
String type = "1".equals(rota) ? "HDD" : "SSD";
diskTypeMap.put(diskName, type);
}
}
process.waitFor();
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
return diskTypeMap;
}
/**
* 获取磁盘已用空间映射设备名 -> 已用字节数
* 使用lsblk获取准确的磁盘-分区关系
*/
public Map<String, Long> getDiskUsedSpaceMap() {
Map<String, Long> diskUsedMap = new HashMap<>();
Map<String, String> partitionToDisk = getPartitionToDiskMap();
try {
ProcessBuilder pb = new ProcessBuilder("df", "-B1", "--output=source,used");
Process process = pb.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
reader.readLine(); // 跳过标题行
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.isEmpty() || line.startsWith("Filesystem")) {
continue;
}
String[] parts = line.split("\\s+");
if (parts.length >= 2 && parts[0].startsWith("/dev/")) {
String device = parts[0];
String usedStr = parts[1];
try {
long used = Long.parseLong(usedStr);
String deviceName = device.replace("/dev/", "");
// 通过lsblk获取的映射关系找到对应的物理磁盘
String diskName = partitionToDisk.get(deviceName);
if (diskName == null) {
// 如果没有找到映射可能是磁盘本身不是分区
diskName = deviceName;
}
if (diskName != null) {
diskUsedMap.merge(diskName, used, Long::sum);
}
} catch (NumberFormatException ignored) {
}
}
}
process.waitFor();
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
return diskUsedMap;
}
/**
* 使用lsblk获取磁盘-分区映射关系
* @return Map<分区设备名, 物理磁盘设备名>
*/
private Map<String, String> getPartitionToDiskMap() {
Map<String, String> partitionToDiskMap = new HashMap<>();
try {
ProcessBuilder pb = new ProcessBuilder("lsblk", "-l", "-o", "name,type");
Process process = pb.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
reader.readLine(); // 跳过标题行
String currentDisk = null;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.isEmpty()) continue;
String[] parts = line.split("\\s+");
if (parts.length >= 2) {
String device = parts[0];
String type = parts[1];
if ("disk".equals(type)) {
// 这是物理磁盘
currentDisk = device;
} else if ("part".equals(type) && currentDisk != null) {
// 这是分区关联到当前磁盘
partitionToDiskMap.put(device, currentDisk);
}
}
}
process.waitFor();
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
return partitionToDiskMap;
}
public Long isDiskHealthy(String diskDevice) {
try {
Process process = new ProcessBuilder("smartctl", "-H", diskDevice)
.redirectErrorStream(true)
.start();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
boolean isHealth = reader.lines()
.anyMatch(line -> line.contains("PASSED") || line.contains("OK"))
&& process.waitFor() == 0;
if(isHealth){
return 1L;
}else{
return 0L;
}
}
} catch (Exception e) {
return null;
}
}
public int getNvmeHealthScore(String diskDevice) {
int nvmeScore = getNvmeHealthScoreByNvmeCli(diskDevice);
if (nvmeScore >= 0) {
return nvmeScore;
}
// 回退到 smartctl
return getNvmeHealthScoreBySmartctl(diskDevice);
}
private int getNvmeHealthScoreByNvmeCli(String diskDevice) {
try {
Process process = new ProcessBuilder("nvme", "smart-log", diskDevice)
.redirectErrorStream(true)
.start();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
int exitCode = process.waitFor();
if (exitCode != 0) {
return -1; // 命令执行失败
}
int percentageUsed = -1;
int availableSpare = -1;
String line;
// 解析 nvme smart-log 输出
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.isEmpty()) {
continue;
}
// 处理下划线和空格两种格式
if (isPercentageUsedLine(line)) {
percentageUsed = extractNvmeValueFromLine(line);
}
// 处理 available spare排除 threshold
else if (isAvailableSpareLine(line)) {
availableSpare = extractNvmeValueFromLine(line);
}
}
// 计算健康分数
if (percentageUsed >= 0) {
// 健康分数 = 100 - 已用百分比
return 100 - percentageUsed;
} else if (availableSpare >= 0) {
// 如果没有 Percentage Used使用 Available Spare
return availableSpare;
} else {
return -1; // 无法获取健康信息
}
}
} catch (Exception e) {
return -1;
}
}
private boolean isPercentageUsedLine(String line) {
String lowerLine = line.toLowerCase();
// 支持多种格式
// 1. percentage_used : 0%
// 2. percentage used : 0%
// 3. Percentage Used: 0%
// 4. percentage used: 0%
// 移除所有空格和下划线统一比较
String unified = lowerLine.replaceAll("[_\\s]", "");
return unified.startsWith("percentageused");
}
private boolean isAvailableSpareLine(String line) {
String lowerLine = line.toLowerCase();
// 排除 threshold
if (lowerLine.contains("threshold")) {
return false;
}
// 支持多种格式
// 1. available_spare : 100%
// 2. available spare : 100%
// 3. Available Spare: 100%
// 4. available spare: 100%
// 移除所有空格和下划线统一比较
String unified = lowerLine.replaceAll("[_\\s]", "");
return unified.startsWith("availablespare");
}
private int extractNvmeValueFromLine(String line) {
try {
// 按冒号分割
String[] parts = line.split(":");
if (parts.length > 1) {
String valuePart = parts[1].trim();
// 移除可能存在的逗号 1,234% -> 1234%
valuePart = valuePart.replace(",", "");
// 查找百分比符号
int percentIndex = valuePart.indexOf('%');
if (percentIndex != -1) {
// 提取%前面的数字部分
String numberStr = valuePart.substring(0, percentIndex).trim();
// 移除所有非数字字符
numberStr = numberStr.replaceAll("[^0-9]", "").trim();
if (!numberStr.isEmpty()) {
return Integer.parseInt(numberStr);
}
} else {
// 如果没有百分号尝试解析第一个数字
String[] tokens = valuePart.split("\\s+");
if (tokens.length > 0) {
String numberStr = tokens[0].trim();
// 移除所有非数字字符
numberStr = numberStr.replaceAll("[^0-9]", "").trim();
if (!numberStr.isEmpty()) {
return Integer.parseInt(numberStr);
}
}
}
}
} catch (NumberFormatException e) {
// 解析失败
}
return -1;
}
private int getNvmeHealthScoreBySmartctl(String diskDevice) {
try {
// 检查是否为 NVMe 设备
if (!diskDevice.startsWith("/dev/nvme")) {
return -1; // NVMe 设备
}
Process process = new ProcessBuilder("smartctl", "-a", diskDevice)
.redirectErrorStream(true)
.start();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
int exitCode = process.waitFor();
if (exitCode != 0) {
return -1; // 命令执行失败
}
int percentageUsed = -1;
int availableSpare = -1;
String line;
// 解析 smartctl 输出
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.isEmpty()) {
continue;
}
// 查找 Percentage Used已用百分比
if (line.contains("Percentage Used:")) {
percentageUsed = extractSmartctlValueFromLine(line);
}
// 查找 Available Spare可用备用空间
else if (line.contains("Available Spare:") && !line.contains("Available Spare Threshold")) {
availableSpare = extractSmartctlValueFromLine(line);
}
}
// 计算健康分数
if (percentageUsed >= 0) {
// 健康分数 = 100 - 已用百分比
return 100 - percentageUsed;
} else if (availableSpare >= 0) {
// 如果没有 Percentage Used使用 Available Spare
return availableSpare;
} else {
return -1; // 无法获取健康信息
}
}
} catch (Exception e) {
return -1;
}
}
private int extractSmartctlValueFromLine(String line) {
try {
// 按冒号分割
String[] parts = line.split(":");
if (parts.length > 1) {
String valuePart = parts[1].trim();
// 处理格式: "Percentage Used: 5%"
if (valuePart.contains("%")) {
String[] valueParts = valuePart.split("\\s+");
for (String part : valueParts) {
if (part.endsWith("%")) {
return Integer.parseInt(part.substring(0, part.length() - 1).trim());
}
}
} else {
// 处理格式: "Percentage Used: 5"
String[] valueParts = valuePart.split("\\s+");
if (valueParts.length > 0) {
return Integer.parseInt(valueParts[0].trim());
}
}
}
} catch (NumberFormatException e) {
// 解析失败
}
return -1;
}
@Override
public List<PointVO> pointList(long timestamp) {
List<PointVO> list = new ArrayList<>();
SystemInfo si = new SystemInfo();
// 获取文件系统信息
System.out.println("=========================================================");
System.out.println("=== 挂载信息 ===");
@@ -1,12 +1,20 @@
package com.tongran.agent.client.service.impl;
import com.tongran.agent.client.core.vo.MemoryDetailsVO;
import com.tongran.agent.client.core.vo.MemoryVO;
import com.tongran.agent.client.service.MemoryService;
import com.tongran.agent.client.utils.AgentDataUtil;
import com.tongran.agent.client.utils.AssertLog;
import org.springframework.stereotype.Service;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Service
public class MemoryServiceImpl implements MemoryService {
@@ -56,19 +64,347 @@ public class MemoryServiceImpl implements MemoryService {
memInfo.getOrDefault("SReclaimable", 0L));
// 总内存使用率
double totalUsage = (double)(total - available) / total * 100;
System.out.printf("总内存使用率: %.2f%%\n", totalUsage);
AssertLog.info("总内存使用率: {}%",totalUsage);
// System.out.printf("总内存使用率: %.2f%%\n", totalUsage);
// 实际内存使用率
long cached = memInfo.getOrDefault("Cached", 0L) +
memInfo.getOrDefault("SReclaimable", 0L);
long buffers = memInfo.getOrDefault("Buffers", 0L);
double actualUsage = (double)(total - available - cached - buffers) / total * 100;
System.out.printf("实际内存使用率: %.2f%%\n", actualUsage);
memoryVO.setUntilzation(actualUsage); //内存利用率
// long cached = memInfo.getOrDefault("Cached", 0L) +
// memInfo.getOrDefault("SReclaimable", 0L);
// long buffers = memInfo.getOrDefault("Buffers", 0L);
// double actualUsage = (double)(total - available - cached - buffers) / total * 100;
// System.out.printf("实际内存使用率: %.2f%%\n", actualUsage);
memoryVO.setUntilzation(totalUsage); //内存利用率
} catch (IOException e) {
e.printStackTrace();
}
return memoryVO;
}
@Override
public List<MemoryDetailsVO> getMemoryDetails() {
List<MemoryDetailsVO> memoryList = new ArrayList<>();
String command = "dmidecode -t memory";
Process process = null;
BufferedReader reader = null;
// 获取 ipmitool sdr type "Memory" 的输出用于后续匹配
List<String> sdrLines = getIpmitoolSdrLines();
try {
// 执行过滤命令
ProcessBuilder processBuilder = new ProcessBuilder(
"bash", "-c", command + " | awk '/^Handle 0x/ {if (buffer && buffer ~ /Size: [0-9]/) print buffer; buffer=$0; next} {buffer=buffer ORS $0} END {if (buffer && buffer ~ /Size: [0-9]/) print buffer}'"
);
processBuilder.redirectErrorStream(true);
process = processBuilder.start();
reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
StringBuilder block = new StringBuilder();
String line;
int totalMemoryCount = 0;
while ((line = reader.readLine()) != null) {
block.append(line).append("\n");
if (line.trim().isEmpty() && block.length() > 0) {
MemoryDetailsVO vo = parseMemoryBlock(block.toString(), sdrLines);
if (vo != null) {
memoryList.add(vo);
totalMemoryCount++;
}
block.setLength(0);
}
}
// 处理最后一个块
if (block.length() > 0) {
MemoryDetailsVO vo = parseMemoryBlock(block.toString(), sdrLines);
if (vo != null) {
memoryList.add(vo);
totalMemoryCount++;
}
}
// 等待命令完成
int exitCode = process.waitFor();
if (exitCode != 0) {
AssertLog.warn("dmidecode命令执行异常, 退出码: {}", exitCode);
}
// 记录统计日志
if (totalMemoryCount > 0) {
AssertLog.info("成功获取内存信息, 共发现 {} 根内存条", totalMemoryCount);
// 计算总容量
long totalCapacityGB = memoryList.stream()
.mapToLong(MemoryDetailsVO::getCapacity)
.sum() / 1024L;
AssertLog.info("总内存容量: {} GB", totalCapacityGB);
} else {
AssertLog.warn("未检测到已安装的内存条");
}
} catch (IOException e) {
AssertLog.error("执行内存信息获取命令失败: {}", e.getMessage(), e);
} catch (InterruptedException e) {
AssertLog.error("命令执行被中断: {}", e.getMessage(), e);
Thread.currentThread().interrupt();
} catch (Exception e) {
AssertLog.error("解析内存信息时发生未知错误: {}", e.getMessage(), e);
} finally {
// 清理资源
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
AssertLog.warn("关闭流时发生错误: {}", e.getMessage());
}
}
if (process != null) {
process.destroy();
}
}
// 处理 dmidecode 未监测到但 ipmitool 检测到异常的插槽
if (!sdrLines.isEmpty()) {
for (String sdrLine : sdrLines) {
// 提取 DIMM 名称 "DIMM110"
String dimmName = sdrLine.split("\\|")[0].trim();
boolean alreadyExists = memoryList.stream()
.anyMatch(mem -> dimmName.equals(mem.getName()));
// 如果 dmidecode 中没有这个插槽 ipmitool 检测到异常
if (!alreadyExists) {
MemoryDetailsVO errorSlot = new MemoryDetailsVO();
errorSlot.setName(dimmName);
errorSlot.setLocation("Unknown");
errorSlot.setManufacturer("Unknown");
errorSlot.setCapacity(0L); // 没有安装内存容量为0
errorSlot.setFrequency(0);
errorSlot.setSerialNumber("Unknown");
errorSlot.setType("Unknown");
errorSlot.setMinVoltage(0);
errorSlot.setRank("Unknown");
errorSlot.setBitWidth("Unknown");
errorSlot.setTechnology("Unknown");
errorSlot.setPartNumber("Unknown");
errorSlot.setHealthStatus("0"); // 异常状态
memoryList.add(errorSlot);
AssertLog.debug("添加未在dmidecode中监测到的异常插槽: {}", dimmName);
}
}
}
return memoryList;
}
@Override
public Integer getMemorySlotsNum() {
ProcessBuilder processBuilder = null;
Process process = null;
BufferedReader reader = null;
try {
// 使用bash执行管道命令
processBuilder = new ProcessBuilder(
"bash", "-c",
"dmidecode -t memory | grep 'Number Of Devices:' | awk '{print $4}'"
);
process = processBuilder.start();
reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
if (!line.trim().isEmpty()) {
try {
Integer slots = Integer.parseInt(line.trim());
AssertLog.info("成功获取内存插槽数量: " + slots);
return slots;
} catch (NumberFormatException e) {
AssertLog.info("解析内存插槽数量失败,输出内容: " + line);
}
}
}
AssertLog.info("未找到内存插槽数量信息");
return null;
} catch (IOException e) {
AssertLog.info("执行命令时发生IO异常: " + e.getMessage());
return null;
} catch (Exception e) {
AssertLog.info("获取内存插槽数量时发生未知异常: " + e.getMessage());
return null;
} finally {
try {
if (reader != null) {
reader.close();
}
if (process != null) {
process.destroy();
}
} catch (IOException e) {
AssertLog.info("关闭资源时发生异常: " + e.getMessage());
}
}
}
// 获取 ipmitool sdr type "Memory" 的输出行列表
private List<String> getIpmitoolSdrLines() {
List<String> lines = new ArrayList<>();
try {
ProcessBuilder processBuilder = new ProcessBuilder(
"bash", "-c", "ipmitool sdr type \"Memory\" | grep -E \"DIMM.*Configuration Error\""
);
processBuilder.redirectErrorStream(true);
Process process = processBuilder.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
lines.add(line.trim());
}
int exitCode = process.waitFor();
if (exitCode != 0) {
AssertLog.warn("ipmitool命令执行异常, 退出码: {}", exitCode);
}
} catch (Exception e) {
AssertLog.error("获取ipmitool sdr信息失败: {}", e.getMessage(), e);
}
return lines;
}
// 解析内存块并注入健康状态
private MemoryDetailsVO parseMemoryBlock(String block, List<String> sdrLines) {
try {
// 跳过空块
if (block.trim().isEmpty()) {
return null;
}
MemoryDetailsVO vo = new MemoryDetailsVO();
// 正则提取字段
Pattern namePattern = Pattern.compile("Locator:\\s*(\\w+)"); // 提取 DIMM000
Pattern locationPattern = Pattern.compile("Bank Locator:\\s*(.+)");
Pattern manufacturerPattern = Pattern.compile("Manufacturer:\\s*(.+)");
Pattern capacityPattern = Pattern.compile("Size:\\s*(\\d+)\\s*GB");
Pattern frequencyPattern = Pattern.compile("Speed:\\s*(\\d+)", Pattern.CASE_INSENSITIVE);
Pattern serialNumberPattern = Pattern.compile("Serial Number:\\s*(.+)");
Pattern typePattern = Pattern.compile("Type:\\s*(.+)");
Pattern minVoltagePattern = Pattern.compile("Minimum Voltage:\\s*(\\d+\\.?\\d*)\\s*V");
Pattern rankPattern = Pattern.compile("Rank:\\s*(.+)");
Pattern bitWidthPattern = Pattern.compile("Total Width:\\s*(.+) bit");
Pattern technologyPattern = Pattern.compile("Type Detail:\\s*(.+)");
Pattern partNumberPattern = Pattern.compile("Part Number:\\s*(.+)");
Matcher nameMatcher = namePattern.matcher(block);
if (nameMatcher.find()) {
vo.setName(nameMatcher.group(1).trim());
} else {
AssertLog.debug("未找到内存名称字段: {}", block);
return null;
}
// 位置固定为 mainboard
vo.setLocation("mainboard");
Matcher manufacturerMatcher = manufacturerPattern.matcher(block);
if (manufacturerMatcher.find()) {
vo.setManufacturer(manufacturerMatcher.group(1).trim());
} else {
vo.setManufacturer("Unknown");
}
Matcher capacityMatcher = capacityPattern.matcher(block);
if (capacityMatcher.find()) {
long gb = Long.parseLong(capacityMatcher.group(1));
vo.setCapacity(gb * 1024L); // GB MB
} else {
AssertLog.debug("未找到内存容量字段: {}", block);
return null;
}
Matcher frequencyMatcher = frequencyPattern.matcher(block);
if (frequencyMatcher.find()) {
vo.setFrequency(Integer.parseInt(frequencyMatcher.group(1)));
} else {
vo.setFrequency(0);
}
Matcher serialNumberMatcher = serialNumberPattern.matcher(block);
if (serialNumberMatcher.find()) {
vo.setSerialNumber(serialNumberMatcher.group(1).trim());
} else {
vo.setSerialNumber("Unknown");
}
Matcher typeMatcher = typePattern.matcher(block);
if (typeMatcher.find()) {
vo.setType(typeMatcher.group(1).trim());
} else {
vo.setType("Unknown");
}
Matcher minVoltageMatcher = minVoltagePattern.matcher(block);
if (minVoltageMatcher.find()) {
String voltageStr = minVoltageMatcher.group(1);
// 处理 1.2 V -> 1200 mV
double voltage = Double.parseDouble(voltageStr);
vo.setMinVoltage((int)(voltage * 1000));
} else {
vo.setMinVoltage(0);
}
Matcher rankMatcher = rankPattern.matcher(block);
if (rankMatcher.find()) {
vo.setRank(rankMatcher.group(1).trim() + " rank");
} else {
vo.setRank("Unknown");
}
Matcher bitWidthMatcher = bitWidthPattern.matcher(block);
if (bitWidthMatcher.find()) {
vo.setBitWidth(bitWidthMatcher.group(1).trim() + " bit");
} else {
vo.setBitWidth("Unknown");
}
Matcher technologyMatcher = technologyPattern.matcher(block);
if (technologyMatcher.find()) {
vo.setTechnology(technologyMatcher.group(1).trim());
} else {
vo.setTechnology("Unknown");
}
Matcher partNumberMatcher = partNumberPattern.matcher(block);
if (partNumberMatcher.find()) {
vo.setPartNumber(partNumberMatcher.group(1).trim());
} else {
vo.setPartNumber("Unknown");
}
// 检查健康状态
String memoryName = vo.getName(); // 例如 "DIMM000"
boolean hasConfigError = sdrLines.stream()
.anyMatch(line -> line.startsWith(memoryName));
if (hasConfigError) {
vo.setHealthStatus("0");
} else {
vo.setHealthStatus("1");
}
// 调试日志
AssertLog.debug("解析内存信息: {} - {}GB - {}MHz - 健康状态: {}",
vo.getName(), vo.getCapacity()/1024L, vo.getFrequency(), vo.getHealthStatus());
return vo;
} catch (Exception e) {
AssertLog.error("解析内存块时发生错误: {}", e.getMessage(), e);
return null;
}
}
}
@@ -0,0 +1,499 @@
package com.tongran.agent.client.service.impl;
import com.tongran.agent.client.core.config.ApplicationProperties;
import com.tongran.agent.client.core.vo.NetBusinessVO;
import com.tongran.agent.client.service.NetBusinessService;
import com.tongran.agent.client.utils.AssertLog;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.HashSet;
import java.util.Set;
@Service
public class NetBusinessServiceImpl implements NetBusinessService {
private static final String CGROUP_BASE = "/sys/fs/cgroup/net_cls";
private static final String CGROUP_PREFIX = "agent_";
private static final int MAX_CHAIN_NAME_LEN = 28;
// 存储每个进程的 classid
private final ConcurrentHashMap<String, Long> processClassIdMap = new ConcurrentHashMap<>();
// 存储每个进程的 cgroup 路径
private final ConcurrentHashMap<String, String> processCgroupPathMap = new ConcurrentHashMap<>();
// 记录当前所有已创建的进程无论是否有进程在运行
private final Set<String> activeProcesses = ConcurrentHashMap.newKeySet();
private List<String> processList = new ArrayList<>();
@Resource
private ApplicationProperties properties;
@PostConstruct
public void init() {
try {
loadProcessListFromFile();
// 为配置文件中有的进程创建 cgroup iptables 但只创建有PID的
for (String processName : processList) {
List<Integer> pids = getProcessPids(processName);
if (!pids.isEmpty()) {
createProcessCgroup(processName);
initProcessChains("iptables", processName);
initProcessChains("ip6tables", processName);
addProcessToCgroup(processName, pids);
activeProcesses.add(processName);
System.out.println("进程 " + processName + " (PID: " + pids + ") 初始化完成");
} else {
System.out.println("进程 " + processName + " 当前无PID运行,跳过初始化");
}
}
System.out.println("=== cgroup多进程监控初始化完成 ===");
System.out.println("监控进程列表: " + processList);
} catch (Exception e) {
System.err.println("初始化失败: " + e.getMessage());
}
}
@PreDestroy
public void cleanup() {
try {
// 清理所有已创建的规则和 cgroup
for (String processName : activeProcesses) {
cleanupProcessRules("iptables", processName);
cleanupProcessRules("ip6tables", processName);
removeProcessCgroup(processName);
}
System.out.println("已清理所有 iptables 规则和 cgroup");
} catch (Exception e) {
System.err.println("清理失败: " + e.getMessage());
}
}
@Override
public List<NetBusinessVO> netList(long timestamp) {
List<NetBusinessVO> resultList = new ArrayList<>();
try {
// 保存旧的进程列表用于对比
List<String> oldProcessList = new ArrayList<>(processList);
// 重新加载配置文件
loadProcessListFromFile();
// 找出被剔除的进程在旧列表中存在在新列表中不存在
Set<String> removedProcesses = new HashSet<>(oldProcessList);
removedProcesses.removeAll(processList);
// 清理被剔除的进程只有这些才清理
if (!removedProcesses.isEmpty()) {
System.out.println("检测到配置文件中已删除的进程: " + removedProcesses + ",开始清理...");
for (String processName : removedProcesses) {
cleanupProcessRules("iptables", processName);
cleanupProcessRules("ip6tables", processName);
removeProcessCgroup(processName);
activeProcesses.remove(processName);
System.out.println("已清理进程 " + processName + " 的所有规则");
}
}
// 找出新增的进程在新列表中存在在旧列表中不存在
Set<String> addedProcesses = new HashSet<>(processList);
addedProcesses.removeAll(oldProcessList);
// 为新增的进程创建规则但只创建有PID的
if (!addedProcesses.isEmpty()) {
System.out.println("检测到新增的进程: " + addedProcesses);
for (String processName : addedProcesses) {
List<Integer> pids = getProcessPids(processName);
if (!pids.isEmpty()) {
// 只有有PID的才创建规则
createProcessCgroup(processName);
initProcessChains("iptables", processName);
initProcessChains("ip6tables", processName);
addProcessToCgroup(processName, pids);
activeProcesses.add(processName);
System.out.println("已为进程 " + processName + " (PID: " + pids + ") 创建规则");
} else {
System.out.println("进程 " + processName + " 当前无PID,暂不创建规则");
}
}
}
// 遍历当前配置文件中的进程列表获取统计信息
for (String processName : processList) {
List<Integer> pids = getProcessPids(processName);
// 如果有PID确保规则存在可能之前无PID时没创建现在需要创建
if (!pids.isEmpty()) {
// 检查是否已有规则如果没有则需要创建
if (!activeProcesses.contains(processName)) {
System.out.println("进程 " + processName + " 已有PID运行但无规则,开始创建规则...");
createProcessCgroup(processName);
initProcessChains("iptables", processName);
initProcessChains("ip6tables", processName);
activeProcesses.add(processName);
}
// 确保进程在cgroup中
addProcessToCgroup(processName, pids);
// 确保链存在
ensureProcessChainsExist("iptables", processName);
ensureProcessChainsExist("ip6tables", processName);
// 获取该进程的流量统计
long[] v4Stats = getTrafficStatsForProcess("iptables", processName);
long[] v6Stats = getTrafficStatsForProcess("ip6tables", processName);
NetBusinessVO vo = new NetBusinessVO();
vo.setIpv4InSpeed(v4Stats[0]);
vo.setIpv4OutSpeed(v4Stats[1]);
vo.setIpv6InSpeed(v6Stats[0]);
vo.setIpv6OutSpeed(v6Stats[1]);
vo.setInSpeed(v4Stats[0] + v6Stats[0]);
vo.setOutSpeed(v4Stats[1] + v6Stats[1]);
vo.setTimestamp(timestamp);
vo.setName("total");
vo.setProcessName(processName);
resultList.add(vo);
if (v4Stats[0] > 0 || v4Stats[1] > 0 || v6Stats[0] > 0 || v6Stats[1] > 0) {
System.out.printf("进程 %s (PID: %s): IPv4收=%d 发=%d, IPv6收=%d 发=%d%n",
processName, pids, v4Stats[0], v4Stats[1], v6Stats[0], v6Stats[1]);
}
} else {
// 没有PID但规则保留因为配置还在
System.out.println("进程 " + processName + " 当前无PID运行,保留已有规则等待进程启动");
}
}
} catch (Exception e) {
System.err.println("获取网络统计失败: " + e.getMessage());
}
return resultList;
}
/**
* 为进程生成唯一的 classid
*/
private long generateClassId(String processName) {
// classid 必须在 0x00000001 - 0xFFFFFFFF 之间
// 使用进程名的hash但限制在 0x1 - 0xFFFFF 之间
int hash = Math.abs(processName.hashCode()) & 0xFFFFF;
if (hash == 0) hash = 1; // 避免0值
return hash & 0xFFFFFFFFL; // 确保在32位无符号范围内
}
/**
* 为进程创建独立的 cgroup
*/
private void createProcessCgroup(String processName) {
try {
long classId = generateClassId(processName);
// 使用原始的 processName 来构建路径不要用 safeName
String cgroupPath = CGROUP_BASE + "/" + CGROUP_PREFIX + processName;
executeCommand("mkdir -p " + cgroupPath);
executeCommand("echo " + classId + " > " + cgroupPath + "/net_cls.classid");
processClassIdMap.put(processName, classId);
processCgroupPathMap.put(processName, cgroupPath);
System.out.println("进程 " + processName + " cgroup 创建完成, classid=" + classId + ", path=" + cgroupPath);
} catch (Exception e) {
System.err.println("创建进程 cgroup 失败 " + processName + ": " + e.getMessage());
}
}
/**
* 删除进程的 cgroup
*/
private void removeProcessCgroup(String processName) {
try {
String cgroupPath = processCgroupPathMap.get(processName);
if (cgroupPath != null) {
// 先移动所有进程到根 cgroup
executeCommand("cat " + cgroupPath + "/tasks | while read pid; do echo $pid > " + CGROUP_BASE + "/tasks 2>/dev/null; done");
// 删除目录
executeCommand("rmdir " + cgroupPath + " 2>/dev/null");
processClassIdMap.remove(processName);
processCgroupPathMap.remove(processName);
System.out.println("已删除进程 " + processName + " 的 cgroup");
}
} catch (Exception e) {
System.err.println("删除进程 cgroup 失败 " + processName + ": " + e.getMessage());
}
}
private void loadProcessListFromFile() {
List<String> newProcessList = new ArrayList<>();
String path = properties.getConfPath() + "/process.conf";
File file = new File(path);
if (file.exists()) {
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
String line;
while ((line = br.readLine()) != null) {
line = line.trim();
if (!line.isEmpty() && !line.startsWith("#")) {
newProcessList.add(line);
}
}
System.out.println("" + path + " 加载进程列表: " + newProcessList);
} catch (Exception e) {
System.err.println("读取配置文件失败 " + path + ": " + e.getMessage());
}
}
// 更新 processList
synchronized (this) {
processList.clear();
processList.addAll(newProcessList);
}
}
private List<Integer> getProcessPids(String processName) {
List<Integer> pids = new ArrayList<>();
try {
ProcessBuilder pb = new ProcessBuilder("sh", "-c",
"pgrep -x " + processName + " 2>/dev/null");
Process p = pb.start();
try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
String line;
while ((line = br.readLine()) != null) {
line = line.trim();
if (!line.isEmpty()) {
pids.add(Integer.parseInt(line));
}
}
}
} catch (Exception e) {
// 忽略
}
return pids;
}
private void addProcessToCgroup(String processName, List<Integer> pids) {
String cgroupPath = processCgroupPathMap.get(processName);
if (cgroupPath == null) return;
for (Integer pid : pids) {
try {
ProcessBuilder checkPb = new ProcessBuilder("sh", "-c",
"cat " + cgroupPath + "/tasks 2>/dev/null | grep -q " + pid);
if (checkPb.start().waitFor() != 0) {
executeCommand("echo " + pid + " > " + cgroupPath + "/tasks");
System.out.println("进程 " + pid + "(" + processName + ") 已加入 cgroup");
}
} catch (Exception e) {
// 忽略
}
}
}
private String generateSafeChainName(String processName) {
String safe = processName.replaceAll("[^a-zA-Z0-9]", "_");
String hash = Integer.toHexString(processName.hashCode());
while (hash.length() < 8) {
hash = "0" + hash;
}
if (hash.length() > 8) {
hash = hash.substring(0, 8);
}
String prefix = safe.length() > 12 ? safe.substring(0, 12) : safe;
return prefix + "_" + hash;
}
private String buildChainName(String safeName, String suffix, String direction) {
// 先为 in out 计算一个公共的 baseName取较短的那个
String baseName = "am_" + safeName;
// 计算 in out 分别需要的长度
int inLen = baseName.length() + 6; // _v4_in
int outLen = baseName.length() + 7; // _v4_out
// 取较长的作为标准确保两者都满足
int maxNeeded = Math.max(inLen, outLen);
if (maxNeeded > MAX_CHAIN_NAME_LEN) {
// 需要截断 baseName
int baseMaxLen = MAX_CHAIN_NAME_LEN - 7; // 按最长的 out
if (baseName.length() > baseMaxLen) {
String prefix = baseName.substring(0, 8);
String suffix_part = baseName.substring(baseName.length() - (baseMaxLen - 9));
baseName = prefix + "_" + suffix_part;
}
}
return baseName + "_" + suffix + "_" + direction;
}
private long getProcessClassId(String processName) {
Long classId = processClassIdMap.get(processName);
if (classId == null) {
classId = generateClassId(processName);
processClassIdMap.put(processName, classId);
}
return classId;
}
private void initProcessChains(String command, String processName) {
long classId = getProcessClassId(processName);
String safeName = generateSafeChainName(processName);
String suffix = command.equals("iptables") ? "v4" : "v6";
String inChain = buildChainName(safeName, suffix, "in");
String outChain = buildChainName(safeName, suffix, "out");
initChain(command, inChain, "INPUT", classId);
initChain(command, outChain, "OUTPUT", classId);
}
private void ensureProcessChainsExist(String command, String processName) {
long classId = getProcessClassId(processName);
String safeName = generateSafeChainName(processName);
String suffix = command.equals("iptables") ? "v4" : "v6";
String inChain = buildChainName(safeName, suffix, "in");
String outChain = buildChainName(safeName, suffix, "out");
ensureChainExists(command, inChain, "INPUT", classId);
ensureChainExists(command, outChain, "OUTPUT", classId);
}
private void ensureChainExists(String command, String chainName, String hookChain, long classId) {
try {
ProcessBuilder checkPb = new ProcessBuilder("sh", "-c",
command + " -L " + chainName + " -n >/dev/null 2>&1");
if (checkPb.start().waitFor() != 0) {
executeCommand(command + " -N " + chainName);
executeCommand(command + " -A " + chainName +
" -m cgroup --cgroup " + classId + " -j RETURN");
executeCommand(command + " -A " + chainName + " -j RETURN");
executeCommand(command + " -I " + hookChain + " 1 -m cgroup --cgroup " +
classId + " -j " + chainName);
System.out.println(command + "" + chainName + " 初始化完成 (classid=" + classId + ")");
}
} catch (Exception e) {
System.err.println("确保链存在失败 " + chainName + ": " + e.getMessage());
}
}
private void initChain(String command, String chainName, String hookChain, long classId) {
try {
// 1. 直接创建链如果已存在会报错但忽略
executeCommand(command + " -N " + chainName + " 2>/dev/null");
// 2. 清空并添加规则
executeCommand(command + " -F " + chainName);
executeCommand(command + " -A " + chainName +
" -m cgroup --cgroup " + classId + " -j RETURN");
executeCommand(command + " -A " + chainName + " -j RETURN");
// 3. 检查是否已存在相同的规则
String checkRuleCmd = command + " -C " + hookChain + " -m cgroup --cgroup " +
classId + " -j " + chainName + " 2>/dev/null";
ProcessBuilder checkPb = new ProcessBuilder("sh", "-c", checkRuleCmd);
if (checkPb.start().waitFor() != 0) {
// 规则不存在才插入
executeCommand(command + " -I " + hookChain + " 1 -m cgroup --cgroup " +
classId + " -j " + chainName);
System.out.println(command + " 插入规则到 " + hookChain + " 成功");
} else {
System.out.println(command + " 规则已存在于 " + hookChain + ",跳过插入");
}
System.out.println(command + "" + chainName + " 初始化完成 (classid=" + classId + ")");
} catch (Exception e) {
System.err.println("初始化链 " + chainName + " 失败: " + e.getMessage());
}
}
private long[] getTrafficStatsForProcess(String command, String processName) {
long classId = getProcessClassId(processName);
String safeName = generateSafeChainName(processName);
String suffix = command.equals("iptables") ? "v4" : "v6";
String inChain = buildChainName(safeName, suffix, "in");
String outChain = buildChainName(safeName, suffix, "out");
long recvBytes = getChainBytes(command, inChain);
long sentBytes = getChainBytes(command, outChain);
return new long[]{recvBytes, sentBytes};
}
private void cleanupProcessRules(String command, String processName) {
try {
Long classId = processClassIdMap.get(processName);
if (classId == null) return;
String safeName = generateSafeChainName(processName);
String suffix = command.equals("iptables") ? "v4" : "v6";
String inChain = buildChainName(safeName, suffix, "in");
String outChain = buildChainName(safeName, suffix, "out");
executeCommand(command + " -D INPUT -m cgroup --cgroup " + classId +
" -j " + inChain + " 2>/dev/null");
executeCommand(command + " -D OUTPUT -m cgroup --cgroup " + classId +
" -j " + outChain + " 2>/dev/null");
executeCommand(command + " -F " + inChain + " 2>/dev/null");
executeCommand(command + " -F " + outChain + " 2>/dev/null");
executeCommand(command + " -X " + inChain + " 2>/dev/null");
executeCommand(command + " -X " + outChain + " 2>/dev/null");
System.out.println("已清理 " + command + " 中进程 " + processName + " 的规则");
} catch (Exception e) {
System.err.println("清理进程规则失败 " + processName + ": " + e.getMessage());
}
}
private long getChainBytes(String command, String chainName) {
try {
ProcessBuilder pb = new ProcessBuilder("sh", "-c",
command + " -L " + chainName + " -v -n -x | grep RETURN | head -1");
Process p = pb.start();
try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
String line = br.readLine();
if (line != null && !line.isEmpty()) {
String[] parts = line.trim().split("\\s+");
if (parts.length >= 2) {
return Long.parseLong(parts[1]);
}
}
}
} catch (Exception e) {
System.err.println("获取链 " + chainName + " 统计失败: " + e.getMessage());
}
return 0;
}
private void executeCommand(String command) throws Exception {
ProcessBuilder pb = new ProcessBuilder("sh", "-c", command);
Process p = pb.start();
int exitCode = p.waitFor();
if (exitCode != 0) {
try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getErrorStream()))) {
String error = br.lines().reduce("", (a, b) -> a + "\n" + b);
if (!error.isEmpty() && !error.contains("No such file") && !error.contains("File exists")) {
System.err.println("命令执行警告: " + command + " -> " + error);
}
}
}
}
}
@@ -1,8 +1,12 @@
package com.tongran.agent.client.service.impl;
import com.tongran.agent.client.core.config.GlobalConfig;
import com.tongran.agent.client.core.vo.NetVO;
import com.tongran.agent.client.service.NetService;
import com.tongran.agent.client.utils.AgentUtil;
import com.tongran.agent.client.utils.AssertLog;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.stereotype.Service;
import oshi.SystemInfo;
import oshi.hardware.HardwareAbstractionLayer;
@@ -11,88 +15,499 @@ import oshi.util.FormatUtil;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@Service
public class NetServiceImpl implements NetService {
// 改为单例只初始化一次
private static final SystemInfo SI = new SystemInfo();
private final HardwareAbstractionLayer hal = SI.getHardware();
// 缓存最近一次的ping结果
private final Map<String, Double> pingCache = new ConcurrentHashMap<>();
@Override
public List<NetVO> netList(long timestamp) {
List<NetVO> list = new ArrayList<>();
try {
SystemInfo si = new SystemInfo();
HardwareAbstractionLayer hal = si.getHardware();
// 获取所有网络接口
List<NetworkIF> networkIFs = hal.getNetworkIFs();
System.out.println("=========================================================");
System.out.println("===== 网卡流量统计 =====");
List<NetVO> temp = new ArrayList<>();
for (NetworkIF net : networkIFs) {
System.out.println("接口名称: " + net.getName()+"("+net.getDisplayName()+")");
System.out.println("MAC地址: " + net.getMacaddr());
System.out.print("运行状态: ");
System.out.println(net.isConnectorPresent() ? "已连接" : "未连接");
System.out.println("接口类型: " + AgentUtil.getInterfaceType(net));
System.out.println("IPv4地址: " + String.join(", ", net.getIPv4addr()));
NetVO netVO = NetVO.builder().build();
netVO.setName(net.getName()+"("+net.getDisplayName()+")");//网卡名称
netVO.setMac(net.getMacaddr());//MAC
netVO.setStatus(net.isConnectorPresent() ? "已连接" : "未连接");//运行状态
netVO.setType(AgentUtil.getInterfaceType(net));//接口类型
netVO.setIpV4(String.join(", ", net.getIPv4addr()));//IPv4
Map<String, String> map = getNetworkMode(net.getName());
if (map != null && !map.isEmpty()) {
netVO.setSpeed(map.get("speed"));
netVO.setDuplex(map.get("duplex"));
}
temp.add(netVO);
// 获取系统所有物理网卡
Set<String> allPhysicalInterfaces = getAllPhysicalInterfaces();
System.out.println("系统所有物理网卡: " + allPhysicalInterfaces);
System.out.println("OSHI识别的网卡: " + networkIFs.stream().map(NetworkIF::getName).collect(Collectors.toList()));
// 创建网卡名称到NetworkIF对象的映射
Map<String, NetworkIF> networkIFMap = new HashMap<>();
for (NetworkIF net : networkIFs) {
networkIFMap.put(net.getName(), net);
}
// 2. 实时带宽监控需要两次采样
System.out.println("\n=== 实时带宽监控 ===");
System.out.println("=========================================================");
System.out.println("===== 网卡流量统计(基于iptables统计) =====");
// 第一次采样
for (NetworkIF net : networkIFs) {
net.updateAttributes();
}
// 等待1秒
TimeUnit.SECONDS.sleep(1);
// 第二次采样并计算速率
for (NetworkIF net : networkIFs) {
long prevBytesRecv = net.getBytesRecv();
long prevBytesSent = net.getBytesSent();
net.updateAttributes();
long bytesRecv = net.getBytesRecv() - prevBytesRecv;
long bytesSent = net.getBytesSent() - prevBytesSent;
System.out.println("接口: " + net.getName());
System.out.println("入站丢包: " + net.getInDrops());
System.out.println("出站丢包: " + net.getCollisions());
System.out.println("接收带宽: " + FormatUtil.formatBytes(bytesRecv) + "/s (" +
bytesToMbps(bytesRecv) + " Mbps)");
System.out.println("发送带宽: " + FormatUtil.formatBytes(bytesSent) + "/s (" +
bytesToMbps(bytesSent) + " Mbps)");
// 等待1秒仅用于获取瞬时流量速率实际计算总流量时不需要
Thread.sleep(1000);
NetVO netVO = temp.stream().filter(n -> n.getIpV4().equals(String.join(", ", net.getIPv4addr()))
&& n.getMac().equals(net.getMacaddr())).findFirst().orElse(null);
if(Objects.nonNull(netVO)){
netVO.setInDropped(net.getInDrops());//入站丢包
netVO.setOutDropped(net.getCollisions());//出站丢包
netVO.setInSpeed(bytesRecv);//接收流量
netVO.setOutSpeed(bytesSent);//发送流量
netVO.setTimestamp(timestamp);
// 获取iptables统计信息
Map<String, Map<String, Long>> iptablesStats = getIptablesStatistics();
// 处理所有物理网卡
for (String interfaceName : allPhysicalInterfaces) {
NetworkIF net = networkIFMap.get(interfaceName);
NetVO netVO = null;
if (net != null) {
// OSHI能识别到的网卡
netVO = processNetworkIF(net, iptablesStats, timestamp);
} else {
// OSHI未识别到的网卡无IP配置的网卡
netVO = processMissingNetworkIF(interfaceName, timestamp);
}
if (netVO != null) {
list.add(netVO);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
private static double bytesToMbps(long bytes) {
return bytes * 8.0 / 1_000_000; // bytes to megabits
/**
* 获取系统所有物理网卡包括无IP的
*/
private Set<String> getAllPhysicalInterfaces() {
Set<String> interfaces = new HashSet<>();
Path netPath = Paths.get("/sys/class/net");
try (DirectoryStream<Path> stream = Files.newDirectoryStream(netPath)) {
for (Path interfacePath : stream) {
String interfaceName = interfacePath.getFileName().toString();
// 跳过虚拟网卡可选根据需求调整
if (interfaceName.startsWith("lo")) {
continue;
}
interfaces.add(interfaceName);
}
} catch (Exception e) {
System.err.println("读取网卡列表失败: " + e.getMessage());
}
return interfaces;
}
/**
* /sys读取网卡统计信息
*/
private Long getBytesFromSys(String interfaceName, String statFile) {
try {
Path statPath = Paths.get("/sys/class/net", interfaceName, "statistics", statFile);
if (Files.exists(statPath)) {
String content = Files.readAllLines(statPath).get(0).trim();
return Long.parseLong(content);
}
} catch (Exception e) {
System.err.println("读取网卡 " + interfaceName + "" + statFile + " 失败: " + e.getMessage());
}
return 0L;
}
/**
* 启动异步ping监控
*/
private void startPingMonitorAsync(String interfaceName, long timeStapm) {
CompletableFuture.runAsync(() -> {
try {
Double lossRate = getPingLossRateSync(interfaceName);
if (lossRate != null) {
AssertLog.info("网卡 " + interfaceName + " 5分钟平均丢包率: " + lossRate + "%");
// 更新缓存供下次使用
pingCache.put(interfaceName+timeStapm, lossRate);
}
} catch (Exception e) {
AssertLog.error("网卡" + interfaceName + "ping监控异常: " + e.getMessage());
}
});
}
/**
* 同步获取ping丢包率在异步线程中调用
*/
private Double getPingLossRateSync(String interfaceName) {
String[] cmd = {
"/bin/sh", "-c",
String.format("ping -I %s -c 290 -i 1 -W 1 -w 290 %s 2>&1 | grep 'packet loss' | awk -F'[ ,%%]+' '{print $6}'",
interfaceName, "223.5.5.5")
};
try {
Process process = Runtime.getRuntime().exec(cmd);
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
int exitCode = process.waitFor();
if (exitCode != 0) {
return null;
}
String result = reader.readLine();
if (result != null && !result.trim().isEmpty()) {
return Double.parseDouble(result.trim());
}
} catch (Exception e) {
AssertLog.error("执行ping命令失败: " + e.getMessage());
}
return null;
}
/**
* 处理OSHI能识别到的网卡
*/
private NetVO processNetworkIF(NetworkIF net, Map<String, Map<String, Long>> iptablesStats, long timestamp) {
try {
net.updateAttributes();
String interfaceName = net.getName();
boolean isBusinessInterface = GlobalConfig.NETNAME.contains(interfaceName);
String[] ipv6Arr = net.getIPv6addr();
String publicIpv6 = "";
for (String ipv6 : ipv6Arr) {
if(AgentUtil.isPublicIPv6(ipv6)){
publicIpv6 = ipv6;
break;
}
}
String[] ipv4Arr = net.getIPv4addr();
CompletableFuture<Double> pingLossFuture = null;
boolean hasIpv4 = false;
Double pingDropped = null;
if(ipv4Arr != null){
for (String ipv4 : ipv4Arr) {
if(ipv4 != "" && !ipv4.startsWith("127.")){
hasIpv4 = true;
break;
}
}
}
System.out.println("接口名称: " + interfaceName + "(" + net.getDisplayName() + ")");
System.out.println("MAC地址: " + net.getMacaddr());
System.out.println("运行状态: " + (net.isConnectorPresent() ? "已连接" : "未连接"));
System.out.println("接口类型: " + AgentUtil.getInterfaceType(net));
System.out.println("IPv4地址: " + String.join(", ", net.getIPv4addr()));
System.out.println("公网IPv6地址: " + publicIpv6);
System.out.println("业务网卡: " + (isBusinessInterface ? "" : ""));
// 从OSHI获取总流量
long totalBytesRecv = net.getBytesRecv();
long totalBytesSent = net.getBytesSent();
Long ipv4BytesRecv = null, ipv4BytesSent = null;
Long ipv6BytesRecv = null, ipv6BytesSent = null;
if (isBusinessInterface) {
Map<String, Long> interfaceStats = iptablesStats.get(interfaceName);
if (interfaceStats != null) {
ipv4BytesRecv = interfaceStats.get("ipv4Recv");
ipv4BytesSent = interfaceStats.get("ipv4Sent");
ipv6BytesRecv = interfaceStats.get("ipv6Recv");
ipv6BytesSent = interfaceStats.get("ipv6Sent");
}
}
System.out.println("总接收流量: " + FormatUtil.formatBytes(totalBytesRecv));
System.out.println("总发送流量: " + FormatUtil.formatBytes(totalBytesSent));
System.out.println("IPv4接收流量: " + (ipv4BytesRecv != null ? FormatUtil.formatBytes(ipv4BytesRecv) : "null"));
System.out.println("IPv4发送流量: " + (ipv4BytesSent != null ? FormatUtil.formatBytes(ipv4BytesSent) : "null"));
System.out.println("IPv6接收流量: " + (ipv6BytesRecv != null ? FormatUtil.formatBytes(ipv6BytesRecv) : "null"));
System.out.println("IPv6发送流量: " + (ipv6BytesSent != null ? FormatUtil.formatBytes(ipv6BytesSent) : "null"));
System.out.println("入站丢包: " + net.getInDrops());
System.out.println("出站丢包: " + net.getCollisions());
NetVO netVO = NetVO.builder()
.name(net.getName() + "(" + net.getDisplayName() + ")")
.mac(net.getMacaddr())
.status(net.isConnectorPresent() ? "已连接" : "未连接")
.type(AgentUtil.getInterfaceType(net))
.ipV4(String.join(", ", net.getIPv4addr()))
.ipV6(publicIpv6)
.inDropped(net.getInDrops())
.outDropped(net.getCollisions())
.inSpeed(totalBytesRecv) // 总接收流量
.outSpeed(totalBytesSent) // 总发送流量
.ipv4InSpeed(ipv4BytesRecv)
.ipv4OutSpeed(ipv4BytesSent)
.ipv6InSpeed(ipv6BytesRecv)
.ipv6OutSpeed(ipv6BytesSent)
.timestamp(timestamp)
.build();
if(hasIpv4){
startPingMonitorAsync(interfaceName, timestamp);
netVO.setPingDropped(pingCache.get(interfaceName+(timestamp-300)));
// 使用完清除防止内存泄漏
pingCache.remove(interfaceName+(timestamp-300));
}
// 设置协商速度和工作模式
Map<String, String> ethtoolMap = getNetworkMode(net.getName());
if (ethtoolMap != null && !ethtoolMap.isEmpty()) {
netVO.setSpeed(ethtoolMap.get("speed"));
netVO.setDuplex(ethtoolMap.get("duplex"));
}
System.out.println("-----------------------------------------");
return netVO;
} catch (Exception e) {
System.err.println("处理网卡 " + net.getName() + " 失败: " + e.getMessage());
return null;
}
}
/**
* 处理OSHI未识别的网卡无IP配置的网卡
*/
private NetVO processMissingNetworkIF(String interfaceName, long timestamp) {
System.out.println("接口名称: " + interfaceName + "(无IP配置)");
try {
// 获取MAC地址
String macAddr = getMacAddress(interfaceName);
System.out.println("MAC地址: " + (macAddr != null ? macAddr : "未知"));
// 获取网卡状态
String status = getInterfaceStatus(interfaceName);
System.out.println("运行状态: " + status);
// /sys获取总流量统计
Long totalBytesRecv = getBytesFromSys(interfaceName, "rx_bytes");
Long totalBytesSent = getBytesFromSys(interfaceName, "tx_bytes");
// 获取丢包数
Long inDropped = getBytesFromSys(interfaceName, "rx_dropped");
Long outDropped = getBytesFromSys(interfaceName, "tx_dropped");
System.out.println("总接收流量: " + FormatUtil.formatBytes(totalBytesRecv));
System.out.println("总发送流量: " + FormatUtil.formatBytes(totalBytesSent));
System.out.println("入站丢包: " + inDropped);
System.out.println("出站丢包: " + outDropped);
// 获取协商速度和工作模式
Map<String, String> ethtoolMap = getNetworkMode(interfaceName);
String speed = null;
String duplex = null;
if (ethtoolMap != null && !ethtoolMap.isEmpty()) {
speed = ethtoolMap.get("speed");
duplex = ethtoolMap.get("duplex");
}
NetVO netVO = NetVO.builder()
.name(interfaceName + "(无IP配置)")
.mac(macAddr != null ? macAddr : "00:00:00:00:00:00")
.status(status)
.type("Ethernet")
.ipV4("")
.ipV6("")
.inDropped(inDropped)
.outDropped(outDropped)
.inSpeed(totalBytesRecv) // 总接收流量
.outSpeed(totalBytesSent) // 总发送流量
.ipv4InSpeed(null) // 无IP配置无法区分IPv4/IPv6流量
.ipv4OutSpeed(null)
.ipv6InSpeed(null)
.ipv6OutSpeed(null)
.speed(speed)
.duplex(duplex)
.timestamp(timestamp)
.build();
System.out.println("-----------------------------------------");
return netVO;
} catch (Exception e) {
System.err.println("处理无IP网卡 " + interfaceName + " 失败: " + e.getMessage());
return null;
}
}
/**
* 获取网卡MAC地址
*/
private String getMacAddress(String interfaceName) {
try {
Path macPath = Paths.get("/sys/class/net", interfaceName, "address");
if (Files.exists(macPath)) {
String mac = Files.readAllLines(macPath).get(0).trim();
return mac.isEmpty() ? null : mac;
}
} catch (Exception e) {
System.err.println("获取MAC地址失败: " + e.getMessage());
}
return null;
}
/**
* 获取网卡状态
*/
private String getInterfaceStatus(String interfaceName) {
try {
Path operstatePath = Paths.get("/sys/class/net", interfaceName, "operstate");
if (Files.exists(operstatePath)) {
String state = Files.readAllLines(operstatePath).get(0).trim();
return "up".equalsIgnoreCase(state) ? "已连接" : "未连接";
}
} catch (Exception e) {
System.err.println("获取网卡状态失败: " + e.getMessage());
}
return "未知";
}
/**
* 获取iptables统计信息 - 使用命令过滤模式优化
*/
private Map<String, Map<String, Long>> getIptablesStatistics() {
Map<String, Map<String, Long>> stats = new HashMap<>();
try {
System.out.println("开始获取iptables统计信息(过滤模式)...");
// 遍历所有业务网卡为每个网卡分别获取统计信息
for (String interfaceName : GlobalConfig.NETNAME.split(";")) {
System.out.println("处理业务网卡: " + interfaceName);
Map<String, Long> interfaceStats = new HashMap<>();
// 获取IPv4 INPUT统计
Long ipv4Recv = getIptablesStatForInterface("iptables", "INPUT", interfaceName);
if (ipv4Recv != null) {
interfaceStats.put("ipv4Recv", ipv4Recv);
}
// 获取IPv4 OUTPUT统计
Long ipv4Sent = getIptablesStatForInterface("iptables", "OUTPUT", interfaceName);
if (ipv4Sent != null) {
interfaceStats.put("ipv4Sent", ipv4Sent);
}
// 获取IPv6 INPUT统计
Long ipv6Recv = getIptablesStatForInterface("ip6tables", "INPUT", interfaceName);
if (ipv6Recv != null) {
interfaceStats.put("ipv6Recv", ipv6Recv);
}
// 获取IPv6 OUTPUT统计
Long ipv6Sent = getIptablesStatForInterface("ip6tables", "OUTPUT", interfaceName);
if (ipv6Sent != null) {
interfaceStats.put("ipv6Sent", ipv6Sent);
}
if (!interfaceStats.isEmpty()) {
stats.put(interfaceName, interfaceStats);
System.out.println("网卡 " + interfaceName + " 的统计信息: " + interfaceStats);
} else {
System.out.println("警告: 未找到网卡 " + interfaceName + " 的iptables统计信息");
}
}
System.out.println("最终iptables统计信息: " + stats);
} catch (Exception e) {
System.err.println("获取iptables统计失败: " + e.getMessage());
e.printStackTrace();
}
return stats;
}
/**
* 获取指定网卡的iptables统计信息
* @param iptablesCmd iptables或ip6tables
* @param chain 链名INPUT/OUTPUT
* @param interfaceName 网卡名
* @return 字节数如果没有找到返回null
*/
private Long getIptablesStatForInterface(String iptablesCmd, String chain, String interfaceName) {
try {
// 构建命令iptables -v -n -x -L INPUT | grep eth0
ProcessBuilder pb = new ProcessBuilder("sh", "-c",
iptablesCmd + " -v -n -x -L " + chain + " | grep " + interfaceName);
Process process = pb.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
long totalBytes = 0;
String line;
boolean found = false;
while ((line = reader.readLine()) != null) {
System.out.println("解析 " + iptablesCmd + " " + chain + " 行: " + line);
Long bytes = parseIptablesLineForInterface(line);
if (bytes != null) {
totalBytes += bytes;
found = true;
System.out.println("找到匹配的规则: " + bytes + " bytes");
}
}
process.waitFor();
if (found) {
System.out.println(iptablesCmd + " " + chain + " 接口 " + interfaceName + " 总流量: " + totalBytes + " bytes");
return totalBytes;
} else {
System.out.println("未找到 " + iptablesCmd + " " + chain + " 接口 " + interfaceName + " 的统计信息");
return null;
}
} catch (Exception e) {
System.err.println("获取 " + iptablesCmd + " " + chain + " 统计失败: " + e.getMessage());
return null;
}
}
/**
* 解析iptables输出行
*/
private Long parseIptablesLineForInterface(String line) {
line = line.trim();
if (line.isEmpty() || line.startsWith("Chain") || line.startsWith("pkts") || line.startsWith("target")) {
return null;
}
// 使用空格分割处理多个连续空格
String[] parts = line.split("\\s+");
if (parts.length >= 7) {
try {
// 字节数在第二列索引1
long bytes = Long.parseLong(parts[1]);
// 由于已经用grep过滤了直接返回字节数
System.out.println("找到匹配的规则: " + bytes + " bytes");
return bytes;
} catch (NumberFormatException e) {
System.err.println("解析iptables行失败,无法解析数字: " + line);
}
}
return null;
}
/**
* 获取网卡协商模式
*/
public static Map<String, String> getNetworkMode(String interfaceName) {
Map<String, String> result = new HashMap<>();
ProcessBuilder pb = new ProcessBuilder("ethtool", interfaceName);
@@ -107,17 +522,12 @@ public class NetServiceImpl implements NetService {
result.put("speed", line.split(":")[1].trim());
} else if (line.contains("Duplex:")) {
result.put("duplex", line.split(":")[1].trim());
} else if (line.contains("Auto-negotiation:")) {
result.put("auto-negotiation", line.split(":")[1].trim());
}
}
int exitCode = process.waitFor();
if (exitCode != 0) {
result.put("error", "ethtool command failed with exit code " + exitCode);
}
process.waitFor();
} catch (Exception e) {
result.put("error", e.getMessage());
}
return result;
}
}
}
@@ -1,571 +1,9 @@
package com.tongran.agent.client.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.tongran.agent.client.core.config.GlobalConfig;
import com.tongran.agent.client.core.vo.SwitchBoardVO;
import com.tongran.agent.client.service.SwitchBoardService;
import com.tongran.agent.client.utils.AssertLog;
import org.apache.commons.lang3.StringUtils;
import org.snmp4j.*;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.smi.GenericAddress;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.VariableBinding;
import org.snmp4j.transport.DefaultUdpTransportMapping;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class SwitchBoardServiceImpl implements SwitchBoardService {
@Override
public List<SwitchBoardVO> switchBoardList(long timestamp) {
List<SwitchBoardVO> list = new ArrayList<>();
// System.out.println("==================== 交换机流量信息 ====================");
try {
// 1. 创建传输映射
TransportMapping transport = new DefaultUdpTransportMapping();
Snmp snmp = new Snmp(transport);
transport.listen();
// 2. 创建目标对象
CommunityTarget target = new CommunityTarget();
target.setCommunity(new OctetString(GlobalConfig.SWITCH_COMMUNITY));
target.setAddress(GenericAddress.parse("udp:" + GlobalConfig.SWITCH_IP + "/" + GlobalConfig.SWITCH_PORT));
target.setRetries(2);
target.setTimeout(5000);
target.setVersion(SnmpConstants.version2c);
// 3. 获取交换机基本信息
getSystemInfo(snmp, target);
// 4. 获取接口信息
list = getInterfaceInfo(snmp, target, timestamp);
snmp.close();
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
@Override
public String getSwitchDataByType(String type) {
JSONObject json = new JSONObject();
json.put("type",type);
switch(type){
case "switchNetCollect":
json.put("value",handleSwitchNet());
break;
case "switchModuleCollect":
json.put("value",handleSwitchModule());
break;
case "switchMpuCollect":
json.put("value",handleSwitchMpu());
break;
case "switchPwrCollect":
json.put("value",handleSwitchPwr());
break;
case "switchFanCollect":
json.put("value",handleSwitchFan());
break;
default:
json.put("value",handleSwitchOther(type));
break;
}
return json.toString();
}
private String handleSwitchNet(){
// System.out.println("==================== 交换机网络信息 ====================");
String result = "";
try {
if(GlobalConfig.SWITCH_NET_OID.isEmpty()){
return result;
}
// 1. 创建传输映射
TransportMapping transport = new DefaultUdpTransportMapping();
Snmp snmp = new Snmp(transport);
transport.listen();
// 2. 创建目标对象
CommunityTarget target = new CommunityTarget();
target.setCommunity(new OctetString(GlobalConfig.SWITCH_COMMUNITY));
target.setAddress(GenericAddress.parse("udp:" + GlobalConfig.SWITCH_IP + "/" + GlobalConfig.SWITCH_PORT));
target.setRetries(2);
target.setTimeout(5000);
target.setVersion(SnmpConstants.version2c);
result = getInterfaceInfoByType(snmp, target, "switchNet", GlobalConfig.SWITCH_NET_OID);
snmp.close();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
private String handleSwitchModule(){
// System.out.println("==================== 交换机光模块信息 ====================");
String result = "";
try {
if(GlobalConfig.SWITCH_MODULE_OID.isEmpty()){
return result;
}
// 1. 创建传输映射
TransportMapping transport = new DefaultUdpTransportMapping();
Snmp snmp = new Snmp(transport);
transport.listen();
// 2. 创建目标对象
CommunityTarget target = new CommunityTarget();
target.setCommunity(new OctetString(GlobalConfig.SWITCH_COMMUNITY));
target.setAddress(GenericAddress.parse("udp:" + GlobalConfig.SWITCH_IP + "/" + GlobalConfig.SWITCH_PORT));
target.setRetries(2);
target.setTimeout(5000);
target.setVersion(SnmpConstants.version2c);
result = getInterfaceInfoByType(snmp, target, "switchModule", GlobalConfig.SWITCH_MODULE_OID);
snmp.close();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
private String handleSwitchMpu(){
// System.out.println("==================== 交换机MPU信息 ====================");
String result = "";
try {
if(GlobalConfig.SWITCH_MPU_OID.isEmpty()){
return result;
}
// 1. 创建传输映射
TransportMapping transport = new DefaultUdpTransportMapping();
Snmp snmp = new Snmp(transport);
transport.listen();
// 2. 创建目标对象
CommunityTarget target = new CommunityTarget();
target.setCommunity(new OctetString(GlobalConfig.SWITCH_COMMUNITY));
target.setAddress(GenericAddress.parse("udp:" + GlobalConfig.SWITCH_IP + "/" + GlobalConfig.SWITCH_PORT));
target.setRetries(2);
target.setTimeout(5000);
target.setVersion(SnmpConstants.version2c);
result = getInterfaceInfoByType(snmp, target, "switchMpu", GlobalConfig.SWITCH_MPU_OID);
snmp.close();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
private String handleSwitchPwr(){
// System.out.println("==================== 交换机电源信息 ====================");
String result = "";
try {
if(GlobalConfig.SWITCH_PWR_OID.isEmpty()){
return result;
}
// 1. 创建传输映射
TransportMapping transport = new DefaultUdpTransportMapping();
Snmp snmp = new Snmp(transport);
transport.listen();
// 2. 创建目标对象
CommunityTarget target = new CommunityTarget();
target.setCommunity(new OctetString(GlobalConfig.SWITCH_COMMUNITY));
target.setAddress(GenericAddress.parse("udp:" + GlobalConfig.SWITCH_IP + "/" + GlobalConfig.SWITCH_PORT));
target.setRetries(2);
target.setTimeout(5000);
target.setVersion(SnmpConstants.version2c);
result = getInterfaceInfoByType(snmp, target, "switchPwr", GlobalConfig.SWITCH_PWR_OID);
snmp.close();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
private String handleSwitchFan(){
// System.out.println("==================== 交换机风扇信息 ====================");
String result = "";
try {
if(GlobalConfig.SWITCH_FAN_OID.isEmpty()){
return result;
}
// 1. 创建传输映射
TransportMapping transport = new DefaultUdpTransportMapping();
Snmp snmp = new Snmp(transport);
transport.listen();
// 2. 创建目标对象
CommunityTarget target = new CommunityTarget();
target.setCommunity(new OctetString(GlobalConfig.SWITCH_COMMUNITY));
target.setAddress(GenericAddress.parse("udp:" + GlobalConfig.SWITCH_IP + "/" + GlobalConfig.SWITCH_PORT));
target.setRetries(2);
target.setTimeout(5000);
target.setVersion(SnmpConstants.version2c);
result = getInterfaceInfoByType(snmp, target, "switchFan", GlobalConfig.SWITCH_FAN_OID);
snmp.close();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
private String handleSwitchOther(String type){
// System.out.println("==================== 交换机系统其他信息 ====================");
String result = "";
try {
if(GlobalConfig.SWITCH_OTHER_OID.isEmpty()){
return result;
}
// 1. 创建传输映射
TransportMapping transport = new DefaultUdpTransportMapping();
Snmp snmp = new Snmp(transport);
transport.listen();
// 2. 创建目标对象
CommunityTarget target = new CommunityTarget();
target.setCommunity(new OctetString(GlobalConfig.SWITCH_COMMUNITY));
target.setAddress(GenericAddress.parse("udp:" + GlobalConfig.SWITCH_IP + "/" + GlobalConfig.SWITCH_PORT));
target.setRetries(2);
target.setTimeout(5000);
target.setVersion(SnmpConstants.version2c);
result = getInterfaceInfoByType(snmp, target, type, GlobalConfig.SWITCH_OTHER_OID);
snmp.close();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
private static void getSystemInfo(Snmp snmp, Target target) throws IOException {
// 系统描述 OID
String[] systemOIDs = {
"1.3.6.1.2.1.1.1.0", // sysDescr
"1.3.6.1.2.1.1.5.0", // sysName
"1.3.6.1.2.1.1.6.0", // sysLocation
"1.3.6.1.2.1.1.4.0", // sysContact
"1.3.6.1.2.1.1.3.0" // sysUpTime
};
PDU pdu = new PDU();
for (String oid : systemOIDs) {
pdu.add(new VariableBinding(new OID(oid)));
}
pdu.setType(PDU.GET);
ResponseEvent event = snmp.send(pdu, target);
if (event != null && event.getResponse() != null) {
// System.out.println("\n=== 交换机基本信息 ===");
for (VariableBinding vb : event.getResponse().getVariableBindings()) {
System.out.printf("%-30s: %s%n",
getOIDDescription(vb.getOid().toString()),
vb.getVariable());
}
}
}
private static List<SwitchBoardVO> getInterfaceInfo(Snmp snmp, Target target, long timestamp) throws IOException {
// 获取接口数量
String ifNumberOID = "1.3.6.1.2.1.2.1.0";
PDU pdu = new PDU();
pdu.add(new VariableBinding(new OID(ifNumberOID)));
pdu.setType(PDU.GET);
ResponseEvent event = snmp.send(pdu, target);
if (event == null || event.getResponse() == null) {
System.err.println("无法获取接口数量");
return null;
}
int ifNumber = event.getResponse().get(0).getVariable().toInt();
System.out.println("\n=== 接口数量: " + ifNumber + " ===");
// 获取每个接口的信息
String[] ifOIDs = {
"1.3.6.1.2.1.2.2.1.2", // ifDescr
"1.3.6.1.2.1.2.2.1.3", // ifType
"1.3.6.1.2.1.2.2.1.5", // ifSpeed
"1.3.6.1.2.1.2.2.1.8", // ifOperStatus
"1.3.6.1.2.1.31.1.1.1.6", // ifInOctets
"1.3.6.1.2.1.31.1.1.1.10" // ifOutOctets
// "1.3.6.1.2.1.2.2.1.10", // ifInOctets
// "1.3.6.1.2.1.2.2.1.16" // ifOutOctets
};
System.out.printf("%-5s %-15s %-10s %-15s %-8s %-12s %-12s%n",
"Index", "Name", "Type", "Speed", "Status", "InBytes", "OutBytes");
List<SwitchBoardVO> list = new ArrayList<>();
for (int i = 1; i <= ifNumber; i++) {
pdu = new PDU();
for (String baseOID : ifOIDs) {
pdu.add(new VariableBinding(new OID(baseOID + "." + i)));
}
pdu.setType(PDU.GET);
event = snmp.send(pdu, target);
if (event != null && event.getResponse() != null) {
VariableBinding[] vbs = event.getResponse().getVariableBindings().toArray(new VariableBinding[0]);
String ifName = vbs[0].getVariable().toString();
String ifType = getInterfaceType(vbs[1].getVariable().toInt());
String speed = formatSpeed(vbs[2].getVariable().toInt());
String status = getOperStatus(vbs[3].getVariable().toInt());
long inBytes = vbs[4].getVariable().toLong();
long outBytes = vbs[5].getVariable().toLong();
SwitchBoardVO boardVO = SwitchBoardVO.builder()
.name(ifName)
.switchIp(GlobalConfig.SWITCH_IP)
.type(ifType)
.status(status)
.inBytes(inBytes)
.outBytes(outBytes)
.timestamp(timestamp)
.build();
list.add(boardVO);
System.out.printf("%-5d %-15s %-10s %-15s %-8s %-12d %-12d%n",
i, ifName, ifType, speed, status, inBytes, outBytes);
}
}
return list;
}
// 辅助方法
private static String getOIDDescription(String oid) {
switch(oid) {
case "1.3.6.1.2.1.1.1.0": return "系统描述";
case "1.3.6.1.2.1.1.5.0": return "系统名称";
case "1.3.6.1.2.1.1.6.0": return "物理位置";
case "1.3.6.1.2.1.1.4.0": return "联系人";
case "1.3.6.1.2.1.1.3.0": return "运行时间";
default: return oid;
}
}
private static String getInterfaceType(int type) {
switch(type) {
case 6: return "Ethernet";
case 23: return "PPP";
case 24: return "Loopback";
case 53: return "VLAN";
case 131: return "Tunnel";
default: return "其他("+type+")";
}
}
private static String formatSpeed(int speed) {
if (speed >= 1000000000) {
return (speed / 1000000000) + " Gbps";
} else if (speed >= 1000000) {
return (speed / 1000000) + " Mbps";
} else if (speed >= 1000) {
return (speed / 1000) + " Kbps";
}
return speed + " bps";
}
private static String getOperStatus(int status) {
switch(status) {
case 1: return "Up";
case 2: return "Down";
case 3: return "Testing";
case 4: return "Unknown";
case 5: return "Dormant";
case 6: return "NotPresent";
case 7: return "LowerLayerDown";
default: return "未知";
}
}
private static String getInterfaceInfoByType(Snmp snmp, Target target, String type, LinkedHashMap<String,String> oidParams) throws IOException {
if(oidParams.isEmpty()){
return "";
}
JSONObject json = new JSONObject();
// 获取接口数量
String ifNumberOID = "";
List<String> filter_value = new ArrayList<>();
if(StringUtils.equals(type,"switchNet")){
ifNumberOID = GlobalConfig.NET_INDEX_OID;
filter_value = GlobalConfig.NET_FILTER;
}else if(StringUtils.equals(type,"switchModule")){
ifNumberOID = GlobalConfig.MODULE_INDEX_OID;
filter_value = GlobalConfig.MODULE_FILTER;
}else if(StringUtils.equals(type,"switchMpu")){
ifNumberOID = GlobalConfig.MPU_INDEX_OID;
filter_value = GlobalConfig.MPU_FILTER;
}else if(StringUtils.equals(type,"switchPwr")){
ifNumberOID = GlobalConfig.PWR_INDEX_OID;
filter_value = GlobalConfig.PWR_FILTER;
}else if(StringUtils.equals(type,"switchFan")){
ifNumberOID = GlobalConfig.FAN_INDEX_OID;
filter_value = GlobalConfig.FAN_FILTER;
}else {
ifNumberOID = GlobalConfig.OTHER_INDEX_OID;
filter_value = GlobalConfig.OTHER_FILTER;
}
AssertLog.info("交换机采集type={},筛选端口={}",type,JSON.toJSONString(filter_value));
if(StringUtils.isBlank(ifNumberOID)){
System.err.println("未初始化索引OID");
return "";
}
OID oid = new OID(ifNumberOID);
PDU pdu = new PDU();
pdu.add(new VariableBinding(new OID(ifNumberOID)));
pdu.setType(PDU.GETBULK);
pdu.setMaxRepetitions(100); // 每次获取最多 100
pdu.setNonRepeaters(0);
List<Integer> indexes = new ArrayList<>();
boolean finished = false;
while (!finished) {
ResponseEvent response = snmp.send(pdu, target);
PDU responsePdu = response.getResponse();
if (responsePdu == null) {
System.err.println("Timeout: No response from device.");
break;
}
if (responsePdu.getErrorStatus() != 0) {
System.err.println("Error: " + responsePdu.getErrorStatusText());
break;
}
// 处理每个返回结果
for (VariableBinding vb : responsePdu.getVariableBindings()) {
OID returnedOid = vb.getOid();
// 检查是否仍在目标 OID 子树下
if (!returnedOid.startsWith(oid)) {
finished = true;
break;
}
// 提取最后一个值就是 ifIndex 编号
int ifIndex = returnedOid.get(returnedOid.size() - 1);
int value = vb.getVariable().toInt();
if(CollectionUtil.isEmpty(filter_value)){
// AssertLog.info("交换机采集type={},索引返回:值={}",type,vb);
indexes.add(ifIndex);
}else{
if(filter_value.contains(String.valueOf(value))){
// AssertLog.info("交换机采集type={},索引返回:值={}",type,vb);
indexes.add(ifIndex);
}
}
}
// 更新下一次请求的起始 OIDSNMP Walk
pdu.set(0, new VariableBinding(responsePdu.getVariableBindings().get(0).getOid()));
}
if(CollectionUtil.isEmpty(indexes)){
System.err.println("返回获取索引列表为空");
return "";
}
//去重
indexes = indexes.stream().distinct().collect(Collectors.toList());
// System.out.println("\n=== 接口数量: " + indexes.size() + " ===");
// System.out.println("\n=== 接口列表: " + JSON.toJSONString(indexes));
String[] ifOIDs = oidParams.keySet().toArray(new String[0]);
String[] params = oidParams.values().toArray(new String[0]);
if(!StringUtils.equals(type,"switchNet") && !StringUtils.equals(type,"switchModule")
&& !StringUtils.equals(type,"switchMpu") && !StringUtils.equals(type,"switchPwr")
&& !StringUtils.equals(type,"switchFan")){
String os = "";
String ps = "";
for (int i = 0; i < params.length; i++) {
if(type.toLowerCase().contains(params[i].toLowerCase())){
if(StringUtils.isBlank(ps)){
ps += params[i];
os += ifOIDs[i];
}else {
ps += "," + params[i];
os += "," + ifOIDs[i];
}
}
}
ifOIDs = os.split(",");
params = ps.split(",");
List<Integer> otherList = new ArrayList<>();
otherList.add(indexes.get(0));
indexes = otherList;
}
// 获取每个接口的信息
// String[] ifOIDs = {
// "1.3.6.1.2.1.2.2.1.2", // ifDescr
// "1.3.6.1.2.1.2.2.1.3", // ifType
// "1.3.6.1.2.1.2.2.1.5", // ifSpeed
// "1.3.6.1.2.1.2.2.1.8", // ifOperStatus
// "1.3.6.1.2.1.31.1.1.1.6", // ifInOctets
// "1.3.6.1.2.1.31.1.1.1.10" // ifOutOctets
//// "1.3.6.1.2.1.2.2.1.10", // ifInOctets
//// "1.3.6.1.2.1.2.2.1.16" // ifOutOctets
// };
if(ifOIDs == null || ifOIDs.length == 0){
return "";
}
List<String> list = new ArrayList<>();
for (int i = 0; i < indexes.size(); i++) {
pdu = new PDU();
for (String baseOID : ifOIDs) {
pdu.add(new VariableBinding(new OID(baseOID + "." + indexes.get(i))));
}
pdu.setType(PDU.GET);
ResponseEvent event = snmp.send(pdu, target);
// System.out.printf("%-5s %-15s %-10s %-15s %-8s %-12s %-12s%n",
// "Index", "Name", "Type", "Speed", "Status", "InBytes", "OutBytes");
// if (event != null && event.getResponse() != null) {
// VariableBinding[] vbs = event.getResponse().getVariableBindings().toArray(new VariableBinding[0]);
// String ifName = vbs[0].getVariable().toString();
// String ifType = getInterfaceType(vbs[1].getVariable().toInt());
// String speed = formatSpeed(vbs[2].getVariable().toInt());
// String status = getOperStatus(vbs[3].getVariable().toInt());
// long inBytes = vbs[4].getVariable().toLong();
// long outBytes = vbs[5].getVariable().toLong();
// System.out.printf("%-15s %-10s %-15s %-8s %-12d %-12d%n",
// ifName, ifType, speed, status, inBytes, outBytes);
// }
if (event != null && event.getResponse() != null) {
VariableBinding[] vbs = event.getResponse().getVariableBindings().toArray(new VariableBinding[0]);
for (int m = 0; m < params.length; m++) {
// AssertLog.info("参数名:{},值={}", params[m],vbs[m]);
json.put(params[m],vbs[m].getVariable().toString());
}
}
list.add(json.toString());
}
return JSON.toJSONString(list);
}
/**
* 结果封装类
*/
public static class MapArrays {
private final String[] keys;
private final String[] values;
public MapArrays(String[] keys, String[] values) {
this.keys = keys != null ? keys : new String[0];
this.values = values != null ? values : new String[0];
}
public String[] getKeys() {
return keys;
}
public String[] getValues() {
return values;
}
public int size() {
return keys.length;
}
public boolean isEmpty() {
return keys.length == 0;
}
@Override
public String toString() {
return "Keys: " + Arrays.toString(keys) +
"\nValues: " + Arrays.toString(values);
}
}
}
@@ -5,6 +5,7 @@ import com.tongran.agent.client.core.vo.SystemVO;
import com.tongran.agent.client.service.SystemService;
import com.tongran.agent.client.utils.AgentDataUtil;
import com.tongran.agent.client.utils.AgentUtil;
import com.tongran.agent.client.utils.AssertLog;
import org.springframework.stereotype.Service;
import oshi.SystemInfo;
import oshi.hardware.CentralProcessor;
@@ -22,14 +23,16 @@ import java.util.Map;
@Service
public class SystemServiceImpl implements SystemService {
// 改为单例只初始化一次
private static final SystemInfo SI = new SystemInfo();
private final HardwareAbstractionLayer hal = SI.getHardware();
private final OperatingSystem os = SI.getOperatingSystem();
@Override
public SystemVO get() {
SystemVO systemVO = SystemVO.builder().build();
try {
SystemInfo si = new SystemInfo();
HardwareAbstractionLayer hal = si.getHardware();
HardwareAbstractionLayer hal = SI.getHardware();
CentralProcessor processor = hal.getProcessor();
OperatingSystem os = si.getOperatingSystem();
System.out.println("=========================================================");
// 1. 操作系统基本信息
@@ -59,7 +62,7 @@ public class SystemServiceImpl implements SystemService {
System.out.println("\n=== 磁盘信息 ===");
systemVO.setDiskSizeTotal(diskSpace()); //硬盘总可用空间
systemVO.setBootTime(systemBootTime(hal.getProcessor())); //系统启动时间
systemVO.setUname(systemDescription(si)); //系统描述
systemVO.setUname(systemDescription(SI)); //系统描述
systemVO.setLocalTime(localTime()); //系统本地时间
systemVO.setUpTime(systemUptime(os)); //系统正常运行时间
System.out.println("硬盘:总可用空间: " + systemVO.getDiskSizeTotal());
@@ -159,11 +162,15 @@ public class SystemServiceImpl implements SystemService {
memInfo.getOrDefault("Buffers", 0L) +
memInfo.getOrDefault("Cached", 0L) +
memInfo.getOrDefault("SReclaimable", 0L));
AssertLog.info("memoryUtilizationCollect,总内存total{}",total);
AssertLog.info("memoryUtilizationCollect,可用内存available{}",available);
// 实际内存使用率
long cached = memInfo.getOrDefault("Cached", 0L) +
memInfo.getOrDefault("SReclaimable", 0L);
long buffers = memInfo.getOrDefault("Buffers", 0L);
double actualUsage = (double)(total - available - cached - buffers) / total * 100;
// long cached = memInfo.getOrDefault("Cached", 0L) +
// memInfo.getOrDefault("SReclaimable", 0L);
// long buffers = memInfo.getOrDefault("Buffers", 0L);
// double actualUsage = (double)(total - available - cached - buffers) / total * 100;
double actualUsage = (double)(total - available) / total * 100;
AssertLog.info("memoryUtilizationCollect,内存使用率:{}",actualUsage);
System.out.printf("实际内存使用率: %.2f%%\n", actualUsage);
return actualUsage;
} catch (IOException e) {
@@ -240,18 +247,17 @@ public class SystemServiceImpl implements SystemService {
}
private String handleSystemSwOs(){
SystemInfo si = new SystemInfo();
OperatingSystem os = si.getOperatingSystem();
System.out.println("=========================================================");
System.out.println("操作系统: " + os.getFamily());
return os.getFamily();
// System.out.println("操作系统: " + os.getFamily());
System.out.println("操作系统: " + os.toString());
return os.toString();
}
private String handleSystemSwArch(){
SystemInfo si = new SystemInfo();
OperatingSystem os = si.getOperatingSystem();
CentralProcessor processor = hal.getProcessor();
System.out.println("=========================================================");
String arch = os.getVersionInfo().getVersion() + " " + os.getBitness() + "";
// String arch = os.getVersionInfo().getVersion() + " " + os.getBitness() + "";
String arch = os.getBitness() + "";
System.out.println("操作系统架构: " + arch);
return arch;
}
@@ -276,8 +282,6 @@ public class SystemServiceImpl implements SystemService {
}
private int handleUsersNum(){
SystemInfo si = new SystemInfo();
OperatingSystem os = si.getOperatingSystem();
System.out.println("=========================================================");
int usersNum = os.getSessions().size();
System.out.println("登录用户数: " + usersNum);
@@ -292,8 +296,6 @@ public class SystemServiceImpl implements SystemService {
}
public long handleSystemBoottime(){
SystemInfo si = new SystemInfo();
HardwareAbstractionLayer hal = si.getHardware();
long boottime = systemBootTime(hal.getProcessor());
System.out.println("=========================================================");
System.out.println("系统启动时间: " + boottime);
@@ -301,8 +303,7 @@ public class SystemServiceImpl implements SystemService {
}
private String handleSystemUname(){
SystemInfo si = new SystemInfo();
String uname = systemDescription(si);
String uname = systemDescription(SI);
System.out.println("=========================================================");
System.out.println("系统描述: " + uname);
return uname;
@@ -316,8 +317,6 @@ public class SystemServiceImpl implements SystemService {
}
private long handleSystemUptime(){
SystemInfo si = new SystemInfo();
OperatingSystem os = si.getOperatingSystem();
System.out.println("=========================================================");
long uptime = systemUptime(os);
System.out.println("系统正常运行时间: " + uptime);
@@ -325,8 +324,6 @@ public class SystemServiceImpl implements SystemService {
}
private long handleProcNum(){
SystemInfo si = new SystemInfo();
OperatingSystem os = si.getOperatingSystem();
System.out.println("=========================================================");
long procNum = os.getProcessCount();
System.out.println("进程数: " + procNum);
@@ -381,8 +378,6 @@ public class SystemServiceImpl implements SystemService {
// 获取系统描述
public String systemDescription(SystemInfo si) {
OperatingSystem os = si.getOperatingSystem();
HardwareAbstractionLayer hal = si.getHardware();
return "操作系统: " + os.toString() + ",系统版本: " + os.getVersionInfo().toString() + "" +
",处理器: " + hal.getProcessor().getProcessorIdentifier().getName() +
",物理内存: " + hal.getMemory().getTotal() / (1024 * 1024 * 1024) + " GB";
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,47 @@
package com.tongran.agent.client.utils;
import com.tongran.agent.client.netty.config.ConnectionConfig;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public class ClientExample {
public static void main(String[] args) {
EnhancedConnectionManager manager = new EnhancedConnectionManager();
try {
// 配置多个连接
List<ConnectionConfig> configs = Arrays.asList(
// new ConnectionConfig("db_server", "192.168.1.101", 3306, 5),
// new ConnectionConfig("redis_server", "192.168.1.102", 6379, 3),
// new ConnectionConfig("api_server", "api.example.com", 8080, 10),
new ConnectionConfig("server1", "127.0.0.1", 6610, 5)
);
// 批量创建连接
Map<String, Boolean> results = manager.createConnections(configs);
System.out.println("连接创建结果: " + results);
// 等待连接建立
Thread.sleep(3000);
// 根据消息类型路由
manager.routeMessageByType("TYPE_A", "Database query");
// manager.routeMessageByType("TYPE_B", "Cache operation");
// manager.routeMessageByType("UNKNOWN", "Broadcast message");
// 检查连接状态
Map<String, Boolean> status = manager.getConnectionStatus();
System.out.println("连接状态: " + status);
Thread.sleep(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// manager.shutdown();
}
}
}
@@ -0,0 +1,70 @@
package com.tongran.agent.client.utils;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
public class CpuUsageFromProcStat {
private static final String PROC_STAT = "/proc/stat";
private static long[] prevCpuTime = null;
public static double getCpuUsage() throws Exception {
List<String> lines = Files.readAllLines(Paths.get(PROC_STAT));
for (String line : lines) {
if (line.startsWith("cpu ")) {
long[] current = parseCpuLine(line);
if (prevCpuTime == null) {
prevCpuTime = current;
Thread.sleep(500); // 初始等待
return 0.0; // 第一次不返回数据
}
double usage = calculateCpuUsage(prevCpuTime, current);
prevCpuTime = current; // 更新
return usage;
}
}
throw new RuntimeException("无法读取 /proc/stat 中的 cpu 数据");
}
private static long[] parseCpuLine(String line) {
String[] parts = line.split("\\s+");
// index: user, nice, system, idle, iowait, irq, softirq, steal
long user = Long.parseLong(parts[1]);
long nice = Long.parseLong(parts[2]);
long system = Long.parseLong(parts[3]);
long idle = Long.parseLong(parts[4]);
long iowait = Long.parseLong(parts[5]);
long irq = Long.parseLong(parts[6]);
long softirq = Long.parseLong(parts[7]);
long idleTotal = idle + iowait;
long systemTotal = user + nice + system + irq + softirq;
long total = idleTotal + systemTotal;
return new long[]{idleTotal, total};
}
private static double calculateCpuUsage(long[] prev, long[] curr) {
long idleDiff = curr[0] - prev[0];
long totalDiff = curr[1] - prev[1];
if (totalDiff <= 0) return 0.0;
return 1.0 - ((double) idleDiff / totalDiff);
}
public static void main(String[] args) throws Exception {
// 预热
getCpuUsage();
Thread.sleep(1000);
// 正式采集
for (int i = 0; i < 5; i++) {
double usage = getCpuUsage();
System.out.printf("CPU 使用率: %.2f%%\n", usage * 100);
Thread.sleep(2000);
}
}
}
@@ -0,0 +1,326 @@
package com.tongran.agent.client.utils;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.concurrent.TimeUnit;
public class CrontabManager {
// // 要添加的定时任务
// private static final String CRON_JOB = "*/10 * * * * /usr/local/tongran/sbin/test2.sh >/dev/null 2>&1";
// // 用于判断是否已存在的关键标识可以是脚本路径
// private static final String JOB_IDENTIFIER = "/usr/local/tongran/sbin/test2.sh";
/**
* 检查并添加定时任务
*/
public static boolean ensureCronJobExists(String CRON_JOB, String JOB_IDENTIFIER) {
try {
// 1. 读取当前用户的 crontab
ProcessBuilder pb = new ProcessBuilder("crontab", "-l");
pb.redirectErrorStream(true);
Process process = pb.start();
// 设置超时防止卡死
if (!process.waitFor(5, TimeUnit.SECONDS)) {
process.destroy();
throw new IOException("crontab -l timeout");
}
StringBuilder crontabContent = new StringBuilder();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
crontabContent.append(line).append("\n");
}
}
int exitCode = process.exitValue();
if (exitCode != 0 && exitCode != 1) {
// exit code 1 表示没有 crontab 文件正常
throw new IOException("crontab -l failed with exit code: " + exitCode);
}
String content = crontabContent.toString();
// 2. 检查是否已存在该任务
if (content.contains(JOB_IDENTIFIER)) {
System.out.println("Crontab 任务已存在,无需添加。");
return true;
}
// 3. 如果不存在追加任务
String newCrontab;
if (content.trim().isEmpty()) {
// 原来没有 crontab
newCrontab = CRON_JOB + "\n";
} else {
// 原来有 crontab在末尾添加新任务
newCrontab = content;
if (!content.endsWith("\n")) {
newCrontab += "\n";
}
newCrontab += CRON_JOB + "\n";
}
// 4. 写入新的 crontab
ProcessBuilder pbWrite = new ProcessBuilder("crontab", "-");
pbWrite.redirectErrorStream(true);
Process writeProcess = pbWrite.start();
try (OutputStreamWriter writer = new OutputStreamWriter(writeProcess.getOutputStream())) {
writer.write(newCrontab);
writer.flush();
}
if (!writeProcess.waitFor(5, TimeUnit.SECONDS)) {
writeProcess.destroy();
throw new IOException("crontab - write timeout");
}
int writeExitCode = writeProcess.exitValue();
if (writeExitCode != 0) {
throw new IOException("crontab - write failed with exit code: " + writeExitCode);
}
System.out.println("Crontab 任务添加成功:\n" + CRON_JOB);
return true;
} catch (IOException | InterruptedException e) {
System.err.println("操作 crontab 失败:" + e.getMessage());
e.printStackTrace();
return false;
}
}
// === 使用示例 ===
public static void main(String[] args) {
// 要添加的定时任务
String CRON_JOB = "*/10 * * * * /usr/local/tongran/sbin/test2.sh >/dev/null 2>&1";
// 用于判断是否已存在的关键标识可以是脚本路径
String JOB_IDENTIFIER = "/usr/local/tongran/sbin/test2.sh";
// boolean success = ensureCronJobExists(CRON_JOB,JOB_IDENTIFIER);
// if (success) {
// System.out.println("✅ Crontab 状态正常。");
// } else {
// System.err.println("❌ 操作失败,请检查权限或路径。");
// }
}
/**
* 验证当前定时任务中是否包含目标脚本
*/
public static boolean isCronEntryExists(String TARGET_SCRIPT) {
try {
AssertLog.info("验证: 检查当前定时任务是否包含 {}",TARGET_SCRIPT);
Process process = Runtime.getRuntime().exec("crontab -l");
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream())
);
String line;
while ((line = reader.readLine()) != null) {
if (line.contains(TARGET_SCRIPT)) {
AssertLog.info("✅ 发现目标脚本在当前定时任务中: {}",line.trim());
reader.close();
return true;
}
}
reader.close();
int exitCode = process.waitFor();
if (exitCode != 0 && exitCode != 1) { // exitCode 1 表示没有 crontab
throw new IOException("获取当前 crontab 失败,退出码: " + exitCode);
}
AssertLog.info("当前定时任务中未找到目标脚本");
return false;
}catch (IOException | InterruptedException e) {
System.err.println("操作 crontab 失败:" + e.getMessage());
e.printStackTrace();
return false;
}
}
/**
* 0. 清空 tr_live.cron 文件内容
*/
public static void clearCronFile(String cronDir, String CRON_FILE_PATH) {
try {
AssertLog.info("步骤0: 清空 {} 文件内容",CRON_FILE_PATH);
// 确保目录存在
new File(cronDir).mkdirs();
// 创建空文件如果不存在或清空现有内容
Files.write(Paths.get(CRON_FILE_PATH), new byte[0]);
AssertLog.info("✅ {} 文件已清空",CRON_FILE_PATH);
}catch (IOException e) {
System.err.println("操作 crontab 失败:" + e.getMessage());
e.printStackTrace();
}
}
/**
* 1. 备份当前定时任务到文件
*/
public static void backupCurrentCrontab(String CRON_FILE_PATH) {
try {
AssertLog.info("步骤1: 备份当前定时任务到 {}",CRON_FILE_PATH);
Process process = Runtime.getRuntime().exec("crontab -l");
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream())
);
// 读取当前 crontab 内容
StringBuilder currentCrontab = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
currentCrontab.append(line).append("\n");
}
reader.close();
int exitCode = process.waitFor();
if (exitCode != 0 && exitCode != 1) { // exitCode 1 表示没有 crontab
throw new IOException("获取当前 crontab 失败,退出码: " + exitCode);
}
// 写入当前定时任务内容
Files.write(Paths.get(CRON_FILE_PATH), currentCrontab.toString().getBytes());
AssertLog.info("✅ 当前定时任务已保存到: {}",CRON_FILE_PATH);
}catch (IOException | InterruptedException e) {
System.err.println("操作 crontab 失败:" + e.getMessage());
e.printStackTrace();
}
}
/**
* 2. 在文件末尾添加新的定时任务
*/
public static void appendNewCronEntry(String CRON_FILE_PATH, String NEW_CRON_ENTRY) {
try {
AssertLog.info("步骤2: 在文件末尾添加新定时任务");
// 检查是否已存在该定时任务
String fileContent = new String(Files.readAllBytes(Paths.get(CRON_FILE_PATH)));
if (fileContent.contains(NEW_CRON_ENTRY)) {
AssertLog.info("⚠️ 定时任务已存在,跳过添加");
return;
}
// 添加新定时任务到文件末尾
Files.write(
Paths.get(CRON_FILE_PATH),
(NEW_CRON_ENTRY + "\n").getBytes(),
StandardOpenOption.APPEND
);
AssertLog.info("✅ 新定时任务已添加到文件末尾");
}catch (IOException e) {
System.err.println("操作 crontab 失败:" + e.getMessage());
e.printStackTrace();
}
}
/**
* 3. 清除当前所有定时任务
*/
public static void clearCurrentCrontab() {
try {
AssertLog.info("步骤3: 清除当前所有定时任务");
// 创建一个空的临时文件
File tempFile = File.createTempFile("empty_cron", ".tmp");
tempFile.deleteOnExit();
// 用空文件替换当前 crontab
Process process = Runtime.getRuntime().exec("crontab " + tempFile.getAbsolutePath());
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new IOException("清除当前 crontab 失败,退出码: " + exitCode);
}
AssertLog.info("✅ 当前所有定时任务已清除");
}catch (IOException | InterruptedException e) {
System.err.println("操作 crontab 失败:" + e.getMessage());
e.printStackTrace();
}
}
/**
* 4. 重新加载定时任务文件
*/
public static void installNewCrontab(String CRON_FILE_PATH) {
try {
AssertLog.info("步骤4: 加载新的定时任务文件");
// 检查文件是否存在
if (!Files.exists(Paths.get(CRON_FILE_PATH))) {
throw new IOException("定时任务文件不存在: " + CRON_FILE_PATH);
}
// 执行 crontab 命令加载新文件
Process process = Runtime.getRuntime().exec("crontab " + CRON_FILE_PATH);
// 读取输出
BufferedReader stdout = new BufferedReader(
new InputStreamReader(process.getInputStream())
);
BufferedReader stderr = new BufferedReader(
new InputStreamReader(process.getErrorStream())
);
String outputLine;
while ((outputLine = stdout.readLine()) != null) {
System.out.println("STDOUT: " + outputLine);
}
String errorLine;
while ((errorLine = stderr.readLine()) != null) {
System.err.println("STDERR: " + errorLine);
}
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new IOException("加载新 crontab 失败,退出码: " + exitCode);
}
AssertLog.info("✅ 新定时任务已成功加载");
}catch (IOException | InterruptedException e) {
System.err.println("操作 crontab 失败:" + e.getMessage());
e.printStackTrace();
}
}
/**
* 验证最终结果
*/
public static void verifyFinalResult(String TARGET_SCRIPT) {
try {
AssertLog.info("\n--- 验证最终定时任务结果 ---");
Process process = Runtime.getRuntime().exec("crontab -l");
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream())
);
boolean found = false;
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
if (line.contains(TARGET_SCRIPT)) {
AssertLog.info("✅ 确认 " + TARGET_SCRIPT + " 已成功添加到定时任务中");
found = true;
}
}
reader.close();
if (!found) {
AssertLog.info("❌ 未在当前定时任务中找到目标脚本");
}
} catch (Exception e) {
AssertLog.info("验证 crontab 时出错: " + e.getMessage());
}
}
}
@@ -0,0 +1,82 @@
package com.tongran.agent.client.utils;
import com.tongran.agent.client.netty.config.ConnectionConfig;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 增强版连接管理器
*/
public class EnhancedConnectionManager {
// private final MultiTargetNettyClient client;
private final Map<String, ConnectionConfig> connectionConfigs;
public EnhancedConnectionManager() {
// this.client = new MultiTargetNettyClient();
this.connectionConfigs = new ConcurrentHashMap<>();
}
/**
* 批量创建连接
*/
public Map<String, Boolean> createConnections(List<ConnectionConfig> configs) {
Map<String, Boolean> results = new HashMap<>();
// configs.forEach(config -> {
// boolean success = client.createConnection(
// config.getConnectionKey(),
// config.getHost(),
// config.getPort(),
// config.getTimeout()
// );
// if (success) {
// connectionConfigs.put(config.getConnectionKey(), config);
// }
// results.put(config.getConnectionKey(), success);
// });
return results;
}
/**
* 根据消息类型路由到不同连接
*/
public void routeMessageByType(String messageType, String message) {
// 这里可以根据业务逻辑决定发送到哪个连接
// switch (messageType) {
// case "TYPE_A":
// client.sendMessage("server1", "[TYPE_A] " + message);
// break;
// case "TYPE_B":
// client.sendMessage("server2", "[TYPE_B] " + message);
// break;
// case "TYPE_C":
// client.sendMessage("server3", "[TYPE_C] " + message);
// break;
// default:
// // 广播到所有连接
// connectionConfigs.keySet().forEach(key ->
// client.sendMessage(key, "[BROADCAST] " + message));
// }
}
/**
* 获取所有连接状态
*/
public Map<String, Boolean> getConnectionStatus() {
Map<String, Boolean> status = new HashMap<>();
// connectionConfigs.forEach((key, config) -> {
// // 这里可以添加更详细的状态检查
// status.put(key, client.sendMessage(key, "PING"));
// });
return status;
}
// public void shutdown() {
// client.shutdown();
// }
}
@@ -0,0 +1,74 @@
package com.tongran.agent.client.utils;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
public class FileCleaner {
/**
* 删除指定目录中 30 天前修改的文件
*
* @param directoryPath 目录路径例如"/tmp/logs"
* @param days 保留天数传入 30 表示删除 30 天以上的文件
*/
public static void cleanOldFiles(String directoryPath, int days) {
Path dir = Paths.get(directoryPath);
// 检查目录是否存在且是目录
if (!Files.exists(dir)) {
System.err.println("目录不存在: " + directoryPath);
return;
}
if (!Files.isDirectory(dir)) {
System.err.println("路径不是目录: " + directoryPath);
return;
}
// 计算 30 天前的时间点Instant
Instant cutoffTime = Instant.now().minus(days, ChronoUnit.DAYS);
List<Path> deletedFiles = new ArrayList<>();
List<Path> failedFiles = new ArrayList<>();
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
for (Path file : stream) {
// 只处理普通文件跳过子目录等
if (Files.isRegularFile(file)) {
try {
BasicFileAttributes attrs = Files.readAttributes(file, BasicFileAttributes.class);
Instant lastModifiedTime = attrs.lastModifiedTime().toInstant();
if (lastModifiedTime.isBefore(cutoffTime)) {
Files.delete(file); // 删除文件
deletedFiles.add(file);
System.out.println("已删除: " + file + " (修改时间: " + lastModifiedTime + ")");
}
} catch (IOException e) {
System.err.println("无法读取或删除文件: " + file + " -> " + e.getMessage());
failedFiles.add(file);
}
}
}
} catch (IOException e) {
System.err.println("遍历目录失败: " + e.getMessage());
}
// 统计结果
System.out.println("✅ 清理完成:");
System.out.println(" 删除文件数: " + deletedFiles.size());
System.out.println(" 删除失败数: " + failedFiles.size());
}
// 使用示例
public static void main(String[] args) {
// String logDir = "/tmp/logs"; // 替换为你的实际目录
// cleanOldFiles(logDir, 30); // 删除 30 天以上的文件
}
}
@@ -0,0 +1,125 @@
package com.tongran.agent.client.utils;
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;
public class MachineFingerprint {
/**
* 获取服务器硬件指纹MD5( MAC + CPU信息 )
*/
public static String getHardwareFingerprint() {
StringBuilder data = new StringBuilder();
// 1. 获取第一个非回环网卡的 MAC 地址
String mac = getFirstValidMacAddress();
if (mac != null) {
data.append(mac);
System.out.println("MAC 信息="+mac);
} else {
System.err.println("无法获取 MAC 地址");
}
// 2. 获取 CPU 信息 CPU 型号序列号
String cpuInfo = getCpuInfo();
if (cpuInfo != null) {
System.out.println("CPU 信息="+cpuInfo);
data.append(cpuInfo);
} else {
System.err.println("无法获取 CPU 信息");
}
data.append(System.currentTimeMillis());
if (data.length() == 0) {
return "unknown";
}
// 3. 生成 MD5
return md5(data.toString());
}
/**
* 获取第一个非回环UP 状态网卡的 MAC 地址
*/
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) {
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) {
e.printStackTrace();
}
return null;
}
/**
* 读取 CPU 信息CPU 型号 + 序列号如果存在
*/
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()));
String line;
boolean found = false;
while ((line = reader.readLine()) != null) {
if (line.contains("model name")) {
cpuInfo.append(line.split(":")[1].trim()).append(";");
found = true;
}
// 树莓派等设备有 cpu serial
if (line.contains("Serial")) {
cpuInfo.append("Serial=").append(line.split(":")[1].trim());
found = true;
}
// 多数 x86 服务器没有 serial可用 processor 数量做补充
if (line.startsWith("processor")) {
// 可选记录核心数
}
}
reader.close();
return found ? cpuInfo.toString() : null;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 生成 MD5 哈希
*/
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);
}
}
// ================== 测试 ==================
public static void main(String[] args) {
String fingerprint = getHardwareFingerprint();
System.out.println("服务器硬件指纹 (MD5): " + fingerprint);
}
}
@@ -0,0 +1,494 @@
package com.tongran.agent.client.utils;
import com.tongran.agent.client.core.vo.NetworkInterfaceInfo;
import org.apache.commons.lang3.StringUtils;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 网络接口工具类通过 ip 命令获取
*/
public class NetworkInterfaceUtil {
public static boolean getmacVlanStatus(String interfaceName) {
try {
ProcessBuilder pb = new ProcessBuilder(
"ping", "-I", interfaceName, "-c", "2", "-W", "2", "-4", "www.baidu.com"
);
pb.redirectErrorStream(true);
Process process = pb.start();
boolean finished = process.waitFor(5, TimeUnit.SECONDS);
if (!finished) {
process.destroy();
AssertLog.warn("接口 {} ping超时", interfaceName);
return false;
}
return process.exitValue() == 0;
} catch (Exception e) {
AssertLog.error("接口 {} ping失败: {}", interfaceName, e.getMessage());
return false;
}
}
/**
* 收集所有 Ethernet 类型网卡信息
*/
public static List<NetworkInterfaceInfo> collectNetworkInfo() throws Exception {
List<NetworkInterfaceInfo> result = new ArrayList<>();
Map<String, NetworkInterfaceInfo> parentMap = new HashMap<>();
List<NetworkInterfaceInfo> childTempList = new ArrayList<>();
// 获取所有网卡详细信息
List<NetworkInterfaceDetails> allInterfaces = getNetworkInterfacesFromIpLink();
AssertLog.info("从 ip link 获取到 {} 个网络接口", allInterfaces.size());
for (NetworkInterfaceDetails interfaceDetail : allInterfaces) {
String name = interfaceDetail.name;
String realName = interfaceDetail.realName;
boolean isUp = "UP".equals(interfaceDetail.state);
// 调试信息
AssertLog.info("开始处理网卡: {}", name);
AssertLog.info(" 状态: {}, 是否虚拟: {}",
isUp ? "UP" : "DOWN", interfaceDetail.isVirtual);
// 跳过回环关闭的接口
if (name.equals("lo") || !isUp) {
AssertLog.info(" 跳过网卡 {}: 回环或未启用", name);
continue;
}
// 判断是否为 Ethernet通过名称约定
if (!isEthernetInterface(interfaceDetail)) {
AssertLog.info(" 跳过网卡 {}: 非以太网接口", name);
continue;
}
// 获取IP地址
String ipv4 = getIPv4AddressFromIpAddr(name);
String ipv6 = getIPv6AddressFromIpAddr(name);
AssertLog.info(" IPv4: {}, IPv6: {}", ipv4, ipv6);
// 检查IP4信息
// if (ipv4.equals("N/A")) {
// AssertLog.info(" 跳过网卡 {}: 无IPv4地址", name);
// continue;
// }
// 检查物理链路状态
boolean isConnected = isInterfaceConnected(name);
if (!isConnected) {
AssertLog.info(" 跳过网卡 {}: 物理链路未连接", name);
continue;
}
// 获取网关
String gateway = AgentUtil.getGateway(name, "www.baidu.com");
if(gateway == null) {
// 降级
gateway = AgentUtil.getGatewayAddress(name);
AssertLog.info("traceroute探测失败,改为ip route方式获取");
}
AssertLog.info("获取到网关为: {}", gateway);
// if ("N/A".equals(gateway)) {
// AssertLog.info(" 跳过网卡 {}: 无法获取网关", name);
// continue;
// }
boolean flag = false;
if(!"N/A".equals(ipv4) && gateway != null){
flag = true;
}
// 获取公网IP
String publicIp = null;
String ipInfo = null;
if (flag) {
List<String> publicIps = PublicIpFetcher.getPublicIps();
if (publicIps != null && !publicIps.isEmpty()) {
AssertLog.info("网卡 {} nslookup获取到 {} 个公网IP: {}", name, publicIps.size(), publicIps);
for (String ip : publicIps) {
if (ip != null && !ip.isEmpty()) {
// 为每个公网IP添加临时路由
boolean routeAdded = AgentUtil.addTempRoute(ip, name, gateway);
}
}
ipInfo = PublicIpFetcher.getPublicIp();
publicIp = PublicIpFetcher.extractIp(ipInfo);
if (publicIp != null) {
AssertLog.info(" 公网 IP: {}", publicIp);
} else {
AssertLog.info(" 未能提取公网 IP 地址");
}
// if (StringUtils.isBlank(publicIp)) {
// AssertLog.info(" 跳过网卡 {}: 无公网IP", name);
// continue;
// }
for (String ip : publicIps) {
if (ip != null && !ip.isEmpty()) {
// 删除临时路由
boolean routeAdded = AgentUtil.delTempRoute(ip, name, gateway);
}
}
}
}
// 创建网卡信息对象
NetworkInterfaceInfo info = NetworkInterfaceInfo.builder()
.name(name)
.type("Ethernet")
.mac(interfaceDetail.macAddress)
.ipv4(ipv4)
.ipv6(ipv6)
.gateway(gateway)
.publicIp(publicIp)
.build();
// 如果有公网 IP查询归属地
if (publicIp != null && !publicIp.isEmpty() && ipInfo != null) {
// 提取归属地信息
PublicIpFetcher.IpLocationInfo location = PublicIpFetcher.extractLocation(ipInfo);
info.setProvince(location.getProvince());
info.setCity(location.getCity());
info.setCarrier(location.getCarrier());
}
// 判断父子关系
if (interfaceDetail.parent != null && !interfaceDetail.parent.isEmpty()) {
// 子接口
AssertLog.info(" 识别为子接口: {}", name);
String parentName = interfaceDetail.parent;
// 递归查找最终的父网卡
parentName = findRootParentInterface(parentName, allInterfaces);
if (!interfaceDetail.parent.equals(parentName)) {
AssertLog.info(" {} 的最终父接口是: {} (原始父接口: {})",
name, parentName, interfaceDetail.parent);
}
info.setParentInterface(parentName);
// 查找父接口
NetworkInterfaceInfo parent = parentMap.get(parentName);
if (parent != null) {
// 设置父子关系
if (parent.getSubInterfaces() == null) {
parent.setSubInterfaces(new ArrayList<>());
}
if(info.getIpv4() != null){
info.setStatus(getmacVlanStatus(info.getName())?"1":"0");
// 获取网卡创建时间戳
long createTime = getInterfaceCreateTime(name);
info.setNetCreateTime(createTime);
}
parent.getSubInterfaces().add(info);
AssertLog.info(" 添加到父接口 {} 的子接口列表", parentName);
} else {
// 父接口还未处理先放到临时列表
childTempList.add(info);
AssertLog.info(" 父接口 {} 还未处理,放入临时列表", parentName);
}
} else {
// 父接口
AssertLog.info(" 识别为父接口: {}", name);
parentMap.put(name, info);
}
}
// 处理父接口加入结果
for (NetworkInterfaceInfo parent : parentMap.values()) {
result.add(parent);
AssertLog.info("父接口 {} 加入结果列表", parent.getName());
}
// 处理子接口父接口未出现的情况
for (NetworkInterfaceInfo child : childTempList) {
String parentName = child.getParentInterface();
NetworkInterfaceInfo parent = parentMap.get(parentName);
if (parent != null) {
// 建立父子关系
if (parent.getSubInterfaces() == null) {
parent.setSubInterfaces(new ArrayList<>());
}
if(child.getIpv4() != null){
child.setStatus(getmacVlanStatus(child.getName())?"1":"0");
// 获取网卡创建时间戳
long createTime = getInterfaceCreateTime(child.getName());
child.setNetCreateTime(createTime);
}
parent.getSubInterfaces().add(child);
AssertLog.info("子接口 {} 关联到父接口 {}", child.getName(), parentName);
} else {
// 确实没有父接口作为独立接口
result.add(child);
AssertLog.info("子接口 {} 无父接口,作为独立接口", child.getName());
}
}
AssertLog.info("共收集到 {} 个有效网卡接口", result.size());
return result;
}
/**
* 递归查找最终的父网卡
*/
private static String findRootParentInterface(String currentParent, List<NetworkInterfaceDetails> allInterfaces) {
// 查找当前父网卡的详细信息
for (NetworkInterfaceDetails iface : allInterfaces) {
if (iface.name.equals(currentParent) && iface.parent != null && !iface.parent.isEmpty()) {
// 如果当前父网卡还有父网卡继续递归查找
return findRootParentInterface(iface.parent, allInterfaces);
}
}
// 返回最终的父网卡名称
return currentParent;
}
/**
* 通过 ip link 命令获取网卡详细信息
*/
private static List<NetworkInterfaceDetails> getNetworkInterfacesFromIpLink() throws Exception {
List<NetworkInterfaceDetails> interfaces = new ArrayList<>();
Process process = Runtime.getRuntime().exec("ip -d link show");
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
NetworkInterfaceDetails currentInterface = null;
Pattern interfacePattern = Pattern.compile("^(\\d+):\\s+(\\S+?)(?:@(\\S+))?\\s*:");
Pattern linkPattern = Pattern.compile("\\s+link/(\\S+)\\s+([0-9a-f:]+)");
Pattern statePattern = Pattern.compile("state\\s+(UP|DOWN|UNKNOWN)");
while ((line = reader.readLine()) != null) {
Matcher interfaceMatcher = interfacePattern.matcher(line);
if (interfaceMatcher.find()) {
// 保存上一个接口
if (currentInterface != null) {
interfaces.add(currentInterface);
}
// 创建新的接口
currentInterface = new NetworkInterfaceDetails();
currentInterface.index = Integer.parseInt(interfaceMatcher.group(1));
currentInterface.name = interfaceMatcher.group(2).trim();
// 提取父接口如果有@符号
if (interfaceMatcher.group(3) != null) {
currentInterface.parent = interfaceMatcher.group(3).trim();
currentInterface.realName = currentInterface.name + "@" + currentInterface.parent;
}
// 检查是否是虚拟接口
String ifaceName = currentInterface.name;
if (ifaceName.contains(".") || ifaceName.startsWith("xdbvl") ||
ifaceName.startsWith("docker") || ifaceName.startsWith("veth") ||
ifaceName.startsWith("br-") || ifaceName.startsWith("virbr")) {
currentInterface.isVirtual = true;
}
}
if (currentInterface != null) {
// 解析状态
Matcher stateMatcher = statePattern.matcher(line);
if (stateMatcher.find()) {
currentInterface.state = stateMatcher.group(1);
}
// 解析MAC地址
Matcher linkMatcher = linkPattern.matcher(line);
if (linkMatcher.find()) {
currentInterface.macAddress = linkMatcher.group(2);
}
}
}
// 添加最后一个接口
if (currentInterface != null) {
interfaces.add(currentInterface);
}
reader.close();
process.waitFor();
// 第一步收集所有实际存在的网卡名称
Set<String> existingInterfaceNames = new HashSet<>();
for (NetworkInterfaceDetails iface : interfaces) {
existingInterfaceNames.add(iface.name);
}
// 第二步检查并清理父网卡设置
for (NetworkInterfaceDetails iface : interfaces) {
if (iface.parent != null) {
// 如果父网卡不存在于实际网卡列表中清空parent字段
if (!existingInterfaceNames.contains(iface.parent)) {
iface.parent = null;
iface.realName = null;
}
}
}
return interfaces;
}
/**
* ip addr 命令获取IPv4地址
*/
private static String getIPv4AddressFromIpAddr(String interfaceName) throws Exception {
String command = String.format("ip -4 addr show %s 2>/dev/null || echo ''", interfaceName);
Process process = Runtime.getRuntime().exec(new String[]{"sh", "-c", command});
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
if (line.contains("inet ")) {
String[] parts = line.trim().split("\\s+");
if (parts.length >= 2) {
String ipWithMask = parts[1];
reader.close();
process.waitFor();
return ipWithMask.split("/")[0];
}
}
}
reader.close();
process.waitFor();
return "N/A";
}
/**
* ip addr 命令获取IPv6地址
*/
private static String getIPv6AddressFromIpAddr(String interfaceName) throws Exception {
String command = String.format("ip -6 addr show %s 2>/dev/null | grep -v scope.link || echo ''", interfaceName);
Process process = Runtime.getRuntime().exec(new String[]{"sh", "-c", command});
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
if (line.contains("inet6 ")) {
String[] parts = line.trim().split("\\s+");
if (parts.length >= 2) {
String ipWithMask = parts[1];
reader.close();
process.waitFor();
return ipWithMask.split("/")[0];
}
}
}
reader.close();
process.waitFor();
return "N/A";
}
/**
* 判断是否为以太网接口
*/
private static boolean isEthernetInterface(NetworkInterfaceDetails interfaceDetail) {
String name = interfaceDetail.name;
// 判断是否为以太网接口
if (name.startsWith("eth") || // Linux 传统
name.startsWith("en") || // systemd 命名 (enp3s0)
name.startsWith("em") // 有些主板网卡
) {
return true;
}
// 如果有父接口也可能是有效的子接口
if (interfaceDetail.parent != null && !interfaceDetail.parent.isEmpty()) {
return true;
}
return false;
}
/**
* 检查物理链路状态
*/
private static boolean isInterfaceConnected(String interfaceName) {
try {
String os = System.getProperty("os.name").toLowerCase();
Process process;
if (os.contains("win")) {
// Windows: 使用 wmic 检查网卡是否启用
process = Runtime.getRuntime().exec(
"wmic nic where \"NetEnabled=true\" get Name");
} else {
// Linux: 检查 operstate
process = Runtime.getRuntime().exec(
"cat /sys/class/net/" + interfaceName + "/operstate");
}
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
if (os.contains("win")) {
if (line.trim().equalsIgnoreCase(interfaceName)) {
return true;
}
} else {
return "up".equals(line.trim());
}
}
reader.close();
return false;
} catch (Exception e) {
System.err.println("无法检查接口状态: " + interfaceName);
return false; // 保守处理
}
}
/**
* 获取网卡创建时间戳单位
* 通过 stat 命令获取 /sys/class/net/<interface>/ 目录的创建时间
* 返回Unix时间戳失败返回-1
*/
public static long getInterfaceCreateTime(String interfaceName) {
try {
ProcessBuilder pb = new ProcessBuilder("stat", "-c", "%Y", "/sys/class/net/" + interfaceName);
Process process = pb.start();
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
String output = reader.readLine();
reader.close();
int exitCode = process.waitFor();
if (exitCode == 0 && output != null && !output.trim().isEmpty()) {
return Long.parseLong(output.trim());
}
return -1;
} catch (Exception e) {
AssertLog.error("获取网卡 {} 创建时间失败: {}", interfaceName, e.getMessage());
return -1;
}
}
/**
* 内部类网卡详情
*/
static class NetworkInterfaceDetails {
int index;
String name;
String realName;
String state = "UNKNOWN";
String macAddress = "";
String parent = null;
boolean isVirtual = false;
}
}
@@ -0,0 +1,54 @@
package com.tongran.agent.client.utils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.stream.Collectors;
public class NetworkUtil {
public static boolean addRoute(String ip, String prefix, String gateway, String dev) {
try {
ProcessBuilder pb = new ProcessBuilder("ip", "route", "add",
ip + "/" + prefix, "via", gateway, "dev", dev);
pb.redirectErrorStream(true);
Process p = pb.start();
String output = readProcessOutput(p);
int exitCode = p.waitFor();
if (exitCode == 0) {
return true;
} else {
// 检查是否是路由已存在的错误
if (output != null && (output.contains("File exists") || output.contains("RTNETLINK answers: File exists"))) {
AssertLog.info("Route already exists");
return true;
}
AssertLog.error("Failed to add route: " + output);
return false;
}
} catch (Exception e) {
AssertLog.error("Exception while adding route: " + e.getMessage());
return false;
}
}
private static String readProcessOutput(Process p) {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
return reader.lines().collect(Collectors.joining("\n"));
} catch (IOException e) {
return "Error reading output: " + e.getMessage();
}
}
public static boolean deleteRoute(String ip, String prefix, String gateway, String dev) {
try {
ProcessBuilder pb = new ProcessBuilder("ip", "route", "del",
ip + "/" + prefix, "via", gateway, "dev", dev);
return pb.start().waitFor() == 0;
} catch (Exception e) {
AssertLog.error("删除路由失败:{}", e.getMessage());
return false;
}
}
}
@@ -0,0 +1,430 @@
package com.tongran.agent.client.utils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.*;
import java.util.concurrent.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class PublicIpFetcher {
// 主备IP查询服务
private static final String PRIMARY_IP_SERVICE = "myip.ipip.net";
private static final String FALLBACK_IP_SERVICE = "cip.cc";
private static final int TIMEOUT_MS = 5_000; // 5秒超时
// 正则表达式匹配
private static final String IPV4_PATTERN = "(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})";
private static final Pattern IP_PATTERN = Pattern.compile(IPV4_PATTERN);
private static final Pattern CIPCC_PATTERN = Pattern.compile(
"IP\\s*:\\s*" + IPV4_PATTERN + "\\s*地址\\s*:\\s*([^\\s]+)\\s+([^\\s]+)\\s+([^\\s]+)\\s*运营商\\s*:\\s*([^\\r\\n]+)"
);
private static final Pattern IPIP_PATTERN = Pattern.compile(
"当前 IP\\s*" + IPV4_PATTERN + "\\s*来自于:([^\\s]+)\\s+([^\\s]+)\\s+([^\\s]+)\\s+([^\\s]+)"
);
/**
* 获取公网 IP 列表带降级策略
* 1. 首先尝试 nslookup myip.ipip.net
* 2. 如果失败尝试 nslookup cip.cc
*/
public static List<String> getPublicIps() {
List<String> ipList = new ArrayList<>();
// 方法1: 尝试nslookup主服务
ipList = parseNslookupResult(PRIMARY_IP_SERVICE);
if (!ipList.isEmpty()) {
AssertLog.info("nslookup {} 解析到IP: {}", PRIMARY_IP_SERVICE, ipList);
return ipList;
}
// 方法2: 尝试nslookup备用服务
ipList = parseNslookupResult(FALLBACK_IP_SERVICE);
if (!ipList.isEmpty()) {
AssertLog.info("nslookup {} 解析到IP: {}", FALLBACK_IP_SERVICE, ipList);
return ipList;
}
return Collections.emptyList();
}
/**
* 执行nslookup并解析结果
*/
private static List<String> parseNslookupResult(String domain) {
List<String> ipList = new ArrayList<>();
try {
// 执行nslookup命令
Process process = new ProcessBuilder("nslookup", domain)
.redirectErrorStream(true)
.start();
// 等待命令执行完成
boolean finished = process.waitFor(TIMEOUT_MS, TimeUnit.MILLISECONDS);
if (!finished) {
process.destroyForcibly();
return ipList;
}
int exitCode = process.exitValue();
if (exitCode != 0) {
return ipList;
}
// 读取并解析输出
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
StringBuilder output = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
// 1. 查找"Address:"
if (line.contains("Address:") && !line.contains("#")) {
Matcher matcher = IP_PATTERN.matcher(line);
while (matcher.find()) {
String ip = matcher.group(1);
// 过滤DNS服务器IP和本地回环地址
if (!isDnsServerIp(ip) && !isLocalIp(ip)) {
ipList.add(ip);
}
}
}
// 2. 查找"Name:"行后的IP地址
else if (line.trim().startsWith("Name:")) {
// 读取下一行通常是Address行
String nextLine = reader.readLine();
if (nextLine != null) {
output.append(nextLine).append("\n");
Matcher matcher = IP_PATTERN.matcher(nextLine);
while (matcher.find()) {
String ip = matcher.group(1);
if (!isDnsServerIp(ip) && !isLocalIp(ip)) {
ipList.add(ip);
}
}
}
}
}
// 3. 如果以上方法都没找到尝试在整个输出中匹配
if (ipList.isEmpty()) {
String allOutput = output.toString();
Matcher matcher = IP_PATTERN.matcher(allOutput);
while (matcher.find()) {
String ip = matcher.group(1);
// 跳过DNS服务器IP和本地地址
if (!isDnsServerIp(ip) && !isLocalIp(ip)) {
// 检查是否是DNS服务器IP通常在输出开头
boolean isDnsLine = false;
String[] lines = allOutput.split("\n");
for (int i = 0; i < Math.min(3, lines.length); i++) {
if (lines[i].contains("Server:") && lines[i].contains(ip)) {
isDnsLine = true;
break;
}
}
if (!isDnsLine) {
ipList.add(ip);
}
}
}
}
}
} catch (IOException e) {
AssertLog.error("执行nslookup命令失败: {}", e.getMessage());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
AssertLog.error("nslookup命令被中断");
} catch (Exception e) {
AssertLog.error("nslookup解析异常: {}", e.getMessage());
}
// 去重
return new ArrayList<>(new LinkedHashSet<>(ipList));
}
/**
* 检查是否为DNS服务器IP
*/
private static boolean isDnsServerIp(String ip) {
// 常见的DNS服务器IP段
return ip.startsWith("8.8.") || // Google DNS
ip.startsWith("114.114.") || // 114DNS
ip.startsWith("119.29.") || // 腾讯DNS
ip.startsWith("223.5.5.") || // 阿里DNS
ip.startsWith("180.76.76.76") || // 百度DNS
ip.startsWith("1.2.4.8") || // CNNIC DNS
ip.startsWith("208.67."); // OpenDNS
}
/**
* 检查是否为本地IP
*/
private static boolean isLocalIp(String ip) {
return ip.startsWith("127.") ||
ip.startsWith("192.168.") ||
ip.startsWith("10.") ||
ip.startsWith("172.16.") ||
ip.startsWith("172.17.") ||
ip.startsWith("172.18.") ||
ip.startsWith("172.19.") ||
ip.startsWith("172.20.") ||
ip.startsWith("172.21.") ||
ip.startsWith("172.22.") ||
ip.startsWith("172.23.") ||
ip.startsWith("172.24.") ||
ip.startsWith("172.25.") ||
ip.startsWith("172.26.") ||
ip.startsWith("172.27.") ||
ip.startsWith("172.28.") ||
ip.startsWith("172.29.") ||
ip.startsWith("172.30.") ||
ip.startsWith("172.31.") ||
ip.equals("0.0.0.0") ||
ip.equals("255.255.255.255");
}
/**
* 获取公网 IP 信息保持原有逻辑用于降级
*/
public static String getPublicIp() {
return getPublicIpInfo();
}
/**
* 原有HTTP查询逻辑
*/
private static String getPublicIpInfo() {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(() -> {
try {
String result = fetchFromUrl("https://" + PRIMARY_IP_SERVICE, "UTF-8");
AssertLog.info("HTTP {}采集IP信息成功", PRIMARY_IP_SERVICE);
return result;
} catch (Exception e) {
AssertLog.error("HTTP主服务请求失败: {}", e.getMessage());
throw e; // 不在这里降级只抛异常
}
});
try {
return future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
AssertLog.error("HTTP请求超时");
future.cancel(true);
return tryHttpFallback(); // 主线程执行降级
} catch (Exception e) {
AssertLog.error("HTTP请求异常: {}", e.getMessage());
} finally {
executor.shutdownNow();
}
return null;
}
/**
* HTTP回退查询
*/
private static String tryHttpFallback() {
try {
Process process = new ProcessBuilder("curl", "-s", "--max-time", "5",
"http://" + FALLBACK_IP_SERVICE)
.redirectErrorStream(true)
.start();
boolean finished = process.waitFor(5_000, TimeUnit.MILLISECONDS);
if (finished && process.exitValue() == 0) {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
StringBuilder result = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
result.append(line).append("\n");
}
AssertLog.info("curl {} 采集IP信息成功", FALLBACK_IP_SERVICE);
return parseCipCcResult(result.toString());
}
}
} catch (Exception e) {
AssertLog.error("HTTP回退失败: {}", e.getMessage());
}
return null;
}
/**
* 从HTTP URL获取IP信息
*/
private static String fetchFromUrl(String urlString, String charset) throws IOException {
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(TIMEOUT_MS);
connection.setReadTimeout(TIMEOUT_MS);
int responseCode = connection.getResponseCode();
if (responseCode != 200) {
throw new IOException("HTTP " + responseCode + " from " + urlString);
}
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(connection.getInputStream(), charset))) {
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
return response.toString().trim();
}
}
/**
* 解析cip.cc的结果
*/
private static String parseCipCcResult(String result) {
if (result == null || result.isEmpty()) {
return null;
}
Matcher matcher = CIPCC_PATTERN.matcher(result);
if (matcher.find()) {
String ip = matcher.group(1);
String country = matcher.group(2);
String province = matcher.group(3);
String city = matcher.group(4);
String carrier = matcher.group(5);
return String.format("当前 IP%s 来自于:%s %s %s %s",
ip, country, province, city, carrier);
}
return result;
}
/**
* 获取本地IP
*/
private static String getLocalIp() {
try {
String localIp = InetAddress.getLocalHost().getHostAddress();
AssertLog.info("当前 IP{} 来自于:本地网络", localIp);
return localIp;
} catch (UnknownHostException e) {
AssertLog.error("当前IP获取失败:{}", e.getMessage());
return null;
}
}
private static String getLocalIpInfo() {
String ip = getLocalIp();
return ip != null ? String.format("当前 IP%s 来自于:本地网络", ip) : null;
}
/**
* 从IP信息中提取IP地址
*/
public static String extractIp(String ipInfo) {
if (ipInfo == null || ipInfo.isEmpty()) return null;
Matcher matcher = IP_PATTERN.matcher(ipInfo);
return matcher.find() ? matcher.group(1) : null;
}
/**
* 从IP信息中提取归属地信息
*/
public static IpLocationInfo extractLocation(String ipInfo) {
IpLocationInfo info = new IpLocationInfo();
if (ipInfo == null || ipInfo.isEmpty()) {
return info;
}
// 尝试匹配ipip.net格式
Matcher ipipMatcher = IPIP_PATTERN.matcher(ipInfo);
if (ipipMatcher.find()) {
info.ip = ipipMatcher.group(1);
info.country = ipipMatcher.group(2);
info.province = ipipMatcher.group(3);
info.city = ipipMatcher.group(4);
info.carrier = ipipMatcher.group(5);
return info;
}
// 尝试匹配cip.cc格式
Matcher cipMatcher = CIPCC_PATTERN.matcher(ipInfo);
if (cipMatcher.find()) {
info.ip = cipMatcher.group(1);
info.country = cipMatcher.group(2);
info.province = cipMatcher.group(3);
info.city = cipMatcher.group(4);
info.carrier = cipMatcher.group(5);
return info;
}
// 如果都不匹配只提取IP
String ip = extractIp(ipInfo);
if (ip != null) {
info.ip = ip;
}
return info;
}
/**
* 完整的归属地信息
*/
public static class IpLocationInfo {
public String ip;
public String country;
public String province;
public String city;
public String carrier;
@Override
public String toString() {
return String.format("IP: %s, 国家: %s, 省份: %s, 城市: %s, 运营商: %s",
ip, country, province, city, carrier);
}
public String getProvince() {
return province != null ? province.replaceAll("[。\\s]", "") : "";
}
public String getCity() {
return city != null ? city.replaceAll("[。\\s]", "") : "";
}
public String getCarrier() {
return carrier != null ? carrier.replaceAll("[。\\s]", "") : "";
}
}
/**
* 测试方法
*/
public static void main(String[] args) {
System.out.println("开始获取公网IP列表...");
// 测试nslookup解析
System.out.println("测试解析myip.ipip.net:");
List<String> ipList1 = parseNslookupResult(PRIMARY_IP_SERVICE);
System.out.println("解析结果: " + ipList1);
System.out.println("\n测试解析cip.cc:");
List<String> ipList2 = parseNslookupResult(FALLBACK_IP_SERVICE);
System.out.println("解析结果: " + ipList2);
// 获取公网IP列表
List<String> allIps = getPublicIps();
System.out.println("\n最终获取到的公网IP列表: " + allIps);
}
}
+15 -8
View File
@@ -1,7 +1,8 @@
server:
port: 7010
port: -1
servlet:
context-path: /tr-agent-client
# 接口文档配置
knife4j:
@@ -13,10 +14,16 @@ logging:
file:
path: /usr/local/tongran/logs
tcp:
netty:
charge:
enable: true
name: AGENT-CLIENT-服务
port: 6610
readerIdleTime: 300
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 # 最大重连延迟(毫秒)
+9 -1
View File
@@ -6,7 +6,15 @@ spring:
matching-strategy: ant_path_matcher
application:
name: tr-agent-client
version: 1.0
version: 1.1.19
conf-path: /usr/local/tongran/conf
script-path: /usr/local/tongran/sbin
tmp-path: /usr/local/tongran/tmp
temp-path: /usr/local/tongran/temp
frp-path: /usr/local/tongran/frp
poe-path: /usr/local/tongran/sbin/pppoe1
temp-traffic-path: /usr/local/tongran/traffictemp
temp-pcap-path: /usr/local/tongran/pcaptemp/
web:
resources:
static-locations: classpath*:/META-INF/resources/
+1 -1
View File
@@ -65,7 +65,7 @@
<!-- 日志输出的样式 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
<pattern>[requestId:%X{requestId}] [time:%d{yyyy-MM-dd HH:mm:ss.SSS}] [thread:%thread] [level:%-5level] [appName:${appName}] msg- %msg%n</pattern>
<pattern>[requestId:%X{requestId}] [time:%d{yyyy-MM-dd HH:mm:ss.SSS,Asia/Shanghai}] [thread:%thread] [level:%-5level] [appName:${appName}] msg- %msg%n</pattern>
</encoder>
</appender>