mtr-agent-client v1.0.0初始化
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.5.6</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>com.tongran.agent</groupId>
|
||||
<artifactId>tr-agent-client</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>tr-agent-client</name>
|
||||
<description>tr-agent-client</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.7.11</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-all</artifactId>
|
||||
<version>4.1.86.Final</version>
|
||||
</dependency>
|
||||
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>io.swagger.core.v3</groupId>-->
|
||||
<!-- <artifactId>swagger-annotations</artifactId>-->
|
||||
<!-- <version>2.2.19</version> <!– 请检查并使用最新版本 –>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.oshi</groupId>
|
||||
<artifactId>oshi-core</artifactId>
|
||||
<version>6.4.4</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.docker-java</groupId>
|
||||
<artifactId>docker-java</artifactId>
|
||||
<version>3.3.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Docker Java API -->
|
||||
<dependency>
|
||||
<groupId>com.github.docker-java</groupId>
|
||||
<artifactId>docker-java-api</artifactId>
|
||||
<version>3.3.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 使用 Apache HttpClient 5 实现 -->
|
||||
<dependency>
|
||||
<groupId>com.github.docker-java</groupId>
|
||||
<artifactId>docker-java-transport-httpclient5</artifactId>
|
||||
<version>3.3.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
<version>2.0.31</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
|
||||
<version>4.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- SNMP4J 核心库 -->
|
||||
<dependency>
|
||||
<groupId>org.snmp4j</groupId>
|
||||
<artifactId>snmp4j</artifactId>
|
||||
<version>2.8.9</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>1.7.36</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
<version>1.7.36</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<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>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,20 @@
|
||||
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) {
|
||||
GlobalConfig.startupTime = System.currentTimeMillis();
|
||||
SpringApplication.run(TrAgentClientApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.tongran.agent.client.core.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "spring.application")
|
||||
public class ApplicationProperties {
|
||||
|
||||
private String name;
|
||||
private String version;
|
||||
private String confPath;
|
||||
private String scriptPath;
|
||||
private String tmpPath;
|
||||
private String tempPath;
|
||||
|
||||
|
||||
// Getter 和 Setter 方法
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public String getConfPath() {
|
||||
return confPath;
|
||||
}
|
||||
|
||||
public void setConfPath(String confPath) {
|
||||
this.confPath = confPath;
|
||||
}
|
||||
|
||||
public String getScriptPath() {
|
||||
return scriptPath;
|
||||
}
|
||||
|
||||
public void setScriptPath(String scriptPath) {
|
||||
this.scriptPath = scriptPath;
|
||||
}
|
||||
|
||||
public String getTmpPath() {
|
||||
return tmpPath;
|
||||
}
|
||||
|
||||
public void setTmpPath(String tmpPath) {
|
||||
this.tmpPath = tmpPath;
|
||||
}
|
||||
|
||||
public String getTempPath() {
|
||||
return tempPath;
|
||||
}
|
||||
|
||||
public void setTempPath(String tempPath) {
|
||||
this.tempPath = tempPath;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
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;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 全局配置类
|
||||
*/
|
||||
|
||||
public class GlobalConfig {
|
||||
|
||||
/**
|
||||
* 服务启动时间
|
||||
*/
|
||||
public static long startupTime;
|
||||
|
||||
/**
|
||||
* 注册标识
|
||||
*/
|
||||
public static boolean isRegister = false;
|
||||
|
||||
|
||||
/**
|
||||
* 客户端ID
|
||||
*/
|
||||
public static String CLIENT_ID;
|
||||
|
||||
|
||||
/**
|
||||
* 设备SN
|
||||
*/
|
||||
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;
|
||||
/**
|
||||
* 采集标识
|
||||
*/
|
||||
public static boolean isCollect = false;
|
||||
public static Map<String, Long> taskIds = new ConcurrentHashMap<>();
|
||||
/**
|
||||
* 采集系统监控
|
||||
*/
|
||||
public static boolean cpuCollect = false; //cpu采集
|
||||
public static long cpuInterval = 300; //cpu采集间隔
|
||||
public static boolean vfsCollect = false; //挂载采集
|
||||
public static long vfsInterval = 300; //挂载采集间隔
|
||||
public static boolean netCollect = false; //网络采集
|
||||
public static long netInterval = 300; //网络采集间隔
|
||||
public static boolean diskCollect = false; //硬盘采集
|
||||
public static long diskInterval = 300; //硬盘采集间隔
|
||||
public static boolean dockerCollect = false; //docker采集
|
||||
public static long dockerInterval = 300; //docker采集间隔
|
||||
/**
|
||||
* 采集系统其他监控
|
||||
*/
|
||||
public static boolean systemSwapSizeFreeCollect = false; //交换卷/文件的可用空间(字节)采集
|
||||
public static long systemSwapSizeFreeInterval = 300; //交换卷/文件的可用空间(字节)采集间隔
|
||||
public static boolean memoryUtilizationCollect = false; //内存利用率采集
|
||||
public static long memoryUtilizationInterval = 300; //内存利用率采集间隔
|
||||
public static boolean systemSwapSizePercentCollect = false; //可用交换空间百分比采集
|
||||
public static long systemSwapSizePercentInterval = 300; //可用交换空间百分比采集间隔
|
||||
public static boolean memorySizeAvailableCollect = false; //可用内存采集
|
||||
public static long memorySizeAvailableInterval = 300; //可用内存采集间隔
|
||||
public static boolean memorySizePercentCollect = false; //可用内存百分比采集
|
||||
public static long memorySizePercentInterval = 300; //可用内存百分比采集间隔
|
||||
public static boolean memorySizeTotalCollect = false; //总内存采集
|
||||
public static long memorySizeTotalInterval = 300; //总内存采集间隔
|
||||
public static boolean systemSwOsCollect = false; //操作系统采集
|
||||
public static long systemSwOsInterval = 300; //操作系统采集间隔
|
||||
public static boolean systemSwArchCollect = false; //操作系统架构采集
|
||||
public static long systemSwArchInterval = 300; //操作系统架构采集间隔
|
||||
public static boolean kernelMaxprocCollect = false; //最大进程数采集
|
||||
public static long kernelMaxprocInterval = 300; //最大进程数采集间隔
|
||||
public static boolean procNumRunCollect = false; //正在运行的进程数采集
|
||||
public static long procNumRunInterval = 300; //正在运行的进程数采集间隔
|
||||
public static boolean systemUsersNumCollect = false; //登录用户数采集
|
||||
public static long systemUsersNumInterval = 300; //登录用户数采集间隔
|
||||
public static boolean systemDiskSizeTotalCollect = false; ///硬盘总可用空间采集
|
||||
public static long systemDiskSizeTotalInterval = 300; //硬盘总可用空间采集间隔
|
||||
public static boolean systemBoottimeCollect = false; //系统启动时间采集
|
||||
public static long systemBoottimeInterval = 300; //系统启动时间采集间隔
|
||||
public static boolean systemUnameCollect = false; //系统描述采集
|
||||
public static long systemUnameInterval = 300; //系统描述采集间隔
|
||||
public static boolean systemLocaltimeCollect = false; //系统本地时间采集
|
||||
public static long systemLocaltimeInterval = 300; //系统本地时间采集间隔
|
||||
public static boolean systemUptimeCollect = false; //系统正常运行时间采集
|
||||
public static long systemUptimeInterval = 300; //系统正常运行时间采集间隔
|
||||
public static boolean procNumCollect = false; //进程数采集
|
||||
public static long procNumInterval = 300; //进程数采集间隔
|
||||
|
||||
/**
|
||||
* 告警
|
||||
*/
|
||||
public static boolean IS_ALARM = false;
|
||||
public static long ALARM_INTERVAL = 60;
|
||||
public static List<AlarmEO> ALARM_LIST = new ArrayList<>(); //告警设置信息
|
||||
|
||||
/**
|
||||
* 所有网络接口
|
||||
*/
|
||||
public static List<NativeNetworkInterfaceEO> NET_LIST = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 脚本文件下载标识
|
||||
*/
|
||||
public static LinkedHashMap<String, Integer> DOWN_FILES = new LinkedHashMap<>();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.tongran.agent.client.core.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 控制码
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public enum MsgEnum {
|
||||
建立连接("CONNECT"),
|
||||
|
||||
建立连接应答("CONNECT_RSP"),
|
||||
|
||||
注册("REGISTER"),
|
||||
|
||||
注册应答("REGISTER_RSP"),
|
||||
|
||||
获取最新策略("GET_POLICY"),
|
||||
|
||||
获取最新策略应答("GET_POLICY_RSP"),
|
||||
|
||||
断开("DISCONNECT"),
|
||||
|
||||
断开应答("DISCONNECT_RSP"),
|
||||
|
||||
心跳上报("HEARTBEAT"),
|
||||
|
||||
CPU上报("CPU"),
|
||||
|
||||
磁盘上报("DISK"),
|
||||
|
||||
容器上报("DOCKER"),
|
||||
|
||||
内存上报("MEMORY"),
|
||||
|
||||
网络上报("NET"),
|
||||
|
||||
挂载上报("POINT"),
|
||||
|
||||
交换机上报("SWITCHBOARD"),
|
||||
|
||||
系统其他上报("OTHER_SYSTEM"),
|
||||
|
||||
告警上报("ALARM"),
|
||||
|
||||
开启或更新系统采集("SYSTEM_COLLECT_START"),
|
||||
|
||||
开启或更新系统采集应答("SYSTEM_COLLECT_START_RSP"),
|
||||
|
||||
关闭所有系统采集("SYSTEM_COLLECT_STOP"),
|
||||
|
||||
关闭所有系统采集应答("SYSTEM_COLLECT_STOP_RSP"),
|
||||
|
||||
告警设置("ALARM_SET"),
|
||||
|
||||
告警设置应答("ALARM_SET_RSP"),
|
||||
|
||||
执行脚本策略("SCRIPT_POLICY"),
|
||||
|
||||
执行脚本策略应答("SCRIPT_POLICY_RSP"),
|
||||
|
||||
Agent版本更新("AGENT_VERSION_UPDATE"),
|
||||
|
||||
Agent版本更新应答("AGENT_VERSION_UPDATE_RSP"),
|
||||
|
||||
多网IP探测上报("NETWORK_DETECT");
|
||||
|
||||
private String value;
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.tongran.agent.client.core.eo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AgentVersionUpdateEO {
|
||||
|
||||
//文件地址,外网HTTP(S)地址
|
||||
private String fileUrl;
|
||||
|
||||
//文件MD5
|
||||
private String fileMd5;
|
||||
|
||||
//执行方式:0、立即执行;1、定时执行;
|
||||
private int method;
|
||||
|
||||
//定时时间,执行方式为1、定时执行时该字段必传
|
||||
private long policyTime;
|
||||
|
||||
//时间戳
|
||||
private long timestamp;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.tongran.agent.client.core.eo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AlarmEO {
|
||||
|
||||
//告警内容类型
|
||||
private String type;
|
||||
|
||||
//是否开启标识:默认false
|
||||
private boolean collect;
|
||||
|
||||
//告警阈值
|
||||
private String threshold;
|
||||
|
||||
//比较类型:0、大于;1、大于且等于;2、小于;3、小于且等于;
|
||||
private int compareType;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.tongran.agent.client.core.eo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CollectEO {
|
||||
private String type;
|
||||
private boolean collect = false;
|
||||
private int interval = 300;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.tongran.agent.client.core.eo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class NativeNetworkInterfaceEO {
|
||||
private String name;
|
||||
private String displayName;
|
||||
private boolean up;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.tongran.agent.client.core.eo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ScriptPolicyEO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -1267013167162440612L;
|
||||
|
||||
private String scriptId;
|
||||
|
||||
private String policyName;
|
||||
|
||||
//文件类型为1、外网HTTP(S)时必传
|
||||
private String fileUrl;
|
||||
|
||||
//脚本参数
|
||||
private String commandParams;
|
||||
|
||||
//执行方式:0、立即执行;1、定时执行;
|
||||
private int method;
|
||||
|
||||
//执行方式为1、定时执行时必传-指定时间
|
||||
private long policyTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.tongran.agent.client.core.session;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* SESSION
|
||||
*
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@Accessors(chain = true)
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class Session implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* sessionId
|
||||
*/
|
||||
private String sessionId;
|
||||
|
||||
/**
|
||||
* 客户端唯一标识
|
||||
*/
|
||||
private String clientId;
|
||||
|
||||
/**
|
||||
* Seesion额外参数
|
||||
*/
|
||||
private ConcurrentHashMap<String,Object> extraMap;
|
||||
|
||||
/**
|
||||
* 是否鉴权
|
||||
*/
|
||||
@Builder.Default
|
||||
private boolean isAuthenticated = false;
|
||||
|
||||
@Builder.Default
|
||||
private long createTime = System.currentTimeMillis();
|
||||
|
||||
@Builder.Default
|
||||
private long updateTime = System.currentTimeMillis();
|
||||
|
||||
/**
|
||||
* 消息渠道
|
||||
*/
|
||||
private ChannelHandlerContext channel;
|
||||
|
||||
@Builder.Default
|
||||
private AtomicInteger serialNo = new AtomicInteger(0);
|
||||
|
||||
public static String buildSessionId(ChannelHandlerContext channel) {
|
||||
return channel.channel().id().asLongText();
|
||||
}
|
||||
|
||||
public static Session buildSession(ChannelHandlerContext channel, String clientId) {
|
||||
return Session.builder().channel(channel).sessionId(buildSessionId(channel)).clientId(clientId).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 自生成流水号
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int nextSerialNo() {
|
||||
int current;
|
||||
int next;
|
||||
do {
|
||||
current = serialNo.get();
|
||||
next = current > 0xffff ? 0 : current;
|
||||
} while (!serialNo.compareAndSet(current, next + 1));
|
||||
return next;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package com.tongran.agent.client.core.session;
|
||||
|
||||
|
||||
import cn.hutool.cache.Cache;
|
||||
import cn.hutool.cache.CacheUtil;
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.tongran.agent.client.exception.ServerException;
|
||||
import com.tongran.agent.client.utils.AssertLog;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.util.Attribute;
|
||||
import io.netty.util.AttributeKey;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Data
|
||||
public class SessionManager {
|
||||
|
||||
private static volatile SessionManager instance = null;
|
||||
|
||||
// clientId-Session
|
||||
private final Map<String, Session> sessionMap = new ConcurrentHashMap<>();
|
||||
|
||||
private final Cache<String, Object> sessionCache = CacheUtil.newTimedCache(10 * 60 * 1000);
|
||||
|
||||
public static SessionManager getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (SessionManager.class) {
|
||||
if (instance == null) {
|
||||
instance = new SessionManager();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public synchronized void put(String clientId, Session session) {
|
||||
if (StringUtils.isNotBlank(session.getClientId())) {
|
||||
sessionMap.put(session.getClientId(), session);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void remove(String clientId) {
|
||||
if (StringUtils.isNotBlank(clientId)) {
|
||||
sessionMap.remove(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void remove(Session session) {
|
||||
if (session != null && StringUtils.isNotBlank(session.getClientId())) {
|
||||
sessionMap.remove(session.getClientId(), session);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void remove(Channel channel) {
|
||||
remove(getSessionByChannel(channel));
|
||||
}
|
||||
|
||||
public Session getSessionById(String clientId) {
|
||||
return sessionMap.get(clientId);
|
||||
}
|
||||
|
||||
public Session getSessionByChannel(Channel channel) {
|
||||
Session session = new Session();
|
||||
sessionMap.values().forEach(s -> {
|
||||
if (s.getChannel().channel() == channel) {
|
||||
BeanUtil.copyProperties(s, session, true);
|
||||
}
|
||||
});
|
||||
return session;
|
||||
}
|
||||
|
||||
public boolean containsSession(String clientId) {
|
||||
return sessionMap.containsKey(clientId);
|
||||
}
|
||||
|
||||
public boolean containsSession(Session session) {
|
||||
return sessionMap.containsValue(session);
|
||||
}
|
||||
|
||||
public void setSessionCache(String clientId, Object value) {
|
||||
sessionCache.put(clientId, value);
|
||||
}
|
||||
|
||||
public Object getSessionCache(String clientId) {
|
||||
return sessionCache.get(clientId);
|
||||
}
|
||||
|
||||
public String client(ChannelHandlerContext ctx) {
|
||||
Channel channel = ctx.channel();
|
||||
Session session = this.getSessionByChannel(channel);
|
||||
if (ObjectUtil.isNotNull(session) && StringUtils.isNotBlank(session.getClientId())) {
|
||||
return channel.remoteAddress().toString() + "/" + session.getClientId();
|
||||
}
|
||||
return channel.remoteAddress().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据channel生成流水号
|
||||
*
|
||||
* @param channel
|
||||
* @return
|
||||
*/
|
||||
public short getSerialNumber(Channel channel, AttributeKey<Short> serialNumber) {
|
||||
Attribute<Short> flowIdAttr = channel.attr(serialNumber);
|
||||
Short flowId = flowIdAttr.get();
|
||||
if (flowId == null) {
|
||||
flowId = 0;
|
||||
} else {
|
||||
flowId++;
|
||||
}
|
||||
flowIdAttr.set(flowId);
|
||||
return flowId;
|
||||
}
|
||||
|
||||
public void writeAndFlush(String clientId, Object msg) {
|
||||
Session session = this.getSessionById(clientId);
|
||||
if (ObjectUtil.isNotNull(session)) {
|
||||
this.writeAndFlush(session.getChannel(), msg);
|
||||
} else {
|
||||
throw new ServerException(500, "终端:" + clientId + "离线或者不存在");
|
||||
}
|
||||
}
|
||||
|
||||
public void writeAndFlush(ChannelHandlerContext ctx, Object msg) {
|
||||
ctx.writeAndFlush(msg).addListener(future -> {
|
||||
if (!future.isSuccess()) {
|
||||
AssertLog.error("消息发送失败:{}", future.cause());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
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;
|
||||
|
||||
@Data
|
||||
@SuperBuilder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(description = "告警信息")
|
||||
public class AlarmVO {
|
||||
@Schema(description = "告警内容类型")
|
||||
private String type;
|
||||
|
||||
private String result;
|
||||
|
||||
@Schema(description = "时间戳")
|
||||
private long timestamp;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
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 = "CPU信息")
|
||||
public class CpuVO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "CPU1分钟负载")
|
||||
private double avg1;
|
||||
|
||||
@Schema(description = "CPU5分钟负载")
|
||||
private double avg5;
|
||||
|
||||
@Schema(description = "CPU15分钟负载")
|
||||
private double avg15;
|
||||
|
||||
@Schema(description = "CPU硬件中断提供服务时间:秒")
|
||||
private double interrupt;
|
||||
|
||||
@Schema(description = "CPU使用率%")
|
||||
private double uti;
|
||||
|
||||
@Schema(description = "CPU数量")
|
||||
private int num;
|
||||
|
||||
@Schema(description = "CPU正常运行时间:秒")
|
||||
private long normal;
|
||||
|
||||
@Schema(description = "CPU空闲时间:秒")
|
||||
private double idle;
|
||||
|
||||
@Schema(description = "CPU等待响应时间:秒")
|
||||
private double iowait;
|
||||
|
||||
@Schema(description = "CPU系统时间:秒")
|
||||
private double system;
|
||||
|
||||
@Schema(description = "CPU软件无响应时间")
|
||||
private double noresp;
|
||||
|
||||
@Schema(description = "CPU用户进程所花费的时间:秒")
|
||||
private double user;
|
||||
|
||||
@Schema(description = "时间戳")
|
||||
private long timestamp;
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
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 DiskVO implements Serializable {
|
||||
private static final long serialVersionUID = 5L;
|
||||
|
||||
@Schema(description = "磁盘名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "序列号")
|
||||
private String serial;
|
||||
|
||||
@Schema(description = "磁盘大小")
|
||||
private long total;
|
||||
|
||||
@Schema(description = "磁盘写入速率")
|
||||
private long writeSpeed;
|
||||
|
||||
@Schema(description = "磁盘读取速率")
|
||||
private long readSpeed;
|
||||
|
||||
@Schema(description = "磁盘写入次数")
|
||||
private long writeTimes;
|
||||
|
||||
@Schema(description = "磁盘读取次数")
|
||||
private long readTimes;
|
||||
|
||||
@Schema(description = "磁盘写入字节")
|
||||
private long writeBytes;
|
||||
|
||||
@Schema(description = "磁盘读取字节")
|
||||
private long readBytes;
|
||||
|
||||
@Schema(description = "时间戳")
|
||||
private long timestamp;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
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 DockerVO implements Serializable {
|
||||
private static final long serialVersionUID = 4L;
|
||||
|
||||
@Schema(description = "容器ID")
|
||||
private String id;
|
||||
|
||||
@Schema(description = "容器名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "容器状态")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "容器CPU使用率")
|
||||
private String cpuUtil;
|
||||
|
||||
@Schema(description = "容器内存使用率")
|
||||
private String memUtil;
|
||||
|
||||
@Schema(description = "容器网络接收速率")
|
||||
private String netInSpeed;
|
||||
|
||||
@Schema(description = "容器网络发送速率")
|
||||
private String netOutSpeed;
|
||||
|
||||
@Schema(description = "时间戳")
|
||||
private long timestamp;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
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 MemoryVO implements Serializable {
|
||||
private static final long serialVersionUID = 2L;
|
||||
|
||||
@Schema(description = "交换卷/文件的可用空间(字节)")
|
||||
private long swapSizeFree;
|
||||
|
||||
@Schema(description = "内存利用率")
|
||||
private double untilzation;
|
||||
|
||||
@Schema(description = "可用交换空间百分比")
|
||||
private double swapSizePercent;
|
||||
|
||||
@Schema(description = "可用内存")
|
||||
private long available;
|
||||
|
||||
@Schema(description = "可用内存百分比")
|
||||
private double percent;
|
||||
|
||||
@Schema(description = "总内存")
|
||||
private long total;
|
||||
|
||||
@Schema(description = "时间戳")
|
||||
private long timestamp;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
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 NetVO implements Serializable {
|
||||
private static final long serialVersionUID = 6L;
|
||||
|
||||
@Schema(description = "网卡名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "MAC")
|
||||
private String mac;
|
||||
|
||||
@Schema(description = "运行状态")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "接口类型")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "IPv4")
|
||||
private String ipV4;
|
||||
|
||||
@Schema(description = "入站丢包")
|
||||
private long inDropped;
|
||||
|
||||
@Schema(description = "出站丢包")
|
||||
private long outDropped;
|
||||
|
||||
@Schema(description = "发送流量(发送总字节)")
|
||||
private long outSpeed;
|
||||
|
||||
@Schema(description = "接收流量(接收总字节)")
|
||||
private long inSpeed;
|
||||
|
||||
@Schema(description = "协商速度")
|
||||
private String speed;
|
||||
|
||||
@Schema(description = "工作模式")
|
||||
private String duplex;
|
||||
|
||||
@Schema(description = "时间戳")
|
||||
private long timestamp;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
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;
|
||||
|
||||
@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; // 市
|
||||
|
||||
}
|
||||
@@ -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.io.Serializable;
|
||||
|
||||
@Data
|
||||
@SuperBuilder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(description = "挂载点信息")
|
||||
public class PointVO implements Serializable {
|
||||
private static final long serialVersionUID = 7L;
|
||||
|
||||
@Schema(description = "挂载点")
|
||||
private String mount;
|
||||
|
||||
@Schema(description = "文件系统类型")
|
||||
private String vfsType;
|
||||
|
||||
@Schema(description = "可用空间")
|
||||
private long vfsFree;
|
||||
|
||||
@Schema(description = "总空间")
|
||||
private long vfsTotal;
|
||||
|
||||
@Schema(description = "空间利用率")
|
||||
private double vfsUtil;
|
||||
|
||||
@Schema(description = "时间戳")
|
||||
private long timestamp;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
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 SwitchBoardVO implements Serializable {
|
||||
private static final long serialVersionUID = 8L;
|
||||
|
||||
@Schema(description = "网口名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "交换机IP")
|
||||
private String switchIp;
|
||||
|
||||
@Schema(description = "网口类型")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "网口状态")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "接收流量")
|
||||
private long inBytes;
|
||||
|
||||
@Schema(description = "发送流量")
|
||||
private long outBytes;
|
||||
|
||||
@Schema(description = "时间戳")
|
||||
private long timestamp;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
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 SystemVO implements Serializable {
|
||||
private static final long serialVersionUID = 3L;
|
||||
|
||||
@Schema(description = "操作系统")
|
||||
private String os;
|
||||
|
||||
@Schema(description = "操作系统架构")
|
||||
private String arch;
|
||||
|
||||
@Schema(description = "最大进程数")
|
||||
private long maxProc;
|
||||
|
||||
@Schema(description = "正在运行的进程数")
|
||||
private int runProcNum;
|
||||
|
||||
@Schema(description = "登录用户数")
|
||||
private int usersNum;
|
||||
|
||||
@Schema(description = "硬盘:总可用空间(字节)")
|
||||
private long diskSizeTotal;
|
||||
|
||||
@Schema(description = "系统启动时间")
|
||||
private long bootTime;
|
||||
|
||||
@Schema(description = "系统描述")
|
||||
private String uname;
|
||||
|
||||
@Schema(description = "系统本地时间")
|
||||
private String localTime;
|
||||
|
||||
@Schema(description = "系统正常运行时间")
|
||||
private long upTime;
|
||||
|
||||
private String uuid;
|
||||
|
||||
@Schema(description = "时间戳")
|
||||
private long timestamp;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.tongran.agent.client.exception;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.tongran.agent.client.exception.base.BaseException;
|
||||
import com.tongran.agent.client.exception.code.ErrorCode;
|
||||
|
||||
public class ServerException extends BaseException {
|
||||
|
||||
private static final long serialVersionUID = -519977195881081739L;
|
||||
|
||||
public ServerException(ErrorCode code) {
|
||||
super(code.getCode(), code.getMsg());
|
||||
}
|
||||
|
||||
public ServerException(Integer code, String msg) {
|
||||
super(code, msg);
|
||||
}
|
||||
|
||||
public ServerException(String msg) {
|
||||
super(500, msg);
|
||||
}
|
||||
|
||||
public ServerException(String msg, Object... arguments) {
|
||||
super(500, StrUtil.format(msg, arguments));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.tongran.agent.client.exception.base;
|
||||
|
||||
|
||||
import com.tongran.agent.client.exception.code.ErrorCode;
|
||||
|
||||
public class BaseException extends RuntimeException implements ErrorCode {
|
||||
|
||||
private static final long serialVersionUID = 1966249840643379123L;
|
||||
|
||||
private Integer code;
|
||||
|
||||
private String msg;
|
||||
|
||||
public BaseException() {
|
||||
}
|
||||
|
||||
public BaseException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
|
||||
public BaseException(Integer code, String msg) {
|
||||
super(msg);
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
public BaseException(Integer code, String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMsg() {
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.tongran.agent.client.exception.code;
|
||||
|
||||
|
||||
import com.tongran.agent.client.utils.R;
|
||||
|
||||
public interface ErrorCode {
|
||||
|
||||
Integer getCode();
|
||||
|
||||
String getMsg();
|
||||
|
||||
default R<?> toResult() {
|
||||
return R.error(getMsg(), getCode());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.tongran.agent.client.exception.code;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum GlobalErrorCode implements ErrorCode {
|
||||
|
||||
SUCCESS(200, "成功"),
|
||||
|
||||
NOT_FOUND(404, "未找到相关资源"),
|
||||
|
||||
ERROR(500, "系统错误");
|
||||
|
||||
private final Integer code;
|
||||
|
||||
private final String msg;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package com.tongran.agent.client.netty;
|
||||
|
||||
import com.tongran.agent.client.netty.config.BaseNettyConfig;
|
||||
import com.tongran.agent.client.utils.AssertLog;
|
||||
import io.netty.bootstrap.AbstractBootstrap;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioChannelOption;
|
||||
import io.netty.channel.socket.nio.NioDatagramChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.util.ResourceLeakDetector;
|
||||
import io.netty.util.concurrent.DefaultEventExecutorGroup;
|
||||
import io.netty.util.concurrent.DefaultThreadFactory;
|
||||
import io.netty.util.concurrent.EventExecutorGroup;
|
||||
import io.netty.util.concurrent.Future;
|
||||
|
||||
/**
|
||||
* 基础Netty服务
|
||||
*/
|
||||
public abstract class BaseNettyServer {
|
||||
|
||||
protected boolean isRunning;
|
||||
|
||||
protected BaseNettyConfig config;
|
||||
|
||||
protected EventLoopGroup bossGroup;
|
||||
|
||||
protected EventLoopGroup workerGroup;
|
||||
|
||||
protected EventExecutorGroup businessGroup;
|
||||
|
||||
protected BaseNettyServer(BaseNettyConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
protected AbstractBootstrap<?, ?> initializeTcp() {
|
||||
bossGroup = new NioEventLoopGroup(1, new DefaultThreadFactory(config.name, Thread.MAX_PRIORITY));
|
||||
workerGroup = new NioEventLoopGroup(config.workerCore, new DefaultThreadFactory(config.name, Thread.MAX_PRIORITY));
|
||||
if (config.businessCore > 0) {
|
||||
businessGroup = new DefaultEventExecutorGroup(config.businessCore);
|
||||
}
|
||||
ServerBootstrap serverBootstrap = new ServerBootstrap();
|
||||
serverBootstrap.group(bossGroup, workerGroup)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.childOption(ChannelOption.SO_REUSEADDR, true)
|
||||
.option(ChannelOption.SO_BACKLOG, 1024)
|
||||
.childOption(NioChannelOption.TCP_NODELAY, true)
|
||||
.childHandler(config.hander);
|
||||
//内存泄漏检测 开发推荐PARANOID 线上SIMPLE
|
||||
ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.SIMPLE);
|
||||
return serverBootstrap;
|
||||
}
|
||||
|
||||
protected AbstractBootstrap<?, ?> initializeUdp() {
|
||||
bossGroup = new NioEventLoopGroup(1, new DefaultThreadFactory(config.name, Thread.MAX_PRIORITY));
|
||||
if (config.businessCore > 0) {
|
||||
businessGroup = new DefaultEventExecutorGroup(config.businessCore);
|
||||
}
|
||||
return new Bootstrap()
|
||||
.group(bossGroup).channel(NioDatagramChannel.class)
|
||||
.option(NioChannelOption.SO_REUSEADDR, true)
|
||||
.option(NioChannelOption.SO_RCVBUF, 1024 * 1024 * 50)
|
||||
.handler(config.hander);
|
||||
}
|
||||
|
||||
public synchronized boolean start() {
|
||||
if (!config.enable) {
|
||||
return false;
|
||||
}
|
||||
if (isRunning) {
|
||||
AssertLog.info("======{}已经启动,port:{}======", config.name, config.port);
|
||||
return isRunning;
|
||||
}
|
||||
AbstractBootstrap<?, ?> bootstrap = config.isTcp ? initializeTcp() : initializeUdp();
|
||||
ChannelFuture future = bootstrap.bind(config.port).awaitUninterruptibly();
|
||||
future.channel().closeFuture().addListener(f -> {
|
||||
if (isRunning) {
|
||||
stop();
|
||||
}
|
||||
});
|
||||
if (future.cause() != null) {
|
||||
AssertLog.error("===启动失败===", future.cause());
|
||||
}
|
||||
if (isRunning = future.isSuccess()) {
|
||||
AssertLog.info("\n\n\t\t\t\t\t\t\t\t======{}启动成功,port:{}======\n", config.name, config.port);
|
||||
}
|
||||
return isRunning;
|
||||
}
|
||||
|
||||
public synchronized void stop() {
|
||||
if (!config.enable) {
|
||||
return;
|
||||
}
|
||||
isRunning = false;
|
||||
try {
|
||||
Future<?> future = this.workerGroup.shutdownGracefully().await();
|
||||
if (!future.isSuccess()) {
|
||||
AssertLog.error("workerGroup 无法正常停止:{}", future.cause());
|
||||
}
|
||||
future = this.bossGroup.shutdownGracefully().await();
|
||||
if (!future.isSuccess()) {
|
||||
AssertLog.error("bossGroup 无法正常停止:{}", future.cause());
|
||||
}
|
||||
future = this.businessGroup.shutdownGracefully().await();
|
||||
if (!future.isSuccess()) {
|
||||
AssertLog.error("businessGroup 无法正常停止:{}", future.cause());
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
AssertLog.info("\n\n\t\t\t\t\t\t\t\t======{} 已经停止,port:{}======\n", config.name, config.port);
|
||||
}
|
||||
}
|
||||
@@ -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,47 @@
|
||||
package com.tongran.agent.client.netty.annotation;
|
||||
|
||||
import com.tongran.agent.client.core.enums.MsgEnum;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 指明类为Agent消息
|
||||
*/
|
||||
@Component
|
||||
@Documented
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface AgentDispatcher {
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
enum VersionEnum {
|
||||
V1("2026");
|
||||
public final String value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息ID
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
MsgEnum msgId();
|
||||
|
||||
/**
|
||||
* 消息版本 默认版本2025
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
VersionEnum version() default VersionEnum.V1;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String desc() default "";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.tongran.agent.client.netty.basics;
|
||||
|
||||
import com.tongran.agent.client.netty.annotation.AgentDispatcher;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Component
|
||||
@Order(1)
|
||||
public class AgentDispatcherManager implements ApplicationContextAware {
|
||||
|
||||
/**
|
||||
* 所有实现的包处理器
|
||||
*/
|
||||
private static Map<String, AgentHandler> MSG_HANDLER_MAP;
|
||||
|
||||
/**
|
||||
* 唤醒时 初始化 packHandlerMap
|
||||
*/
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
// 仅一次性初始化完成
|
||||
if (MSG_HANDLER_MAP == null) {
|
||||
MSG_HANDLER_MAP = new ConcurrentHashMap<>();
|
||||
Map<String, Object> handlers = applicationContext.getBeansWithAnnotation(AgentDispatcher.class);
|
||||
if (!CollectionUtils.isEmpty(handlers)) {
|
||||
handlers.values().forEach(tempHandler -> {
|
||||
boolean result = tempHandler.getClass().isAnnotationPresent(AgentDispatcher.class);
|
||||
if (result) {
|
||||
AgentDispatcher annotation = tempHandler.getClass().getAnnotation(AgentDispatcher.class);
|
||||
MSG_HANDLER_MAP.put(annotation.msgId().getValue() + "&" + annotation.version().value, (AgentHandler) tempHandler);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AgentHandler getHandler(String msgIdVersion) {
|
||||
return MSG_HANDLER_MAP == null ? null : MSG_HANDLER_MAP.get(msgIdVersion);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.tongran.agent.client.netty.basics;
|
||||
|
||||
|
||||
import com.tongran.agent.client.netty.model.UpMsgResponse;
|
||||
|
||||
public interface AgentHandler {
|
||||
|
||||
/**
|
||||
* 处理终端传入的消息, 然后进行返回
|
||||
*
|
||||
* @return 需要发送给终端的消息
|
||||
*/
|
||||
|
||||
default UpMsgResponse upHandle(String data, String clientId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
default String downHandle(String data) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -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,58 @@
|
||||
package com.tongran.agent.client.netty.config;
|
||||
|
||||
import com.tongran.agent.client.core.session.SessionManager;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.util.NettyRuntime;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 基础Netty配置
|
||||
*/
|
||||
@Data
|
||||
public class BaseNettyConfig implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -2736718499972710895L;
|
||||
|
||||
/**
|
||||
* 是否开启
|
||||
*/
|
||||
public Boolean enable = false;
|
||||
|
||||
/**
|
||||
* 是否TCP
|
||||
*/
|
||||
public Boolean isTcp = true;
|
||||
|
||||
/**
|
||||
* 服务名称
|
||||
*/
|
||||
public String name = "Netty";
|
||||
|
||||
/**
|
||||
* 端口号
|
||||
*/
|
||||
public Integer port = 11111;
|
||||
|
||||
public Integer workerCore = NettyRuntime.availableProcessors() * 2;
|
||||
|
||||
public Integer businessCore = Math.max(1, NettyRuntime.availableProcessors() >> 1);
|
||||
|
||||
public Integer readerIdleTime = 240;
|
||||
|
||||
public Integer writerIdleTime = 0;
|
||||
|
||||
public Integer allIdleTime = 0;
|
||||
|
||||
/**
|
||||
* netty Session管理
|
||||
*/
|
||||
public SessionManager sessionManager;
|
||||
|
||||
/**
|
||||
* netty Channel处理列表
|
||||
*/
|
||||
public ChannelHandler hander;
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
package com.tongran.agent.client.netty.enpoint;
|
||||
|
||||
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;
|
||||
import com.tongran.agent.client.core.eo.AlarmEO;
|
||||
import com.tongran.agent.client.core.eo.ScriptPolicyEO;
|
||||
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.service.AgentService;
|
||||
import com.tongran.agent.client.utils.AgentDataUtil;
|
||||
import com.tongran.agent.client.utils.AgentUtil;
|
||||
import com.tongran.agent.client.utils.AssertLog;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class AgentEndpoint {
|
||||
|
||||
@Resource
|
||||
private AgentService agentService;
|
||||
|
||||
@Resource
|
||||
private ApplicationProperties properties;
|
||||
|
||||
@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 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(AdvancedAsyncDownloader.createSingleDirectoryIfNotExists(properties.getConfPath())){
|
||||
String[] lines = {
|
||||
"register=1"
|
||||
};
|
||||
AgentUtil.bufferedWriter(properties.getConfPath()+"/register.conf",lines);
|
||||
}
|
||||
GlobalConfig.isRegister = true;
|
||||
//取消注册定时任务
|
||||
agentService.cancelTask("register");
|
||||
agentService.start();
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@AgentDispatcher(msgId = MsgEnum.获取最新策略应答)
|
||||
public class GetPolicyRspHandler implements AgentHandler {
|
||||
@Override
|
||||
public UpMsgResponse upHandle(String data, String clientId) {
|
||||
agentService.getPolicy(data);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@AgentDispatcher(msgId = MsgEnum.告警设置)
|
||||
public class AlarmSetHandler implements AgentHandler {
|
||||
@Override
|
||||
public UpMsgResponse upHandle(String data, String clientId) {
|
||||
//更改全局变量
|
||||
JSONObject jsonObject = JSONObject.parseObject(data);
|
||||
if(jsonObject.containsKey("alarms")){
|
||||
String alarms = jsonObject.getString("alarms");
|
||||
if(StringUtils.isNotBlank(alarms)){
|
||||
AssertLog.info("告警设置,alarms={}", alarms);
|
||||
GlobalConfig.ALARM_LIST = JSON.parseObject(alarms, new TypeReference<List<AlarmEO>>() {});
|
||||
AssertLog.info("告警设置,监控项={}", JSON.toJSONString(GlobalConfig.ALARM_LIST));
|
||||
if(CollectionUtil.isNotEmpty(GlobalConfig.ALARM_LIST)){
|
||||
AssertLog.info("告警设置,is_alarm={}", AgentDataUtil.hasAnyActiveAlarm(GlobalConfig.ALARM_LIST));
|
||||
GlobalConfig.IS_ALARM = AgentDataUtil.hasAnyActiveAlarm(GlobalConfig.ALARM_LIST);
|
||||
}
|
||||
}
|
||||
}
|
||||
agentService.alarmMonitor();
|
||||
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 ScriptPolicyHandler implements AgentHandler {
|
||||
@Override
|
||||
public UpMsgResponse upHandle(String data, String clientId) {
|
||||
JSONObject jsonObject = JSONObject.parseObject(data);
|
||||
ScriptPolicyEO policy = ScriptPolicyEO.builder().build();
|
||||
if(jsonObject.containsKey("policyName")) {
|
||||
policy.setPolicyName(jsonObject.getString("policyName"));
|
||||
}
|
||||
if(jsonObject.containsKey("fileUrl")) {
|
||||
policy.setFileUrl(jsonObject.getString("fileUrl"));
|
||||
}
|
||||
if(jsonObject.containsKey("commandParams")) {
|
||||
policy.setCommandParams(jsonObject.getString("commandParams"));
|
||||
}
|
||||
if(jsonObject.containsKey("method")) {
|
||||
policy.setMethod(jsonObject.getInteger("method"));
|
||||
}
|
||||
if(jsonObject.containsKey("policyTime")) {
|
||||
policy.setPolicyTime(jsonObject.getLong("policyTime"));
|
||||
}
|
||||
boolean isFalse = false;
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
//判断是否需要保存文件
|
||||
if(!isFalse){
|
||||
agentService.command(policy, clientId,MsgEnum.执行脚本策略应答.getValue());
|
||||
return null;
|
||||
}else{
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("resCode",1);
|
||||
json.put("resMsg", "执行脚本策略文件保存中");
|
||||
return UpMsgResponse.builder().clientId(clientId).dataType(MsgEnum.执行脚本策略应答.getValue()).content(json.toString()).build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AgentDispatcher(msgId = MsgEnum.Agent版本更新)
|
||||
public class VersionUpdateHandler implements AgentHandler {
|
||||
@Override
|
||||
public UpMsgResponse upHandle(String data, String clientId) {
|
||||
AgentVersionUpdateEO versionUpdateEO = JSON.parseObject(data, AgentVersionUpdateEO.class);
|
||||
ScriptPolicyEO policy = ScriptPolicyEO.builder()
|
||||
.method(versionUpdateEO.getMethod())
|
||||
.commandParams("systemctl restart tragent")
|
||||
.policyTime(versionUpdateEO.getPolicyTime())
|
||||
.build();
|
||||
boolean isFalse = false;
|
||||
if(StringUtils.isNotBlank(versionUpdateEO.getFileUrl())){
|
||||
isFalse = true;
|
||||
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 {
|
||||
//验证文件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){
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package com.tongran.agent.client.netty.handler;
|
||||
|
||||
import cn.hutool.cache.Cache;
|
||||
import cn.hutool.cache.CacheUtil;
|
||||
import cn.hutool.core.date.DateUnit;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.tongran.agent.client.core.session.SessionManager;
|
||||
import com.tongran.agent.client.netty.annotation.AgentDispatcher;
|
||||
import com.tongran.agent.client.netty.basics.AgentDispatcherManager;
|
||||
import com.tongran.agent.client.netty.basics.AgentHandler;
|
||||
import com.tongran.agent.client.netty.model.Message;
|
||||
import com.tongran.agent.client.netty.model.UpMsgResponse;
|
||||
import com.tongran.agent.client.utils.AssertLog;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.util.CharsetUtil;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Objects;
|
||||
|
||||
@Component
|
||||
@ChannelHandler.Sharable
|
||||
public class AgentDecoderHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
protected final SessionManager sessionManager;
|
||||
|
||||
@Resource
|
||||
private AgentDispatcherManager agentDispatcherManager;
|
||||
|
||||
public AgentDecoderHandler() {
|
||||
this.sessionManager = SessionManager.getInstance();
|
||||
}
|
||||
|
||||
// 用来临时保留没有处理过的请求报文
|
||||
private final Cache<String, String> lruCache = CacheUtil.newLRUCache(3000);
|
||||
|
||||
/**
|
||||
* 消息解码器
|
||||
*/
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
// 检查是否为 ByteBuf(未解码的原始数据)
|
||||
if (msg instanceof ByteBuf) {
|
||||
ByteBuf byteBuf = (ByteBuf) msg;
|
||||
String messages = byteBuf.toString(CharsetUtil.UTF_8); // 指定字符集解码
|
||||
AssertLog.info("<<[up1]:IP:{},[up-content]==>{}", sessionManager.client(ctx),messages);
|
||||
|
||||
String content = "";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean isClear = false;
|
||||
String tempMsg = lruCache.get(sessionManager.client(ctx));
|
||||
tempMsg = tempMsg == null ? "" : tempMsg;
|
||||
int tmpMsgSize = tempMsg.length();
|
||||
|
||||
boolean startsWith = messages.startsWith("agent-server:");
|
||||
boolean endsWith = messages.endsWith("@tong-ran");
|
||||
String ms = messages.replaceAll("agent-server:","");
|
||||
String[] arr_msg = ms.split("@tong-ran");
|
||||
if(startsWith){
|
||||
//判定是否整包
|
||||
if(arr_msg.length == 1){
|
||||
//判定是否拆包
|
||||
if(endsWith){
|
||||
lruCache.remove(sessionManager.client(ctx));
|
||||
tmpMsgSize = 0;
|
||||
sb.append(arr_msg[0]+"@tong-ran");
|
||||
// content = arr_msg[0]+"@tong-ran";
|
||||
}else{
|
||||
lruCache.remove(sessionManager.client(ctx));
|
||||
tmpMsgSize = 0;
|
||||
sb.append(arr_msg[0]);
|
||||
// content = arr_msg[0];
|
||||
lruCache.put(sessionManager.client(ctx), sb.toString(), DateUnit.SECOND.getMillis() * 3000);
|
||||
}
|
||||
}
|
||||
//判定是否粘包
|
||||
if(arr_msg.length > 1){
|
||||
if(!endsWith){
|
||||
lruCache.remove(sessionManager.client(ctx));
|
||||
tmpMsgSize = 0;
|
||||
for (int i = 0; i < arr_msg.length; i++) {
|
||||
if(i == arr_msg.length -1){
|
||||
lruCache.put(sessionManager.client(ctx), arr_msg[i], DateUnit.SECOND.getMillis() * 5000);
|
||||
}else{
|
||||
sb.append(arr_msg[i].replaceAll("agent-server:","")+"@tong-ran");
|
||||
}
|
||||
}
|
||||
|
||||
}else{
|
||||
lruCache.remove(sessionManager.client(ctx));
|
||||
tmpMsgSize = 0;
|
||||
sb.append(String.join("@tong-ran", arr_msg));
|
||||
// content = String.join("@tong-ran", arr_msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
content = sb.toString();
|
||||
if (tmpMsgSize > 0) {
|
||||
content = tempMsg + messages;
|
||||
endsWith = content.endsWith("@tong-ran");
|
||||
if(endsWith){
|
||||
isClear = true;
|
||||
}else{
|
||||
lruCache.remove(sessionManager.client(ctx));
|
||||
lruCache.put(sessionManager.client(ctx), content, DateUnit.SECOND.getMillis() * 3000);
|
||||
}
|
||||
}
|
||||
AssertLog.info("<<[up2]:IP:{},[up-content]==>{}", sessionManager.client(ctx),content);
|
||||
endsWith = content.endsWith("@tong-ran");
|
||||
//判断是否是整包
|
||||
if(endsWith){
|
||||
content = content.replaceAll("agent-server:","");
|
||||
String[] arr = content.split("@tong-ran");
|
||||
try {
|
||||
for (String message : arr) {
|
||||
JSONObject jsonObject = JSONObject.parseObject(message);
|
||||
String clientId = jsonObject.getString("clientId");
|
||||
String dataType = jsonObject.getString("dataType");
|
||||
String data = jsonObject.getString("data");
|
||||
AgentHandler msgHandler = agentDispatcherManager.getHandler(dataType + "&" + AgentDispatcher.VersionEnum.V1.value);
|
||||
if (ObjectUtil.isNotEmpty(msgHandler)) {
|
||||
UpMsgResponse response = msgHandler.upHandle(data, clientId );
|
||||
if(Objects.nonNull(response)){
|
||||
AssertLog.info("<<[up-after-handle]:clientId:{},type={},[handle-content]={}", response.getClientId(), dataType, message);
|
||||
Message agentMessage = Message.builder().build();
|
||||
agentMessage.setClientId(response.getClientId());
|
||||
agentMessage.setDataType(response.getDataType());
|
||||
agentMessage.setData(response.getContent());
|
||||
ctx.fireChannelRead(agentMessage);//传递到下一个handler
|
||||
}
|
||||
}
|
||||
}
|
||||
}catch (Exception e){
|
||||
AssertLog.error("=====channelRead:{}=====" + e.getMessage());
|
||||
}
|
||||
}
|
||||
if (isClear) {
|
||||
lruCache.remove(sessionManager.client(ctx));
|
||||
}
|
||||
byteBuf.release(); // 释放 ByteBuf 资源(重要!)
|
||||
} else {
|
||||
System.out.println("Unexpected message type: " + msg.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
//拆包
|
||||
public byte[] subByte(byte[] b, int off, int length) {
|
||||
byte[] bytes = new byte[length];
|
||||
System.arraycopy(b, off, bytes, 0, length);
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.tongran.agent.client.netty.handler;
|
||||
|
||||
import com.tongran.agent.client.core.session.Session;
|
||||
import com.tongran.agent.client.core.session.SessionManager;
|
||||
import com.tongran.agent.client.netty.model.Message;
|
||||
import com.tongran.agent.client.utils.AssertLog;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@ChannelHandler.Sharable
|
||||
public class AgentDispatcherHandler extends SimpleChannelInboundHandler<Message> {
|
||||
|
||||
protected final SessionManager sessionManager;
|
||||
|
||||
public AgentDispatcherHandler() {
|
||||
this.sessionManager = SessionManager.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, Message msg) {
|
||||
// AssertLog.info(">>>>>>[要处理的终端数据] {}", msg.toString());
|
||||
String clientId = msg.getClientId();
|
||||
if (StringUtils.isBlank(clientId)) {
|
||||
AssertLog.error("<<<<<<错误的信息from:{}", ctx.channel().remoteAddress());
|
||||
return;
|
||||
}
|
||||
Session session = Session.buildSession(ctx, clientId);
|
||||
sessionManager.put(clientId, session);
|
||||
if (StringUtils.isNotBlank(msg.getData())) {
|
||||
// AssertLog.info(">>>>>>[getSessionById的终端数据] {}", sessionManager.getSessionById(clientId));
|
||||
sessionManager.writeAndFlush(sessionManager.getSessionById(clientId).getChannel(), msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.tongran.agent.client.netty.handler;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.tongran.agent.client.core.session.SessionManager;
|
||||
import com.tongran.agent.client.netty.model.Message;
|
||||
import com.tongran.agent.client.utils.AssertLog;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelOutboundHandlerAdapter;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@Component
|
||||
@ChannelHandler.Sharable
|
||||
public class AgentEncoderHandler extends ChannelOutboundHandlerAdapter {
|
||||
|
||||
protected final SessionManager sessionManager;
|
||||
|
||||
public AgentEncoderHandler() {
|
||||
this.sessionManager = SessionManager.getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息解码器
|
||||
*/
|
||||
@Override
|
||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
||||
if (msg instanceof Message) {
|
||||
Message entity = (Message) msg;
|
||||
if (StringUtils.isBlank(entity.getData())) {
|
||||
AssertLog.error(">>[down]:IP:{},errorContent:{}", sessionManager.client(ctx), msg);
|
||||
return;
|
||||
}
|
||||
AssertLog.info(">>[down]:IP:{},[content]==>{}", sessionManager.client(ctx), entity.getData());
|
||||
// String json = JSON.toJSONString(entity);
|
||||
String json = "agent-client:"+ JSON.toJSONString(entity)+"@tong-ran";
|
||||
byte[] bytes = json.getBytes(StandardCharsets.UTF_8); // 显式指定 UTF-8
|
||||
// byte[] bytes = EscapeUtil.hexStringToByteArray(entity.getContent());
|
||||
ByteBuf buf = Unpooled.wrappedBuffer(bytes);
|
||||
ctx.write(buf, promise);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.tongran.agent.client.netty.model;
|
||||
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author egrias
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class Message implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -1267013167162440610L;
|
||||
|
||||
/**
|
||||
* clientId
|
||||
*/
|
||||
private String clientId;
|
||||
|
||||
/**
|
||||
* 数据类型:LOGIN、HEARTBEAT、CPU、MEMORY、SYSTEM、POINT、NET、DISK、DOCKER、SWITCHBOARD
|
||||
*/
|
||||
private String dataType;
|
||||
|
||||
/**
|
||||
* 发送内容
|
||||
*/
|
||||
private String data;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.tongran.agent.client.netty.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
@Data
|
||||
@SuperBuilder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class UpMsgResponse {
|
||||
|
||||
|
||||
private String clientId;// 终端号
|
||||
|
||||
/**
|
||||
* type
|
||||
*/
|
||||
private String dataType;
|
||||
|
||||
private String content;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.tongran.agent.client.scheduler.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
@Configuration
|
||||
@EnableScheduling
|
||||
@EnableAsync
|
||||
public class SchedulerConfig {
|
||||
|
||||
@Bean
|
||||
public ThreadPoolTaskScheduler taskScheduler() {
|
||||
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
|
||||
scheduler.setPoolSize(50); // 核心线程数
|
||||
scheduler.setThreadNamePrefix("dynamic-scheduler-");
|
||||
scheduler.setAwaitTerminationSeconds(60);
|
||||
scheduler.setWaitForTasksToCompleteOnShutdown(true);
|
||||
scheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
scheduler.setRemoveOnCancelPolicy(true);
|
||||
return scheduler;
|
||||
}
|
||||
// @Bean("taskExecutor")
|
||||
// public ThreadPoolTaskScheduler taskExecutor() {
|
||||
// ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
|
||||
// executor.setPoolSize(50); // 工作线程数
|
||||
// executor.setThreadNamePrefix("task-worker-");
|
||||
// executor.setAwaitTerminationSeconds(30);
|
||||
// executor.setWaitForTasksToCompleteOnShutdown(true);
|
||||
// return executor;
|
||||
// }
|
||||
|
||||
@Bean("taskExecutor")
|
||||
public ThreadPoolTaskExecutor taskExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
// 核心配置
|
||||
executor.setCorePoolSize(44);
|
||||
executor.setMaxPoolSize(60);
|
||||
executor.setQueueCapacity(100);
|
||||
executor.setKeepAliveSeconds(60);
|
||||
// 线程配置
|
||||
executor.setThreadNamePrefix("task-worker-");
|
||||
executor.setAwaitTerminationSeconds(30);
|
||||
executor.setWaitForTasksToCompleteOnShutdown(true);
|
||||
// 拒绝策略
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
executor.initialize(); // 重要:必须调用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());
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
package com.tongran.agent.client.scheduler.service;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class AdvancedAsyncDownloader {
|
||||
private static final ExecutorService downloadExecutor =
|
||||
Executors.newFixedThreadPool(5);
|
||||
|
||||
/**
|
||||
* 带进度回调的异步下载
|
||||
*/
|
||||
public static CompletableFuture<String> downloadWithProgress(
|
||||
String fileUrl,
|
||||
String saveDir,
|
||||
String fileName,
|
||||
Consumer<Double> progressCallback) {
|
||||
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
URL url = new URL(fileUrl);
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
|
||||
// 获取文件大小
|
||||
long fileSize = connection.getContentLengthLong();
|
||||
|
||||
// 创建保存目录
|
||||
Path directory = Paths.get(saveDir);
|
||||
if (!Files.exists(directory)) {
|
||||
Files.createDirectories(directory);
|
||||
}
|
||||
|
||||
String filePath = directory.resolve(fileName != null ? fileName :
|
||||
extractFileNameFromUrl(fileUrl)).toString();
|
||||
|
||||
try (BufferedInputStream in = new BufferedInputStream(connection.getInputStream());
|
||||
FileOutputStream out = new FileOutputStream(filePath)) {
|
||||
|
||||
byte[] buffer = new byte[8192];
|
||||
int bytesRead;
|
||||
long totalRead = 0;
|
||||
|
||||
while ((bytesRead = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, bytesRead);
|
||||
totalRead += bytesRead;
|
||||
|
||||
// 回调进度
|
||||
if (progressCallback != null && fileSize > 0) {
|
||||
double progress = (double) totalRead / fileSize * 100;
|
||||
progressCallback.accept(progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return filePath;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("下载失败", e);
|
||||
}
|
||||
}, downloadExecutor);
|
||||
}
|
||||
|
||||
public static String extractFileNameFromUrl(String fileUrl) {
|
||||
// 实现文件名提取逻辑
|
||||
return fileUrl.substring(fileUrl.lastIndexOf('/') + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查目录是否存在,不存在则创建(不创建父目录)
|
||||
* @param directoryPath 目录路径
|
||||
* @return 创建成功返回true,否则返回false
|
||||
*/
|
||||
public static boolean createSingleDirectoryIfNotExists(String directoryPath) {
|
||||
try {
|
||||
Path path = Paths.get(directoryPath);
|
||||
|
||||
if (!Files.exists(path)) {
|
||||
// 只创建单级目录(父目录必须存在)
|
||||
Files.createDirectory(path);
|
||||
System.out.println("目录创建成功: " + directoryPath);
|
||||
return true;
|
||||
} else {
|
||||
System.out.println("目录已存在: " + directoryPath);
|
||||
return true;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.err.println("创建目录失败: " + directoryPath);
|
||||
System.err.println("错误信息: " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用示例
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
String fileUrl = "https://www.tzdsp.net/ksc-andromedae";
|
||||
String saveDir = "D:/down";
|
||||
|
||||
downloadWithProgress(fileUrl, saveDir, null, progress -> {
|
||||
System.out.printf("下载进度: %.1f%%\n", progress);
|
||||
}).thenAccept(filePath -> {
|
||||
System.out.println("下载完成: " + filePath);
|
||||
}).exceptionally(ex -> {
|
||||
System.err.println("下载错误: " + ex.getMessage());
|
||||
return null;
|
||||
});
|
||||
|
||||
// 主线程继续执行
|
||||
System.out.println("异步下载已启动...");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
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 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) {
|
||||
this.dynamicTaskService = dynamicTaskService;
|
||||
this.businessTasks = businessTasks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
AssertLog.info("创建连接");
|
||||
// 建立连接
|
||||
initConnection();
|
||||
// 检查保活脚本
|
||||
agentService.checkTrAgent();
|
||||
AssertLog.info("创建连接完成");
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// 创建多网IP探测上报
|
||||
AssertLog.info("启动多网IP探测定时任务 - 延迟: {}ms, 间隔: {}ms", milli, 300000);
|
||||
dynamicTaskService.scheduleTask("networkDetect", businessTasks::networkDetectTask, milli, 300000);
|
||||
|
||||
// 检测监控策略配置
|
||||
AssertLog.info("检测监控策略配置");
|
||||
agentService.checkMonitor();
|
||||
}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();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
package com.tongran.agent.client.scheduler.service;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class AsyncCommandExecutor {
|
||||
|
||||
// 使用线程池管理异步任务
|
||||
private static final ExecutorService executorService = Executors.newCachedThreadPool();
|
||||
private static final ScheduledExecutorService timeoutExecutor = Executors.newScheduledThreadPool(5);
|
||||
|
||||
/**
|
||||
* 异步为文件添加可执行权限
|
||||
*/
|
||||
public static CompletableFuture<Boolean> makeFileExecutableAsync(String filePath) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
Path path = Paths.get(filePath);
|
||||
|
||||
if (!Files.exists(path)) {
|
||||
throw new RuntimeException("文件不存在: " + filePath);
|
||||
}
|
||||
|
||||
Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(path);
|
||||
permissions.add(PosixFilePermission.OWNER_EXECUTE);
|
||||
permissions.add(PosixFilePermission.GROUP_EXECUTE);
|
||||
permissions.add(PosixFilePermission.OTHERS_EXECUTE);
|
||||
|
||||
Files.setPosixFilePermissions(path, permissions);
|
||||
return true;
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("设置执行权限失败: " + e.getMessage(), e);
|
||||
}
|
||||
}, executorService);
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步执行命令(基础版本)
|
||||
*/
|
||||
public static CompletableFuture<CommandResult> executeCommandAsync(
|
||||
List<String> command,
|
||||
long timeout,
|
||||
TimeUnit timeUnit) {
|
||||
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
Process process = null;
|
||||
try {
|
||||
ProcessBuilder processBuilder = new ProcessBuilder(command);
|
||||
processBuilder.redirectErrorStream(true);
|
||||
// processBuilder.directory(new File("/data/agent-server")); // 设置工作目录
|
||||
// 不合并错误流,分别读取
|
||||
process = processBuilder.start();
|
||||
|
||||
// 异步读取输出
|
||||
Process finalProcess = process;
|
||||
Future<String> outputFuture = executorService.submit(() -> {
|
||||
StringBuilder output = new StringBuilder();
|
||||
// String output = "";
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(finalProcess.getInputStream()))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
output.append(line).append("\n");
|
||||
// output = line;
|
||||
}
|
||||
}
|
||||
return output.toString();
|
||||
});
|
||||
|
||||
// 设置超时
|
||||
if (timeout > 0) {
|
||||
boolean finished = process.waitFor(timeout, timeUnit);
|
||||
if (!finished) {
|
||||
process.destroy();
|
||||
throw new TimeoutException("命令执行超时");
|
||||
}
|
||||
} else {
|
||||
process.waitFor();
|
||||
}
|
||||
|
||||
String output = outputFuture.get(1, TimeUnit.SECONDS);
|
||||
int exitCode = process.exitValue();
|
||||
return new CommandResult(exitCode, output, "");
|
||||
} catch (Exception e) {
|
||||
return new CommandResult(-1, "", "执行失败: " + e.getMessage());
|
||||
} finally {
|
||||
if (process != null) {
|
||||
process.destroy();
|
||||
}
|
||||
}
|
||||
}, executorService);
|
||||
}
|
||||
|
||||
/**
|
||||
* 带实时输出的异步命令执行
|
||||
*/
|
||||
public static CompletableFuture<CommandResult> executeCommandWithRealtimeOutput(
|
||||
String[] command,
|
||||
long timeout,
|
||||
TimeUnit timeUnit,
|
||||
Consumer<String> outputConsumer,
|
||||
Consumer<String> errorConsumer) {
|
||||
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
Process process = null;
|
||||
try {
|
||||
ProcessBuilder processBuilder = new ProcessBuilder(command);
|
||||
process = processBuilder.start();
|
||||
|
||||
// 启动输出读取线程
|
||||
CompletableFuture<Void> outputFuture = readStreamAsync(
|
||||
process.getInputStream(), outputConsumer, "OUTPUT");
|
||||
|
||||
CompletableFuture<Void> errorFuture = readStreamAsync(
|
||||
process.getErrorStream(), errorConsumer, "ERROR");
|
||||
|
||||
// 设置超时监控
|
||||
ScheduledFuture<?> timeoutFuture = null;
|
||||
if (timeout > 0) {
|
||||
Process finalProcess = process;
|
||||
timeoutFuture = timeoutExecutor.schedule(() -> {
|
||||
if (finalProcess.isAlive()) {
|
||||
finalProcess.destroy();
|
||||
}
|
||||
}, timeout, timeUnit);
|
||||
}
|
||||
|
||||
// 等待进程完成
|
||||
int exitCode = process.waitFor();
|
||||
|
||||
// 取消超时任务
|
||||
if (timeoutFuture != null) {
|
||||
timeoutFuture.cancel(false);
|
||||
}
|
||||
|
||||
// 等待输出读取完成
|
||||
CompletableFuture.allOf(outputFuture, errorFuture).get(2, TimeUnit.SECONDS);
|
||||
|
||||
return new CommandResult(exitCode, "", "");
|
||||
|
||||
} catch (Exception e) {
|
||||
return new CommandResult(-1, "", "执行异常: " + e.getMessage());
|
||||
} finally {
|
||||
if (process != null) {
|
||||
process.destroy();
|
||||
}
|
||||
}
|
||||
}, executorService);
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步读取流数据
|
||||
*/
|
||||
private static CompletableFuture<Void> readStreamAsync(
|
||||
InputStream inputStream,
|
||||
Consumer<String> lineConsumer,
|
||||
String streamType) {
|
||||
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(inputStream))) {
|
||||
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (lineConsumer != null) {
|
||||
lineConsumer.accept("[" + streamType + "] " + line);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (lineConsumer != null) {
|
||||
lineConsumer.accept("[" + streamType + "-ERROR] " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}, executorService);
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步执行脚本文件
|
||||
*/
|
||||
public static CompletableFuture<CommandResult> executeScriptAsync(
|
||||
String scriptPath,
|
||||
String[] args,
|
||||
long timeout,
|
||||
TimeUnit timeUnit,
|
||||
Consumer<String> realtimeOutputConsumer) {
|
||||
|
||||
return makeFileExecutableAsync(scriptPath)
|
||||
.thenCompose(permissionSuccess -> {
|
||||
if (!permissionSuccess) {
|
||||
return CompletableFuture.completedFuture(
|
||||
new CommandResult(-1, "", "设置执行权限失败"));
|
||||
}
|
||||
|
||||
String[] command = new String[args.length + 1];
|
||||
command[0] = scriptPath;
|
||||
System.arraycopy(args, 0, command, 1, args.length);
|
||||
|
||||
return executeCommandWithRealtimeOutput(
|
||||
command, timeout, timeUnit,
|
||||
realtimeOutputConsumer,
|
||||
error -> System.err.println(error));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量异步执行命令
|
||||
*/
|
||||
// public static List<CompletableFuture<CommandResult>> executeCommandsAsync(
|
||||
// List<String[]> commands,
|
||||
// long timeout,
|
||||
// TimeUnit timeUnit) {
|
||||
//
|
||||
// return commands.stream()
|
||||
// .map(command -> executeCommandAsync(command, timeout, timeUnit))
|
||||
// .collect(java.util.stream.Collectors.toList());
|
||||
// }
|
||||
|
||||
/**
|
||||
* 关闭执行器
|
||||
*/
|
||||
public static void shutdown() {
|
||||
executorService.shutdown();
|
||||
timeoutExecutor.shutdown();
|
||||
try {
|
||||
if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
if (!timeoutExecutor.awaitTermination(2, TimeUnit.SECONDS)) {
|
||||
timeoutExecutor.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
executorService.shutdownNow();
|
||||
timeoutExecutor.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行结果封装类
|
||||
*/
|
||||
public static class CommandResult {
|
||||
private final int exitCode;
|
||||
private final String output;
|
||||
private final String error;
|
||||
|
||||
public CommandResult(int exitCode, String output, String error) {
|
||||
this.exitCode = exitCode;
|
||||
this.output = output;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public int getExitCode() { return exitCode; }
|
||||
public String getOutput() { return output; }
|
||||
public String getError() { return error; }
|
||||
public boolean isSuccess() { return exitCode == 0; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Exit Code: %d\nOutput: %s\nError: %s",
|
||||
exitCode, output, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,967 @@
|
||||
package com.tongran.agent.client.scheduler.service;
|
||||
|
||||
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.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 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.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@Component
|
||||
public class BusinessTasks {
|
||||
|
||||
private final AtomicInteger heartbeatTask = new AtomicInteger(0);
|
||||
private final AtomicInteger cpuTask = new AtomicInteger(0);
|
||||
private final AtomicInteger diskTask = new AtomicInteger(0);
|
||||
private final AtomicInteger dockerTask = new AtomicInteger(0);
|
||||
private final AtomicInteger memoryTask = new AtomicInteger(0);
|
||||
private final AtomicInteger netTask = new AtomicInteger(0);
|
||||
private final AtomicInteger pointTask = new AtomicInteger(0);
|
||||
private final AtomicInteger alarmTask = new AtomicInteger(0);
|
||||
private final AtomicInteger systemSwapSizeFreeTask = new AtomicInteger(0);
|
||||
private final AtomicInteger memoryUtilizationTask = new AtomicInteger(0);
|
||||
private final AtomicInteger systemSwapSizePercentTask = new AtomicInteger(0);
|
||||
private final AtomicInteger memorySizeAvailableTask = new AtomicInteger(0);
|
||||
private final AtomicInteger memorySizePercentTask = new AtomicInteger(0);
|
||||
private final AtomicInteger memorySizeTotalTask = new AtomicInteger(0);
|
||||
private final AtomicInteger systemSwOsTask = new AtomicInteger(0);
|
||||
private final AtomicInteger systemSwArchTask = new AtomicInteger(0);
|
||||
private final AtomicInteger kernelMaxprocTask = new AtomicInteger(0);
|
||||
private final AtomicInteger procNumRunTask = new AtomicInteger(0);
|
||||
private final AtomicInteger systemUsersNumTask = new AtomicInteger(0);
|
||||
private final AtomicInteger systemDiskSizeTotalTask = new AtomicInteger(0);
|
||||
private final AtomicInteger systemBoottimeTask = new AtomicInteger(0);
|
||||
private final AtomicInteger systemUnameTask = new AtomicInteger(0);
|
||||
private final AtomicInteger systemLocaltimeTask = new AtomicInteger(0);
|
||||
private final AtomicInteger systemUptimeTask = new AtomicInteger(0);
|
||||
private final AtomicInteger procNumTask = 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);
|
||||
|
||||
|
||||
|
||||
protected final SessionManager sessionManager;
|
||||
|
||||
private final BusinessTasks businessTasks;
|
||||
private final DynamicTaskService dynamicTaskService;
|
||||
|
||||
// 使用构造函数注入,避免循环依赖
|
||||
public BusinessTasks(DynamicTaskService dynamicTaskService,
|
||||
@Lazy BusinessTasks businessTasks) {
|
||||
this.dynamicTaskService = dynamicTaskService;
|
||||
this.businessTasks = businessTasks;
|
||||
this.sessionManager = SessionManager.getInstance();
|
||||
}
|
||||
|
||||
@Resource
|
||||
private CPUService cpuService;
|
||||
|
||||
@Resource
|
||||
private DiskService diskService;
|
||||
|
||||
@Resource
|
||||
private DockerService dockerService;
|
||||
|
||||
@Resource
|
||||
private MemoryService memoryService;
|
||||
|
||||
@Resource
|
||||
private NetService netService;
|
||||
|
||||
@Resource
|
||||
private SystemService systemService;
|
||||
|
||||
@Resource
|
||||
private AlarmService alarmService;
|
||||
|
||||
@Resource
|
||||
private ApplicationProperties properties;
|
||||
|
||||
@Resource
|
||||
private MultiTargetNettyClient client;
|
||||
|
||||
@Resource
|
||||
private AgentNettyConfig config;
|
||||
|
||||
@Resource
|
||||
private AgentService agentService;
|
||||
|
||||
/**
|
||||
* 任务1:心跳上报任务
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
public void heartbeatTask() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
timestamp = Math.round(timestamp / 1000.0);
|
||||
int count = heartbeatTask.incrementAndGet();
|
||||
AssertLog.info("心跳定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务2:cpu信息采集任务
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
public void cpuTask() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
timestamp = Math.round(timestamp / 1000.0);
|
||||
int count = cpuTask.incrementAndGet();
|
||||
AssertLog.info("CPU信息采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
|
||||
// 判定客户端与服务端是否连接
|
||||
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
|
||||
// 发送CPU信息包
|
||||
CpuVO cpuVO = cpuService.get();
|
||||
cpuVO.setTimestamp(timestamp);
|
||||
String data = JSON.toJSONString(cpuVO);
|
||||
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.CPU上报.getValue()).data(data).build();
|
||||
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message);
|
||||
AssertLog.info("发送CPU信息包={}",JSON.toJSONString(message));
|
||||
}
|
||||
AssertLog.info("CPU信息采集定时任务执行 - task #{} completed", count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务3:磁盘信息采集任务
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
public void diskTask() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
timestamp = Math.round(timestamp / 1000.0);
|
||||
int count = diskTask.incrementAndGet();
|
||||
AssertLog.info("磁盘信息采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
|
||||
// 判定客户端与服务端是否连接
|
||||
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
|
||||
// 发送磁盘信息包
|
||||
String data = "";
|
||||
List<DiskVO> list = diskService.diskList(timestamp);
|
||||
if(CollectionUtil.isNotEmpty(list)){
|
||||
data = JSONArray.toJSONString(list);
|
||||
}
|
||||
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.磁盘上报.getValue()).data(data).build();
|
||||
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message);
|
||||
AssertLog.info("发送磁盘信息包={}",JSON.toJSONString(message));
|
||||
}
|
||||
AssertLog.info("磁盘信息采集定时任务执行 - task #{} completed", count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务4:容器信息采集任务
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
public void dockerTask() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
timestamp = Math.round(timestamp / 1000.0);
|
||||
int count = dockerTask.incrementAndGet();
|
||||
AssertLog.info("容器信息采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
|
||||
// 判定客户端与服务端是否连接
|
||||
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
|
||||
// 发送容器包
|
||||
String data = "";
|
||||
List<DockerVO> list = dockerService.dockerList(timestamp);
|
||||
if(CollectionUtil.isNotEmpty(list)){
|
||||
data = JSONArray.toJSONString(list);
|
||||
}
|
||||
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.容器上报.getValue()).data(data).build();
|
||||
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message);
|
||||
AssertLog.info("发送容器信息包={}",JSON.toJSONString(message));
|
||||
}
|
||||
AssertLog.info("容器信息采集定时任务执行 - task #{} completed", count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务5:内存信息采集任务
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
public void memoryTask() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
timestamp = Math.round(timestamp / 1000.0);
|
||||
int count = memoryTask.incrementAndGet();
|
||||
AssertLog.info("内存信息采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
|
||||
// 判定客户端与服务端是否连接
|
||||
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
|
||||
// 发送内存信息包
|
||||
MemoryVO memoryVO = memoryService.get();
|
||||
memoryVO.setTimestamp(timestamp);
|
||||
String data = JSON.toJSONString(memoryVO);
|
||||
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.内存上报.getValue()).data(data).build();
|
||||
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message);
|
||||
AssertLog.info("发送内存信息包={}",JSON.toJSONString(message));
|
||||
}
|
||||
AssertLog.info("内存信息采集定时任务执行 - task #{} completed", count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务6:网络信息采集任务
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
public void netTask() {
|
||||
long timestamp = AgentUtil.roundMinutes();
|
||||
int count = netTask.incrementAndGet();
|
||||
AssertLog.info("网络信息采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
|
||||
// 判定客户端与服务端是否连接
|
||||
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
|
||||
// 发送网卡信息包
|
||||
String data = "";
|
||||
List<NetVO> list = netService.netList(timestamp);
|
||||
if(CollectionUtil.isNotEmpty(list)){
|
||||
data = JSONArray.toJSONString(list);
|
||||
}
|
||||
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.网络上报.getValue()).data(data).build();
|
||||
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message);
|
||||
AssertLog.info("发送网络信息包={}",JSON.toJSONString(message));
|
||||
}
|
||||
AssertLog.info("网络信息采集定时任务执行 - task #{} completed", count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务7:挂载信息采集任务
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
public void pointTask() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
timestamp = Math.round(timestamp / 1000.0);
|
||||
int count = pointTask.incrementAndGet();
|
||||
AssertLog.info("挂载信息采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
|
||||
// 判定客户端与服务端是否连接
|
||||
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
|
||||
// 发送挂载点信息包
|
||||
String data = "";
|
||||
List<PointVO> list = diskService.pointList(timestamp);
|
||||
if(CollectionUtil.isNotEmpty(list)){
|
||||
data = JSONArray.toJSONString(list);
|
||||
}
|
||||
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.挂载上报.getValue()).data(data).build();
|
||||
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message);
|
||||
AssertLog.info("发送挂载信息包={}",JSON.toJSONString(message));
|
||||
}
|
||||
AssertLog.info("挂载点信息采集定时任务执行 - task #{} completed", count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务8:告警监控任务
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
public void alarmTask() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
timestamp = Math.round(timestamp / 1000.0);
|
||||
int count = alarmTask.incrementAndGet();
|
||||
AssertLog.info("告警监控定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
|
||||
// 判定客户端与服务端是否连接
|
||||
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
|
||||
String data = "";
|
||||
List<AlarmVO> list = alarmService.alarmMonitor(timestamp);
|
||||
AssertLog.info("告警监控定时任务执行={}", JSON.toJSONString(list));
|
||||
if(CollectionUtil.isNotEmpty(list)){
|
||||
data = JSONArray.toJSONString(list);
|
||||
}
|
||||
Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.告警上报.getValue()).data(data).build();
|
||||
sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message);
|
||||
AssertLog.info("发送告警监控信息包={}",JSON.toJSONString(message));
|
||||
}
|
||||
AssertLog.info("告警监控定时任务执行 - task #{} completed", count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务9:系统机信息采集任务-交换卷/文件的可用空间(字节)采集
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
public void systemSwapSizeFreeTask() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
timestamp = Math.round(timestamp / 1000.0);
|
||||
int count = systemSwapSizeFreeTask.incrementAndGet();
|
||||
AssertLog.info("系统其他信息-交换卷/文件的可用空间采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
|
||||
// 判定客户端与服务端是否连接
|
||||
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
String data = systemService.otherSystem("systemSwapSizeFreeCollect");
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务10:系统机信息采集任务-内存利用率采集
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
public void memoryUtilizationTask() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
timestamp = Math.round(timestamp / 1000.0);
|
||||
int count = memoryUtilizationTask.incrementAndGet();
|
||||
AssertLog.info("系统其他信息-内存利用率采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
|
||||
// 判定客户端与服务端是否连接
|
||||
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
|
||||
// 发送系统信息包
|
||||
// SystemVO systemVO = systemService.get();
|
||||
// systemVO.setTimestamp(timestamp);
|
||||
// String data = JSON.toJSONString(systemVO);
|
||||
// Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.系统其他上报.getValue()).data(data).build();
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
String data = systemService.otherSystem("memoryUtilizationCollect");
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务11:系统机信息采集任务-可用交换空间百分比采集
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
public void systemSwapSizePercentTask() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
timestamp = Math.round(timestamp / 1000.0);
|
||||
int count = systemSwapSizePercentTask.incrementAndGet();
|
||||
AssertLog.info("系统其他信息-可用交换空间百分比采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
|
||||
// 判定客户端与服务端是否连接
|
||||
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
|
||||
// 发送系统信息包
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
String data = systemService.otherSystem("systemSwapSizePercentCollect");
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务12:系统机信息采集任务-可用内存采集
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
public void memorySizeAvailableTask() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
timestamp = Math.round(timestamp / 1000.0);
|
||||
int count = memorySizeAvailableTask.incrementAndGet();
|
||||
AssertLog.info("系统其他信息-可用内存采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
|
||||
// 判定客户端与服务端是否连接
|
||||
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
|
||||
// 发送系统信息包
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
String data = systemService.otherSystem("memorySizeAvailableCollect");
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务13:系统机信息采集任务-可用内存百分比内存采集
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
public void memorySizePercentTask() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
timestamp = Math.round(timestamp / 1000.0);
|
||||
int count = memorySizePercentTask.incrementAndGet();
|
||||
AssertLog.info("系统其他信息-可用内存百分比采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
|
||||
// 判定客户端与服务端是否连接
|
||||
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
|
||||
// 发送系统信息包
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
String data = systemService.otherSystem("memorySizePercentCollect");
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务14:系统机信息采集任务-总内存采集
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
public void memorySizeTotalTask() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
timestamp = Math.round(timestamp / 1000.0);
|
||||
int count = memorySizeTotalTask.incrementAndGet();
|
||||
AssertLog.info("系统其他信息-总内存采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
|
||||
// 判定客户端与服务端是否连接
|
||||
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
|
||||
// 发送系统信息包
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
String data = systemService.otherSystem("memorySizeTotalCollect");
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务15:系统机信息采集任务-操作系统采集
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
public void systemSwOsTask() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
timestamp = Math.round(timestamp / 1000.0);
|
||||
int count = systemSwOsTask.incrementAndGet();
|
||||
AssertLog.info("系统其他信息-操作系统采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
|
||||
// 判定客户端与服务端是否连接
|
||||
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
|
||||
// 发送系统信息包
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
String data = systemService.otherSystem("systemSwOsCollect");
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务16:系统机信息采集任务-操作系统架构采集
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
public void systemSwArchTask() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
timestamp = Math.round(timestamp / 1000.0);
|
||||
int count = systemSwArchTask.incrementAndGet();
|
||||
AssertLog.info("系统其他信息-操作系统架构采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
|
||||
// 判定客户端与服务端是否连接
|
||||
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
|
||||
// 发送系统信息包
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
String data = systemService.otherSystem("systemSwArchCollect");
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务17:系统机信息采集任务-最大进程数采集
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
public void kernelMaxprocTask() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
timestamp = Math.round(timestamp / 1000.0);
|
||||
int count = kernelMaxprocTask.incrementAndGet();
|
||||
AssertLog.info("系统其他信息-最大进程数采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
|
||||
// 判定客户端与服务端是否连接
|
||||
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
|
||||
// 发送系统信息包
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
String data = systemService.otherSystem("kernelMaxprocCollect");
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务18:系统机信息采集任务-正在运行的进程数采集
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
public void procNumRunTask() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
timestamp = Math.round(timestamp / 1000.0);
|
||||
int count = procNumRunTask.incrementAndGet();
|
||||
AssertLog.info("系统其他信息-正在运行的进程数采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
|
||||
// 判定客户端与服务端是否连接
|
||||
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
|
||||
// 发送系统信息包
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
String data = systemService.otherSystem("procNumRunCollect");
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务19:系统机信息采集任务-登录用户数采集
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
public void systemUsersNumTask() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
timestamp = Math.round(timestamp / 1000.0);
|
||||
int count = systemUsersNumTask.incrementAndGet();
|
||||
AssertLog.info("系统其他信息-登录用户数采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
|
||||
// 判定客户端与服务端是否连接
|
||||
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
|
||||
// 发送系统信息包
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
String data = systemService.otherSystem("systemUsersNumCollect");
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务20:系统机信息采集任务-硬盘总可用空间采集
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
public void systemDiskSizeTotalTask() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
timestamp = Math.round(timestamp / 1000.0);
|
||||
int count = systemDiskSizeTotalTask.incrementAndGet();
|
||||
AssertLog.info("系统其他信息-硬盘总可用空间采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
|
||||
// 判定客户端与服务端是否连接
|
||||
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
|
||||
// 发送系统信息包
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
String data = systemService.otherSystem("systemDiskSizeTotalCollect");
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务21:系统机信息采集任务-系统启动时间采集
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
public void systemBoottimeTask() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
timestamp = Math.round(timestamp / 1000.0);
|
||||
int count = systemBoottimeTask.incrementAndGet();
|
||||
AssertLog.info("系统其他信息-系统启动时间采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
|
||||
// 判定客户端与服务端是否连接
|
||||
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
|
||||
// 发送系统信息包
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
String data = systemService.otherSystem("systemBoottimeCollect");
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务22:系统机信息采集任务-系统描述采集
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
public void systemUnameTask() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
timestamp = Math.round(timestamp / 1000.0);
|
||||
int count = systemUnameTask.incrementAndGet();
|
||||
AssertLog.info("系统其他信息-系统描述采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
|
||||
// 判定客户端与服务端是否连接
|
||||
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
|
||||
// 发送系统信息包
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
String data = systemService.otherSystem("systemUnameCollect");
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务23:系统机信息采集任务-系统本地时间采集
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
public void systemLocaltimeTask() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
timestamp = Math.round(timestamp / 1000.0);
|
||||
int count = systemLocaltimeTask.incrementAndGet();
|
||||
AssertLog.info("系统其他信息-系统本地时间采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
|
||||
// 判定客户端与服务端是否连接
|
||||
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
|
||||
// 发送系统信息包
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
String data = systemService.otherSystem("systemLocaltimeCollect");
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务24:系统机信息采集任务-系统正常运行时间采集
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
public void systemUptimeTask() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
timestamp = Math.round(timestamp / 1000.0);
|
||||
int count = systemUptimeTask.incrementAndGet();
|
||||
AssertLog.info("系统其他信息-系统正常运行时间采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
|
||||
// 判定客户端与服务端是否连接
|
||||
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
|
||||
// 发送系统信息包
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
String data = systemService.otherSystem("systemUptimeCollect");
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务25:系统机信息采集任务-进程数采集
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
public void procNumTask() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
timestamp = Math.round(timestamp / 1000.0);
|
||||
int count = procNumTask.incrementAndGet();
|
||||
AssertLog.info("系统其他信息-进程数采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
|
||||
// 判定客户端与服务端是否连接
|
||||
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
|
||||
// 发送系统信息包
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
String data = systemService.otherSystem("procNumCollect");
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务26:获取最新策略
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
public void policyTask() {
|
||||
long timestamp = AgentUtil.roundMinutes();
|
||||
int count = policyTask.incrementAndGet();
|
||||
AssertLog.info("获取最新策略定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
|
||||
// 判定客户端与服务端是否连接
|
||||
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("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("获取最新策略定时任务执行 - task #{} completed", count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务27:注册重试
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
public void registerTask() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
timestamp = Math.round(timestamp / 1000.0);
|
||||
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){
|
||||
success = agentService.connection();
|
||||
}
|
||||
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();
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务28:建立连接重试
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
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();
|
||||
}
|
||||
//连接成功,发送初始连接消息
|
||||
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);
|
||||
}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();
|
||||
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("connectionTask");
|
||||
}
|
||||
AssertLog.info("建立连接重试定时任务执行 - task #{} completed", count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务29:多网IP探测
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
public void networkDetectTask() {
|
||||
int count = networkDetectTask.incrementAndGet();
|
||||
AssertLog.info("多网IP探测定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
|
||||
// 判定客户端与服务端是否连接
|
||||
if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
|
||||
if(StringUtils.isBlank(GlobalConfig.DEVICE_SN)){
|
||||
GlobalConfig.DEVICE_SN = AgentUtil.getDeviceSN();
|
||||
}
|
||||
// 发送多网IP探测
|
||||
long timestamps = System.currentTimeMillis();
|
||||
timestamps = Math.round(timestamps / 1000.0);
|
||||
List<NetworkInterfaceInfo> infos = null;
|
||||
try {
|
||||
infos = AgentUtil.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("发送多网IP探测信息包={}",JSON.toJSONString(message));
|
||||
}
|
||||
AssertLog.info("发送多网IP探测定时任务执行 - task #{} completed", count);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//
|
||||
// @Async("taskExecutor")
|
||||
// public void systemTask(String type) {
|
||||
// long timestamp = System.currentTimeMillis();
|
||||
// timestamp = Math.round(timestamp / 1000.0);
|
||||
// int count = task9Counter.incrementAndGet();
|
||||
// AssertLog.info("系统信息采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
|
||||
// // 判定客户端与服务端是否连接
|
||||
// if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
|
||||
// // 发送系统信息包
|
||||
//// SystemVO systemVO = systemService.get();
|
||||
//// systemVO.setTimestamp(timestamp);
|
||||
//// String data = JSON.toJSONString(systemVO);
|
||||
//// Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.系统其他上报.getValue()).data(data).build();
|
||||
// JSONObject jsonObject = new JSONObject();
|
||||
// String data = systemService.otherSystem(type);
|
||||
// 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);
|
||||
// }
|
||||
|
||||
|
||||
// /**
|
||||
// * 任务8:交换机信息采集任务
|
||||
// */
|
||||
// @Async("taskExecutor")
|
||||
// public void switchBoardTask(String type) {
|
||||
// long timestamp = System.currentTimeMillis();
|
||||
// timestamp = Math.round(timestamp / 1000.0);
|
||||
//// long timestamp = AgentUtil.roundMinutes();
|
||||
// int count = task8Counter.incrementAndGet();
|
||||
// AssertLog.info("交换机信息采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count);
|
||||
// // 判定客户端与服务端是否连接
|
||||
// if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) {
|
||||
// // 发送交换机信息包
|
||||
//// List<SwitchBoardVO> list = switchBoardService.switchBoardList(timestamp);
|
||||
//// String data = JSONArray.toJSONString(list);
|
||||
//// Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.交换机上报.getValue()).data(data).build();
|
||||
// JSONObject jsonObject = new JSONObject();
|
||||
// String data = switchBoardService.getSwitchDataByType(type);
|
||||
// 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);
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.tongran.agent.client.scheduler.service;
|
||||
|
||||
import org.springframework.scheduling.Trigger;
|
||||
import org.springframework.scheduling.TriggerContext;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
public class CompensatedTrigger implements Trigger {
|
||||
|
||||
private final long period;
|
||||
private final AtomicLong nextExecutionTime;
|
||||
private final TimeUnit timeUnit;
|
||||
|
||||
public CompensatedTrigger(long initialDelay, long period) {
|
||||
this(initialDelay, period, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public CompensatedTrigger(long initialDelay, long period, TimeUnit timeUnit) {
|
||||
this.period = period;
|
||||
this.timeUnit = timeUnit;
|
||||
this.nextExecutionTime = new AtomicLong(
|
||||
System.currentTimeMillis() + timeUnit.toMillis(initialDelay)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date nextExecutionTime(TriggerContext triggerContext) {
|
||||
long lastCompletionTime = triggerContext.lastCompletionTime() != null
|
||||
? triggerContext.lastCompletionTime().getTime()
|
||||
: System.currentTimeMillis();
|
||||
|
||||
// 计算下一次执行时间(考虑补偿)
|
||||
long currentNextTime = nextExecutionTime.get();
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
// 如果当前时间已经超过计划时间,立即执行
|
||||
if (now >= currentNextTime) {
|
||||
nextExecutionTime.set(now + timeUnit.toMillis(period));
|
||||
return new Date(now);
|
||||
}
|
||||
|
||||
// 否则按计划时间执行
|
||||
nextExecutionTime.set(currentNextTime + timeUnit.toMillis(period));
|
||||
return new Date(currentNextTime);
|
||||
}
|
||||
|
||||
public void updatePeriod(long newPeriod) {
|
||||
// 更新执行间隔
|
||||
long currentTime = System.currentTimeMillis();
|
||||
long remaining = nextExecutionTime.get() - currentTime;
|
||||
if (remaining > 0) {
|
||||
nextExecutionTime.set(currentTime + timeUnit.toMillis(newPeriod));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package com.tongran.agent.client.scheduler.service;
|
||||
|
||||
import com.tongran.agent.client.core.config.GlobalConfig;
|
||||
import com.tongran.agent.client.utils.AssertLog;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
@Service
|
||||
public class DynamicTaskService {
|
||||
|
||||
@Autowired
|
||||
private TaskScheduler taskScheduler;
|
||||
|
||||
private final Map<String, ScheduledFuture<?>> taskFutures = new ConcurrentHashMap<>();
|
||||
private final Map<String, TaskConfig> taskConfigs = new ConcurrentHashMap<>();
|
||||
private final Map<String, AtomicLong> taskExecutions = new ConcurrentHashMap<>();
|
||||
private final Map<String, Long> lastExecutionTimes = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 添加或更新定时任务
|
||||
*/
|
||||
public void scheduleTask(String taskId, Runnable task, long initialDelay, long period) {
|
||||
// 取消现有任务
|
||||
cancelTask(taskId);
|
||||
|
||||
TaskConfig config = new TaskConfig(initialDelay, period, System.currentTimeMillis());
|
||||
taskConfigs.put(taskId, config);
|
||||
taskExecutions.put(taskId, new AtomicLong(0));
|
||||
|
||||
// 使用补偿机制调度任务
|
||||
ScheduledFuture<?> future = taskScheduler.schedule(
|
||||
createCompensatedTask(taskId, task),
|
||||
new CompensatedTrigger(initialDelay, period)
|
||||
);
|
||||
|
||||
taskFutures.put(taskId, future);
|
||||
GlobalConfig.taskIds.put(taskId, 0L);
|
||||
AssertLog.info("添加或更新定时任务taskId={}",taskId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建带补偿的任务
|
||||
*/
|
||||
private Runnable createCompensatedTask(String taskId, Runnable originalTask) {
|
||||
return () -> {
|
||||
long startTime = System.currentTimeMillis();
|
||||
lastExecutionTimes.put(taskId, startTime);
|
||||
|
||||
try {
|
||||
System.out.printf("开始执行任务: %s, 线程: %s%n",
|
||||
taskId, Thread.currentThread().getName());
|
||||
// 添加前置检查
|
||||
if (originalTask == null) {
|
||||
throw new IllegalArgumentException("原始任务不能为null");
|
||||
}
|
||||
originalTask.run();
|
||||
System.out.printf("任务 %s 执行成功%n", taskId);
|
||||
} catch (Exception e) {
|
||||
System.err.printf("任务 %s 执行失败: %s%n", taskId, e.getMessage());
|
||||
// System.err.println("Task " + taskId + " execution failed: " + e.getMessage());
|
||||
} finally {
|
||||
long endTime = System.currentTimeMillis();
|
||||
long executionTime = endTime - startTime;
|
||||
taskExecutions.get(taskId).incrementAndGet();
|
||||
// System.out.printf("Task %s executed in %dms, total executions: %d%n",
|
||||
// taskId, executionTime, taskExecutions.get(taskId).get());
|
||||
System.out.printf("任务 %s 执行耗时: %dms, 总执行次数: %d%n",
|
||||
taskId, executionTime, taskExecutions.get(taskId).get());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消任务
|
||||
*/
|
||||
public boolean cancelTask(String taskId) {
|
||||
ScheduledFuture<?> future = taskFutures.remove(taskId);
|
||||
if (future != null) {
|
||||
future.cancel(false);
|
||||
taskConfigs.remove(taskId);
|
||||
taskExecutions.remove(taskId);
|
||||
lastExecutionTimes.remove(taskId);
|
||||
GlobalConfig.taskIds.remove(taskId);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新任务间隔
|
||||
*/
|
||||
public void updateTaskInterval(String taskId, long newPeriod) {
|
||||
TaskConfig config = taskConfigs.get(taskId);
|
||||
if (config != null) {
|
||||
Runnable task = () -> {}; // 这里需要根据实际情况获取原始任务
|
||||
scheduleTask(taskId, task, 0, newPeriod); // 立即重新调度
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务状态
|
||||
*/
|
||||
public TaskStatus getTaskStatus(String taskId) {
|
||||
TaskConfig config = taskConfigs.get(taskId);
|
||||
AtomicLong executions = taskExecutions.get(taskId);
|
||||
Long lastTime = lastExecutionTimes.get(taskId);
|
||||
|
||||
if (config != null && executions != null) {
|
||||
return new TaskStatus(
|
||||
taskId,
|
||||
config.getPeriod(),
|
||||
executions.get(),
|
||||
lastTime,
|
||||
taskFutures.get(taskId) != null && !taskFutures.get(taskId).isCancelled()
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有任务状态
|
||||
*/
|
||||
public Map<String, TaskStatus> getAllTaskStatus() {
|
||||
Map<String, TaskStatus> statusMap = new ConcurrentHashMap<>();
|
||||
for (String taskId : taskConfigs.keySet()) {
|
||||
statusMap.put(taskId, getTaskStatus(taskId));
|
||||
}
|
||||
return statusMap;
|
||||
}
|
||||
|
||||
// 配置类
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public static class TaskConfig {
|
||||
private long initialDelay;
|
||||
private long period;
|
||||
private long createTime;
|
||||
}
|
||||
|
||||
// 状态类
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public static class TaskStatus {
|
||||
private String taskId;
|
||||
private long interval;
|
||||
private long executionCount;
|
||||
private Long lastExecutionTime;
|
||||
private boolean isRunning;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
package com.tongran.agent.client.scheduler.service;
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Service
|
||||
public class DynamicTcpPortService {
|
||||
private static final Logger log = LoggerFactory.getLogger(DynamicTcpPortService.class);
|
||||
|
||||
private final Map<Integer, TcpServerInstance> activeServers = new ConcurrentHashMap<>();
|
||||
private EventLoopGroup bossGroup;
|
||||
private EventLoopGroup workerGroup;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
bossGroup = new NioEventLoopGroup(1);
|
||||
workerGroup = new NioEventLoopGroup();
|
||||
log.info("Dynamic TCP port service initialized");
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态创建TCP服务器
|
||||
*/
|
||||
public TcpServerInstance createTcpServer(int port, ChannelHandler handler) throws Exception {
|
||||
return createTcpServer(port, handler, null);
|
||||
}
|
||||
|
||||
public TcpServerInstance createTcpServer(int port, ChannelHandler handler,
|
||||
Map<String, Object> options) throws Exception {
|
||||
if (activeServers.containsKey(port)) {
|
||||
throw new IllegalArgumentException("Port " + port + " is already in use");
|
||||
}
|
||||
|
||||
if (!isPortAvailable(port)) {
|
||||
throw new IllegalArgumentException("Port " + port + " is not available");
|
||||
}
|
||||
|
||||
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||
bootstrap.group(bossGroup, workerGroup)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.childHandler(handler);
|
||||
|
||||
// 设置可选参数
|
||||
if (options != null) {
|
||||
if (options.containsKey("soBacklog")) {
|
||||
bootstrap.option(ChannelOption.SO_BACKLOG, (Integer) options.get("soBacklog"));
|
||||
}
|
||||
if (options.containsKey("soKeepalive")) {
|
||||
bootstrap.childOption(ChannelOption.SO_KEEPALIVE, (Boolean) options.get("soKeepalive"));
|
||||
}
|
||||
if (options.containsKey("tcpNoDelay")) {
|
||||
bootstrap.childOption(ChannelOption.TCP_NODELAY, (Boolean) options.get("tcpNoDelay"));
|
||||
}
|
||||
}
|
||||
|
||||
ChannelFuture future = bootstrap.bind(port).sync();
|
||||
TcpServerInstance instance = new TcpServerInstance(port, future, handler, new Date());
|
||||
activeServers.put(port, instance);
|
||||
|
||||
log.info("TCP server started on port: {}", port);
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止TCP服务器
|
||||
*/
|
||||
public boolean stopTcpServer(int port) {
|
||||
TcpServerInstance instance = activeServers.get(port);
|
||||
if (instance != null) {
|
||||
try {
|
||||
instance.getChannelFuture().channel().close().sync();
|
||||
activeServers.remove(port);
|
||||
log.info("TCP server stopped on port: {}", port);
|
||||
return true;
|
||||
} catch (InterruptedException e) {
|
||||
log.error("Error stopping TCP server on port {}", port, e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有活跃的TCP服务器
|
||||
*/
|
||||
public List<TcpServerInstance> getActiveServers() {
|
||||
return new ArrayList<>(activeServers.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查端口是否可用
|
||||
*/
|
||||
public boolean isPortAvailable(int port) {
|
||||
try (ServerSocket socket = new ServerSocket(port)) {
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可用端口列表
|
||||
*/
|
||||
public List<Integer> getAvailablePorts(int start, int end) {
|
||||
List<Integer> availablePorts = new ArrayList<>();
|
||||
for (int port = start; port <= end; port++) {
|
||||
if (isPortAvailable(port) && !activeServers.containsKey(port)) {
|
||||
availablePorts.add(port);
|
||||
}
|
||||
}
|
||||
return availablePorts;
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void shutdown() {
|
||||
// 停止所有TCP服务器
|
||||
for (Integer port : new ArrayList<>(activeServers.keySet())) {
|
||||
stopTcpServer(port);
|
||||
}
|
||||
|
||||
// 关闭EventLoopGroup
|
||||
if (bossGroup != null) {
|
||||
bossGroup.shutdownGracefully();
|
||||
}
|
||||
if (workerGroup != null) {
|
||||
workerGroup.shutdownGracefully();
|
||||
}
|
||||
|
||||
log.info("Dynamic TCP port service shutdown completed");
|
||||
}
|
||||
|
||||
/**
|
||||
* TCP服务器实例信息类
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public static class TcpServerInstance {
|
||||
private int port;
|
||||
private ChannelFuture channelFuture;
|
||||
private ChannelHandler handler;
|
||||
private Date startTime;
|
||||
private int connectionCount;
|
||||
|
||||
public TcpServerInstance(int port, ChannelFuture channelFuture,
|
||||
ChannelHandler handler, Date startTime) {
|
||||
this.port = port;
|
||||
this.channelFuture = channelFuture;
|
||||
this.handler = handler;
|
||||
this.startTime = startTime;
|
||||
this.connectionCount = 0;
|
||||
}
|
||||
|
||||
public void incrementConnectionCount() {
|
||||
this.connectionCount++;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return channelFuture.channel().isActive() ? "ACTIVE" : "INACTIVE";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package com.tongran.agent.client.scheduler.service;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public class Java8FileDownloader {
|
||||
|
||||
/**
|
||||
* 下载结果类
|
||||
*/
|
||||
public static class DownloadResult {
|
||||
private boolean success;
|
||||
private String message;
|
||||
private long expectedSize;
|
||||
private long downloadedSize;
|
||||
private String filePath;
|
||||
|
||||
public DownloadResult() {
|
||||
this.success = false;
|
||||
this.message = "";
|
||||
}
|
||||
|
||||
// Getter和Setter方法
|
||||
public boolean isSuccess() { return success; }
|
||||
public void setSuccess(boolean success) { this.success = success; }
|
||||
|
||||
public String getMessage() { return message; }
|
||||
public void setMessage(String message) { this.message = message; }
|
||||
|
||||
public long getExpectedSize() { return expectedSize; }
|
||||
public void setExpectedSize(long expectedSize) { this.expectedSize = expectedSize; }
|
||||
|
||||
public long getDownloadedSize() { return downloadedSize; }
|
||||
public void setDownloadedSize(long downloadedSize) { this.downloadedSize = downloadedSize; }
|
||||
|
||||
public String getFilePath() { return filePath; }
|
||||
public void setFilePath(String filePath) { this.filePath = filePath; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (success) {
|
||||
return String.format("下载成功: %s (%d/%d 字节)",
|
||||
filePath, downloadedSize, expectedSize);
|
||||
} else {
|
||||
return String.format("下载失败: %s", message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步下载文件
|
||||
* @param fileURL 文件URL地址
|
||||
* @param saveDir 保存目录
|
||||
* @param fileName 文件名(如果为null则从URL获取)
|
||||
* @return 下载结果
|
||||
*/
|
||||
public static DownloadResult downloadFile(String fileURL, String saveDir, String fileName) {
|
||||
DownloadResult result = new DownloadResult();
|
||||
|
||||
try {
|
||||
// 创建保存目录
|
||||
Path dirPath = Paths.get(saveDir);
|
||||
if (!Files.exists(dirPath)) {
|
||||
Files.createDirectories(dirPath);
|
||||
}
|
||||
|
||||
// 获取文件名
|
||||
String actualFileName = getFileName(fileURL, fileName);
|
||||
String savePath = Paths.get(saveDir, actualFileName).toString();
|
||||
result.setFilePath(savePath);
|
||||
|
||||
URL url = new URL(fileURL);
|
||||
URLConnection connection = url.openConnection();
|
||||
connection.setConnectTimeout(10000); // 10秒连接超时
|
||||
connection.setReadTimeout(30000); // 30秒读取超时
|
||||
|
||||
// 获取文件信息
|
||||
long expectedSize = connection.getContentLengthLong();
|
||||
String contentType = connection.getContentType();
|
||||
result.setExpectedSize(expectedSize);
|
||||
|
||||
System.out.println("开始下载: " + actualFileName);
|
||||
System.out.println("文件大小: " + formatFileSize(expectedSize));
|
||||
System.out.println("文件类型: " + contentType);
|
||||
|
||||
// 下载文件
|
||||
try (BufferedInputStream in = new BufferedInputStream(connection.getInputStream());
|
||||
FileOutputStream out = new FileOutputStream(savePath)) {
|
||||
|
||||
byte[] buffer = new byte[8192]; // 8KB缓冲区
|
||||
int bytesRead;
|
||||
long totalRead = 0;
|
||||
|
||||
while ((bytesRead = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, bytesRead);
|
||||
totalRead += bytesRead;
|
||||
|
||||
// 显示进度(可选)
|
||||
if (expectedSize > 0) {
|
||||
int progress = (int) ((totalRead * 100) / expectedSize);
|
||||
if (progress % 10 == 0) { // 每10%显示一次进度
|
||||
System.out.printf("下载进度: %d%%\n", progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.setDownloadedSize(totalRead);
|
||||
result.setSuccess(true);
|
||||
|
||||
// 验证文件完整性
|
||||
if (expectedSize > 0 && totalRead != expectedSize) {
|
||||
result.setSuccess(false);
|
||||
result.setMessage(String.format("文件不完整: 期望 %d 字节, 实际 %d 字节",
|
||||
expectedSize, totalRead));
|
||||
} else {
|
||||
result.setMessage("下载完成,文件完整性验证通过");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
result.setSuccess(false);
|
||||
result.setMessage("下载失败: " + e.getClass().getSimpleName() + " - " + e.getMessage());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从URL中提取文件名
|
||||
*/
|
||||
private static String getFileName(String fileURL, String customFileName) {
|
||||
if (customFileName != null && !customFileName.trim().isEmpty()) {
|
||||
return customFileName;
|
||||
}
|
||||
|
||||
try {
|
||||
URL url = new URL(fileURL);
|
||||
String path = url.getPath();
|
||||
if (path.contains("/")) {
|
||||
return path.substring(path.lastIndexOf("/") + 1);
|
||||
}
|
||||
return "downloaded_file";
|
||||
} catch (Exception e) {
|
||||
return "downloaded_file";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化文件大小
|
||||
*/
|
||||
private static String formatFileSize(long size) {
|
||||
if (size <= 0) return "0 B";
|
||||
|
||||
final String[] units = new String[]{"B", "KB", "MB", "GB", "TB"};
|
||||
int digitGroups = (int) (Math.log10(size) / Math.log10(1024));
|
||||
|
||||
return String.format("%.1f %s", size / Math.pow(1024, digitGroups), units[digitGroups]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证文件是否完整下载
|
||||
*/
|
||||
public static boolean verifyFileCompletion(String filePath, long expectedSize) {
|
||||
try {
|
||||
Path path = Paths.get(filePath);
|
||||
if (Files.exists(path)) {
|
||||
long actualSize = Files.size(path);
|
||||
return actualSize == expectedSize;
|
||||
}
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.tongran.agent.client.scheduler.service;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/scheduler")
|
||||
public class SchedulerController {
|
||||
|
||||
@Autowired
|
||||
private DynamicTaskService dynamicTaskService;
|
||||
|
||||
/**
|
||||
* 获取所有任务状态
|
||||
*/
|
||||
@GetMapping("/tasks")
|
||||
public Map<String, DynamicTaskService.TaskStatus> getAllTasks() {
|
||||
return dynamicTaskService.getAllTaskStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新任务间隔
|
||||
*/
|
||||
@PostMapping("/{taskId}/interval")
|
||||
public String updateInterval(@PathVariable String taskId,
|
||||
@RequestParam long intervalMs) {
|
||||
dynamicTaskService.updateTaskInterval(taskId, intervalMs);
|
||||
return "Task " + taskId + " interval updated to " + intervalMs + "ms";
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂停任务
|
||||
*/
|
||||
@PostMapping("/{taskId}/pause")
|
||||
public String pauseTask(@PathVariable String taskId) {
|
||||
boolean success = dynamicTaskService.cancelTask(taskId);
|
||||
return success ? "Task paused" : "Task not found";
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复任务
|
||||
*/
|
||||
@PostMapping("/{taskId}/resume")
|
||||
public String resumeTask(@PathVariable String taskId,
|
||||
@RequestParam(defaultValue = "30000") long intervalMs) {
|
||||
// 这里需要根据taskId获取对应的Runnable任务
|
||||
// 实际项目中可能需要一个任务注册表
|
||||
Runnable task = getTaskById(taskId);
|
||||
if (task != null) {
|
||||
// dynamicTaskService.scheduleTask(taskId, task, 0, intervalMs);
|
||||
return "Task resumed with interval " + intervalMs + "ms";
|
||||
}
|
||||
return "Task not found";
|
||||
}
|
||||
|
||||
private Runnable getTaskById(String taskId) {
|
||||
// 根据taskId返回对应的Runnable
|
||||
// 实际项目中可以维护一个任务注册表
|
||||
return () -> {};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.tongran.agent.client.scheduler.task;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SpecificTimeRequest {
|
||||
private String taskId;
|
||||
private String taskName;
|
||||
private String taskData;
|
||||
|
||||
private String scriptId;
|
||||
private String clientId;
|
||||
private String dataType;
|
||||
|
||||
// 多种时间指定方式
|
||||
private String cronExpression; // Cron表达式
|
||||
private LocalDateTime specificDateTime; // 具体日期时间
|
||||
private String time; // 每天固定时间(HH:mm:ss)
|
||||
private Integer delaySeconds; // 延迟秒数
|
||||
|
||||
// // 构造方法、getter、setter
|
||||
// public SpecificTimeRequest() {}
|
||||
//
|
||||
// // getter 和 setter 方法
|
||||
// public String getTaskId() { return taskId; }
|
||||
// public void setTaskId(String taskId) { this.taskId = taskId; }
|
||||
//
|
||||
// public String getTaskName() { return taskName; }
|
||||
// public void setTaskName(String taskName) { this.taskName = taskName; }
|
||||
//
|
||||
// public String getTaskData() { return taskData; }
|
||||
// public void setTaskData(String taskData) { this.taskData = taskData; }
|
||||
//
|
||||
// public String getCronExpression() { return cronExpression; }
|
||||
// public void setCronExpression(String cronExpression) { this.cronExpression = cronExpression; }
|
||||
//
|
||||
// public LocalDateTime getSpecificDateTime() { return specificDateTime; }
|
||||
// public void setSpecificDateTime(LocalDateTime specificDateTime) { this.specificDateTime = specificDateTime; }
|
||||
//
|
||||
// public String getTime() { return time; }
|
||||
// public void setTime(String time) { this.time = time; }
|
||||
//
|
||||
// public Integer getDelaySeconds() { return delaySeconds; }
|
||||
// public void setDelaySeconds(Integer delaySeconds) { this.delaySeconds = delaySeconds; }
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.tongran.agent.client.scheduler.task;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
||||
import org.springframework.scheduling.config.CronTask;
|
||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
||||
@Data
|
||||
@Configuration
|
||||
public class SpecificTimeTaskConfig implements SchedulingConfigurer {
|
||||
|
||||
private ScheduledTaskRegistrar taskRegistrar;
|
||||
private final Map<String, ScheduledFuture<?>> taskFutures = new ConcurrentHashMap<>();
|
||||
private final Map<String, Object> taskMap = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
|
||||
this.taskRegistrar = taskRegistrar;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加Cron表达式任务
|
||||
*/
|
||||
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.getTrigger()
|
||||
);
|
||||
|
||||
taskMap.put(taskId, cronTask);
|
||||
taskFutures.put(taskId, future);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加指定日期时间任务(只执行一次)
|
||||
*/
|
||||
public void addSpecificDateTimeTask(String taskId, Runnable task, LocalDateTime dateTime) {
|
||||
removeTaskIfExists(taskId);
|
||||
|
||||
Date startTime = Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant());
|
||||
long delay = startTime.getTime() - System.currentTimeMillis();
|
||||
|
||||
if (delay < 0) {
|
||||
throw new IllegalArgumentException("指定时间不能是过去时间");
|
||||
}
|
||||
|
||||
ScheduledFuture<?> future = taskRegistrar.getScheduler().schedule(
|
||||
task,
|
||||
new Date(System.currentTimeMillis() + delay)
|
||||
);
|
||||
|
||||
taskMap.put(taskId, "ONCE_" + dateTime.toString());
|
||||
taskFutures.put(taskId, future);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加每天固定时间任务
|
||||
*/
|
||||
public void addDailyTimeTask(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);
|
||||
addCronTask(taskId, task, cronExpression);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加延迟任务
|
||||
*/
|
||||
public void addDelayTask(String taskId, Runnable task, int delaySeconds) {
|
||||
removeTaskIfExists(taskId);
|
||||
|
||||
ScheduledFuture<?> future = taskRegistrar.getScheduler().schedule(
|
||||
task,
|
||||
new Date(System.currentTimeMillis() + delaySeconds * 1000L)
|
||||
);
|
||||
|
||||
taskMap.put(taskId, "DELAY_" + delaySeconds);
|
||||
taskFutures.put(taskId, future);
|
||||
}
|
||||
|
||||
private void removeTaskIfExists(String taskId) {
|
||||
if (taskFutures.containsKey(taskId)) {
|
||||
taskFutures.get(taskId).cancel(true);
|
||||
taskFutures.remove(taskId);
|
||||
taskMap.remove(taskId);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeTask(String taskId) {
|
||||
removeTaskIfExists(taskId);
|
||||
}
|
||||
|
||||
public boolean containsTask(String taskId) {
|
||||
return taskMap.containsKey(taskId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package com.tongran.agent.client.scheduler.task;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.tongran.agent.client.core.config.ApplicationProperties;
|
||||
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.AsyncCommandExecutor;
|
||||
import com.tongran.agent.client.utils.AgentDataUtil;
|
||||
import com.tongran.agent.client.utils.AgentUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Service
|
||||
public class SpecificTimeTaskService {
|
||||
|
||||
protected final SessionManager sessionManager;
|
||||
|
||||
@Resource
|
||||
private ApplicationProperties properties;
|
||||
|
||||
@Resource
|
||||
private SpecificTimeTaskConfig taskConfig;
|
||||
|
||||
public SpecificTimeTaskService() {
|
||||
this.sessionManager = SessionManager.getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建指定时间任务
|
||||
*/
|
||||
public boolean createSpecificTimeTask(SpecificTimeRequest request) {
|
||||
String taskId = request.getTaskId() != null ?
|
||||
request.getTaskId() : "task-" + System.currentTimeMillis();
|
||||
|
||||
Runnable task = createTaskRunnable(request);
|
||||
|
||||
try {
|
||||
if (request.getCronExpression() != null) {
|
||||
taskConfig.addCronTask(taskId, task, request.getCronExpression());
|
||||
} else if (request.getSpecificDateTime() != null) {
|
||||
taskConfig.addSpecificDateTimeTask(taskId, task, request.getSpecificDateTime());
|
||||
} else if (request.getTime() != null) {
|
||||
taskConfig.addDailyTimeTask(taskId, task, request.getTime());
|
||||
} else if (request.getDelaySeconds() != null) {
|
||||
taskConfig.addDelayTask(taskId, task, request.getDelaySeconds());
|
||||
} else {
|
||||
throw new IllegalArgumentException("必须指定一种时间方式");
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("创建任务失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
||||
private void executeBusinessLogic(SpecificTimeRequest request) {
|
||||
// 实现你的业务逻辑
|
||||
try {
|
||||
System.out.println("处理业务: " + request.getTaskData());
|
||||
String key = request.getTaskName()+"-"+System.currentTimeMillis();
|
||||
if(StringUtils.equals(request.getDataType(), MsgEnum.Agent版本更新应答.getValue())){
|
||||
try {
|
||||
//设置检查回滚任务
|
||||
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(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 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.tongran.agent.client.service;
|
||||
|
||||
import com.tongran.agent.client.core.eo.ScriptPolicyEO;
|
||||
|
||||
public interface AgentService {
|
||||
void cancelCollect();
|
||||
|
||||
void systemCollectStop();
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.tongran.agent.client.service;
|
||||
|
||||
import com.tongran.agent.client.core.vo.AlarmVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface AlarmService {
|
||||
List<AlarmVO> alarmMonitor(long timestamp);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.tongran.agent.client.service;
|
||||
|
||||
import com.tongran.agent.client.core.vo.CpuVO;
|
||||
|
||||
public interface CPUService {
|
||||
CpuVO get();
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.tongran.agent.client.service;
|
||||
|
||||
import com.tongran.agent.client.core.vo.DiskVO;
|
||||
import com.tongran.agent.client.core.vo.PointVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface DiskService {
|
||||
List<DiskVO> diskList(long timestamp);
|
||||
|
||||
List<PointVO> pointList(long timestamp);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.tongran.agent.client.service;
|
||||
|
||||
import com.tongran.agent.client.core.vo.DockerVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface DockerService {
|
||||
List<DockerVO> dockerList(long timestamp);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.tongran.agent.client.service;
|
||||
|
||||
import com.tongran.agent.client.core.vo.MemoryVO;
|
||||
|
||||
public interface MemoryService {
|
||||
MemoryVO get();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.tongran.agent.client.service;
|
||||
|
||||
import com.tongran.agent.client.core.vo.NetVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface NetService {
|
||||
List<NetVO> netList(long timestamp);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.tongran.agent.client.service;
|
||||
|
||||
import com.tongran.agent.client.core.vo.SwitchBoardVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface SwitchBoardService {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.tongran.agent.client.service;
|
||||
|
||||
import com.tongran.agent.client.core.vo.SystemVO;
|
||||
|
||||
public interface SystemService {
|
||||
SystemVO get();
|
||||
|
||||
String otherSystem(String type);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,486 @@
|
||||
package com.tongran.agent.client.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.github.dockerjava.api.DockerClient;
|
||||
import com.github.dockerjava.api.model.Container;
|
||||
import com.github.dockerjava.core.DefaultDockerClientConfig;
|
||||
import com.github.dockerjava.core.DockerClientImpl;
|
||||
import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
|
||||
import com.github.dockerjava.transport.DockerHttpClient;
|
||||
import com.tongran.agent.client.core.config.GlobalConfig;
|
||||
import com.tongran.agent.client.core.eo.AlarmEO;
|
||||
import com.tongran.agent.client.core.eo.NativeNetworkInterfaceEO;
|
||||
import com.tongran.agent.client.core.vo.AlarmVO;
|
||||
import com.tongran.agent.client.service.AlarmService;
|
||||
import com.tongran.agent.client.utils.AgentDataUtil;
|
||||
import com.tongran.agent.client.utils.AssertLog;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import oshi.SystemInfo;
|
||||
import oshi.hardware.CentralProcessor;
|
||||
import oshi.hardware.HardwareAbstractionLayer;
|
||||
import oshi.hardware.NetworkIF;
|
||||
import oshi.software.os.OSFileStore;
|
||||
import oshi.software.os.OperatingSystem;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class AlarmServiceImpl implements AlarmService {
|
||||
@Override
|
||||
public List<AlarmVO> alarmMonitor(long timestamp) {
|
||||
AssertLog.info("告警监控定时任务执行alarmMonitor()");
|
||||
List<AlarmVO> alarmVOList = new ArrayList<>();
|
||||
List<AlarmEO> list = GlobalConfig.ALARM_LIST.stream().filter(a -> a.isCollect() == true).collect(Collectors.toList());
|
||||
if(CollectionUtil.isNotEmpty(list)){
|
||||
for (AlarmEO alarmEO : list) {
|
||||
String type = alarmEO.getType();
|
||||
String threshold = alarmEO.getThreshold();
|
||||
int compareType = alarmEO.getCompareType();
|
||||
AlarmVO alarmVO = AlarmVO.builder().type(type).timestamp(timestamp).build();
|
||||
String result = "";
|
||||
switch(type) {
|
||||
case "systemCpuUti": //CPU使用率
|
||||
result = handleSystemCpuUti(threshold, compareType);
|
||||
if(StringUtils.isNotBlank(result)){
|
||||
alarmVO.setResult(result);
|
||||
alarmVOList.add(alarmVO);
|
||||
}
|
||||
break;
|
||||
case "memoryUtilization": //内存利用率
|
||||
result = handleMemoryUtilization(threshold, compareType);
|
||||
if(StringUtils.isNotBlank(result)){
|
||||
alarmVO.setResult(result);
|
||||
alarmVOList.add(alarmVO);
|
||||
}
|
||||
break;
|
||||
case "systemSwapSizePercent": //可用交换空间百分比
|
||||
result = handleSystemSwapSizePercent(threshold, compareType);
|
||||
if(StringUtils.isNotBlank(result)){
|
||||
alarmVO.setResult(result);
|
||||
alarmVOList.add(alarmVO);
|
||||
}
|
||||
break;
|
||||
case "systemUsersNum": //登录用户数
|
||||
result = handleSystemUsersNum(threshold, compareType);
|
||||
if(StringUtils.isNotBlank(result)){
|
||||
alarmVO.setResult(result);
|
||||
alarmVOList.add(alarmVO);
|
||||
}
|
||||
break;
|
||||
case "vfsFsUtil": //挂载点的空间利用率
|
||||
result = handleVfsFsUtil(threshold, compareType);
|
||||
if(StringUtils.isNotBlank(result)){
|
||||
alarmVO.setResult(result);
|
||||
alarmVOList.add(alarmVO);
|
||||
}
|
||||
break;
|
||||
case "netUtil": //网络带宽使用率
|
||||
result = handleNetUtil(threshold, compareType);
|
||||
if(StringUtils.isNotBlank(result)){
|
||||
alarmVO.setResult(result);
|
||||
alarmVOList.add(alarmVO);
|
||||
}
|
||||
break;
|
||||
case "containerMemUtil": //容器内存使用率
|
||||
result = handleContainerMemUtil(threshold, compareType);
|
||||
if(StringUtils.isNotBlank(result)){
|
||||
alarmVO.setResult(result);
|
||||
alarmVOList.add(alarmVO);
|
||||
}
|
||||
break;
|
||||
case "netIfStatus": //网络运行状态UP变down
|
||||
result = handleNetIfStatus();
|
||||
if(StringUtils.isNotBlank(result)){
|
||||
alarmVO.setResult(result);
|
||||
alarmVOList.add(alarmVO);
|
||||
}
|
||||
break;
|
||||
case "extraPorts": //多余端口
|
||||
result = handleExtraPorts(threshold);
|
||||
if(StringUtils.isNotBlank(result)){
|
||||
alarmVO.setResult(result);
|
||||
alarmVOList.add(alarmVO);
|
||||
}
|
||||
break;
|
||||
default: //其他
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return alarmVOList;
|
||||
}
|
||||
|
||||
private String handleSystemCpuUti(String threshold, int compareType){
|
||||
try {
|
||||
SystemInfo si = new SystemInfo();
|
||||
HardwareAbstractionLayer hal = si.getHardware();
|
||||
CentralProcessor processor = hal.getProcessor();
|
||||
long[] prevTicks = processor.getSystemCpuLoadTicks();
|
||||
try {
|
||||
TimeUnit.SECONDS.sleep(1); // 等待1秒
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
// 计算使用率
|
||||
double cpuUsage = processor.getSystemCpuLoadBetweenTicks(prevTicks) * 100;
|
||||
boolean isFalse = false;
|
||||
if(StringUtils.isNotBlank(threshold)){
|
||||
isFalse = compare(cpuUsage, threshold, compareType);
|
||||
}
|
||||
if(isFalse){
|
||||
return "当前CPU使用率:"+cpuUsage +"%";
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private String handleMemoryUtilization(String threshold, int compareType){
|
||||
try {
|
||||
Map<String, Long> memInfo = AgentDataUtil.parseMemInfo();
|
||||
long total = memInfo.get("MemTotal");
|
||||
long available = memInfo.getOrDefault("MemAvailable",
|
||||
memInfo.get("MemFree") +
|
||||
memInfo.getOrDefault("Buffers", 0L) +
|
||||
memInfo.getOrDefault("Cached", 0L) +
|
||||
memInfo.getOrDefault("SReclaimable", 0L));
|
||||
// 总内存使用率
|
||||
double totalUsage = (double)(total - available) / total * 100;
|
||||
boolean isFalse = false;
|
||||
if(StringUtils.isNotBlank(threshold)){
|
||||
isFalse = compare(totalUsage, threshold, compareType);
|
||||
}
|
||||
if(isFalse){
|
||||
return "当前内存利用率:"+totalUsage +"%";
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private String handleSystemSwapSizePercent(String threshold, int compareType){
|
||||
try {
|
||||
Map<String, Long> memInfo = AgentDataUtil.parseMemInfo();
|
||||
long swapTotal = memInfo.getOrDefault("SwapTotal", 0L);
|
||||
long swapFree = memInfo.getOrDefault("SwapFree", 0L);
|
||||
double swapSizePercent = (double) swapFree / swapTotal * 100; //可用交换空间百分比
|
||||
boolean isFalse = false;
|
||||
if(StringUtils.isNotBlank(threshold)){
|
||||
isFalse = compare(swapSizePercent, threshold, compareType);
|
||||
}
|
||||
if(isFalse){
|
||||
return "当前可用交换空间百分比:"+swapSizePercent +"%";
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private String handleSystemUsersNum(String threshold, int compareType){
|
||||
SystemInfo si = new SystemInfo();
|
||||
OperatingSystem os = si.getOperatingSystem();
|
||||
int userNum = os.getSessions().size();
|
||||
boolean isFalse = false;
|
||||
if(StringUtils.isNotBlank(threshold)){
|
||||
isFalse = compare(userNum, threshold, compareType);
|
||||
}
|
||||
if(isFalse){
|
||||
return "当前登录用户数:"+userNum;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private String handleVfsFsUtil(String threshold, int compareType){
|
||||
String result = "";
|
||||
SystemInfo si = new SystemInfo();
|
||||
for (OSFileStore fs : si.getOperatingSystem().getFileSystem().getFileStores()) {
|
||||
String mount = fs.getMount();
|
||||
long totalSpace = fs.getTotalSpace();
|
||||
long freeSpace = fs.getFreeSpace();
|
||||
double usagePercentage = totalSpace > 0 ?
|
||||
(double) (totalSpace - freeSpace) / totalSpace * 100 : 0;
|
||||
if(StringUtils.isNotBlank(threshold)){
|
||||
boolean isFalse = compare(usagePercentage, threshold, compareType);
|
||||
if(isFalse){
|
||||
result +="挂载点:"+mount+",当前空间利用率:"+usagePercentage+"%;";
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String handleNetUtil(String threshold, int compareType){
|
||||
String result = "";
|
||||
try {
|
||||
SystemInfo si = new SystemInfo();
|
||||
HardwareAbstractionLayer hal = si.getHardware();
|
||||
// 获取所有网络接口
|
||||
List<NetworkIF> networkIFs = hal.getNetworkIFs();
|
||||
// 第一次采样
|
||||
for (NetworkIF net : networkIFs) {
|
||||
net.updateAttributes();
|
||||
}
|
||||
// 等待1秒
|
||||
TimeUnit.SECONDS.sleep(1);
|
||||
// 第二次采样并计算速率
|
||||
for (NetworkIF net : networkIFs) {
|
||||
// 只显示以太网接口
|
||||
if (net.getName().startsWith("eth") ||
|
||||
net.getName().startsWith("en") ||
|
||||
net.getDisplayName().toLowerCase().contains("ethernet")) {
|
||||
long prevBytesSent = net.getBytesSent();
|
||||
net.updateAttributes();
|
||||
long bytesSent = net.getBytesSent() - prevBytesSent;
|
||||
// 获取最大带宽
|
||||
double maxBandwidth = getInterfaceMaxBandwidth(net);
|
||||
// 计算使用率百分比
|
||||
double sentUsagePercentage = (bytesToMbps(bytesSent) / maxBandwidth) * 100;
|
||||
boolean isFalse = false;
|
||||
if(StringUtils.isNotBlank(threshold)){
|
||||
isFalse = compare(sentUsagePercentage, threshold, compareType);
|
||||
}
|
||||
if(isFalse){
|
||||
result += "网络接口:"+ net.getName() + ",当前发送流量带宽使用率:" + sentUsagePercentage +"%;";
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String handleContainerMemUtil(String threshold, int compareType){
|
||||
// 配置Docker客户端
|
||||
DefaultDockerClientConfig config = DefaultDockerClientConfig.createDefaultConfigBuilder()
|
||||
.withDockerHost("unix:///var/run/docker.sock")
|
||||
.withDockerTlsVerify(false) // 根据你的配置调整
|
||||
.build();
|
||||
DockerHttpClient httpClient = new ApacheDockerHttpClient.Builder()
|
||||
.dockerHost(config.getDockerHost())
|
||||
.sslConfig(config.getSSLConfig())
|
||||
.maxConnections(100)
|
||||
.connectionTimeout(Duration.ofSeconds(30))
|
||||
.responseTimeout(Duration.ofSeconds(45))
|
||||
.build();
|
||||
DockerClient dockerClient = DockerClientImpl.getInstance(config, httpClient);
|
||||
String result = "";
|
||||
try {
|
||||
// 获取正在运行的容器列表
|
||||
List<Container> containers = dockerClient.listContainersCmd()
|
||||
.withShowAll(false) // 只显示运行中的容器
|
||||
.exec();
|
||||
// 打印容器信息
|
||||
System.out.println("运行中的Docker容器:");
|
||||
System.out.println("容器ID\t\t镜像\t\t状态\t\t名称");
|
||||
for (Container container : containers) {
|
||||
String id = container.getId().substring(0, 12); // 只显示短ID
|
||||
String name = container.getNames()[0].replaceFirst("/", "");
|
||||
// 执行 docker stats 命令(--no-stream 表示只输出一次)
|
||||
Process process = new ProcessBuilder(
|
||||
"docker", "stats", "--no-stream", id,
|
||||
"--format", "'table {{.ID}}\t{{.Name}}\t{{.CPUPerc}}\t{{.MemPerc}}\t{{.NetIO}}"
|
||||
).start();
|
||||
// 读取命令输出
|
||||
BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(process.getInputStream())
|
||||
);
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
String[] parts = line.trim().split("\\s+");
|
||||
String memUtil = parts[4]; // 内存使用率
|
||||
memUtil = memUtil.replace("%","");
|
||||
if(StringUtils.isNotBlank(memUtil)){
|
||||
boolean isFalse = false;
|
||||
if(StringUtils.isNotBlank(threshold)){
|
||||
isFalse = compare(Double.parseDouble(memUtil), threshold, compareType);
|
||||
}
|
||||
if(isFalse){
|
||||
result += "容器:"+name+",当前内存使用率"+memUtil+"%;";
|
||||
}
|
||||
}
|
||||
}
|
||||
// 等待命令执行完成并获取退出码
|
||||
int exitCode = process.waitFor();
|
||||
if (exitCode != 0) {
|
||||
System.err.println("命令执行失败,退出码: " + exitCode);
|
||||
}
|
||||
reader.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
dockerClient.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String handleNetIfStatus(){
|
||||
String result = "";
|
||||
try {
|
||||
List<NativeNetworkInterfaceEO> list = getAllNetworkInterfaces();
|
||||
if(CollectionUtil.isNotEmpty(GlobalConfig.NET_LIST)){
|
||||
List<String> natList = list.stream().filter(n -> n.isUp() == false).map(NativeNetworkInterfaceEO::getName).collect(Collectors.toList());
|
||||
List<NativeNetworkInterfaceEO> temp = GlobalConfig.NET_LIST.stream().filter(n -> n.isUp())
|
||||
.collect(Collectors.toList());
|
||||
for (NativeNetworkInterfaceEO n : temp) {
|
||||
if(natList.contains(n.getName())){
|
||||
result += "网络接口:"+n.getName()+",运行状态由UP转为DOWN;";
|
||||
}
|
||||
}
|
||||
}
|
||||
GlobalConfig.NET_LIST = list;
|
||||
} catch (SocketException e) {
|
||||
System.err.println("获取网络接口信息失败: " + e.getMessage());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String handleExtraPorts(String threshold){
|
||||
String result = "";
|
||||
try {
|
||||
List<Integer> list = scanPortsUsingNmap();
|
||||
for (Integer port : list) {
|
||||
boolean flag = verifyPort(port, threshold);
|
||||
if(!flag){
|
||||
result += "端口:"+port+"已开放;";
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean compare(double value, String threshold, int compareType){
|
||||
boolean isFalse = false;
|
||||
switch(compareType) {
|
||||
case 1: //大于且等于
|
||||
if(value >= Double.parseDouble(threshold)){
|
||||
isFalse = true;
|
||||
}
|
||||
break;
|
||||
case 2: //小于
|
||||
if(value < Double.parseDouble(threshold)){
|
||||
isFalse = true;
|
||||
}
|
||||
break;
|
||||
case 3: //小于且等于
|
||||
if(value <= Double.parseDouble(threshold)){
|
||||
isFalse = true;
|
||||
}
|
||||
break;
|
||||
default: //大于
|
||||
if(value > Double.parseDouble(threshold)){
|
||||
isFalse = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return isFalse;
|
||||
}
|
||||
|
||||
private static double bytesToMbps(long bytes) {
|
||||
return bytes * 8.0 / 1_000_000; // bytes to megabits
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取接口的理论最大带宽(Mbps)
|
||||
*/
|
||||
private double getInterfaceMaxBandwidth(NetworkIF netIf) {
|
||||
String name = netIf.getName().toLowerCase();
|
||||
long speed = netIf.getSpeed(); // 获取接口速度(bps)
|
||||
|
||||
if (speed > 0) {
|
||||
return speed / 1_000_000.0; // 转换为Mbps
|
||||
}
|
||||
|
||||
// 如果无法获取速度,根据常见接口类型估算
|
||||
if (name.contains("eth") || name.contains("en") || name.contains("gigabit")) {
|
||||
return 1000.0; // 千兆以太网
|
||||
} else if (name.contains("10g") || name.contains("10000")) {
|
||||
return 10000.0; // 万兆以太网
|
||||
} else {
|
||||
return 100.0; // 默认百兆以太网
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取所有网络接口信息
|
||||
*/
|
||||
public List<NativeNetworkInterfaceEO> getAllNetworkInterfaces() throws SocketException {
|
||||
List<NativeNetworkInterfaceEO> interfaces = new ArrayList<>();
|
||||
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
|
||||
while (networkInterfaces.hasMoreElements()) {
|
||||
NetworkInterface ni = networkInterfaces.nextElement();
|
||||
NativeNetworkInterfaceEO interfaceInfo = NativeNetworkInterfaceEO.builder()
|
||||
.name(ni.getName())
|
||||
.displayName(ni.getDisplayName())
|
||||
.up(ni.isUp())
|
||||
.build();
|
||||
interfaces.add(interfaceInfo);
|
||||
}
|
||||
return interfaces;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有开放端口
|
||||
*/
|
||||
public List<Integer> scanPortsUsingNmap() throws Exception {
|
||||
List<Integer> openPorts = new ArrayList<>();
|
||||
Process process = Runtime.getRuntime().exec("nmap -p 1-65535 localhost");
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||
String line;
|
||||
Pattern pattern = Pattern.compile("\\d+/tcp open"); // Adjust the regex based on nmap output format you expect
|
||||
while ((line = reader.readLine()) != null) {
|
||||
Matcher matcher = pattern.matcher(line);
|
||||
if (matcher.find()) { // Assuming the output format is "port/protocol state" where state is "open" for open ports
|
||||
String portStr = matcher.group(0).split("/")[0]; // Extract the port number part before "/tcp" or "/udp" etc.
|
||||
openPorts.add(Integer.parseInt(portStr)); // Add the port number to the list of open ports
|
||||
}
|
||||
}
|
||||
reader.close();
|
||||
return openPorts;
|
||||
}
|
||||
|
||||
public boolean verifyPort(int port, String threshold){
|
||||
String[] ports = threshold.split(";");
|
||||
for (int i = 0; i < ports.length; i++) {
|
||||
String p = ports[i];
|
||||
int start = 0;
|
||||
int end = 0;
|
||||
String[] arr = p.split("-");
|
||||
start = Integer.parseInt(arr[0]);
|
||||
if(arr.length == 2){
|
||||
end = Integer.parseInt(arr[1]);
|
||||
}
|
||||
if(start <= port && port <= end){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
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.software.os.OperatingSystem;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Service
|
||||
public class CPUServiceImpl implements CPUService {
|
||||
|
||||
@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数量
|
||||
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");
|
||||
// 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分钟负载
|
||||
System.out.println("1分钟平均负载: " + loadAverage[0]);
|
||||
System.out.println("5分钟平均负载: " + loadAverage[1]);
|
||||
System.out.println("15分钟平均负载: " + loadAverage[2]);
|
||||
// 3. CPU使用率(需要两次采样)
|
||||
AssertLog.info("\n=== CPU使用率 ===");
|
||||
// System.out.println("\n=== CPU使用率 ===");
|
||||
|
||||
// 预热:第一次调用可能不准
|
||||
processor.getSystemCpuLoadBetweenTicks(processor.getSystemCpuLoadTicks());
|
||||
TimeUnit.SECONDS.sleep(1);
|
||||
// 正式采样
|
||||
long[] prevTicks = processor.getSystemCpuLoadTicks();
|
||||
TimeUnit.SECONDS.sleep(1); // 更长间隔,减少抖动
|
||||
// 计算使用率
|
||||
double cpuUsage = processor.getSystemCpuLoadBetweenTicks(prevTicks);
|
||||
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);
|
||||
// System.out.printf("CPU 使用率: %.2f%%\n", cpuUsage * 100);
|
||||
long[] ticks = processor.getSystemCpuLoadTicks();
|
||||
long user = ticks[CentralProcessor.TickType.USER.getIndex()] -
|
||||
prevTicks[CentralProcessor.TickType.USER.getIndex()];
|
||||
long nice = ticks[CentralProcessor.TickType.NICE.getIndex()] -
|
||||
prevTicks[CentralProcessor.TickType.NICE.getIndex()];
|
||||
long sys = ticks[CentralProcessor.TickType.SYSTEM.getIndex()] -
|
||||
prevTicks[CentralProcessor.TickType.SYSTEM.getIndex()];
|
||||
long idle = ticks[CentralProcessor.TickType.IDLE.getIndex()] -
|
||||
prevTicks[CentralProcessor.TickType.IDLE.getIndex()];
|
||||
long iowait = ticks[CentralProcessor.TickType.IOWAIT.getIndex()] -
|
||||
prevTicks[CentralProcessor.TickType.IOWAIT.getIndex()];
|
||||
long irq = ticks[CentralProcessor.TickType.IRQ.getIndex()] -
|
||||
prevTicks[CentralProcessor.TickType.IRQ.getIndex()];
|
||||
long softirq = ticks[CentralProcessor.TickType.SOFTIRQ.getIndex()] -
|
||||
prevTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()];
|
||||
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("中断累计时间: " + 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.println("软件相关时间(近似无响应时间): " + AgentUtil.ticksToSeconds(softwareTime));
|
||||
System.out.println("用户进程累计时间: " + AgentUtil.ticksToSeconds(allTicks[CentralProcessor.TickType.USER.getIndex()]));
|
||||
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正常运行时间:秒
|
||||
System.out.println("CPU正常运行时间: " + cpuVO.getNormal());
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return cpuVO;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.tongran.agent.client.service.impl;
|
||||
|
||||
import com.tongran.agent.client.core.vo.DiskVO;
|
||||
import com.tongran.agent.client.core.vo.PointVO;
|
||||
import com.tongran.agent.client.service.DiskService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import oshi.SystemInfo;
|
||||
import oshi.hardware.HWDiskStore;
|
||||
import oshi.software.os.OSFileStore;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@Service
|
||||
public class DiskServiceImpl implements DiskService {
|
||||
@Override
|
||||
public List<DiskVO> diskList(long timestamp) {
|
||||
List<DiskVO> tempList = new ArrayList<>();
|
||||
List<DiskVO> resultList = new ArrayList<>();
|
||||
SystemInfo si = new SystemInfo();
|
||||
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());//磁盘读取字节
|
||||
tempList.add(diskVO);
|
||||
|
||||
System.out.println("磁盘名称: " + diskVO.getName());
|
||||
System.out.println("序列号: " + diskVO.getSerial());
|
||||
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();
|
||||
long[] readBytes1 = new long[disks1.size()];
|
||||
long[] writeBytes1 = new long[disks1.size()];
|
||||
for (int i = 0; i < disks1.size(); i++) {
|
||||
readBytes1[i] = disks1.get(i).getReadBytes();
|
||||
writeBytes1[i] = disks1.get(i).getWriteBytes();
|
||||
}
|
||||
// 等待1秒
|
||||
Thread.sleep(1000);
|
||||
// 第二次采样
|
||||
List<HWDiskStore> disks2 = si.getHardware().getDiskStores();
|
||||
for (int i = 0; i < disks2.size(); i++) {
|
||||
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);
|
||||
if(Objects.nonNull(diskVO)){
|
||||
diskVO.setWriteSpeed(readDiff);//磁盘写入速率
|
||||
diskVO.setReadSpeed(writeDiff);//磁盘读取速率
|
||||
System.out.println("磁盘名称: " + diskVO.getName());
|
||||
System.out.println("磁盘写入速率: " + diskVO.getWriteSpeed());
|
||||
System.out.println("磁盘读取速率: " + diskVO.getReadSpeed());
|
||||
}
|
||||
resultList.add(diskVO);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return resultList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PointVO> pointList(long timestamp) {
|
||||
List<PointVO> list = new ArrayList<>();
|
||||
SystemInfo si = new SystemInfo();
|
||||
// 获取文件系统信息
|
||||
System.out.println("=========================================================");
|
||||
System.out.println("=== 挂载信息 ===");
|
||||
for (OSFileStore fs : si.getOperatingSystem().getFileSystem().getFileStores()) {
|
||||
long totalSpace = fs.getTotalSpace();
|
||||
long usableSpace = fs.getUsableSpace();
|
||||
long freeSpace = fs.getFreeSpace();
|
||||
double usagePercentage = totalSpace > 0 ?
|
||||
(double) (totalSpace - freeSpace) / totalSpace * 100 : 0;
|
||||
|
||||
PointVO pointVO = PointVO.builder().timestamp(timestamp).build();
|
||||
pointVO.setMount(fs.getMount());//挂载点
|
||||
pointVO.setVfsType(fs.getType());//文件系统类型
|
||||
pointVO.setVfsTotal(totalSpace);//总空间
|
||||
pointVO.setVfsFree(usableSpace);//可用空间
|
||||
pointVO.setVfsUtil(usagePercentage);//空间利用率
|
||||
list.add(pointVO);
|
||||
System.out.println("挂载点: " + pointVO.getMount());
|
||||
System.out.println("文件系统类型: " + pointVO.getVfsType());
|
||||
System.out.println("总空间: " + pointVO.getVfsTotal());
|
||||
System.out.println("可用空间: " + pointVO.getVfsFree());
|
||||
System.out.printf("空间利用率: %.2f%%\n", usagePercentage);
|
||||
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package com.tongran.agent.client.service.impl;
|
||||
|
||||
import com.github.dockerjava.api.DockerClient;
|
||||
import com.github.dockerjava.api.model.Container;
|
||||
import com.github.dockerjava.core.DefaultDockerClientConfig;
|
||||
import com.github.dockerjava.core.DockerClientImpl;
|
||||
import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
|
||||
import com.github.dockerjava.transport.DockerHttpClient;
|
||||
import com.tongran.agent.client.core.vo.DockerVO;
|
||||
import com.tongran.agent.client.service.DockerService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class DockerServiceImpl implements DockerService {
|
||||
@Override
|
||||
public List<DockerVO> dockerList(long timestamp) {
|
||||
List<DockerVO> list = new ArrayList<>();
|
||||
// 配置Docker客户端
|
||||
DefaultDockerClientConfig config = DefaultDockerClientConfig.createDefaultConfigBuilder()
|
||||
.withDockerHost("unix:///var/run/docker.sock")
|
||||
.withDockerTlsVerify(false) // 根据你的配置调整
|
||||
.build();
|
||||
|
||||
DockerHttpClient httpClient = new ApacheDockerHttpClient.Builder()
|
||||
.dockerHost(config.getDockerHost())
|
||||
.sslConfig(config.getSSLConfig())
|
||||
.maxConnections(100)
|
||||
.connectionTimeout(Duration.ofSeconds(30))
|
||||
.responseTimeout(Duration.ofSeconds(45))
|
||||
.build();
|
||||
|
||||
DockerClient dockerClient = DockerClientImpl.getInstance(config, httpClient);
|
||||
try {
|
||||
// 获取正在运行的容器列表
|
||||
List<Container> containers = dockerClient.listContainersCmd()
|
||||
.withShowAll(false) // 只显示运行中的容器
|
||||
.exec();
|
||||
|
||||
// 打印容器信息
|
||||
System.out.println("运行中的Docker容器:");
|
||||
System.out.println("容器ID\t\t镜像\t\t状态\t\t名称");
|
||||
for (Container container : containers) {
|
||||
DockerVO dockerVO = DockerVO.builder().timestamp(timestamp).build();
|
||||
String id = container.getId().substring(0, 12); // 只显示短ID
|
||||
String image = container.getImage().length() > 15 ?
|
||||
container.getImage().substring(0, 15) + "..." : container.getImage();
|
||||
String status = container.getStatus();
|
||||
String name = container.getNames()[0].replaceFirst("/", "");
|
||||
System.out.printf("%s\t%s\t%s\t%s%n", id, image, status, name);
|
||||
dockerVO.setId(id);
|
||||
dockerVO.setName(name);
|
||||
dockerVO.setStatus(status);
|
||||
DockerVO res = dockerStats(dockerVO);
|
||||
list.add(res);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
dockerClient.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public DockerVO dockerStats(DockerVO dockerVO) {
|
||||
//判定目标容器 ID
|
||||
if(StringUtils.isBlank(dockerVO.getId())){
|
||||
return dockerVO;
|
||||
}
|
||||
try {
|
||||
// 执行 docker stats 命令(--no-stream 表示只输出一次)
|
||||
Process process = new ProcessBuilder(
|
||||
"docker", "stats", "--no-stream", dockerVO.getId(),
|
||||
"--format", "'table {{.ID}}\t{{.Name}}\t{{.CPUPerc}}\t{{.MemPerc}}\t{{.NetIO}}"
|
||||
).start();
|
||||
// 读取命令输出
|
||||
BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(process.getInputStream())
|
||||
);
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
// 解析输出(示例输出格式):
|
||||
// "your_container_id 0.00% 0.000 CPU % 0 B / 0 B 0 packets / 0 packets"
|
||||
// 实际输出可能因 Docker 版本不同而变化,需根据实际情况调整正则表达式
|
||||
if (line.contains(dockerVO.getId())) {
|
||||
String[] parts = line.trim().split("\\s+");
|
||||
String cpuUtil = parts[3]; // cpu使用率
|
||||
String memUtil = parts[4]; // 内存使用率
|
||||
// 网络流量通常在第 4 列(接收)和第 5 列(发送),格式为 "B/s" 或 "B"
|
||||
dockerVO.setCpuUtil(cpuUtil);
|
||||
String rxRate = "0B";// 接收速率(如 "1.23kB/s")
|
||||
String txRate = "0B";// 发送速率(如 "4.56kB/s")
|
||||
//数据间有空格
|
||||
if(parts.length > 9){
|
||||
rxRate = parts[5]+parts[6];
|
||||
txRate = parts[8]+parts[9];
|
||||
}else{
|
||||
rxRate = parts[5];
|
||||
txRate = parts[7];
|
||||
|
||||
}
|
||||
// 网络流量通常在第 4 列(接收)和第 5 列(发送),格式为 "B/s" 或 "B"
|
||||
dockerVO.setCpuUtil(cpuUtil);
|
||||
dockerVO.setMemUtil(memUtil);
|
||||
dockerVO.setNetInSpeed(rxRate);
|
||||
dockerVO.setNetOutSpeed(txRate);
|
||||
|
||||
System.out.println("==================== 容器网络流量 ====================");
|
||||
System.out.println("接收速率: " + rxRate);
|
||||
System.out.println("发送速率: " + txRate);
|
||||
}
|
||||
}
|
||||
// 等待命令执行完成并获取退出码
|
||||
int exitCode = process.waitFor();
|
||||
if (exitCode != 0) {
|
||||
System.err.println("命令执行失败,退出码: " + exitCode);
|
||||
}
|
||||
reader.close();
|
||||
} catch (IOException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return dockerVO;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.tongran.agent.client.service.impl;
|
||||
|
||||
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.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class MemoryServiceImpl implements MemoryService {
|
||||
@Override
|
||||
public MemoryVO get() {
|
||||
MemoryVO memoryVO = MemoryVO.builder().build();
|
||||
try {
|
||||
Map<String, Long> memInfo = AgentDataUtil.parseMemInfo();
|
||||
System.out.println("=========================================================");
|
||||
// 1. 基本内存信息
|
||||
System.out.println("=== 基本内存信息 (单位KB) ===");
|
||||
System.out.println("总内存: " + memInfo.get("MemTotal"));
|
||||
System.out.println("空闲内存: " + memInfo.get("MemFree"));
|
||||
System.out.println("可用内存: " +
|
||||
memInfo.getOrDefault("MemAvailable",
|
||||
memInfo.get("MemFree") +
|
||||
memInfo.getOrDefault("Buffers", 0L) +
|
||||
memInfo.getOrDefault("Cached", 0L) +
|
||||
memInfo.getOrDefault("SReclaimable", 0L)));
|
||||
memoryVO.setAvailable(memInfo.getOrDefault("MemAvailable",
|
||||
memInfo.get("MemFree") +
|
||||
memInfo.getOrDefault("Buffers", 0L) +
|
||||
memInfo.getOrDefault("Cached", 0L) +
|
||||
memInfo.getOrDefault("SReclaimable", 0L))); //可用内存
|
||||
memoryVO.setTotal(memInfo.get("MemTotal")); //总内存
|
||||
memoryVO.setPercent((double) memoryVO.getAvailable() / memoryVO.getTotal() * 100); //可用内存百分比
|
||||
// 3. 交换空间信息
|
||||
System.out.println("\n=== 交换空间信息 ===");
|
||||
long swapTotal = memInfo.getOrDefault("SwapTotal", 0L);
|
||||
long swapFree = memInfo.getOrDefault("SwapFree", 0L);
|
||||
System.out.println("总交换空间: " + swapTotal);
|
||||
System.out.println("空闲交换空间: " + swapFree);
|
||||
System.out.println("已用交换空间: " + (swapTotal - swapFree));
|
||||
memoryVO.setSwapSizeFree(swapFree); //交换卷/文件的可用空间(字节)
|
||||
memoryVO.setSwapSizePercent((double) swapFree / swapTotal * 100); //可用交换空间百分比
|
||||
if (swapTotal > 0) {
|
||||
System.out.printf("交换空间使用率: %.2f%%\n",
|
||||
(double)(swapTotal - swapFree) / swapTotal * 100);
|
||||
}
|
||||
// 4. 内存使用率分析
|
||||
System.out.println("\n=== 内存使用率分析 ===");
|
||||
long total = memInfo.get("MemTotal");
|
||||
long available = memInfo.getOrDefault("MemAvailable",
|
||||
memInfo.get("MemFree") +
|
||||
memInfo.getOrDefault("Buffers", 0L) +
|
||||
memInfo.getOrDefault("Cached", 0L) +
|
||||
memInfo.getOrDefault("SReclaimable", 0L));
|
||||
// 总内存使用率
|
||||
double totalUsage = (double)(total - available) / total * 100;
|
||||
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(totalUsage); //内存利用率
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return memoryVO;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package com.tongran.agent.client.service.impl;
|
||||
|
||||
import com.tongran.agent.client.core.vo.NetVO;
|
||||
import com.tongran.agent.client.service.NetService;
|
||||
import com.tongran.agent.client.utils.AgentUtil;
|
||||
import org.springframework.stereotype.Service;
|
||||
import oshi.SystemInfo;
|
||||
import oshi.hardware.HardwareAbstractionLayer;
|
||||
import oshi.hardware.NetworkIF;
|
||||
import oshi.util.FormatUtil;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Service
|
||||
public class NetServiceImpl implements NetService {
|
||||
@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);
|
||||
}
|
||||
// 2. 实时带宽监控(需要两次采样)
|
||||
System.out.println("\n=== 实时带宽监控 ===");
|
||||
// 第一次采样
|
||||
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();
|
||||
long bytesSent = net.getBytesSent();
|
||||
// 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)");
|
||||
|
||||
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);
|
||||
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
|
||||
}
|
||||
|
||||
public static Map<String, String> getNetworkMode(String interfaceName) {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
ProcessBuilder pb = new ProcessBuilder("ethtool", interfaceName);
|
||||
pb.redirectErrorStream(true);
|
||||
try {
|
||||
Process process = pb.start();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||
String line;
|
||||
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (line.contains("Speed:")) {
|
||||
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);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
result.put("error", e.getMessage());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.tongran.agent.client.service.impl;
|
||||
|
||||
import com.tongran.agent.client.service.SwitchBoardService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class SwitchBoardServiceImpl implements SwitchBoardService {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,410 @@
|
||||
package com.tongran.agent.client.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
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;
|
||||
import oshi.hardware.HardwareAbstractionLayer;
|
||||
import oshi.software.os.OperatingSystem;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class SystemServiceImpl implements SystemService {
|
||||
@Override
|
||||
public SystemVO get() {
|
||||
SystemVO systemVO = SystemVO.builder().build();
|
||||
try {
|
||||
SystemInfo si = new SystemInfo();
|
||||
HardwareAbstractionLayer hal = si.getHardware();
|
||||
CentralProcessor processor = hal.getProcessor();
|
||||
OperatingSystem os = si.getOperatingSystem();
|
||||
|
||||
System.out.println("=========================================================");
|
||||
// 1. 操作系统基本信息
|
||||
System.out.println("=== 操作系统信息 ===");
|
||||
systemVO.setOs(os.getFamily()); //操作系统
|
||||
systemVO.setArch(os.getVersionInfo().getVersion() + " " + os.getBitness() + "位"); //操作系统架构
|
||||
systemVO.setUuid(AgentUtil.getMotherboardUUID());
|
||||
System.out.println("操作系统: " + systemVO.getOs());
|
||||
System.out.println("操作系统架构: " + systemVO.getArch());
|
||||
System.out.println("UUID: " + AgentUtil.getMotherboardUUID());
|
||||
|
||||
// 2. 进程信息
|
||||
System.out.println("\n=== 进程信息 ===");
|
||||
long maxProcesses = getMaxProcessesLinux();
|
||||
int runningProcesses = getRunningProcessesLinux();
|
||||
System.out.println("最大进程数: " + maxProcesses);
|
||||
System.out.println("正在运行的进程数: " + runningProcesses);
|
||||
systemVO.setMaxProc(maxProcesses); //最大进程数
|
||||
systemVO.setRunProcNum(runningProcesses); //正在运行的进程数
|
||||
|
||||
// 3. 登录用户数
|
||||
System.out.println("\n=== 登录用户 ===");
|
||||
systemVO.setUsersNum(os.getSessions().size()); //登录用户数
|
||||
System.out.println("登录用户数: " + systemVO.getUsersNum());
|
||||
|
||||
// 4. 磁盘信息
|
||||
System.out.println("\n=== 磁盘信息 ===");
|
||||
systemVO.setDiskSizeTotal(diskSpace()); //硬盘:总可用空间
|
||||
systemVO.setBootTime(systemBootTime(hal.getProcessor())); //系统启动时间
|
||||
systemVO.setUname(systemDescription(si)); //系统描述
|
||||
systemVO.setLocalTime(localTime()); //系统本地时间
|
||||
systemVO.setUpTime(systemUptime(os)); //系统正常运行时间
|
||||
System.out.println("硬盘:总可用空间: " + systemVO.getDiskSizeTotal());
|
||||
System.out.println("系统启动时间: " + systemVO.getBootTime());
|
||||
System.out.println("系统描述: " + systemVO.getUname());
|
||||
System.out.println("系统本地时间: " + systemVO.getLocalTime());
|
||||
System.out.println("系统正常运行时间: " + systemVO.getUpTime());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return systemVO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String otherSystem(String type) {
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("type",type);
|
||||
switch(type){
|
||||
case "systemSwapSizeFreeCollect":
|
||||
json.put("value", String.valueOf(handleSystemSwapSizeFree()));
|
||||
break;
|
||||
case "memoryUtilizationCollect":
|
||||
json.put("value", String.valueOf(handleMemoryUtilization()));
|
||||
break;
|
||||
case "systemSwapSizePercentCollect":
|
||||
json.put("value", String.valueOf(handleSystemSwapSizePercent()));
|
||||
break;
|
||||
case "memorySizeAvailableCollect":
|
||||
json.put("value", String.valueOf(handleMemorySizeAvailable()));
|
||||
break;
|
||||
case "memorySizePercentCollect":
|
||||
json.put("value", String.valueOf(handleMemorySizePercent()));
|
||||
break;
|
||||
case "memorySizeTotalCollect":
|
||||
json.put("value", String.valueOf(handleMemorySizeTotal()));
|
||||
break;
|
||||
case "systemSwOsCollect":
|
||||
json.put("value",handleSystemSwOs());
|
||||
break;
|
||||
case "systemSwArchCollect":
|
||||
json.put("value",handleSystemSwArch());
|
||||
break;
|
||||
case "kernelMaxprocCollect":
|
||||
json.put("value", String.valueOf(handleKernelMaxproc()));
|
||||
break;
|
||||
case "procNumRunCollect":
|
||||
json.put("value", String.valueOf(handleProcNumRun()));
|
||||
break;
|
||||
case "systemUsersNumCollect":
|
||||
json.put("value", String.valueOf(handleUsersNum()));
|
||||
break;
|
||||
case "systemDiskSizeTotalCollect":
|
||||
json.put("value", String.valueOf(handleSystemDiskSizeTotal()));
|
||||
break;
|
||||
case "systemBoottimeCollect":
|
||||
json.put("value", String.valueOf(handleSystemBoottime()));
|
||||
break;
|
||||
case "systemUnameCollect":
|
||||
json.put("value",handleSystemUname());
|
||||
break;
|
||||
case "systemLocaltimeCollect":
|
||||
json.put("value",handleSystemLocaltime());
|
||||
break;
|
||||
case "systemUptimeCollect":
|
||||
json.put("value", String.valueOf(handleSystemUptime()));
|
||||
break;
|
||||
case "procNumCollect":
|
||||
json.put("value", String.valueOf(handleProcNum()));
|
||||
break;
|
||||
default:
|
||||
json.put("value",handleDefault());
|
||||
break;
|
||||
}
|
||||
return json.toString();
|
||||
}
|
||||
|
||||
private long handleSystemSwapSizeFree(){
|
||||
try {
|
||||
Map<String, Long> memInfo = AgentDataUtil.parseMemInfo();
|
||||
long swapFree = memInfo.getOrDefault("SwapFree", 0L);
|
||||
System.out.println("=========================================================");
|
||||
System.out.println("交换卷/文件的可用空间(字节): " + swapFree);
|
||||
return swapFree;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return 0L;
|
||||
}
|
||||
|
||||
private double handleMemoryUtilization(){
|
||||
try {
|
||||
Map<String, Long> memInfo = AgentDataUtil.parseMemInfo();
|
||||
System.out.println("=========================================================");
|
||||
long total = memInfo.get("MemTotal");
|
||||
long available = memInfo.getOrDefault("MemAvailable",
|
||||
memInfo.get("MemFree") +
|
||||
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;
|
||||
double actualUsage = (double)(total - available) / total * 100;
|
||||
AssertLog.info("memoryUtilizationCollect,内存使用率:{}",actualUsage);
|
||||
System.out.printf("实际内存使用率: %.2f%%\n", actualUsage);
|
||||
return actualUsage;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private double handleSystemSwapSizePercent(){
|
||||
try {
|
||||
Map<String, Long> memInfo = AgentDataUtil.parseMemInfo();
|
||||
System.out.println("=========================================================");
|
||||
long swapTotal = memInfo.getOrDefault("SwapTotal", 0L);
|
||||
long swapFree = memInfo.getOrDefault("SwapFree", 0L);
|
||||
System.out.printf("交换空间使用率: %.2f%%\n",
|
||||
(double)(swapTotal - swapFree) / swapTotal * 100);
|
||||
return (double) swapFree / swapTotal * 100;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private long handleMemorySizeAvailable(){
|
||||
try {
|
||||
Map<String, Long> memInfo = AgentDataUtil.parseMemInfo();
|
||||
System.out.println("=========================================================");
|
||||
System.out.println("可用内存: " +
|
||||
memInfo.getOrDefault("MemAvailable",
|
||||
memInfo.get("MemFree") +
|
||||
memInfo.getOrDefault("Buffers", 0L) +
|
||||
memInfo.getOrDefault("Cached", 0L) +
|
||||
memInfo.getOrDefault("SReclaimable", 0L)));
|
||||
return memInfo.getOrDefault("MemAvailable",
|
||||
memInfo.get("MemFree") +
|
||||
memInfo.getOrDefault("Buffers", 0L) +
|
||||
memInfo.getOrDefault("Cached", 0L) +
|
||||
memInfo.getOrDefault("SReclaimable", 0L));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private double handleMemorySizePercent(){
|
||||
try {
|
||||
Map<String, Long> memInfo = AgentDataUtil.parseMemInfo();
|
||||
System.out.println("=========================================================");
|
||||
long available = memInfo.getOrDefault("MemAvailable",
|
||||
memInfo.get("MemFree") +
|
||||
memInfo.getOrDefault("Buffers", 0L) +
|
||||
memInfo.getOrDefault("Cached", 0L) +
|
||||
memInfo.getOrDefault("SReclaimable", 0L));
|
||||
long total = memInfo.get("MemTotal");
|
||||
System.out.println("可用内存百分比: " + (double) available / total * 100);
|
||||
return (double) available / total * 100;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private long handleMemorySizeTotal(){
|
||||
try {
|
||||
Map<String, Long> memInfo = AgentDataUtil.parseMemInfo();
|
||||
System.out.println("=========================================================");
|
||||
long total = memInfo.get("MemTotal");
|
||||
System.out.println("总内存: " + total);
|
||||
return total;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private String handleSystemSwOs(){
|
||||
SystemInfo si = new SystemInfo();
|
||||
OperatingSystem os = si.getOperatingSystem();
|
||||
System.out.println("=========================================================");
|
||||
// System.out.println("操作系统: " + os.getFamily());
|
||||
System.out.println("操作系统: " + os.toString());
|
||||
return os.toString();
|
||||
}
|
||||
|
||||
private String handleSystemSwArch(){
|
||||
SystemInfo si = new SystemInfo();
|
||||
OperatingSystem os = si.getOperatingSystem();
|
||||
HardwareAbstractionLayer hardware = si.getHardware();
|
||||
CentralProcessor processor = hardware.getProcessor();
|
||||
System.out.println("=========================================================");
|
||||
// String arch = os.getVersionInfo().getVersion() + " " + os.getBitness() + "位";
|
||||
String arch = os.getBitness() + "位";
|
||||
System.out.println("操作系统架构: " + arch);
|
||||
return arch;
|
||||
}
|
||||
|
||||
private long handleKernelMaxproc(){
|
||||
System.out.println("=========================================================");
|
||||
long maxProcesses = 0;
|
||||
try {
|
||||
maxProcesses = getMaxProcessesLinux();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
System.out.println("最大进程数: " + maxProcesses);
|
||||
return maxProcesses;
|
||||
}
|
||||
|
||||
private long handleProcNumRun(){
|
||||
System.out.println("=========================================================");
|
||||
int runningProcesses = getRunningProcessesLinux();
|
||||
System.out.println("正在运行的进程数: " + runningProcesses);
|
||||
return runningProcesses;
|
||||
}
|
||||
|
||||
private int handleUsersNum(){
|
||||
SystemInfo si = new SystemInfo();
|
||||
OperatingSystem os = si.getOperatingSystem();
|
||||
System.out.println("=========================================================");
|
||||
int usersNum = os.getSessions().size();
|
||||
System.out.println("登录用户数: " + usersNum);
|
||||
return usersNum;
|
||||
}
|
||||
|
||||
private long handleSystemDiskSizeTotal(){
|
||||
System.out.println("=========================================================");
|
||||
long diskSizeTotal = diskSpace();
|
||||
System.out.println("硬盘:总可用空间: " + diskSizeTotal);
|
||||
return diskSizeTotal;
|
||||
}
|
||||
|
||||
public long handleSystemBoottime(){
|
||||
SystemInfo si = new SystemInfo();
|
||||
HardwareAbstractionLayer hal = si.getHardware();
|
||||
long boottime = systemBootTime(hal.getProcessor());
|
||||
System.out.println("=========================================================");
|
||||
System.out.println("系统启动时间: " + boottime);
|
||||
return boottime;
|
||||
}
|
||||
|
||||
private String handleSystemUname(){
|
||||
SystemInfo si = new SystemInfo();
|
||||
String uname = systemDescription(si);
|
||||
System.out.println("=========================================================");
|
||||
System.out.println("系统描述: " + uname);
|
||||
return uname;
|
||||
}
|
||||
|
||||
private String handleSystemLocaltime(){
|
||||
System.out.println("=========================================================");
|
||||
String time = localTime();
|
||||
System.out.println("系统本地时间: " + time);
|
||||
return time;
|
||||
}
|
||||
|
||||
private long handleSystemUptime(){
|
||||
SystemInfo si = new SystemInfo();
|
||||
OperatingSystem os = si.getOperatingSystem();
|
||||
System.out.println("=========================================================");
|
||||
long uptime = systemUptime(os);
|
||||
System.out.println("系统正常运行时间: " + uptime);
|
||||
return uptime;
|
||||
}
|
||||
|
||||
private long handleProcNum(){
|
||||
SystemInfo si = new SystemInfo();
|
||||
OperatingSystem os = si.getOperatingSystem();
|
||||
System.out.println("=========================================================");
|
||||
long procNum = os.getProcessCount();
|
||||
System.out.println("进程数: " + procNum);
|
||||
return procNum;
|
||||
}
|
||||
|
||||
|
||||
private String handleDefault(){
|
||||
return "";
|
||||
}
|
||||
|
||||
// 读取 /proc/sys/kernel/pid_max 获取最大进程数
|
||||
private static long getMaxProcessesLinux() throws IOException {
|
||||
File pidMaxFile = new File("/proc/sys/kernel/pid_max");
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader(pidMaxFile))) {
|
||||
String line = reader.readLine().trim();
|
||||
return Long.parseLong(line);
|
||||
}
|
||||
}
|
||||
|
||||
// 统计 /proc 下的数字目录数(每个目录对应一个进程)
|
||||
private static int getRunningProcessesLinux() {
|
||||
File procDir = new File("/proc");
|
||||
File[] files = procDir.listFiles();
|
||||
if (files == null) return 0;
|
||||
|
||||
int count = 0;
|
||||
for (File file : files) {
|
||||
if (file.isDirectory() && file.getName().matches("\\d+")) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// 获取硬盘总可用空间
|
||||
public long diskSpace() {
|
||||
long diskSizeTotal = 0;
|
||||
File[] roots = File.listRoots();
|
||||
for (File root : roots) {
|
||||
diskSizeTotal += root.getFreeSpace();
|
||||
}
|
||||
return diskSizeTotal;
|
||||
}
|
||||
|
||||
// 获取系统启动时间
|
||||
public long systemBootTime(CentralProcessor processor) {
|
||||
long[] systemCpuLoadTicks = processor.getSystemCpuLoadTicks();
|
||||
long bootTime = ManagementFactory.getRuntimeMXBean().getStartTime();
|
||||
return bootTime;
|
||||
}
|
||||
|
||||
// 获取系统描述
|
||||
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";
|
||||
}
|
||||
|
||||
// 获取系统本地时间
|
||||
public String localTime() {
|
||||
return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
|
||||
// 获取系统正常运行时间
|
||||
public long systemUptime(OperatingSystem os) {
|
||||
long uptimeSeconds = os.getSystemUptime();
|
||||
return uptimeSeconds;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.tongran.agent.client.utils;
|
||||
|
||||
import com.tongran.agent.client.core.eo.AlarmEO;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class AgentDataUtil {
|
||||
|
||||
public static Map<String, Long> parseMemInfo() throws IOException {
|
||||
Map<String, Long> memInfo = new HashMap<>();
|
||||
try (BufferedReader br = new BufferedReader(new FileReader("/proc/meminfo"))) {
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
String[] parts = line.split("\\s+");
|
||||
if (parts.length >= 2) {
|
||||
String key = parts[0].replace(":", "");
|
||||
long value = Long.parseLong(parts[1]);
|
||||
memInfo.put(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return memInfo;
|
||||
}
|
||||
|
||||
public static boolean hasAnyActiveAlarm(List<AlarmEO> alarmEOList) {
|
||||
return alarmEOList.stream()
|
||||
.anyMatch(AlarmEO::isCollect);
|
||||
}
|
||||
|
||||
public static void chmod(String filePath, String mode) throws IOException, InterruptedException {
|
||||
ProcessBuilder processBuilder = new ProcessBuilder("chmod", mode, filePath);
|
||||
processBuilder.redirectErrorStream(true);
|
||||
|
||||
Process process = processBuilder.start();
|
||||
int exitCode = process.waitFor();
|
||||
|
||||
if (exitCode != 0) {
|
||||
throw new IOException("chmod命令执行失败,退出码: " + exitCode);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
chmod("/opt/app/init.sh", "755"); // 或者 "+x"
|
||||
System.out.println("权限设置成功!");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,780 @@
|
||||
package com.tongran.agent.client.utils;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.tongran.agent.client.core.config.GlobalConfig;
|
||||
import com.tongran.agent.client.core.vo.NetworkInterfaceInfo;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.snmp4j.smi.OID;
|
||||
import oshi.hardware.NetworkIF;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class AgentUtil {
|
||||
|
||||
@Resource
|
||||
private GlobalConfig globalConfig;
|
||||
|
||||
public static String getMotherboardUUID() {
|
||||
String hostName = getHostname();
|
||||
String primaryIp = getPrimaryIp();
|
||||
// SystemInfo si = new SystemInfo();
|
||||
// HardwareAbstractionLayer hal = si.getHardware();
|
||||
// Baseboard baseboard = hal.getComputerSystem().getBaseboard();
|
||||
// if(StringUtils.isNotBlank(baseboard.getSerialNumber())){
|
||||
// return baseboard.getSerialNumber();
|
||||
// }
|
||||
return hostName+":"+primaryIp;
|
||||
}
|
||||
|
||||
public static String getHostname() {
|
||||
try {
|
||||
return InetAddress.getLocalHost().getHostName();
|
||||
} catch (UnknownHostException e) {
|
||||
return "unknown-host";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本机首选 IP 地址(通常是第一个非回环、非虚拟网卡的 IPv4 地址)
|
||||
*/
|
||||
public static String getPrimaryIp() {
|
||||
try {
|
||||
InetAddress localHost = InetAddress.getLocalHost();
|
||||
String ip = localHost.getHostAddress();
|
||||
|
||||
// 如果是 127.x.x.x,说明可能 hosts 配置有问题,需要手动查找
|
||||
if (ip.startsWith("127.")) {
|
||||
return getExternalIp();
|
||||
}
|
||||
return ip;
|
||||
} catch (UnknownHostException e) {
|
||||
return "127.0.0.1";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取第一个非回环、非虚拟网卡的 IPv4 地址
|
||||
*/
|
||||
public static String getExternalIp() {
|
||||
try {
|
||||
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
|
||||
while (interfaces.hasMoreElements()) {
|
||||
NetworkInterface iface = interfaces.nextElement();
|
||||
// 跳过虚拟网卡(如 docker, veth, lo)
|
||||
if (iface.isLoopback() || iface.isVirtual() || !iface.isUp()) {
|
||||
continue;
|
||||
}
|
||||
Enumeration<InetAddress> addresses = iface.getInetAddresses();
|
||||
while (addresses.hasMoreElements()) {
|
||||
InetAddress addr = addresses.nextElement();
|
||||
if (addr.isLoopbackAddress()) {
|
||||
continue; // 跳过 127.0.0.1
|
||||
}
|
||||
if (addr.getHostAddress().contains(":")) {
|
||||
continue; // 跳过 IPv6
|
||||
}
|
||||
return addr.getHostAddress();
|
||||
}
|
||||
}
|
||||
} catch (SocketException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "127.0.0.1";
|
||||
}
|
||||
|
||||
|
||||
public static String toJsonString(List<Map<String, String>> list) {
|
||||
try {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
return mapper.writeValueAsString(list);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("JSON转换失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void base64ToFile(String base64String, String filePath) throws IOException {
|
||||
// 解码Base64字符串
|
||||
byte[] decodedBytes = Base64.getDecoder().decode(base64String);
|
||||
|
||||
// 写入文件
|
||||
try (FileOutputStream fos = new FileOutputStream(filePath)) {
|
||||
fos.write(decodedBytes);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getInterfaceType(NetworkIF net) {
|
||||
String name = net.getName().toLowerCase();
|
||||
String displayName = net.getDisplayName().toLowerCase();
|
||||
|
||||
if (name.startsWith("eth") || name.startsWith("en") || displayName.contains("ethernet")) {
|
||||
return "Ethernet";
|
||||
} else if (name.startsWith("wlan") || name.startsWith("wl") || displayName.contains("wireless")) {
|
||||
return "Wi-Fi";
|
||||
} else if (name.startsWith("lo") || displayName.contains("loopback")) {
|
||||
return "Loopback";
|
||||
} else if (name.startsWith("ppp") || displayName.contains("point-to-point")) {
|
||||
return "PPP";
|
||||
} else if (name.startsWith("vmnet") || displayName.contains("virtual")) {
|
||||
return "Virtual";
|
||||
} else if (name.startsWith("tun") || name.startsWith("tap")) {
|
||||
return "TUN/TAP";
|
||||
} else if (name.startsWith("br") || displayName.contains("bridge")) {
|
||||
return "Bridge";
|
||||
} else if (name.startsWith("bond") || displayName.contains("bond")) {
|
||||
return "Bond";
|
||||
} else {
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前时间距离下一个“分钟为 0 或 5”的时间点还差多少毫秒
|
||||
* 例如:xx:00, xx:05, xx:10, ..., xx:55
|
||||
*/
|
||||
public static long millisecondsToNext5Minute() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
// 当前分钟
|
||||
int minute = now.getMinute();
|
||||
|
||||
// 计算下一个 5 分钟整点(向上取整)
|
||||
int nextMinute = ((minute / 5) + 1) * 5;
|
||||
|
||||
LocalDateTime nextTime;
|
||||
if (nextMinute < 60) {
|
||||
// 在当前小时内
|
||||
nextTime = now.withMinute(nextMinute).withSecond(0).withNano(0);
|
||||
} else {
|
||||
// 跨小时,如 10:58 → 11:00
|
||||
nextTime = now.plusHours(1).withMinute(0).withSecond(0).withNano(0);
|
||||
}
|
||||
|
||||
// 计算相差的毫秒数
|
||||
return ChronoUnit.MILLIS.between(now, nextTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取距离下一个分钟整点的毫秒数
|
||||
*/
|
||||
public static long getMillisToNextMinute() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
// 获取下一个分钟整点时间
|
||||
LocalDateTime nextMinute = now
|
||||
.truncatedTo(ChronoUnit.MINUTES) // 截断到当前分钟
|
||||
.plusMinutes(1); // 加1分钟
|
||||
|
||||
// 计算时间差(毫秒)
|
||||
return ChronoUnit.MILLIS.between(now, nextMinute);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取距离下一个指定分钟整点的毫秒数
|
||||
*/
|
||||
public static long getMillisToNextMinuteInterval(int intervalMinutes) {
|
||||
if (intervalMinutes <= 0) {
|
||||
throw new IllegalArgumentException("Interval must be positive");
|
||||
}
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
// 计算当前分钟在间隔中的位置
|
||||
int currentMinute = now.getMinute();
|
||||
int remainder = currentMinute % intervalMinutes;
|
||||
|
||||
// 计算需要增加的分钟数
|
||||
int minutesToAdd = remainder == 0 ? intervalMinutes : intervalMinutes - remainder;
|
||||
|
||||
// 获取下一个间隔整点时间
|
||||
LocalDateTime nextInterval = now
|
||||
.truncatedTo(ChronoUnit.MINUTES) // 截断到当前分钟
|
||||
.plusMinutes(minutesToAdd) // 增加到下一个整点
|
||||
.withSecond(0) // 秒设为0
|
||||
.withNano(0); // 纳秒设为0
|
||||
|
||||
return ChronoUnit.MILLIS.between(now, nextInterval);
|
||||
}
|
||||
|
||||
public static long roundMinutes(){
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
int minute = now.getMinute();
|
||||
int roundedMinute = (int) (Math.round(minute / 5.0) * 5);
|
||||
|
||||
LocalDateTime roundedTime ;
|
||||
if (roundedMinute == 60) {
|
||||
roundedTime = now.plusHours(1).withMinute(0).withSecond(0).withNano(0);
|
||||
} else {
|
||||
roundedTime = now.withMinute(roundedMinute).withSecond(0).withNano(0);
|
||||
}
|
||||
// 转换为10位时间戳
|
||||
long timestamp = roundedTime.atZone(ZoneId.systemDefault()).toEpochSecond();
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public static LocalDateTime toLocalDateTime(long timestamp, boolean isSeconds) {
|
||||
java.time.Instant instant = isSeconds ?
|
||||
java.time.Instant.ofEpochSecond(timestamp) :
|
||||
java.time.Instant.ofEpochMilli(timestamp);
|
||||
|
||||
return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
|
||||
}
|
||||
|
||||
public static String getLastOid(OID oid){
|
||||
if(oid.size() > 0){
|
||||
int lastValue = oid.get(oid.size() - 1);
|
||||
return String.valueOf(lastValue);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static LinkedHashMap<String, String> swapMap(LinkedHashMap<String, String> original) {
|
||||
LinkedHashMap<String, String> swapped = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, String> entry : original.entrySet()) {
|
||||
if (entry.getValue() != null) { // 避免 null key
|
||||
swapped.put(entry.getValue(), entry.getKey());
|
||||
}
|
||||
}
|
||||
return swapped;
|
||||
}
|
||||
|
||||
// 存储网关信息:接口名 -> 网关IP
|
||||
private static Map<String, String> gatewayMap = new HashMap<>();
|
||||
|
||||
// 正则表达式匹配:当前 IP:xxx.xxx.xxx.xxx 来自于:中国 江苏省 南京市 电信
|
||||
private static final String REGEX = "来自于:(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)";
|
||||
/**
|
||||
* 收集所有 Ethernet 类型网卡信息
|
||||
*/
|
||||
public static List<NetworkInterfaceInfo> collectNetworkInfo() throws Exception {
|
||||
List<NetworkInterfaceInfo> result = new ArrayList<>();
|
||||
// String publicIp = getPublicIp(); // 获取公网 IP(全局出口)
|
||||
// String ipInfo = PublicIpFetcher.getPublicIp();
|
||||
// String publicIp = PublicIpFetcher.extractIp(ipInfo);
|
||||
// if (publicIp != null) {
|
||||
// System.out.println("公网 IP: " + publicIp);
|
||||
// } else {
|
||||
// System.out.println("未能提取 IP 地址");
|
||||
// }
|
||||
|
||||
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
|
||||
|
||||
while (interfaces.hasMoreElements()) {
|
||||
NetworkInterface ni = interfaces.nextElement();
|
||||
String ipv4 = getIPv4Address(ni);
|
||||
AssertLog.info("ipv4={},接口状态={}",ipv4,ni.isUp());
|
||||
|
||||
// 跳过回环、虚拟、关闭的接口
|
||||
if (ni.isLoopback() || ni.isVirtual() || !ni.isUp()) continue;
|
||||
|
||||
// 判断是否为 Ethernet(通过名称约定:eth*, en*, 等)
|
||||
String name = ni.getName();
|
||||
AssertLog.info("ipv4={},名称={}",ipv4,name);
|
||||
if (!isEthernetInterface(name)) continue;
|
||||
|
||||
AssertLog.info("ipv4={},物理链路状态={}",ipv4,isInterfaceConnected(name));
|
||||
// 检查是否“已连接”(物理链路状态)
|
||||
if (!isInterfaceConnected(name)) continue;
|
||||
|
||||
// 检查IP4信息
|
||||
if (ipv4.equals("N/A")) continue;
|
||||
|
||||
String gateway = getGatewayAddress();
|
||||
AssertLog.info("ipv4={},网关={}",ipv4,gateway);
|
||||
// 检查IP4信息
|
||||
if (gateway.equals("N/A")) continue;
|
||||
|
||||
// 添加临时路由
|
||||
boolean flag = addTempRoute(ipv4, name, gateway);
|
||||
if (!flag) continue;
|
||||
|
||||
String ipInfo = PublicIpFetcher.getPublicIp();
|
||||
String publicIp = PublicIpFetcher.extractIp(ipInfo);
|
||||
if (publicIp != null) {
|
||||
System.out.println("公网 IP: " + publicIp);
|
||||
} else {
|
||||
System.out.println("未能提取 IP 地址");
|
||||
}
|
||||
if (StringUtils.isBlank(publicIp)) continue;
|
||||
|
||||
// 删除临时路由
|
||||
delTempRoute(ipv4, name, gateway);
|
||||
|
||||
|
||||
NetworkInterfaceInfo info = NetworkInterfaceInfo.builder()
|
||||
.name(name)
|
||||
.type("Ethernet")
|
||||
.mac(getMacAddress(ni))
|
||||
.ipv4(ipv4)
|
||||
.gateway(gateway) // 网关通常是默认路由,全局一致
|
||||
.publicIp(publicIp)
|
||||
.build();
|
||||
|
||||
// 如果有公网 IP,查询归属地
|
||||
if (publicIp != null && !publicIp.isEmpty()) {
|
||||
Pattern pattern = Pattern.compile(REGEX);
|
||||
Matcher matcher = pattern.matcher(ipInfo);
|
||||
if (matcher.find()) {
|
||||
// group(1): 国家(通常是“中国”)
|
||||
// group(2): 省份
|
||||
// group(3): 城市
|
||||
// group(4): 运营商
|
||||
String province = matcher.group(2);
|
||||
String city = matcher.group(3);
|
||||
String isp = matcher.group(4);
|
||||
// 去掉可能的标点符号(如句号)
|
||||
province = province.replaceAll("[。]", "");
|
||||
city = city.replaceAll("[。]", "");
|
||||
isp = isp.replaceAll("[。]", "");
|
||||
info.setCarrier(isp);
|
||||
info.setProvince(province);
|
||||
info.setCity(city);
|
||||
}
|
||||
// ipInfo.substring(ipInfo.indexOf("来自于:")+4,ipInfo.length())
|
||||
// JSONObject location = queryIpLocation(publicIp);
|
||||
// if (location != null) {
|
||||
// info.setCarrier(location.getStr("org", "未知").replaceAll("^AS\\d+\\s*", ""));
|
||||
// info.setProvince(location.getStr("region", "未知"));
|
||||
// info.setCity(location.getStr("city", "未知"));
|
||||
// } else {
|
||||
// info.setCarrier("查询失败");
|
||||
// info.setProvince("查询失败");
|
||||
// info.setCity("查询失败");
|
||||
// }
|
||||
}
|
||||
result.add(info);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加临时路由
|
||||
*/
|
||||
private static boolean addTempRoute(String ipv4, String name, String gateway) {
|
||||
String prefix = "32";
|
||||
AssertLog.info("添加临时路由,ipv4:{}",ipv4);
|
||||
boolean flag = NetworkUtil.addRoute(ipv4,prefix,gateway,name);
|
||||
if(flag){
|
||||
AssertLog.info("添加临时路由成功,ipv4:{}",ipv4);
|
||||
}else{
|
||||
AssertLog.info("添加临时路由失败,ipv4:{}",ipv4);
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除临时路由
|
||||
*/
|
||||
private static boolean delTempRoute(String ipv4, String name, String gateway) {
|
||||
String prefix = "32";
|
||||
AssertLog.info("删除临时路由,ipv4:{}",ipv4);
|
||||
boolean flag = NetworkUtil.deleteRoute(ipv4,prefix,gateway,name);
|
||||
if(flag){
|
||||
AssertLog.info("删除临时路由成功,ipv4:{}",ipv4);
|
||||
}else{
|
||||
AssertLog.info("删除临时路由失败,ipv4:{}",ipv4);
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为 Ethernet 类型网卡(基于常见命名)
|
||||
*/
|
||||
private static boolean isEthernetInterface(String name) {
|
||||
return name.startsWith("eth") || // Linux 传统
|
||||
name.startsWith("en") || // systemd 命名 (enp3s0)
|
||||
name.startsWith("em"); // 有些主板网卡
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 MAC 地址
|
||||
*/
|
||||
private static String getMacAddress(NetworkInterface ni) {
|
||||
try {
|
||||
byte[] mac = ni.getHardwareAddress();
|
||||
if (mac == null) return "N/A";
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取第一个 IPv4 地址
|
||||
*/
|
||||
private static String getIPv4Address(NetworkInterface ni) {
|
||||
Enumeration<InetAddress> addresses = ni.getInetAddresses();
|
||||
while (addresses.hasMoreElements()) {
|
||||
InetAddress addr = addresses.nextElement();
|
||||
if (addr instanceof Inet4Address) {
|
||||
return addr.getHostAddress();
|
||||
}
|
||||
}
|
||||
return "N/A";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认网关(调用 shell 命令)
|
||||
*/
|
||||
private static String getGatewayAddress() {
|
||||
try {
|
||||
Process process = Runtime.getRuntime().exec("ip route");
|
||||
java.util.Scanner scanner = new java.util.Scanner(process.getInputStream());
|
||||
while (scanner.hasNextLine()) {
|
||||
String line = scanner.nextLine();
|
||||
if (line.startsWith("default")) {
|
||||
String[] parts = line.split(" ");
|
||||
return parts[2]; // default via <gateway> dev ...
|
||||
}
|
||||
}
|
||||
scanner.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "N/A";
|
||||
}
|
||||
|
||||
// 检查接口是否“已连接”(物理链路状态)
|
||||
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; // 保守处理
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取公网 IP
|
||||
*/
|
||||
private static String getPublicIp() {
|
||||
return sendHttpGet("https://ifconfig.me");
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询 IP 归属地(使用 ipinfo.io)
|
||||
*/
|
||||
private static JSONObject queryIpLocation(String ip) {
|
||||
String url = "https://ipinfo.io/" + ip + "/json";
|
||||
String response = sendHttpGet(url);
|
||||
AssertLog.info("查询 IP 归属地={}", response);
|
||||
if (response != null) {
|
||||
try {
|
||||
return new JSONObject(response);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送 HTTP GET 请求
|
||||
*/
|
||||
private static String sendHttpGet(String urlString) {
|
||||
try {
|
||||
URL url = new URL(urlString);
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod("GET");
|
||||
conn.setConnectTimeout(5000);
|
||||
conn.setReadTimeout(5000);
|
||||
|
||||
if (conn.getResponseCode() == 200) {
|
||||
java.util.Scanner scanner = new java.util.Scanner(conn.getInputStream());
|
||||
String result = scanner.useDelimiter("\\A").next();
|
||||
scanner.close();
|
||||
return result;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("HTTP 请求失败: " + e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void bufferedWriter(String filePath, String[] lines){
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(
|
||||
Paths.get(filePath),
|
||||
StandardCharsets.UTF_8,
|
||||
StandardOpenOption.CREATE, // 创建文件
|
||||
StandardOpenOption.TRUNCATE_EXISTING // 覆盖写入
|
||||
)) {
|
||||
for (String line : lines) {
|
||||
writer.write(line);
|
||||
writer.newLine(); // 换行
|
||||
}
|
||||
System.out.println("文件写入完成!");
|
||||
} catch (IOException e) {
|
||||
System.err.println("写入异常:" + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 /etc/issue 文件的第二行内容
|
||||
* @return 第二行字符串,如果不存在则返回 null
|
||||
*/
|
||||
public static String getDeviceSN() {
|
||||
Path issuePath = Paths.get("/etc/issue");
|
||||
if (!Files.exists(issuePath)) {
|
||||
System.err.println("文件不存在: /etc/issue");
|
||||
return null;
|
||||
}
|
||||
if (!Files.isReadable(issuePath)) {
|
||||
System.err.println("无读取权限: /etc/issue");
|
||||
return null;
|
||||
}
|
||||
try (BufferedReader reader = Files.newBufferedReader(issuePath)) {
|
||||
String line;
|
||||
int lineNumber = 0;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
line = line.trim();
|
||||
if (!line.isEmpty()) { // 只计数非空行
|
||||
lineNumber++;
|
||||
if (lineNumber == 2) {
|
||||
return line;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 如果文件少于两行
|
||||
System.err.println("文件行数不足,只有 " + lineNumber + " 行");
|
||||
return null;
|
||||
} catch (NoSuchFileException e) {
|
||||
System.err.println("文件未找到: " + e.getMessage());
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
System.err.println("读取文件异常: " + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
List<NetworkInterfaceInfo> infos = collectNetworkInfo();
|
||||
for (NetworkInterfaceInfo info : infos) {
|
||||
System.out.println(info);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getFileMD5(String filePath) {
|
||||
// 检查文件是否存在
|
||||
Path path = Paths.get(filePath);
|
||||
if (!Files.exists(path)) {
|
||||
System.out.println("文件不存在: " + filePath);
|
||||
return null;
|
||||
}
|
||||
// 检查是否是文件(不是目录)
|
||||
if (!Files.isRegularFile(path)) {
|
||||
System.out.println("路径不是文件: " + filePath);
|
||||
return null;
|
||||
}
|
||||
// 检查文件是否可读
|
||||
if (!Files.isReadable(path)) {
|
||||
System.out.println("文件不可读: " + filePath);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
try (FileInputStream fis = new FileInputStream(filePath)) {
|
||||
byte[] buffer = new byte[4096];
|
||||
int length;
|
||||
while ((length = fis.read(buffer)) != -1) {
|
||||
md.update(buffer, 0, length);
|
||||
}
|
||||
}
|
||||
byte[] digest = md.digest();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : digest) {
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
return sb.toString();
|
||||
} catch (NoSuchAlgorithmException | IOException e) {
|
||||
System.out.println("计算MD5失败: " + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 安排脚本在 3 分钟后执行
|
||||
*/
|
||||
public static void scheduleScriptIn3Minutes(String SCRIPT_PATH) {
|
||||
// 使用 at 命令:3 minutes from now
|
||||
String atCommand = String.format("echo '%s' | at now + 3 minutes", SCRIPT_PATH);
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder("bash", "-c", atCommand);
|
||||
pb.redirectErrorStream(true); // 合并 stdout 和 stderr
|
||||
|
||||
try {
|
||||
Process process = pb.start();
|
||||
|
||||
// 读取命令输出(at 通常会打印任务编号)
|
||||
java.io.BufferedReader reader = new java.io.BufferedReader(
|
||||
new java.io.InputStreamReader(process.getInputStream())
|
||||
);
|
||||
|
||||
String line;
|
||||
StringBuilder output = new StringBuilder();
|
||||
while ((line = reader.readLine()) != null) {
|
||||
output.append(line).append("\n");
|
||||
}
|
||||
reader.close();
|
||||
|
||||
int exitCode = process.waitFor();
|
||||
if (exitCode == 0) {
|
||||
System.out.println("✅ 成功安排脚本在 3 分钟后执行:");
|
||||
System.out.println(" 脚本: " + SCRIPT_PATH);
|
||||
if (output.length() > 0) {
|
||||
System.out.println(" at 响应: " + output.toString().trim());
|
||||
}
|
||||
} else {
|
||||
System.err.println("❌ 调度失败,exit code: " + exitCode);
|
||||
System.err.println(" 输出: " + output.toString().trim());
|
||||
}
|
||||
|
||||
} catch (IOException | InterruptedException e) {
|
||||
System.err.println("执行 at 命令时出错: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static String getNetworkParam(String filePath, String key){
|
||||
Properties props = new Properties();
|
||||
String result = "";
|
||||
try (InputStream input = Files.newInputStream(Paths.get(filePath))) {
|
||||
// 加载配置文件
|
||||
props.load(input);
|
||||
result = props.getProperty(key, "");
|
||||
System.out.println("配置文件加载成功");
|
||||
} catch (IOException e) {
|
||||
System.err.println("无法加载配置文件,使用默认值");
|
||||
} catch (NumberFormatException e) {
|
||||
System.err.println("配置文件格式错误: " + e.getMessage());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取文件,跳过空行,将所有非空行拼接成一个字符串
|
||||
* @param filePath 文件路径
|
||||
* @return 拼接后的字符串(空行已去除)
|
||||
*/
|
||||
public static String readNonEmptyLinesAsString(String filePath) throws Exception {
|
||||
return Files.lines(Paths.get(filePath))
|
||||
.filter(line -> !line.trim().isEmpty()) // 去除 null 和 空/空白行
|
||||
.collect(Collectors.joining("\n")); // 用换行符连接
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件最后修改时间
|
||||
* @param filePath
|
||||
* @return
|
||||
*/
|
||||
public static LocalDateTime getLastModifiedTime(String filePath) {
|
||||
try {
|
||||
BasicFileAttributes attrs = Files.readAttributes(Paths.get(filePath), BasicFileAttributes.class);
|
||||
return attrs.lastModifiedTime()
|
||||
.toInstant()
|
||||
.atZone(ZoneId.systemDefault())
|
||||
.toLocalDateTime();
|
||||
} catch (IOException e) {
|
||||
System.err.println("无法读取文件时间: " + filePath);
|
||||
return null;
|
||||
}
|
||||
// LocalDateTime mtime = FileUtils.getLastModifiedTime("/tmp/data.log");
|
||||
// if (mtime != null) {
|
||||
// System.out.println("修改时间: " + mtime);
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统 HZ(每秒 tick 数)
|
||||
* 典型值:100, 250, 300, 1000
|
||||
*/
|
||||
public static int getSystemHz() {
|
||||
if (GlobalConfig.systemHz != null) {
|
||||
return GlobalConfig.systemHz;
|
||||
}
|
||||
try {
|
||||
Process process = Runtime.getRuntime().exec(
|
||||
"grep CONFIG_HZ /boot/config-$(uname -r)"
|
||||
);
|
||||
BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(process.getInputStream())
|
||||
);
|
||||
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (line.contains("CONFIG_HZ=")) {
|
||||
String[] parts = line.split("=");
|
||||
int hz = Integer.parseInt(parts[1].trim());
|
||||
GlobalConfig.systemHz = hz;
|
||||
return hz;
|
||||
}
|
||||
}
|
||||
reader.close();
|
||||
} catch (Exception e) {
|
||||
System.err.println("无法读取系统 HZ,使用默认值 1000");
|
||||
}
|
||||
|
||||
// 默认值(大多数现代 Linux 发行版使用 1000 Hz)
|
||||
GlobalConfig.systemHz = 1000;
|
||||
return GlobalConfig.systemHz;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 ticks 转换为秒
|
||||
*/
|
||||
public static double ticksToSeconds(long ticks) {
|
||||
int hz = getSystemHz();
|
||||
return (double) ticks / hz;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.tongran.agent.client.utils;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 日志断言类
|
||||
*
|
||||
* @author BAO
|
||||
*
|
||||
*/
|
||||
@Slf4j
|
||||
public class AssertLog {
|
||||
|
||||
/**
|
||||
* 打印Info 日志
|
||||
*
|
||||
* @param format
|
||||
* @param arguments
|
||||
*/
|
||||
public static void info(String format, Object... arguments) {
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info(format, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印Debug 日志
|
||||
*
|
||||
* @param format
|
||||
* @param arguments
|
||||
*/
|
||||
public static void debug(String format, Object... arguments) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(format, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印Error 日志
|
||||
*
|
||||
* @param format
|
||||
* @param arguments
|
||||
*/
|
||||
public static void error(String format, Object... arguments) {
|
||||
if (log.isErrorEnabled()) {
|
||||
log.error(format, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印Trace 日志
|
||||
*
|
||||
* @param format
|
||||
* @param arguments
|
||||
*/
|
||||
public static void trace(String format, Object... arguments) {
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace(format, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印Warn 日志
|
||||
*
|
||||
* @param format
|
||||
* @param arguments
|
||||
*/
|
||||
public static void warn(String format, Object... arguments) {
|
||||
if (log.isWarnEnabled()) {
|
||||
log.warn(format, arguments);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,27 @@
|
||||
package com.tongran.agent.client.utils;
|
||||
|
||||
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();
|
||||
return p.waitFor() == 0;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
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.URL;
|
||||
|
||||
public class PublicIpFetcher {
|
||||
|
||||
private static final String IP_SERVICE_URL = "https://myip.ipip.net";
|
||||
private static final int TIMEOUT_MS = 10_000; // 10秒超时
|
||||
|
||||
/**
|
||||
* 获取公网 IP(包含归属地信息)
|
||||
*
|
||||
* @return IP 信息字符串,如 "当前 IP:112.96.15.145,来自于:中国 广东省 深圳市 联通"
|
||||
* @throws IOException 如果网络请求失败
|
||||
*/
|
||||
public static String getPublicIp() throws IOException {
|
||||
URL url = new URL(IP_SERVICE_URL);
|
||||
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 " + IP_SERVICE_URL);
|
||||
}
|
||||
|
||||
// 读取响应(注意:myip.ipip.net 返回的是 GBK 编码)
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(connection.getInputStream(), "UTF-8"))) { // 使用 GBK 解码
|
||||
StringBuilder response = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
response.append(line);
|
||||
}
|
||||
return response.toString().trim();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从返回文本中提取纯 IP 地址(如 112.96.15.145)
|
||||
*
|
||||
* @param ipInfo 来自 getPublicIp() 的完整信息
|
||||
* @return 纯 IP 字符串,提取失败返回 null
|
||||
*/
|
||||
public static String extractIp(String ipInfo) {
|
||||
if (ipInfo == null || ipInfo.isEmpty()) return null;
|
||||
|
||||
// 匹配 IP 地址的正则
|
||||
java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})");
|
||||
java.util.regex.Matcher matcher = pattern.matcher(ipInfo);
|
||||
return matcher.find() ? matcher.group(1) : null;
|
||||
}
|
||||
|
||||
// === 使用示例 ===
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
String ipInfo = getPublicIp();
|
||||
System.out.println("完整信息: " + ipInfo);
|
||||
|
||||
String pureIp = extractIp(ipInfo);
|
||||
if (pureIp != null) {
|
||||
System.out.println("公网 IP: " + pureIp);
|
||||
} else {
|
||||
System.out.println("未能提取 IP 地址");
|
||||
}
|
||||
String province = "";
|
||||
String city = "";
|
||||
String carrier = "";
|
||||
|
||||
System.out.println(ipInfo.substring(ipInfo.indexOf("来自于:")+4,ipInfo.length()));
|
||||
|
||||
|
||||
} catch (IOException e) {
|
||||
System.err.println("获取公网 IP 失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.tongran.agent.client.utils;
|
||||
|
||||
import com.tongran.agent.client.exception.code.ErrorCode;
|
||||
import com.tongran.agent.client.exception.code.GlobalErrorCode;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author BAO
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "接口交互统一数据返回标准")
|
||||
public class R<T> implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "返回代码")
|
||||
private Integer code;
|
||||
|
||||
@Schema(description = "消息描述")
|
||||
private String msg;
|
||||
|
||||
@Schema(description = "结果对象")
|
||||
private T data;
|
||||
|
||||
public static <T> R<T> success(String msg, T t) {
|
||||
R<T> r = new R<>();
|
||||
r.setData(t);
|
||||
r.setMsg(msg);
|
||||
r.setCode(GlobalErrorCode.SUCCESS.getCode());
|
||||
return r;
|
||||
}
|
||||
|
||||
public static <T> R<T> success(T t) {
|
||||
return R.success(GlobalErrorCode.SUCCESS.getMsg(), t);
|
||||
}
|
||||
|
||||
public static <T> R<T> success() {
|
||||
return R.success(null);
|
||||
}
|
||||
|
||||
public static <T> R<T> error(String msg, Integer code) {
|
||||
R<T> r = new R<>();
|
||||
r.setMsg(msg);
|
||||
r.setCode(code);
|
||||
return r;
|
||||
}
|
||||
|
||||
public static <T> R<T> error(Integer code, String msg) {
|
||||
R<T> r = new R<>();
|
||||
r.setMsg(msg);
|
||||
r.setCode(code);
|
||||
return r;
|
||||
}
|
||||
|
||||
public static <T> R<T> error(ErrorCode err) {
|
||||
return R.error(err.getMsg(), err.getCode());
|
||||
}
|
||||
|
||||
public static <T> R<T> error() {
|
||||
return R.error(GlobalErrorCode.ERROR.getMsg(), GlobalErrorCode.ERROR.getCode());
|
||||
}
|
||||
|
||||
public static <T> R<T> error(String msg) {
|
||||
return R.error(msg, GlobalErrorCode.ERROR.getCode());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.tongran.agent.client.utils;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
|
||||
public class RoundMinutes {
|
||||
public static long convertMillisToRoundedSeconds(long millisTimestamp) {
|
||||
// 1. 先将毫秒时间戳四舍五入到秒
|
||||
long roundedSeconds = Math.round(millisTimestamp / 1000.0);
|
||||
|
||||
// 2. 转换为LocalDateTime进行分钟调整
|
||||
LocalDateTime dateTime = LocalDateTime.ofInstant(
|
||||
Instant.ofEpochSecond(roundedSeconds),
|
||||
ZoneId.systemDefault()
|
||||
);
|
||||
|
||||
// 3. 将分钟四舍五入到5分钟间隔
|
||||
int minute = dateTime.getMinute();
|
||||
int roundedMinute = (int) (Math.round(minute / 5.0) * 5);
|
||||
|
||||
// 4. 调整时间
|
||||
LocalDateTime adjustedDateTime = dateTime
|
||||
.withMinute(roundedMinute % 60)
|
||||
.withSecond(0)
|
||||
.withNano(0);
|
||||
|
||||
// 处理进位
|
||||
if (roundedMinute == 60) {
|
||||
adjustedDateTime = adjustedDateTime.plusHours(1);
|
||||
}
|
||||
|
||||
// 5. 转换回时间戳
|
||||
return adjustedDateTime.atZone(ZoneId.systemDefault()).toEpochSecond();
|
||||
}
|
||||
|
||||
// public static void main(String[] args) {
|
||||
// long millisTimestamp = System.currentTimeMillis();
|
||||
// System.out.println("原始13位时间戳: " + millisTimestamp);
|
||||
// System.out.println("原始时间: " + new java.util.Date(millisTimestamp));
|
||||
//
|
||||
// long result = convertMillisToRoundedSeconds(millisTimestamp);
|
||||
// System.out.println("调整后10位时间戳: " + result);
|
||||
// System.out.println("调整后时间: " + new java.util.Date(result * 1000));
|
||||
// }
|
||||
//
|
||||
public static void main(String[] args) {
|
||||
long timestamp = 1757471699499L;
|
||||
long time = Math.round(timestamp / 1000.0);
|
||||
System.out.println(time);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
server:
|
||||
port: -1
|
||||
servlet:
|
||||
context-path: /tr-agent-client
|
||||
|
||||
|
||||
# 接口文档配置
|
||||
knife4j:
|
||||
enable: true
|
||||
production: false # 开启屏蔽文档资源
|
||||
|
||||
# 日志配置
|
||||
logging:
|
||||
file:
|
||||
path: /usr/local/tongran/logs
|
||||
|
||||
netty:
|
||||
server:
|
||||
host: 120.211.95.173
|
||||
port: 6620
|
||||
client:
|
||||
client-id: client-001
|
||||
reconnect-interval: 5
|
||||
maxReconnectAttempts: 10 # 最大重连次数
|
||||
initialReconnectDelay: 1000 # 初始重连延迟(毫秒)
|
||||
maxReconnectDelay: 30000 # 最大重连延迟(毫秒)
|
||||
@@ -0,0 +1,35 @@
|
||||
spring:
|
||||
profiles:
|
||||
active: dev
|
||||
mvc:
|
||||
pathmatch:
|
||||
matching-strategy: ant_path_matcher
|
||||
application:
|
||||
name: tr-agent-client
|
||||
version: 1.1.4
|
||||
conf-path: /usr/local/tongran/conf
|
||||
script-path: /usr/local/tongran/sbin
|
||||
tmp-path: /usr/local/tongran/tmp
|
||||
temp-path: /usr/local/tongran/temp
|
||||
web:
|
||||
resources:
|
||||
static-locations: classpath*:/META-INF/resources/
|
||||
logging:
|
||||
config: classpath:logback/logback-${spring.profiles.active}.xml
|
||||
|
||||
# springdoc-openapi项目配置
|
||||
knife4j:
|
||||
setting:
|
||||
enable-footer-custom: true
|
||||
footer-custom-content: Apache License 2.0 | Copyright © 2025-[]
|
||||
springdoc:
|
||||
config:
|
||||
title: AGENT客户端服务
|
||||
description: AGENT客户端服务接口文档
|
||||
contact: AGENT-CLIENT
|
||||
email:
|
||||
version: ${spring.application.version}
|
||||
group-configs:
|
||||
- group: 'AGENT客户端服务'
|
||||
paths-to-match: '/**'
|
||||
packages-to-scan: com.tongran.agent.client
|
||||
@@ -0,0 +1,116 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration scan="true" scanPeriod="60000" debug="false">
|
||||
<springProperty scope="context" name="appName" source="spring.application.name" defaultValue="default"/>
|
||||
<springProperty scope="context" name="logPath" source="logging.file.path" defaultValue="/logs/${appName}"/>
|
||||
<contextName>${appName}</contextName>
|
||||
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径 -->
|
||||
<property name="LOG_HOME" value="${logPath}/${appName}"/>
|
||||
<!-- 控制台输出 -->
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<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>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 按照每天生成日志文件, debug级别 -->
|
||||
<appender name="DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<!-- 日志日常打印文件 -->
|
||||
<file>${LOG_HOME}/debug.log</file>
|
||||
<!-- 日志滚动规则 -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!--日志滚动文件名命令规则 -->
|
||||
<FileNamePattern>${LOG_HOME}/debug.%d{yyyy-MM-dd}.%i.AssertLog.zip</FileNamePattern>
|
||||
<!--日志文件保留天数 -->
|
||||
<MaxHistory>30</MaxHistory>
|
||||
<!-- 日志保存总量 -->
|
||||
<totalSizeCap>10GB</totalSizeCap>
|
||||
<!-- 日志切分规则 -->
|
||||
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
||||
<!-- 文件切分压缩的大小阈值 -->
|
||||
<maxFileSize>10MB</maxFileSize>
|
||||
</timeBasedFileNamingAndTriggeringPolicy>
|
||||
</rollingPolicy>
|
||||
<!-- 日志输出的样式 -->
|
||||
<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>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 按照每天生成日志文件, info级别 -->
|
||||
<appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<!-- 精确过滤, 只保存info级别 -->
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>INFO</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
<!-- 日志日常打印文件 -->
|
||||
<file>${LOG_HOME}/info.log</file>
|
||||
<!-- 日志滚动规则 -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- 日志滚动文件名命令规则 -->
|
||||
<FileNamePattern>${LOG_HOME}/info.%d{yyyy-MM-dd}.%i.AssertLog.zip</FileNamePattern>
|
||||
<!--日志文件保留天数 -->
|
||||
<MaxHistory>30</MaxHistory>
|
||||
<!-- 日志保存总量 -->
|
||||
<totalSizeCap>10GB</totalSizeCap>
|
||||
<!-- 日志切分规则 -->
|
||||
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
||||
<!-- 文件切分压缩的大小阈值 -->
|
||||
<maxFileSize>10MB</maxFileSize>
|
||||
</timeBasedFileNamingAndTriggeringPolicy>
|
||||
</rollingPolicy>
|
||||
<!-- 日志输出的样式 -->
|
||||
<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,Asia/Shanghai}] [thread:%thread] [level:%-5level] [appName:${appName}] msg- %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 按照固定大小生成日志文件, error级别 -->
|
||||
<appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<!-- 精确过滤, 只保存error级别 -->
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>ERROR</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
<file>${LOG_HOME}/error.log</file>
|
||||
<!-- 日志滚动规则 -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- 日志滚动文件名命令规则 -->
|
||||
<FileNamePattern>${LOG_HOME}/error.%d{yyyy-MM-dd}.%i.AssertLog.zip</FileNamePattern>
|
||||
<!--日志文件保留天数 -->
|
||||
<MaxHistory>30</MaxHistory>
|
||||
<!-- 日志保存总量 -->
|
||||
<totalSizeCap>10GB</totalSizeCap>
|
||||
<!-- 日志切分规则 -->
|
||||
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
||||
<!-- 文件切分压缩的大小阈值 -->
|
||||
<maxFileSize>10MB</maxFileSize>
|
||||
</timeBasedFileNamingAndTriggeringPolicy>
|
||||
</rollingPolicy>
|
||||
<!-- 日志输出的样式 -->
|
||||
<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>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="com.alibaba.nacos.client" level="OFF"></logger>
|
||||
<logger name="com.netflix" level="OFF"></logger>
|
||||
<logger name="RocketmqClient" additivity="false">
|
||||
<level value="warn" />
|
||||
<appender-ref ref="ERROR"/>
|
||||
</logger>
|
||||
<!-- 日志输出级别 -->
|
||||
<root level="info">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
<appender-ref ref="INFO"/>
|
||||
<appender-ref ref="ERROR"/>
|
||||
<appender-ref ref="DEBUG"/>
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.tongran.agent.client;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class TrAgentClientApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user