mtr-agent-client v1.0.0初始化

This commit is contained in:
gaoyutao
2025-11-13 17:52:19 +08:00
commit ae084bfa52
89 changed files with 10160 additions and 0 deletions
+158
View File
@@ -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> &lt;!&ndash; 请检查并使用最新版本 &ndash;&gt;-->
<!-- </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 {
//文件地址,外网HTTPS)地址
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());
}
}
@@ -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<>();
// 正则表达式匹配:当前 IPxxx.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 信息字符串,如 "当前 IP112.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);
}
}
+26
View File
@@ -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 # 最大重连延迟(毫秒)
+35
View File
@@ -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
+116
View File
@@ -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() {
}
}