From ae084bfa5249c03a2abf4633e7468c61489dffd5 Mon Sep 17 00:00:00 2001 From: gaoyutao Date: Thu, 13 Nov 2025 17:52:19 +0800 Subject: [PATCH] =?UTF-8?q?mtr-agent-client=20v1.0.0=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 158 ++ .../client/TrAgentClientApplication.java | 20 + .../core/config/ApplicationProperties.java | 66 + .../client/core/config/GlobalConfig.java | 132 ++ .../agent/client/core/enums/MsgEnum.java | 76 + .../client/core/eo/AgentVersionUpdateEO.java | 29 + .../tongran/agent/client/core/eo/AlarmEO.java | 25 + .../agent/client/core/eo/CollectEO.java | 16 + .../core/eo/NativeNetworkInterfaceEO.java | 16 + .../agent/client/core/eo/ScriptPolicyEO.java | 34 + .../agent/client/core/session/Session.java | 85 + .../client/core/session/SessionManager.java | 137 ++ .../tongran/agent/client/core/vo/AlarmVO.java | 22 + .../tongran/agent/client/core/vo/CpuVO.java | 60 + .../tongran/agent/client/core/vo/DiskVO.java | 50 + .../agent/client/core/vo/DockerVO.java | 44 + .../agent/client/core/vo/MemoryVO.java | 40 + .../tongran/agent/client/core/vo/NetVO.java | 55 + .../client/core/vo/NetworkInterfaceInfo.java | 25 + .../tongran/agent/client/core/vo/PointVO.java | 36 + .../agent/client/core/vo/SwitchBoardVO.java | 41 + .../agent/client/core/vo/SystemVO.java | 55 + .../client/exception/ServerException.java | 27 + .../client/exception/base/BaseException.java | 42 + .../client/exception/code/ErrorCode.java | 16 + .../exception/code/GlobalErrorCode.java | 20 + .../agent/client/netty/BaseNettyServer.java | 117 ++ .../client/netty/MultiTargetNettyClient.java | 301 ++++ .../netty/annotation/AgentDispatcher.java | 47 + .../netty/basics/AgentDispatcherManager.java | 48 + .../client/netty/basics/AgentHandler.java | 21 + .../client/netty/config/AgentNettyConfig.java | 16 + .../client/netty/config/BaseNettyConfig.java | 58 + .../client/netty/config/ConnectionConfig.java | 39 + .../client/netty/enpoint/AgentEndpoint.java | 265 +++ .../netty/handler/AgentDecoderHandler.java | 156 ++ .../netty/handler/AgentDispatcherHandler.java | 38 + .../netty/handler/AgentEncoderHandler.java | 49 + .../agent/client/netty/model/Message.java | 37 + .../client/netty/model/UpMsgResponse.java | 24 + .../scheduler/config/SchedulerConfig.java | 55 + .../agent/client/scheduler/job/DailyJob.java | 45 + .../service/AdvancedAsyncDownloader.java | 121 ++ .../scheduler/service/AppInitializer.java | 124 ++ .../service/AsyncCommandExecutor.java | 273 ++++ .../scheduler/service/BusinessTasks.java | 967 +++++++++++ .../scheduler/service/CompensatedTrigger.java | 57 + .../scheduler/service/DynamicTaskService.java | 158 ++ .../service/DynamicTcpPortService.java | 181 +++ .../service/Java8FileDownloader.java | 182 +++ .../service/SchedulerController.java | 63 + .../scheduler/task/SpecificTimeRequest.java | 53 + .../task/SpecificTimeTaskConfig.java | 114 ++ .../task/SpecificTimeTaskService.java | 146 ++ .../agent/client/service/AgentService.java | 33 + .../agent/client/service/AlarmService.java | 9 + .../agent/client/service/CPUService.java | 7 + .../agent/client/service/DiskService.java | 12 + .../agent/client/service/DockerService.java | 9 + .../agent/client/service/MemoryService.java | 7 + .../agent/client/service/NetService.java | 9 + .../client/service/SwitchBoardService.java | 8 + .../agent/client/service/SystemService.java | 9 + .../client/service/impl/AgentServiceImpl.java | 1435 +++++++++++++++++ .../client/service/impl/AlarmServiceImpl.java | 486 ++++++ .../client/service/impl/CPUServiceImpl.java | 127 ++ .../client/service/impl/DiskServiceImpl.java | 108 ++ .../service/impl/DockerServiceImpl.java | 135 ++ .../service/impl/MemoryServiceImpl.java | 76 + .../client/service/impl/NetServiceImpl.java | 125 ++ .../service/impl/SwitchBoardServiceImpl.java | 9 + .../service/impl/SystemServiceImpl.java | 410 +++++ .../agent/client/utils/AgentDataUtil.java | 55 + .../tongran/agent/client/utils/AgentUtil.java | 780 +++++++++ .../tongran/agent/client/utils/AssertLog.java | 73 + .../agent/client/utils/ClientExample.java | 47 + .../client/utils/CpuUsageFromProcStat.java | 70 + .../agent/client/utils/CrontabManager.java | 326 ++++ .../utils/EnhancedConnectionManager.java | 82 + .../agent/client/utils/FileCleaner.java | 74 + .../client/utils/MachineFingerprint.java | 125 ++ .../agent/client/utils/NetworkUtil.java | 27 + .../agent/client/utils/PublicIpFetcher.java | 86 + .../com/tongran/agent/client/utils/R.java | 76 + .../agent/client/utils/RoundMinutes.java | 53 + src/main/resources/application-dev.yml | 26 + src/main/resources/application.yml | 35 + src/main/resources/logback/logback-dev.xml | 116 ++ .../client/TrAgentClientApplicationTests.java | 13 + 89 files changed, 10160 insertions(+) create mode 100644 pom.xml create mode 100644 src/main/java/com/tongran/agent/client/TrAgentClientApplication.java create mode 100644 src/main/java/com/tongran/agent/client/core/config/ApplicationProperties.java create mode 100644 src/main/java/com/tongran/agent/client/core/config/GlobalConfig.java create mode 100644 src/main/java/com/tongran/agent/client/core/enums/MsgEnum.java create mode 100644 src/main/java/com/tongran/agent/client/core/eo/AgentVersionUpdateEO.java create mode 100644 src/main/java/com/tongran/agent/client/core/eo/AlarmEO.java create mode 100644 src/main/java/com/tongran/agent/client/core/eo/CollectEO.java create mode 100644 src/main/java/com/tongran/agent/client/core/eo/NativeNetworkInterfaceEO.java create mode 100644 src/main/java/com/tongran/agent/client/core/eo/ScriptPolicyEO.java create mode 100644 src/main/java/com/tongran/agent/client/core/session/Session.java create mode 100644 src/main/java/com/tongran/agent/client/core/session/SessionManager.java create mode 100644 src/main/java/com/tongran/agent/client/core/vo/AlarmVO.java create mode 100644 src/main/java/com/tongran/agent/client/core/vo/CpuVO.java create mode 100644 src/main/java/com/tongran/agent/client/core/vo/DiskVO.java create mode 100644 src/main/java/com/tongran/agent/client/core/vo/DockerVO.java create mode 100644 src/main/java/com/tongran/agent/client/core/vo/MemoryVO.java create mode 100644 src/main/java/com/tongran/agent/client/core/vo/NetVO.java create mode 100644 src/main/java/com/tongran/agent/client/core/vo/NetworkInterfaceInfo.java create mode 100644 src/main/java/com/tongran/agent/client/core/vo/PointVO.java create mode 100644 src/main/java/com/tongran/agent/client/core/vo/SwitchBoardVO.java create mode 100644 src/main/java/com/tongran/agent/client/core/vo/SystemVO.java create mode 100644 src/main/java/com/tongran/agent/client/exception/ServerException.java create mode 100644 src/main/java/com/tongran/agent/client/exception/base/BaseException.java create mode 100644 src/main/java/com/tongran/agent/client/exception/code/ErrorCode.java create mode 100644 src/main/java/com/tongran/agent/client/exception/code/GlobalErrorCode.java create mode 100644 src/main/java/com/tongran/agent/client/netty/BaseNettyServer.java create mode 100644 src/main/java/com/tongran/agent/client/netty/MultiTargetNettyClient.java create mode 100644 src/main/java/com/tongran/agent/client/netty/annotation/AgentDispatcher.java create mode 100644 src/main/java/com/tongran/agent/client/netty/basics/AgentDispatcherManager.java create mode 100644 src/main/java/com/tongran/agent/client/netty/basics/AgentHandler.java create mode 100644 src/main/java/com/tongran/agent/client/netty/config/AgentNettyConfig.java create mode 100644 src/main/java/com/tongran/agent/client/netty/config/BaseNettyConfig.java create mode 100644 src/main/java/com/tongran/agent/client/netty/config/ConnectionConfig.java create mode 100644 src/main/java/com/tongran/agent/client/netty/enpoint/AgentEndpoint.java create mode 100644 src/main/java/com/tongran/agent/client/netty/handler/AgentDecoderHandler.java create mode 100644 src/main/java/com/tongran/agent/client/netty/handler/AgentDispatcherHandler.java create mode 100644 src/main/java/com/tongran/agent/client/netty/handler/AgentEncoderHandler.java create mode 100644 src/main/java/com/tongran/agent/client/netty/model/Message.java create mode 100644 src/main/java/com/tongran/agent/client/netty/model/UpMsgResponse.java create mode 100644 src/main/java/com/tongran/agent/client/scheduler/config/SchedulerConfig.java create mode 100644 src/main/java/com/tongran/agent/client/scheduler/job/DailyJob.java create mode 100644 src/main/java/com/tongran/agent/client/scheduler/service/AdvancedAsyncDownloader.java create mode 100644 src/main/java/com/tongran/agent/client/scheduler/service/AppInitializer.java create mode 100644 src/main/java/com/tongran/agent/client/scheduler/service/AsyncCommandExecutor.java create mode 100644 src/main/java/com/tongran/agent/client/scheduler/service/BusinessTasks.java create mode 100644 src/main/java/com/tongran/agent/client/scheduler/service/CompensatedTrigger.java create mode 100644 src/main/java/com/tongran/agent/client/scheduler/service/DynamicTaskService.java create mode 100644 src/main/java/com/tongran/agent/client/scheduler/service/DynamicTcpPortService.java create mode 100644 src/main/java/com/tongran/agent/client/scheduler/service/Java8FileDownloader.java create mode 100644 src/main/java/com/tongran/agent/client/scheduler/service/SchedulerController.java create mode 100644 src/main/java/com/tongran/agent/client/scheduler/task/SpecificTimeRequest.java create mode 100644 src/main/java/com/tongran/agent/client/scheduler/task/SpecificTimeTaskConfig.java create mode 100644 src/main/java/com/tongran/agent/client/scheduler/task/SpecificTimeTaskService.java create mode 100644 src/main/java/com/tongran/agent/client/service/AgentService.java create mode 100644 src/main/java/com/tongran/agent/client/service/AlarmService.java create mode 100644 src/main/java/com/tongran/agent/client/service/CPUService.java create mode 100644 src/main/java/com/tongran/agent/client/service/DiskService.java create mode 100644 src/main/java/com/tongran/agent/client/service/DockerService.java create mode 100644 src/main/java/com/tongran/agent/client/service/MemoryService.java create mode 100644 src/main/java/com/tongran/agent/client/service/NetService.java create mode 100644 src/main/java/com/tongran/agent/client/service/SwitchBoardService.java create mode 100644 src/main/java/com/tongran/agent/client/service/SystemService.java create mode 100644 src/main/java/com/tongran/agent/client/service/impl/AgentServiceImpl.java create mode 100644 src/main/java/com/tongran/agent/client/service/impl/AlarmServiceImpl.java create mode 100644 src/main/java/com/tongran/agent/client/service/impl/CPUServiceImpl.java create mode 100644 src/main/java/com/tongran/agent/client/service/impl/DiskServiceImpl.java create mode 100644 src/main/java/com/tongran/agent/client/service/impl/DockerServiceImpl.java create mode 100644 src/main/java/com/tongran/agent/client/service/impl/MemoryServiceImpl.java create mode 100644 src/main/java/com/tongran/agent/client/service/impl/NetServiceImpl.java create mode 100644 src/main/java/com/tongran/agent/client/service/impl/SwitchBoardServiceImpl.java create mode 100644 src/main/java/com/tongran/agent/client/service/impl/SystemServiceImpl.java create mode 100644 src/main/java/com/tongran/agent/client/utils/AgentDataUtil.java create mode 100644 src/main/java/com/tongran/agent/client/utils/AgentUtil.java create mode 100644 src/main/java/com/tongran/agent/client/utils/AssertLog.java create mode 100644 src/main/java/com/tongran/agent/client/utils/ClientExample.java create mode 100644 src/main/java/com/tongran/agent/client/utils/CpuUsageFromProcStat.java create mode 100644 src/main/java/com/tongran/agent/client/utils/CrontabManager.java create mode 100644 src/main/java/com/tongran/agent/client/utils/EnhancedConnectionManager.java create mode 100644 src/main/java/com/tongran/agent/client/utils/FileCleaner.java create mode 100644 src/main/java/com/tongran/agent/client/utils/MachineFingerprint.java create mode 100644 src/main/java/com/tongran/agent/client/utils/NetworkUtil.java create mode 100644 src/main/java/com/tongran/agent/client/utils/PublicIpFetcher.java create mode 100644 src/main/java/com/tongran/agent/client/utils/R.java create mode 100644 src/main/java/com/tongran/agent/client/utils/RoundMinutes.java create mode 100644 src/main/resources/application-dev.yml create mode 100644 src/main/resources/application.yml create mode 100644 src/main/resources/logback/logback-dev.xml create mode 100644 src/test/java/com/tongran/agent/client/TrAgentClientApplicationTests.java diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..2e349d2 --- /dev/null +++ b/pom.xml @@ -0,0 +1,158 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.5.6 + + + com.tongran.agent + tr-agent-client + 0.0.1-SNAPSHOT + tr-agent-client + tr-agent-client + + + 1.8 + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.boot + spring-boot-configuration-processor + + + + org.springframework.boot + spring-boot-starter-aop + + + + org.springframework.boot + spring-boot-starter-validation + + + + org.projectlombok + lombok + true + + + + cn.hutool + hutool-all + 5.7.11 + + + + org.apache.commons + commons-lang3 + + + + io.netty + netty-all + 4.1.86.Final + + + + + + + + + + com.github.oshi + oshi-core + 6.4.4 + + + + com.github.docker-java + docker-java + 3.3.0 + + + + + com.github.docker-java + docker-java-api + 3.3.0 + + + + + com.github.docker-java + docker-java-transport-httpclient5 + 3.3.0 + + + + org.projectlombok + lombok + provided + + + + com.alibaba.fastjson2 + fastjson2 + 2.0.31 + + + + com.github.xiaoymin + knife4j-openapi3-spring-boot-starter + 4.5.0 + + + + + org.snmp4j + snmp4j + 2.8.9 + + + + org.slf4j + slf4j-api + 1.7.36 + + + org.slf4j + slf4j-simple + 1.7.36 + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + true + + + + + + diff --git a/src/main/java/com/tongran/agent/client/TrAgentClientApplication.java b/src/main/java/com/tongran/agent/client/TrAgentClientApplication.java new file mode 100644 index 0000000..99064fa --- /dev/null +++ b/src/main/java/com/tongran/agent/client/TrAgentClientApplication.java @@ -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); + } + +} diff --git a/src/main/java/com/tongran/agent/client/core/config/ApplicationProperties.java b/src/main/java/com/tongran/agent/client/core/config/ApplicationProperties.java new file mode 100644 index 0000000..627daf3 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/core/config/ApplicationProperties.java @@ -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; + } +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/core/config/GlobalConfig.java b/src/main/java/com/tongran/agent/client/core/config/GlobalConfig.java new file mode 100644 index 0000000..13c5ef5 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/core/config/GlobalConfig.java @@ -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 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 ALARM_LIST = new ArrayList<>(); //告警设置信息 + + /** + * 所有网络接口 + */ + public static List NET_LIST = new ArrayList<>(); + + /** + * 脚本文件下载标识 + */ + public static LinkedHashMap DOWN_FILES = new LinkedHashMap<>(); + +} diff --git a/src/main/java/com/tongran/agent/client/core/enums/MsgEnum.java b/src/main/java/com/tongran/agent/client/core/enums/MsgEnum.java new file mode 100644 index 0000000..1892650 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/core/enums/MsgEnum.java @@ -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; + + + +} diff --git a/src/main/java/com/tongran/agent/client/core/eo/AgentVersionUpdateEO.java b/src/main/java/com/tongran/agent/client/core/eo/AgentVersionUpdateEO.java new file mode 100644 index 0000000..71a060c --- /dev/null +++ b/src/main/java/com/tongran/agent/client/core/eo/AgentVersionUpdateEO.java @@ -0,0 +1,29 @@ +package com.tongran.agent.client.core.eo; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class AgentVersionUpdateEO { + + //文件地址,外网HTTP(S)地址 + private String fileUrl; + + //文件MD5 + private String fileMd5; + + //执行方式:0、立即执行;1、定时执行; + private int method; + + //定时时间,执行方式为1、定时执行时该字段必传 + private long policyTime; + + //时间戳 + private long timestamp; + +} diff --git a/src/main/java/com/tongran/agent/client/core/eo/AlarmEO.java b/src/main/java/com/tongran/agent/client/core/eo/AlarmEO.java new file mode 100644 index 0000000..c36edeb --- /dev/null +++ b/src/main/java/com/tongran/agent/client/core/eo/AlarmEO.java @@ -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; +} diff --git a/src/main/java/com/tongran/agent/client/core/eo/CollectEO.java b/src/main/java/com/tongran/agent/client/core/eo/CollectEO.java new file mode 100644 index 0000000..c239110 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/core/eo/CollectEO.java @@ -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; +} diff --git a/src/main/java/com/tongran/agent/client/core/eo/NativeNetworkInterfaceEO.java b/src/main/java/com/tongran/agent/client/core/eo/NativeNetworkInterfaceEO.java new file mode 100644 index 0000000..5fb38c7 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/core/eo/NativeNetworkInterfaceEO.java @@ -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; +} diff --git a/src/main/java/com/tongran/agent/client/core/eo/ScriptPolicyEO.java b/src/main/java/com/tongran/agent/client/core/eo/ScriptPolicyEO.java new file mode 100644 index 0000000..926386a --- /dev/null +++ b/src/main/java/com/tongran/agent/client/core/eo/ScriptPolicyEO.java @@ -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; + +} diff --git a/src/main/java/com/tongran/agent/client/core/session/Session.java b/src/main/java/com/tongran/agent/client/core/session/Session.java new file mode 100644 index 0000000..4aad421 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/core/session/Session.java @@ -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 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; + } + +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/core/session/SessionManager.java b/src/main/java/com/tongran/agent/client/core/session/SessionManager.java new file mode 100644 index 0000000..ae3bd62 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/core/session/SessionManager.java @@ -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 sessionMap = new ConcurrentHashMap<>(); + + private final Cache 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 serialNumber) { + Attribute 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()); + } + }); + } + +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/core/vo/AlarmVO.java b/src/main/java/com/tongran/agent/client/core/vo/AlarmVO.java new file mode 100644 index 0000000..3d8aa1d --- /dev/null +++ b/src/main/java/com/tongran/agent/client/core/vo/AlarmVO.java @@ -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; +} diff --git a/src/main/java/com/tongran/agent/client/core/vo/CpuVO.java b/src/main/java/com/tongran/agent/client/core/vo/CpuVO.java new file mode 100644 index 0000000..fe11f60 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/core/vo/CpuVO.java @@ -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; + + + +} diff --git a/src/main/java/com/tongran/agent/client/core/vo/DiskVO.java b/src/main/java/com/tongran/agent/client/core/vo/DiskVO.java new file mode 100644 index 0000000..adb67d4 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/core/vo/DiskVO.java @@ -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; + + +} diff --git a/src/main/java/com/tongran/agent/client/core/vo/DockerVO.java b/src/main/java/com/tongran/agent/client/core/vo/DockerVO.java new file mode 100644 index 0000000..8dfdd8e --- /dev/null +++ b/src/main/java/com/tongran/agent/client/core/vo/DockerVO.java @@ -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; + + +} diff --git a/src/main/java/com/tongran/agent/client/core/vo/MemoryVO.java b/src/main/java/com/tongran/agent/client/core/vo/MemoryVO.java new file mode 100644 index 0000000..a190702 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/core/vo/MemoryVO.java @@ -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; + +} diff --git a/src/main/java/com/tongran/agent/client/core/vo/NetVO.java b/src/main/java/com/tongran/agent/client/core/vo/NetVO.java new file mode 100644 index 0000000..f7eb9dd --- /dev/null +++ b/src/main/java/com/tongran/agent/client/core/vo/NetVO.java @@ -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; + +} diff --git a/src/main/java/com/tongran/agent/client/core/vo/NetworkInterfaceInfo.java b/src/main/java/com/tongran/agent/client/core/vo/NetworkInterfaceInfo.java new file mode 100644 index 0000000..a136109 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/core/vo/NetworkInterfaceInfo.java @@ -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; // 市 + +} diff --git a/src/main/java/com/tongran/agent/client/core/vo/PointVO.java b/src/main/java/com/tongran/agent/client/core/vo/PointVO.java new file mode 100644 index 0000000..0661ffe --- /dev/null +++ b/src/main/java/com/tongran/agent/client/core/vo/PointVO.java @@ -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; +} diff --git a/src/main/java/com/tongran/agent/client/core/vo/SwitchBoardVO.java b/src/main/java/com/tongran/agent/client/core/vo/SwitchBoardVO.java new file mode 100644 index 0000000..182f0b7 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/core/vo/SwitchBoardVO.java @@ -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; + + +} diff --git a/src/main/java/com/tongran/agent/client/core/vo/SystemVO.java b/src/main/java/com/tongran/agent/client/core/vo/SystemVO.java new file mode 100644 index 0000000..8ccbebe --- /dev/null +++ b/src/main/java/com/tongran/agent/client/core/vo/SystemVO.java @@ -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; + + +} diff --git a/src/main/java/com/tongran/agent/client/exception/ServerException.java b/src/main/java/com/tongran/agent/client/exception/ServerException.java new file mode 100644 index 0000000..818606b --- /dev/null +++ b/src/main/java/com/tongran/agent/client/exception/ServerException.java @@ -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)); + } + +} diff --git a/src/main/java/com/tongran/agent/client/exception/base/BaseException.java b/src/main/java/com/tongran/agent/client/exception/base/BaseException.java new file mode 100644 index 0000000..39d432f --- /dev/null +++ b/src/main/java/com/tongran/agent/client/exception/base/BaseException.java @@ -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; + } +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/exception/code/ErrorCode.java b/src/main/java/com/tongran/agent/client/exception/code/ErrorCode.java new file mode 100644 index 0000000..834eb11 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/exception/code/ErrorCode.java @@ -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()); + } + +} diff --git a/src/main/java/com/tongran/agent/client/exception/code/GlobalErrorCode.java b/src/main/java/com/tongran/agent/client/exception/code/GlobalErrorCode.java new file mode 100644 index 0000000..0109be6 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/exception/code/GlobalErrorCode.java @@ -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; + +} diff --git a/src/main/java/com/tongran/agent/client/netty/BaseNettyServer.java b/src/main/java/com/tongran/agent/client/netty/BaseNettyServer.java new file mode 100644 index 0000000..ebfbb1b --- /dev/null +++ b/src/main/java/com/tongran/agent/client/netty/BaseNettyServer.java @@ -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); + } +} diff --git a/src/main/java/com/tongran/agent/client/netty/MultiTargetNettyClient.java b/src/main/java/com/tongran/agent/client/netty/MultiTargetNettyClient.java new file mode 100644 index 0000000..2b0aea0 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/netty/MultiTargetNettyClient.java @@ -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 CONNECTION_KEY = AttributeKey.valueOf("connectionKey"); + + // 连接管理器 + private final Map connectionMap = new ConcurrentHashMap<>(); + private final Map 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() { + @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 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 { +// @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 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; + } +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/netty/annotation/AgentDispatcher.java b/src/main/java/com/tongran/agent/client/netty/annotation/AgentDispatcher.java new file mode 100644 index 0000000..70b97d2 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/netty/annotation/AgentDispatcher.java @@ -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 ""; + +} diff --git a/src/main/java/com/tongran/agent/client/netty/basics/AgentDispatcherManager.java b/src/main/java/com/tongran/agent/client/netty/basics/AgentDispatcherManager.java new file mode 100644 index 0000000..bbc092f --- /dev/null +++ b/src/main/java/com/tongran/agent/client/netty/basics/AgentDispatcherManager.java @@ -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 MSG_HANDLER_MAP; + + /** + * 唤醒时 初始化 packHandlerMap + */ + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + // 仅一次性初始化完成 + if (MSG_HANDLER_MAP == null) { + MSG_HANDLER_MAP = new ConcurrentHashMap<>(); + Map 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); + } + +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/netty/basics/AgentHandler.java b/src/main/java/com/tongran/agent/client/netty/basics/AgentHandler.java new file mode 100644 index 0000000..2bfe897 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/netty/basics/AgentHandler.java @@ -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 ""; + } +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/netty/config/AgentNettyConfig.java b/src/main/java/com/tongran/agent/client/netty/config/AgentNettyConfig.java new file mode 100644 index 0000000..98f6d04 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/netty/config/AgentNettyConfig.java @@ -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; + +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/netty/config/BaseNettyConfig.java b/src/main/java/com/tongran/agent/client/netty/config/BaseNettyConfig.java new file mode 100644 index 0000000..b6f6480 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/netty/config/BaseNettyConfig.java @@ -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; + +} diff --git a/src/main/java/com/tongran/agent/client/netty/config/ConnectionConfig.java b/src/main/java/com/tongran/agent/client/netty/config/ConnectionConfig.java new file mode 100644 index 0000000..6bb5f68 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/netty/config/ConnectionConfig.java @@ -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); + } +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/netty/enpoint/AgentEndpoint.java b/src/main/java/com/tongran/agent/client/netty/enpoint/AgentEndpoint.java new file mode 100644 index 0000000..087c926 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/netty/enpoint/AgentEndpoint.java @@ -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>() {}); + 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; + } + } + + + + + +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/netty/handler/AgentDecoderHandler.java b/src/main/java/com/tongran/agent/client/netty/handler/AgentDecoderHandler.java new file mode 100644 index 0000000..a12ba6c --- /dev/null +++ b/src/main/java/com/tongran/agent/client/netty/handler/AgentDecoderHandler.java @@ -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 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; + } +} diff --git a/src/main/java/com/tongran/agent/client/netty/handler/AgentDispatcherHandler.java b/src/main/java/com/tongran/agent/client/netty/handler/AgentDispatcherHandler.java new file mode 100644 index 0000000..28b5586 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/netty/handler/AgentDispatcherHandler.java @@ -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 { + + 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); + } + } +} diff --git a/src/main/java/com/tongran/agent/client/netty/handler/AgentEncoderHandler.java b/src/main/java/com/tongran/agent/client/netty/handler/AgentEncoderHandler.java new file mode 100644 index 0000000..da820c5 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/netty/handler/AgentEncoderHandler.java @@ -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); + } + } + +} diff --git a/src/main/java/com/tongran/agent/client/netty/model/Message.java b/src/main/java/com/tongran/agent/client/netty/model/Message.java new file mode 100644 index 0000000..e676e2f --- /dev/null +++ b/src/main/java/com/tongran/agent/client/netty/model/Message.java @@ -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; + +} diff --git a/src/main/java/com/tongran/agent/client/netty/model/UpMsgResponse.java b/src/main/java/com/tongran/agent/client/netty/model/UpMsgResponse.java new file mode 100644 index 0000000..a3ea551 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/netty/model/UpMsgResponse.java @@ -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; + +} diff --git a/src/main/java/com/tongran/agent/client/scheduler/config/SchedulerConfig.java b/src/main/java/com/tongran/agent/client/scheduler/config/SchedulerConfig.java new file mode 100644 index 0000000..1c4975e --- /dev/null +++ b/src/main/java/com/tongran/agent/client/scheduler/config/SchedulerConfig.java @@ -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; + } +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/scheduler/job/DailyJob.java b/src/main/java/com/tongran/agent/client/scheduler/job/DailyJob.java new file mode 100644 index 0000000..311de83 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/scheduler/job/DailyJob.java @@ -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()); + + } + + + +} diff --git a/src/main/java/com/tongran/agent/client/scheduler/service/AdvancedAsyncDownloader.java b/src/main/java/com/tongran/agent/client/scheduler/service/AdvancedAsyncDownloader.java new file mode 100644 index 0000000..de7c711 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/scheduler/service/AdvancedAsyncDownloader.java @@ -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 downloadWithProgress( + String fileUrl, + String saveDir, + String fileName, + Consumer 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("异步下载已启动..."); + } +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/scheduler/service/AppInitializer.java b/src/main/java/com/tongran/agent/client/scheduler/service/AppInitializer.java new file mode 100644 index 0000000..2031546 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/scheduler/service/AppInitializer.java @@ -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 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); + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/scheduler/service/AsyncCommandExecutor.java b/src/main/java/com/tongran/agent/client/scheduler/service/AsyncCommandExecutor.java new file mode 100644 index 0000000..2e21c22 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/scheduler/service/AsyncCommandExecutor.java @@ -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 makeFileExecutableAsync(String filePath) { + return CompletableFuture.supplyAsync(() -> { + try { + Path path = Paths.get(filePath); + + if (!Files.exists(path)) { + throw new RuntimeException("文件不存在: " + filePath); + } + + Set 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 executeCommandAsync( + List 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 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 executeCommandWithRealtimeOutput( + String[] command, + long timeout, + TimeUnit timeUnit, + Consumer outputConsumer, + Consumer errorConsumer) { + + return CompletableFuture.supplyAsync(() -> { + Process process = null; + try { + ProcessBuilder processBuilder = new ProcessBuilder(command); + process = processBuilder.start(); + + // 启动输出读取线程 + CompletableFuture outputFuture = readStreamAsync( + process.getInputStream(), outputConsumer, "OUTPUT"); + + CompletableFuture 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 readStreamAsync( + InputStream inputStream, + Consumer 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 executeScriptAsync( + String scriptPath, + String[] args, + long timeout, + TimeUnit timeUnit, + Consumer 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> executeCommandsAsync( +// List 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); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/scheduler/service/BusinessTasks.java b/src/main/java/com/tongran/agent/client/scheduler/service/BusinessTasks.java new file mode 100644 index 0000000..7288315 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/scheduler/service/BusinessTasks.java @@ -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 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 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 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 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 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 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 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 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 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); +// } + + + + + +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/scheduler/service/CompensatedTrigger.java b/src/main/java/com/tongran/agent/client/scheduler/service/CompensatedTrigger.java new file mode 100644 index 0000000..506280d --- /dev/null +++ b/src/main/java/com/tongran/agent/client/scheduler/service/CompensatedTrigger.java @@ -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)); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/scheduler/service/DynamicTaskService.java b/src/main/java/com/tongran/agent/client/scheduler/service/DynamicTaskService.java new file mode 100644 index 0000000..c96fe67 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/scheduler/service/DynamicTaskService.java @@ -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> taskFutures = new ConcurrentHashMap<>(); + private final Map taskConfigs = new ConcurrentHashMap<>(); + private final Map taskExecutions = new ConcurrentHashMap<>(); + private final Map 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 getAllTaskStatus() { + Map 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; + } +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/scheduler/service/DynamicTcpPortService.java b/src/main/java/com/tongran/agent/client/scheduler/service/DynamicTcpPortService.java new file mode 100644 index 0000000..7fdc122 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/scheduler/service/DynamicTcpPortService.java @@ -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 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 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 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 getAvailablePorts(int start, int end) { + List 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"; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/scheduler/service/Java8FileDownloader.java b/src/main/java/com/tongran/agent/client/scheduler/service/Java8FileDownloader.java new file mode 100644 index 0000000..6238e25 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/scheduler/service/Java8FileDownloader.java @@ -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; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/scheduler/service/SchedulerController.java b/src/main/java/com/tongran/agent/client/scheduler/service/SchedulerController.java new file mode 100644 index 0000000..b55d153 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/scheduler/service/SchedulerController.java @@ -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 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 () -> {}; + } +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/scheduler/task/SpecificTimeRequest.java b/src/main/java/com/tongran/agent/client/scheduler/task/SpecificTimeRequest.java new file mode 100644 index 0000000..4fc599c --- /dev/null +++ b/src/main/java/com/tongran/agent/client/scheduler/task/SpecificTimeRequest.java @@ -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; } +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/scheduler/task/SpecificTimeTaskConfig.java b/src/main/java/com/tongran/agent/client/scheduler/task/SpecificTimeTaskConfig.java new file mode 100644 index 0000000..31ef6cb --- /dev/null +++ b/src/main/java/com/tongran/agent/client/scheduler/task/SpecificTimeTaskConfig.java @@ -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> taskFutures = new ConcurrentHashMap<>(); + private final Map 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); + } +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/scheduler/task/SpecificTimeTaskService.java b/src/main/java/com/tongran/agent/client/scheduler/task/SpecificTimeTaskService.java new file mode 100644 index 0000000..cdb6b35 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/scheduler/task/SpecificTimeTaskService.java @@ -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 cmd = Arrays.asList(request.getTaskData().split("\\s+")); + CompletableFuture 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()); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/service/AgentService.java b/src/main/java/com/tongran/agent/client/service/AgentService.java new file mode 100644 index 0000000..92be1de --- /dev/null +++ b/src/main/java/com/tongran/agent/client/service/AgentService.java @@ -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(); +} diff --git a/src/main/java/com/tongran/agent/client/service/AlarmService.java b/src/main/java/com/tongran/agent/client/service/AlarmService.java new file mode 100644 index 0000000..b94485e --- /dev/null +++ b/src/main/java/com/tongran/agent/client/service/AlarmService.java @@ -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 alarmMonitor(long timestamp); +} diff --git a/src/main/java/com/tongran/agent/client/service/CPUService.java b/src/main/java/com/tongran/agent/client/service/CPUService.java new file mode 100644 index 0000000..f14cdfd --- /dev/null +++ b/src/main/java/com/tongran/agent/client/service/CPUService.java @@ -0,0 +1,7 @@ +package com.tongran.agent.client.service; + +import com.tongran.agent.client.core.vo.CpuVO; + +public interface CPUService { + CpuVO get(); +} diff --git a/src/main/java/com/tongran/agent/client/service/DiskService.java b/src/main/java/com/tongran/agent/client/service/DiskService.java new file mode 100644 index 0000000..135b80b --- /dev/null +++ b/src/main/java/com/tongran/agent/client/service/DiskService.java @@ -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 diskList(long timestamp); + + List pointList(long timestamp); +} diff --git a/src/main/java/com/tongran/agent/client/service/DockerService.java b/src/main/java/com/tongran/agent/client/service/DockerService.java new file mode 100644 index 0000000..bb6143e --- /dev/null +++ b/src/main/java/com/tongran/agent/client/service/DockerService.java @@ -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 dockerList(long timestamp); +} diff --git a/src/main/java/com/tongran/agent/client/service/MemoryService.java b/src/main/java/com/tongran/agent/client/service/MemoryService.java new file mode 100644 index 0000000..72a8980 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/service/MemoryService.java @@ -0,0 +1,7 @@ +package com.tongran.agent.client.service; + +import com.tongran.agent.client.core.vo.MemoryVO; + +public interface MemoryService { + MemoryVO get(); +} diff --git a/src/main/java/com/tongran/agent/client/service/NetService.java b/src/main/java/com/tongran/agent/client/service/NetService.java new file mode 100644 index 0000000..6f30f5a --- /dev/null +++ b/src/main/java/com/tongran/agent/client/service/NetService.java @@ -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 netList(long timestamp); +} diff --git a/src/main/java/com/tongran/agent/client/service/SwitchBoardService.java b/src/main/java/com/tongran/agent/client/service/SwitchBoardService.java new file mode 100644 index 0000000..4a49acc --- /dev/null +++ b/src/main/java/com/tongran/agent/client/service/SwitchBoardService.java @@ -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 { +} diff --git a/src/main/java/com/tongran/agent/client/service/SystemService.java b/src/main/java/com/tongran/agent/client/service/SystemService.java new file mode 100644 index 0000000..e14e40b --- /dev/null +++ b/src/main/java/com/tongran/agent/client/service/SystemService.java @@ -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); +} diff --git a/src/main/java/com/tongran/agent/client/service/impl/AgentServiceImpl.java b/src/main/java/com/tongran/agent/client/service/impl/AgentServiceImpl.java new file mode 100644 index 0000000..1ddf808 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/service/impl/AgentServiceImpl.java @@ -0,0 +1,1435 @@ +package com.tongran.agent.client.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.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.CollectEO; +import com.tongran.agent.client.core.eo.ScriptPolicyEO; +import com.tongran.agent.client.core.session.SessionManager; +import com.tongran.agent.client.netty.MultiTargetNettyClient; +import com.tongran.agent.client.netty.config.AgentNettyConfig; +import com.tongran.agent.client.netty.model.Message; +import com.tongran.agent.client.scheduler.service.AdvancedAsyncDownloader; +import com.tongran.agent.client.scheduler.service.AsyncCommandExecutor; +import com.tongran.agent.client.scheduler.service.BusinessTasks; +import com.tongran.agent.client.scheduler.service.DynamicTaskService; +import com.tongran.agent.client.scheduler.task.SpecificTimeRequest; +import com.tongran.agent.client.scheduler.task.SpecificTimeTaskService; +import com.tongran.agent.client.service.AgentService; +import com.tongran.agent.client.utils.*; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.*; +import java.time.LocalDateTime; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +@Service +public class AgentServiceImpl implements AgentService { + protected final SessionManager sessionManager; + + private final DynamicTaskService dynamicTaskService; + + private final BusinessTasks businessTasks; + + @Resource + private SpecificTimeTaskService taskService; + + @Resource + private ApplicationProperties properties; + + @Resource + private MultiTargetNettyClient client; + + @Resource + private AgentNettyConfig config; + + // 在注入点使用@Lazy + @Autowired + public AgentServiceImpl(DynamicTaskService dynamicTaskService, + @Lazy BusinessTasks businessTasks) { + this.dynamicTaskService = dynamicTaskService; + this.businessTasks = businessTasks; + this.sessionManager = SessionManager.getInstance(); + } + + @Override + public void cancelCollect() { + systemCollectStop(); + dynamicTaskService.cancelTask("alarmTask"); + dynamicTaskService.cancelTask("heartbeat"); + } + + @Override + public void systemCollectStop() { + String[] arr = {"cpuCollect","vfsCollect","netCollect","diskCollect","dockerCollect","systemSwapSizeFreeCollect","memoryUtilizationCollect" + ,"systemSwapSizePercentCollect","memorySizeAvailableCollect","memorySizePercentCollect","memorySizeTotalCollect","systemSwOsCollect" + ,"systemSwArchCollect","kernelMaxprocCollect","procNumRunCollect","systemUsersNumCollect","systemDiskSizeTotalCollect","systemBoottimeCollect" + ,"systemUnameCollect","systemLocaltimeCollect","systemUptimeCollect","procNumCollect"}; + for (String taskId : arr) { + dynamicTaskService.cancelTask(taskId); + } + /** + * 系统监控 + */ + GlobalConfig.cpuCollect = false; //cpu采集 + GlobalConfig.cpuInterval = 300; //cpu采集间隔 + GlobalConfig.vfsCollect = false; //挂载采集 + GlobalConfig.vfsInterval = 300; //挂载采集间隔 + GlobalConfig.netCollect = false; //网络采集 + GlobalConfig.netInterval = 300; //网络采集间隔 + GlobalConfig.diskCollect = false; //硬盘采集 + GlobalConfig.diskInterval = 300; //硬盘采集间隔 + GlobalConfig.dockerCollect = false; //docker采集 + GlobalConfig.dockerInterval = 300; //docker采集间隔 + /** + * 系统其他监控 + */ + GlobalConfig.systemSwapSizeFreeCollect = false; //交换卷/文件的可用空间(字节)采集 + GlobalConfig.systemSwapSizeFreeInterval = 300; //交换卷/文件的可用空间(字节)采集间隔 + GlobalConfig.memoryUtilizationCollect = false; //内存利用率采集 + GlobalConfig.memoryUtilizationInterval = 300; //内存利用率采集间隔 + GlobalConfig.systemSwapSizePercentCollect = false; //可用交换空间百分比采集 + GlobalConfig.systemSwapSizePercentInterval = 300; //可用交换空间百分比采集间隔 + GlobalConfig.memorySizeAvailableCollect = false; //可用内存采集 + GlobalConfig.memorySizeAvailableInterval = 300; //可用内存采集间隔 + GlobalConfig.memorySizePercentCollect = false; //可用内存百分比采集 + GlobalConfig.memorySizePercentInterval = 300; //可用内存百分比采集间隔 + GlobalConfig.memorySizeTotalCollect = false; //总内存采集 + GlobalConfig.memorySizeTotalInterval = 300; //总内存采集间隔 + GlobalConfig.systemSwOsCollect = false; //操作系统采集 + GlobalConfig.systemSwOsInterval = 300; //操作系统采集间隔 + GlobalConfig.systemSwArchCollect = false; //操作系统架构采集 + GlobalConfig.systemSwArchInterval = 300; //操作系统架构采集间隔 + GlobalConfig.kernelMaxprocCollect = false; //最大进程数采集 + GlobalConfig.kernelMaxprocInterval = 300; //最大进程数采集间隔 + GlobalConfig.procNumRunCollect = false; //正在运行的进程数采集 + GlobalConfig.procNumRunInterval = 300; //正在运行的进程数采集间隔 + GlobalConfig.systemUsersNumCollect = false; //登录用户数采集 + GlobalConfig.systemUsersNumInterval = 300; //登录用户数采集间隔 + GlobalConfig.systemDiskSizeTotalCollect = false; ///硬盘总可用空间采集 + GlobalConfig.systemDiskSizeTotalInterval = 300; //硬盘总可用空间采集间隔 + GlobalConfig.systemBoottimeCollect = false; //系统启动时间采集 + GlobalConfig.systemBoottimeInterval = 300; //系统启动时间采集间隔 + GlobalConfig.systemUnameCollect = false; //系统描述采集 + GlobalConfig.systemUnameInterval = 300; //系统描述采集间隔 + GlobalConfig.systemLocaltimeCollect = false; //系统本地时间采集 + GlobalConfig.systemLocaltimeInterval = 300; //系统本地时间采集间隔 + GlobalConfig.systemUptimeCollect = false; //系统正常运行时间采集 + GlobalConfig.systemUptimeInterval = 300; //系统正常运行时间采集间隔 + GlobalConfig.procNumCollect = false; //进程数采集 + GlobalConfig.procNumInterval = 300; //进程数采集间隔 + } + + @Override + public void alarmMonitor() { + if(GlobalConfig.IS_ALARM){ + //开启告警监控 + AssertLog.info("开启告警监控,监控项={}", JSON.toJSONString(GlobalConfig.ALARM_LIST)); + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask("alarmTask", + businessTasks::alarmTask, milli, GlobalConfig.ALARM_INTERVAL*1000L); + }else{ + //关闭告警监控 + AssertLog.info("关闭告警监控"); + dynamicTaskService.cancelTask("alarmTask"); + } + + } + + @Override + public void command(ScriptPolicyEO policy, String clientId, String dataType) { + long timestamp = System.currentTimeMillis(); + timestamp = Math.round(timestamp / 1000.0); + //执行方式:0、立即执行;1、定时执行; + if(policy.getMethod() == 1){ + SpecificTimeRequest request = SpecificTimeRequest.builder() + .taskId("") + .taskName(policy.getPolicyName()) + .taskData(policy.getCommandParams()) + .scriptId(policy.getScriptId()) + .specificDateTime(AgentUtil.toLocalDateTime(policy.getPolicyTime(),true)) + .clientId(clientId) + .dataType(dataType) + .build(); + try { + taskService.createSpecificTimeTask(request); + JSONObject json = new JSONObject(); + json.put("resCode",1); + json.put("resMsg", "执行脚本策略定时任务保存成功"); + json.put("timestamp",timestamp); + // 判定客户端与服务端是否连接 + if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) { + Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.执行脚本策略应答.getValue()) + .data(json.toString()).build(); + sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message); + AssertLog.info("发送执行脚本策略应答={}",JSON.toJSONString(message)); + } + } catch (Exception e) { + JSONObject json = new JSONObject(); + json.put("resCode",0); + json.put("resMsg", "执行脚本策略定时任务保存失败"); + json.put("timestamp",timestamp); + // 判定客户端与服务端是否连接 + if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) { + Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.执行脚本策略应答.getValue()) + .data(json.toString()).build(); + sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message); + AssertLog.info("发送执行脚本策略应答={}",JSON.toJSONString(message)); + } + } + }else{ + if(StringUtils.equals(dataType, MsgEnum.Agent版本更新应答.getValue())){ + try { + //设置检查回滚任务 + String SCRIPT_PATH = properties.getScriptPath()+"/rollback-tragent.sh"; + AgentDataUtil.chmod(SCRIPT_PATH,"775"); + AgentUtil.scheduleScriptIn3Minutes(SCRIPT_PATH); + AssertLog.info("版本策略重启进程已启动,当前服务退出"); + ProcessBuilder pb = new ProcessBuilder("/bin/sh", "-c", + policy.getCommandParams()); + pb.start(); + System.exit(0); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + }else{ + List cmd = Arrays.asList(policy.getCommandParams().split("\\s+")); + CompletableFuture future = + AsyncCommandExecutor.executeCommandAsync( + cmd, + 100, TimeUnit.SECONDS); + future.thenAccept(result -> { + if (result.isSuccess()) { + System.out.println("脚本执行成功"); + System.out.println("[成功resOut] " + result.getOutput()); + } else { + System.out.println("脚本执行失败"); + System.out.println("[失败resOut] " + result.getOutput()); + } + JSONObject rse = new JSONObject(); + rse.put("scriptId", policy.getScriptId()); + rse.put("command",policy.getCommandParams()); + rse.put("resOut", result.getOutput()); + long timestamps = System.currentTimeMillis(); + timestamps = Math.round(timestamps / 1000.0); + JSONObject json = new JSONObject(); + json.put("resCode",1); + json.put("resMsg", ""); + json.put("timestamp",timestamps); + json.put("result", rse.toString()); + // 判定客户端与服务端是否连接 + if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) { + Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(dataType) + .data(json.toString()).build(); + sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message); + AssertLog.info("发送执行脚本策略应答={}",JSON.toJSONString(message)); + } + }).exceptionally(ex -> { + System.err.println("执行失败: " + ex.getMessage()); + JSONObject rse = new JSONObject(); + rse.put("scriptId", policy.getScriptId()); + rse.put("command",policy.getCommandParams()); + rse.put("resOut", "脚本执行失败"); + long timestamps = System.currentTimeMillis(); + timestamps = Math.round(timestamps / 1000.0); + JSONObject json = new JSONObject(); + json.put("resCode",0); + json.put("resMsg", ""); + json.put("timestamp",timestamps); + json.put("result", rse.toString()); + // 判定客户端与服务端是否连接 + if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) { + Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(dataType) + .data(json.toString()).build(); + sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message); + AssertLog.info("发送执行脚本策略应答={}",JSON.toJSONString(message)); + } + return null; + }); + } + } + } + + @Override + public void cancelTask(String taskId) { + dynamicTaskService.cancelTask(taskId); + } + + @Override + public void start() { + // 创建心跳定时任务 + 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); + + // 检测监控策略配置 + AssertLog.info("检测监控策略配置"); + checkMonitor(); + } + + @Override + public void getPolicy(String data) { + JSONObject jsonObject = JSONObject.parseObject(data); + //监控策略 + if(jsonObject.containsKey("monitors")){ + String monitors = jsonObject.getString("monitors"); + JSONObject monitorJson = JSONObject.parseObject(monitors); + long upTime = 0; + if(monitorJson.containsKey("upTime")){ + upTime = monitorJson.getLong("upTime"); + } + //判断监控策略最新更新时间戳,不一致则进行更新策略 + if(upTime != 0 && GlobalConfig.MONITOR_TIME != upTime){ + if(monitorJson.containsKey("contents")){ + List list = new ArrayList<>(); + list.add("{\"upTime\":"+upTime+","); + list.add("\"contents\":["); + String contents = monitorJson.getString("contents"); + List collectList = JSON.parseObject(contents, new TypeReference>() {}); + if(CollectionUtil.isNotEmpty(collectList)){ + AssertLog.info("开启或更新监控策略={}", JSON.toJSONString(collectList)); + int i = 1; + for (CollectEO c : collectList) { + String content = JSON.toJSONString(c); + if(i != collectList.size()){ + content = JSON.toJSONString(c)+","; + } + list.add(content); + String type = c.getType(); + boolean collect = c.isCollect(); + int interval = c.getInterval(); + switch(type) { + case "cpuCollect": //cpu采集 + //判断与上次有无变化 + if(GlobalConfig.cpuCollect != collect || GlobalConfig.cpuInterval != interval){ + GlobalConfig.cpuCollect = collect; + if(collect){ + GlobalConfig.cpuInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::cpuTask, milli, GlobalConfig.cpuInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.cpuInterval = 300; + } + } + break; + case "vfsCollect": //挂载采集 + if(GlobalConfig.vfsCollect != collect || GlobalConfig.vfsInterval != interval){ + GlobalConfig.vfsCollect = collect; + if(collect){ + GlobalConfig.vfsInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::pointTask, milli, GlobalConfig.vfsInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.vfsInterval = 300; + } + } + break; + case "netCollect": //网络采集 + if(GlobalConfig.netCollect != collect || GlobalConfig.netInterval != interval){ + GlobalConfig.netCollect = collect; + if(collect){ +// GlobalConfig.netInterval = interval; + long milli = AgentUtil.millisecondsToNext5Minute(); + dynamicTaskService.scheduleTask(type, + businessTasks::netTask, milli, GlobalConfig.netInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.netInterval = 300; + } + } + break; + case "diskCollect": //磁盘采集 + if(GlobalConfig.diskCollect != collect || GlobalConfig.diskInterval != interval){ + GlobalConfig.diskCollect = collect; + if(collect){ + GlobalConfig.diskInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::diskTask, milli, GlobalConfig.diskInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.diskInterval = 300; + } + } + break; + case "dockerCollect": //docker采集 + if(GlobalConfig.dockerCollect != collect || GlobalConfig.dockerInterval != interval){ + GlobalConfig.dockerCollect = collect; + if(collect){ + GlobalConfig.dockerInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::dockerTask, milli, GlobalConfig.dockerInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.dockerInterval = 300; + } + } + break; + case "systemSwapSizeFreeCollect": //交换卷/文件的可用空间(字节)采集 + if(GlobalConfig.systemSwapSizeFreeCollect != collect || GlobalConfig.systemSwapSizeFreeInterval != interval){ + GlobalConfig.systemSwapSizeFreeCollect = collect; + if(collect){ + GlobalConfig.systemSwapSizeFreeInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::systemSwapSizeFreeTask, milli, GlobalConfig.systemSwapSizeFreeInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.systemSwapSizeFreeInterval = 300; + } + } + break; + case "memoryUtilizationCollect": //内存利用率采集 + if(GlobalConfig.memoryUtilizationCollect != collect || GlobalConfig.memoryUtilizationInterval != interval){ + GlobalConfig.memoryUtilizationCollect = collect; + if(collect){ + GlobalConfig.memoryUtilizationInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::memoryUtilizationTask, milli, GlobalConfig.memoryUtilizationInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.memoryUtilizationInterval = 300; + } + } + break; + case "systemSwapSizePercentCollect": //可用交换空间百分比采集 + if(GlobalConfig.systemSwapSizePercentCollect != collect || GlobalConfig.systemSwapSizePercentInterval != interval){ + GlobalConfig.systemSwapSizePercentCollect = collect; + if(collect){ + GlobalConfig.systemSwapSizePercentInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::systemSwapSizePercentTask, milli, GlobalConfig.systemSwapSizePercentInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.systemSwapSizePercentInterval = 300; + } + } + break; + case "memorySizeAvailableCollect": //可用内存采集 + if(GlobalConfig.memorySizeAvailableCollect != collect || GlobalConfig.memorySizeAvailableInterval != interval){ + GlobalConfig.memorySizeAvailableCollect = collect; + if(collect){ + GlobalConfig.memorySizeAvailableInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::memorySizeAvailableTask, milli, GlobalConfig.memorySizeAvailableInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.memorySizeAvailableInterval = 300; + } + } + break; + case "memorySizePercentCollect": //可用内存百分比采集 + if(GlobalConfig.memorySizePercentCollect != collect || GlobalConfig.memorySizePercentInterval != interval){ + GlobalConfig.memorySizePercentCollect = collect; + if(collect){ + GlobalConfig.memorySizePercentInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::memorySizePercentTask, milli, GlobalConfig.memorySizePercentInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.memorySizePercentInterval = 300; + } + } + break; + case "memorySizeTotalCollect": //总内存采集 + if(GlobalConfig.memorySizeTotalCollect != collect || GlobalConfig.memorySizeTotalInterval != interval){ + GlobalConfig.memorySizeTotalCollect = collect; + if(collect){ + GlobalConfig.memorySizeTotalInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::memorySizeTotalTask, milli, GlobalConfig.memorySizeTotalInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.memorySizeTotalInterval = 300; + } + } + break; + case "systemSwOsCollect": //操作系统采集 + if(GlobalConfig.systemSwOsCollect != collect || GlobalConfig.systemSwOsInterval != interval){ + GlobalConfig.systemSwOsCollect = collect; + if(collect){ + GlobalConfig.systemSwOsInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::systemSwOsTask, milli, GlobalConfig.systemSwOsInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.systemSwOsInterval = 300; + } + } + break; + case "systemSwArchCollect": //操作系统架构采集 + if(GlobalConfig.systemSwArchCollect != collect || GlobalConfig.systemSwArchInterval != interval){ + GlobalConfig.systemSwArchCollect = collect; + if(collect){ + GlobalConfig.systemSwArchInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::systemSwArchTask, milli, GlobalConfig.systemSwArchInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.systemSwArchInterval = 300; + } + } + break; + case "kernelMaxprocCollect": //最大进程数采集 + if(GlobalConfig.kernelMaxprocCollect != collect || GlobalConfig.kernelMaxprocInterval != interval){ + GlobalConfig.kernelMaxprocCollect = collect; + if(collect){ + GlobalConfig.kernelMaxprocInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::kernelMaxprocTask, milli, GlobalConfig.kernelMaxprocInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.kernelMaxprocInterval = 300; + } + } + break; + case "procNumRunCollect": //正在运行的进程数采集 + if(GlobalConfig.procNumRunCollect != collect || GlobalConfig.procNumRunInterval != interval){ + GlobalConfig.procNumRunCollect = collect; + if(collect){ + GlobalConfig.procNumRunInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::procNumRunTask, milli, GlobalConfig.procNumRunInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.procNumRunInterval = 300; + } + } + break; + case "systemUsersNumCollect": //登录用户数采集 + if(GlobalConfig.systemUsersNumCollect != collect || GlobalConfig.systemUsersNumInterval != interval){ + GlobalConfig.systemUsersNumCollect = collect; + if(collect){ + GlobalConfig.systemUsersNumInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::systemUsersNumTask, milli, GlobalConfig.systemUsersNumInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.systemUsersNumInterval = 300; + } + } + break; + case "systemDiskSizeTotalCollect": //硬盘总可用空间采集 + if(GlobalConfig.systemDiskSizeTotalCollect != collect || GlobalConfig.systemDiskSizeTotalInterval != interval){ + GlobalConfig.systemDiskSizeTotalCollect = collect; + if(collect){ + GlobalConfig.systemDiskSizeTotalInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::systemDiskSizeTotalTask, milli, GlobalConfig.systemDiskSizeTotalInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.systemDiskSizeTotalInterval = 300; + } + } + break; + case "systemBoottimeCollect": //系统启动时间采集 + if(GlobalConfig.systemBoottimeCollect != collect || GlobalConfig.systemBoottimeInterval != interval){ + GlobalConfig.systemBoottimeCollect = collect; + if(collect){ + GlobalConfig.systemBoottimeInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::systemBoottimeTask, milli, GlobalConfig.systemBoottimeInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.systemBoottimeInterval = 300; + } + } + break; + case "systemUnameCollect": //系统描述采集 + if(GlobalConfig.systemUnameCollect != collect || GlobalConfig.systemUnameInterval != interval){ + GlobalConfig.systemUnameCollect = collect; + if(collect){ + GlobalConfig.systemUnameInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::systemUnameTask, milli, GlobalConfig.systemUnameInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.systemUnameInterval = 300; + } + } + break; + case "systemLocaltimeCollect": //系统本地时间采集 + if(GlobalConfig.systemLocaltimeCollect != collect || GlobalConfig.systemLocaltimeInterval != interval){ + GlobalConfig.systemLocaltimeCollect = collect; + if(collect){ + GlobalConfig.systemLocaltimeInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::systemLocaltimeTask, milli, GlobalConfig.systemLocaltimeInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.systemLocaltimeInterval = 300; + } + } + break; + case "systemUptimeCollect": //系统正常运行时间采集 + if(GlobalConfig.systemUptimeCollect != collect || GlobalConfig.systemUptimeInterval != interval){ + GlobalConfig.systemUptimeCollect = collect; + if(collect){ + GlobalConfig.systemUptimeInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::systemUptimeTask, milli, GlobalConfig.systemUptimeInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.systemUptimeInterval = 300; + } + } + break; + default: //系统其他采集 + GlobalConfig.procNumCollect = collect; + if(collect){ + GlobalConfig.procNumInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::procNumTask, milli, GlobalConfig.procNumInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.procNumInterval = 300; + } + } + i++; + } + } + list.add("]}"); + String[] lines = list.stream().toArray(String[]::new); + //检查外置目录是否存在 + if(AdvancedAsyncDownloader.createSingleDirectoryIfNotExists(properties.getConfPath())){ + AgentUtil.bufferedWriter(properties.getConfPath()+"/monitor.conf",lines); + } + } + GlobalConfig.MONITOR_TIME = upTime; + } + } + //脚本策略 + if(jsonObject.containsKey("scripts")){ + String scripts = jsonObject.getString("scripts"); + JSONObject scriptJson = JSONObject.parseObject(scripts); + long upTime = 0; + if(scriptJson.containsKey("upTime")){ + upTime = scriptJson.getLong("upTime"); + } + //判断脚本策略最新更新时间戳,不一致则进行更新策略 + if(upTime != 0 && GlobalConfig.SCRIPT_TIME != upTime){ + if(scriptJson.containsKey("contents")) { + String contents = scriptJson.getString("contents"); + List scriptList = JSON.parseObject(contents, new TypeReference>() { + }); + AssertLog.info("开启或更新脚本策略={}", JSON.toJSONString(scriptList)); + for (ScriptPolicyEO policy : scriptList) { + if(AdvancedAsyncDownloader.createSingleDirectoryIfNotExists(properties.getTmpPath()+"/script")){ + if(StringUtils.isNotBlank(policy.getFileUrl())){ + 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); + //所有文件下载完成,执行脚本命令 + command(policy, GlobalConfig.CLIENT_ID,MsgEnum.执行脚本策略应答.getValue()); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException 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); + // 判定客户端与服务端是否连接 + if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) { + Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.执行脚本策略应答.getValue()) + .data(json.toString()).build(); + sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message); + AssertLog.info("发送脚本策略应答={}",JSON.toJSONString(message)); + } + return null; + }); + } + } + } + } + GlobalConfig.SCRIPT_TIME = upTime; + } + } + //版本策略 + if(jsonObject.containsKey("versions")){ + String versions = jsonObject.getString("versions"); + JSONObject versionJson = JSONObject.parseObject(versions); + long upTime = 0; + if(versionJson.containsKey("upTime")){ + upTime = versionJson.getLong("upTime"); + } + //判断版本策略最新更新时间戳,不一致则进行更新策略 + if(upTime != 0 && GlobalConfig.VERSION_TIME != upTime){ + if(versionJson.containsKey("contents")) { + String contents = versionJson.getString("contents"); + List versionList = JSON.parseObject(contents, new TypeReference>() { + }); + AssertLog.info("开启或更新版本策略={}", JSON.toJSONString(versionList)); + try { + AgentDataUtil.chmod(properties.getScriptPath()+"/restart-tragent.sh","775"); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + for (AgentVersionUpdateEO versionUpdateEO : versionList) { + ScriptPolicyEO policy = ScriptPolicyEO.builder() + .method(versionUpdateEO.getMethod()) + .commandParams(properties.getScriptPath()+"/restart-tragent.sh") + .policyTime(versionUpdateEO.getPolicyTime()) + .build(); + if(StringUtils.isNotBlank(versionUpdateEO.getFileUrl())){ + 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 -> { + AssertLog.info("版本策略下载完成: {}", 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; + //关闭采集任务 + cancelCollect(); + //所有文件下载完成,执行脚本命令 + AssertLog.info("版本策略MD5验证成功"); + command(policy, GlobalConfig.CLIENT_ID,MsgEnum.Agent版本更新应答.getValue()); + }else{ + AssertLog.info("版本策略MD5验证失败"); + //发送更新失败 + 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); + // 判定客户端与服务端是否连接 + if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) { + Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.Agent版本更新应答.getValue()) + .data(json.toString()).build(); + sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message); + AssertLog.info("发送版本策略应答={}",JSON.toJSONString(message)); + } + } + } 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); + // 判定客户端与服务端是否连接 + if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) { + Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.Agent版本更新应答.getValue()) + .data(json.toString()).build(); + sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message); + AssertLog.info("发送版本策略应答={}",JSON.toJSONString(message)); + } + return null; + }); + } + } + } + GlobalConfig.VERSION_TIME = upTime; + } + } + //静态路由策略 + if(jsonObject.containsKey("routes")){ + String routes = jsonObject.getString("routes"); + JSONObject routeJson = JSONObject.parseObject(routes); + long upTime = 0; + if(routeJson.containsKey("upTime")){ + upTime = routeJson.getLong("upTime"); + } + //判断监控策略最新更新时间戳,不一致则进行更新策略 + if(upTime != 0 && GlobalConfig.ROUTE_TIME != upTime){ + if(routeJson.containsKey("delRoute")) { + String delRoute = routeJson.getString("delRoute"); + JSONObject delRouteJson = JSONObject.parseObject(delRoute); + String name = ""; + String gateway = ""; + if(delRouteJson.containsKey("name")){ + name = delRouteJson.getString("name"); + } + if(delRouteJson.containsKey("gateway")){ + gateway = delRouteJson.getString("gateway"); + } + if(StringUtils.isNotBlank(name) && StringUtils.isNotBlank(gateway)){ + dellRoute(name, gateway); + } + } + if(routeJson.containsKey("addRoute")) { + String addRoute = routeJson.getString("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)){ + addRoute(name,gateway); + } + } + } + + } + + } + + @Override + public void checkTrAgent() { + String CRON_FILE_PATH = properties.getScriptPath()+"/tr_live.cron"; + String NEW_CRON_ENTRY = "*/10 * * * * "+properties.getScriptPath()+"/check-tragent.sh >/dev/null 2>&1"; + String TARGET_SCRIPT = properties.getScriptPath()+"/check-tragent.sh"; + try { + // 验证当前定时任务中是否已包含目标脚本 + if (CrontabManager.isCronEntryExists(TARGET_SCRIPT)) { + AssertLog.info("当前定时任务中已包含 {}",TARGET_SCRIPT); + AssertLog.info("无需重复添加定时任务"); + return; // 如果已存在,直接退出 + } + AssertLog.info("当前定时任务中未找到 {}",TARGET_SCRIPT); + AssertLog.info("开始执行定时任务管理流程..."); + + // 0. 清空 tr_live.cron 文件内容 + CrontabManager.clearCronFile(properties.getScriptPath(), CRON_FILE_PATH); + + // 1. 备份当前定时任务到文件 + CrontabManager.backupCurrentCrontab(CRON_FILE_PATH); + + // 2. 在文件末尾添加新的定时任务 + CrontabManager.appendNewCronEntry(CRON_FILE_PATH, NEW_CRON_ENTRY); + + // 3. 清除当前所有定时任务 + CrontabManager.clearCurrentCrontab(); + + // 4. 重新加载定时任务文件 + CrontabManager.installNewCrontab(CRON_FILE_PATH); + + AssertLog.info("定时任务管理完成!"); + + // 验证最终结果 + CrontabManager.verifyFinalResult(TARGET_SCRIPT); + + } catch (Exception e) { + AssertLog.info("执行定时任务管理时发生错误: " + e.getMessage()); + e.printStackTrace(); + } + +//----------------------------------- +// // 脚本路径(用于验证脚本是否存在) +// String SCRIPT_PATH = properties.getScriptPath()+"/check-tragent.sh"; +// try { +// AgentDataUtil.chmod(SCRIPT_PATH,"775"); +// } catch (IOException e) { +// e.printStackTrace(); +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } +// // 要添加的定时任务(每10分钟执行一次,且静默输出) +// String TARGET_CRON = "*/10 * * * * "+properties.getScriptPath()+"/check-tragent.sh >/dev/null 2>&1"; +// +// boolean success = CrontabManager.ensureCronJobExists(TARGET_CRON, SCRIPT_PATH); +// if (success) { +// System.out.println("✅ Crontab 状态正常。"); +// } else { +// System.err.println("❌ 操作失败,请检查权限或路径。"); +// } + } + + @Override + public void sendMessage(String type, String data) { + // 判定客户端与服务端是否连接 + if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) { + Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(type) + .data(data).build(); + sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message); + AssertLog.info("发送={}",JSON.toJSONString(message)); + } + } + + @Override + public boolean connection() { + String clientId = MachineFingerprint.getHardwareFingerprint(); + //初始化SN信息 + GlobalConfig.DEVICE_SN = AgentUtil.getDeviceSN(); + //检查外置目录是否存在 + if(AdvancedAsyncDownloader.createSingleDirectoryIfNotExists(properties.getConfPath())){ + //检查agent是否已注册 + File regFile = new File(properties.getConfPath()+"/register.conf"); + if (regFile.exists()) { + Properties props = new Properties(); + try (InputStream input = Files.newInputStream(Paths.get(properties.getConfPath()+"/register.conf"))) { + // 加载配置文件 + props.load(input); + // 为全局变量赋值 + String register = props.getProperty("register", "0"); + if(StringUtils.equals(register,"1")){ + GlobalConfig.isRegister = true; + } + } catch (IOException e) { + System.err.println("无法加载注册配置文件,使用默认值"); + } catch (NumberFormatException e) { + System.err.println("注册配置文件格式错误: " + e.getMessage()); + } + } else { + String[] lines = { + "register=0" + }; + AgentUtil.bufferedWriter(properties.getConfPath()+"/register.conf",lines); + } + //检查clientID是否存在 + File clientFile = new File(properties.getConfPath()+"/client.conf"); + if (clientFile.exists()) { + Properties props = new Properties(); + try (InputStream input = Files.newInputStream(Paths.get(properties.getConfPath()+"/client.conf"))) { + // 加载配置文件 + props.load(input); + // 为全局变量赋值 + String id = props.getProperty("clientId", ""); + if(StringUtils.isNotBlank(id)){ + GlobalConfig.CLIENT_ID = id; + }else{ + String[] lines = { + "clientId="+clientId + }; + AgentUtil.bufferedWriter(properties.getConfPath()+"/client.conf",lines); + GlobalConfig.CLIENT_ID = clientId; + } + } catch (IOException e) { + System.err.println("无法加载client配置文件,使用默认值"); + } catch (NumberFormatException e) { + System.err.println("client配置文件格式错误: " + e.getMessage()); + } + } else { + String[] lines = { + "clientId="+clientId + }; + AgentUtil.bufferedWriter(properties.getConfPath()+"/client.conf",lines); + GlobalConfig.CLIENT_ID = clientId; + } + } + //先进行断开连接 + client.closeConnection(GlobalConfig.CLIENT_ID); + 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); + // 等待3秒 + try { + TimeUnit.SECONDS.sleep(3); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + return success; + } + + @Override + public void addRoute(String name, String gateway) { + String ip = ""; + String prefix = "32"; + String command = ""; + if(StringUtils.isNotBlank(name) && StringUtils.isNotBlank(gateway)){ + ip = config.getHost(); + command = "ip route add "+ip+"/"+prefix+" via "+gateway+" dev "+name; + }else{ + String filePath = properties.getConfPath()+"/route.conf"; + try { + String result = AgentUtil.readNonEmptyLinesAsString(filePath); + if(StringUtils.isNotBlank(result)){ + String[] arr = result.split(" "); + if(arr.length == 8){ + ip = arr[3].substring(0,arr[3].indexOf("/")); + prefix = arr[3].substring(arr[3].indexOf("/")+1,arr[3].length()); + name = arr[7]; + gateway = arr[5]; + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + AssertLog.info("addRoute脚本开始执行,command={}",command); + boolean flag = NetworkUtil.addRoute(ip,prefix,gateway,name); + if(flag){ + AssertLog.info("addRoute脚本执行成功,command={}",command); + //检查外置目录是否存在 + if(AdvancedAsyncDownloader.createSingleDirectoryIfNotExists(properties.getConfPath())){ + String[] lines = { + command + }; + AgentUtil.bufferedWriter(properties.getConfPath()+"/route.conf",lines); + } + }else{ + AssertLog.info("addRoute脚本执行失败,command={}",command); + } + } + + @Override + public void dellRoute(String name, String gateway) { + String command = "ip route del "+config.getHost()+"/32 via "+gateway+" dev "+name; + try { + AssertLog.info("delRoute脚本开始执行,command={}",command); + boolean flag = NetworkUtil.deleteRoute(config.getHost(),String.valueOf(config.getPort()),gateway,name); + if(flag){ + AssertLog.info("dellRoute脚本执行成功,command={}",command); + Path path = Paths.get(properties.getConfPath()+"/route.conf"); + try { + Files.delete(path); + } catch (NoSuchFileException e) { + } catch (DirectoryNotEmptyException e) { + } catch (IOException e) { + System.err.println("删除文件失败:" + e.getMessage()); + } + }else{ + AssertLog.info("dellRoute脚本执行失败,command={}",command); + } + }catch (Exception e){ + System.err.println(e.getMessage()); + } + } + + @Override + public String getLogicalNode() { + String filePath = properties.getConfPath()+"/logical.conf"; + String result = ""; + Path path = Paths.get(filePath); + // 判断文件是否存在 + if (!Files.exists(path)) { + System.err.println("文件不存在: " + filePath); + return result; + } + // 判断文件最后更新时间 + LocalDateTime lastTime = AgentUtil.getLastModifiedTime(filePath); + if(GlobalConfig.LOGICAL_NODE_LAST_TIME != null){ + if(lastTime.equals(GlobalConfig.LOGICAL_NODE_LAST_TIME) && StringUtils.isNotBlank(GlobalConfig.LOGICAL_NODE)){ + return GlobalConfig.LOGICAL_NODE; + } + } + Properties props = new Properties(); + try (InputStreamReader reader = new InputStreamReader(new FileInputStream(filePath), StandardCharsets.UTF_8)) { // 明确指定 UTF-8 编码 + props.load(reader); + result = props.getProperty("logicalNode", ""); + GlobalConfig.LOGICAL_NODE_LAST_TIME = lastTime; + GlobalConfig.LOGICAL_NODE = result; + } catch (IOException e) { + System.err.println("读取文件失败: " + e.getMessage()); + e.printStackTrace(); + } + return result; + } + + @Override + public void checkMonitor() { + String filePath = properties.getConfPath()+"/monitor.conf"; + try { + Path path = Paths.get(filePath); + // 判断文件是否存在 + if (!Files.exists(path)) { + System.err.println("文件不存在: " + filePath); + }else { + String content = Files.lines(Paths.get(filePath), StandardCharsets.UTF_8) + .filter(line -> !line.trim().isEmpty()) // 过滤掉空行 + .collect(Collectors.joining()); + AssertLog.info("监控策略配置文件信息:{}",content); + JSONObject jsonObject = JSONObject.parseObject(content); + List collectList = new ArrayList<>(); + if(jsonObject.containsKey("contents")){ + String contents = jsonObject.getString("contents"); + collectList = JSON.parseObject(contents, new TypeReference>() {}); + } + if(CollectionUtil.isNotEmpty(collectList)){ + AssertLog.info("监控策略配置信息:{}",JSON.toJSONString(collectList)); + for (CollectEO c : collectList) { + String type = c.getType(); + boolean collect = c.isCollect(); + int interval = c.getInterval(); + switch(type) { + case "cpuCollect": //cpu采集 + //判断与上次有无变化 + if(GlobalConfig.cpuCollect != collect || GlobalConfig.cpuInterval != interval){ + GlobalConfig.cpuCollect = collect; + if(collect){ + GlobalConfig.cpuInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::cpuTask, milli, GlobalConfig.cpuInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.cpuInterval = 300; + } + } + break; + case "vfsCollect": //挂载采集 + if(GlobalConfig.vfsCollect != collect || GlobalConfig.vfsInterval != interval){ + GlobalConfig.vfsCollect = collect; + if(collect){ + GlobalConfig.vfsInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::pointTask, milli, GlobalConfig.vfsInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.vfsInterval = 300; + } + } + break; + case "netCollect": //网络采集 + if(GlobalConfig.netCollect != collect || GlobalConfig.netInterval != interval){ + GlobalConfig.netCollect = collect; + if(collect){ +// GlobalConfig.netInterval = interval; + long milli = AgentUtil.millisecondsToNext5Minute(); + dynamicTaskService.scheduleTask(type, + businessTasks::netTask, milli, GlobalConfig.netInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.netInterval = 300; + } + } + break; + case "diskCollect": //磁盘采集 + if(GlobalConfig.diskCollect != collect || GlobalConfig.diskInterval != interval){ + GlobalConfig.diskCollect = collect; + if(collect){ + GlobalConfig.diskInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::diskTask, milli, GlobalConfig.diskInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.diskInterval = 300; + } + } + break; + case "dockerCollect": //docker采集 + if(GlobalConfig.dockerCollect != collect || GlobalConfig.dockerInterval != interval){ + GlobalConfig.dockerCollect = collect; + if(collect){ + GlobalConfig.dockerInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::dockerTask, milli, GlobalConfig.dockerInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.dockerInterval = 300; + } + } + break; + case "systemSwapSizeFreeCollect": //交换卷/文件的可用空间(字节)采集 + if(GlobalConfig.systemSwapSizeFreeCollect != collect || GlobalConfig.systemSwapSizeFreeInterval != interval){ + GlobalConfig.systemSwapSizeFreeCollect = collect; + if(collect){ + GlobalConfig.systemSwapSizeFreeInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::systemSwapSizeFreeTask, milli, GlobalConfig.systemSwapSizeFreeInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.systemSwapSizeFreeInterval = 300; + } + } + break; + case "memoryUtilizationCollect": //内存利用率采集 + if(GlobalConfig.memoryUtilizationCollect != collect || GlobalConfig.memoryUtilizationInterval != interval){ + GlobalConfig.memoryUtilizationCollect = collect; + if(collect){ + GlobalConfig.memoryUtilizationInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::memoryUtilizationTask, milli, GlobalConfig.memoryUtilizationInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.memoryUtilizationInterval = 300; + } + } + break; + case "systemSwapSizePercentCollect": //可用交换空间百分比采集 + if(GlobalConfig.systemSwapSizePercentCollect != collect || GlobalConfig.systemSwapSizePercentInterval != interval){ + GlobalConfig.systemSwapSizePercentCollect = collect; + if(collect){ + GlobalConfig.systemSwapSizePercentInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::systemSwapSizePercentTask, milli, GlobalConfig.systemSwapSizePercentInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.systemSwapSizePercentInterval = 300; + } + } + break; + case "memorySizeAvailableCollect": //可用内存采集 + if(GlobalConfig.memorySizeAvailableCollect != collect || GlobalConfig.memorySizeAvailableInterval != interval){ + GlobalConfig.memorySizeAvailableCollect = collect; + if(collect){ + GlobalConfig.memorySizeAvailableInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::memorySizeAvailableTask, milli, GlobalConfig.memorySizeAvailableInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.memorySizeAvailableInterval = 300; + } + } + break; + case "memorySizePercentCollect": //可用内存百分比采集 + if(GlobalConfig.memorySizePercentCollect != collect || GlobalConfig.memorySizePercentInterval != interval){ + GlobalConfig.memorySizePercentCollect = collect; + if(collect){ + GlobalConfig.memorySizePercentInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::memorySizePercentTask, milli, GlobalConfig.memorySizePercentInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.memorySizePercentInterval = 300; + } + } + break; + case "memorySizeTotalCollect": //总内存采集 + if(GlobalConfig.memorySizeTotalCollect != collect || GlobalConfig.memorySizeTotalInterval != interval){ + GlobalConfig.memorySizeTotalCollect = collect; + if(collect){ + GlobalConfig.memorySizeTotalInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::memorySizeTotalTask, milli, GlobalConfig.memorySizeTotalInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.memorySizeTotalInterval = 300; + } + } + break; + case "systemSwOsCollect": //操作系统采集 + if(GlobalConfig.systemSwOsCollect != collect || GlobalConfig.systemSwOsInterval != interval){ + GlobalConfig.systemSwOsCollect = collect; + if(collect){ + GlobalConfig.systemSwOsInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::systemSwOsTask, milli, GlobalConfig.systemSwOsInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.systemSwOsInterval = 300; + } + } + break; + case "systemSwArchCollect": //操作系统架构采集 + if(GlobalConfig.systemSwArchCollect != collect || GlobalConfig.systemSwArchInterval != interval){ + GlobalConfig.systemSwArchCollect = collect; + if(collect){ + GlobalConfig.systemSwArchInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::systemSwArchTask, milli, GlobalConfig.systemSwArchInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.systemSwArchInterval = 300; + } + } + break; + case "kernelMaxprocCollect": //最大进程数采集 + if(GlobalConfig.kernelMaxprocCollect != collect || GlobalConfig.kernelMaxprocInterval != interval){ + GlobalConfig.kernelMaxprocCollect = collect; + if(collect){ + GlobalConfig.kernelMaxprocInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::kernelMaxprocTask, milli, GlobalConfig.kernelMaxprocInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.kernelMaxprocInterval = 300; + } + } + break; + case "procNumRunCollect": //正在运行的进程数采集 + if(GlobalConfig.procNumRunCollect != collect || GlobalConfig.procNumRunInterval != interval){ + GlobalConfig.procNumRunCollect = collect; + if(collect){ + GlobalConfig.procNumRunInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::procNumRunTask, milli, GlobalConfig.procNumRunInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.procNumRunInterval = 300; + } + } + break; + case "systemUsersNumCollect": //登录用户数采集 + if(GlobalConfig.systemUsersNumCollect != collect || GlobalConfig.systemUsersNumInterval != interval){ + GlobalConfig.systemUsersNumCollect = collect; + if(collect){ + GlobalConfig.systemUsersNumInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::systemUsersNumTask, milli, GlobalConfig.systemUsersNumInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.systemUsersNumInterval = 300; + } + } + break; + case "systemDiskSizeTotalCollect": //硬盘总可用空间采集 + if(GlobalConfig.systemDiskSizeTotalCollect != collect || GlobalConfig.systemDiskSizeTotalInterval != interval){ + GlobalConfig.systemDiskSizeTotalCollect = collect; + if(collect){ + GlobalConfig.systemDiskSizeTotalInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::systemDiskSizeTotalTask, milli, GlobalConfig.systemDiskSizeTotalInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.systemDiskSizeTotalInterval = 300; + } + } + break; + case "systemBoottimeCollect": //系统启动时间采集 + if(GlobalConfig.systemBoottimeCollect != collect || GlobalConfig.systemBoottimeInterval != interval){ + GlobalConfig.systemBoottimeCollect = collect; + if(collect){ + GlobalConfig.systemBoottimeInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::systemBoottimeTask, milli, GlobalConfig.systemBoottimeInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.systemBoottimeInterval = 300; + } + } + break; + case "systemUnameCollect": //系统描述采集 + if(GlobalConfig.systemUnameCollect != collect || GlobalConfig.systemUnameInterval != interval){ + GlobalConfig.systemUnameCollect = collect; + if(collect){ + GlobalConfig.systemUnameInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::systemUnameTask, milli, GlobalConfig.systemUnameInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.systemUnameInterval = 300; + } + } + break; + case "systemLocaltimeCollect": //系统本地时间采集 + if(GlobalConfig.systemLocaltimeCollect != collect || GlobalConfig.systemLocaltimeInterval != interval){ + GlobalConfig.systemLocaltimeCollect = collect; + if(collect){ + GlobalConfig.systemLocaltimeInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::systemLocaltimeTask, milli, GlobalConfig.systemLocaltimeInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.systemLocaltimeInterval = 300; + } + } + break; + case "systemUptimeCollect": //系统正常运行时间采集 + if(GlobalConfig.systemUptimeCollect != collect || GlobalConfig.systemUptimeInterval != interval){ + GlobalConfig.systemUptimeCollect = collect; + if(collect){ + GlobalConfig.systemUptimeInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::systemUptimeTask, milli, GlobalConfig.systemUptimeInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.systemUptimeInterval = 300; + } + } + break; + default: //系统其他采集 + GlobalConfig.procNumCollect = collect; + if(collect){ + GlobalConfig.procNumInterval = interval; + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(type, + businessTasks::procNumTask, milli, GlobalConfig.procNumInterval*1000L); + }else{ + dynamicTaskService.cancelTask(type); + GlobalConfig.procNumInterval = 300; + } + } + } + } + } + } catch (Exception e) { + System.err.println("读取文件失败: " + e.getMessage()); + e.printStackTrace(); + } + + } + + + public void caseTypeBySystem(String type, int interval, boolean collect){ + + } +} diff --git a/src/main/java/com/tongran/agent/client/service/impl/AlarmServiceImpl.java b/src/main/java/com/tongran/agent/client/service/impl/AlarmServiceImpl.java new file mode 100644 index 0000000..af310b8 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/service/impl/AlarmServiceImpl.java @@ -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 alarmMonitor(long timestamp) { + AssertLog.info("告警监控定时任务执行alarmMonitor()"); + List alarmVOList = new ArrayList<>(); + List 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 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 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 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 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 list = getAllNetworkInterfaces(); + if(CollectionUtil.isNotEmpty(GlobalConfig.NET_LIST)){ + List natList = list.stream().filter(n -> n.isUp() == false).map(NativeNetworkInterfaceEO::getName).collect(Collectors.toList()); + List 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 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 getAllNetworkInterfaces() throws SocketException { + List interfaces = new ArrayList<>(); + Enumeration 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 scanPortsUsingNmap() throws Exception { + List 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; + } + + +} diff --git a/src/main/java/com/tongran/agent/client/service/impl/CPUServiceImpl.java b/src/main/java/com/tongran/agent/client/service/impl/CPUServiceImpl.java new file mode 100644 index 0000000..d173799 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/service/impl/CPUServiceImpl.java @@ -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; + } +} diff --git a/src/main/java/com/tongran/agent/client/service/impl/DiskServiceImpl.java b/src/main/java/com/tongran/agent/client/service/impl/DiskServiceImpl.java new file mode 100644 index 0000000..e88baac --- /dev/null +++ b/src/main/java/com/tongran/agent/client/service/impl/DiskServiceImpl.java @@ -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 diskList(long timestamp) { + List tempList = new ArrayList<>(); + List 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 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 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 pointList(long timestamp) { + List 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; + } +} diff --git a/src/main/java/com/tongran/agent/client/service/impl/DockerServiceImpl.java b/src/main/java/com/tongran/agent/client/service/impl/DockerServiceImpl.java new file mode 100644 index 0000000..155718c --- /dev/null +++ b/src/main/java/com/tongran/agent/client/service/impl/DockerServiceImpl.java @@ -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 dockerList(long timestamp) { + List 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 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; + } +} diff --git a/src/main/java/com/tongran/agent/client/service/impl/MemoryServiceImpl.java b/src/main/java/com/tongran/agent/client/service/impl/MemoryServiceImpl.java new file mode 100644 index 0000000..53a51a0 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/service/impl/MemoryServiceImpl.java @@ -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 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; + } + + +} diff --git a/src/main/java/com/tongran/agent/client/service/impl/NetServiceImpl.java b/src/main/java/com/tongran/agent/client/service/impl/NetServiceImpl.java new file mode 100644 index 0000000..e9e60e6 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/service/impl/NetServiceImpl.java @@ -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 netList(long timestamp) { + List list = new ArrayList<>(); + try { + SystemInfo si = new SystemInfo(); + HardwareAbstractionLayer hal = si.getHardware(); + // 获取所有网络接口 + List networkIFs = hal.getNetworkIFs(); + System.out.println("========================================================="); + System.out.println("===== 网卡流量统计 ====="); + List 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 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 getNetworkMode(String interfaceName) { + Map 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; + } +} diff --git a/src/main/java/com/tongran/agent/client/service/impl/SwitchBoardServiceImpl.java b/src/main/java/com/tongran/agent/client/service/impl/SwitchBoardServiceImpl.java new file mode 100644 index 0000000..616d66d --- /dev/null +++ b/src/main/java/com/tongran/agent/client/service/impl/SwitchBoardServiceImpl.java @@ -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 { + +} diff --git a/src/main/java/com/tongran/agent/client/service/impl/SystemServiceImpl.java b/src/main/java/com/tongran/agent/client/service/impl/SystemServiceImpl.java new file mode 100644 index 0000000..34f7832 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/service/impl/SystemServiceImpl.java @@ -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 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 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 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 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 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 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; + } +} diff --git a/src/main/java/com/tongran/agent/client/utils/AgentDataUtil.java b/src/main/java/com/tongran/agent/client/utils/AgentDataUtil.java new file mode 100644 index 0000000..92a34b5 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/utils/AgentDataUtil.java @@ -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 parseMemInfo() throws IOException { + Map 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 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(); + } + } +} diff --git a/src/main/java/com/tongran/agent/client/utils/AgentUtil.java b/src/main/java/com/tongran/agent/client/utils/AgentUtil.java new file mode 100644 index 0000000..06d22e1 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/utils/AgentUtil.java @@ -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 interfaces = NetworkInterface.getNetworkInterfaces(); + while (interfaces.hasMoreElements()) { + NetworkInterface iface = interfaces.nextElement(); + // 跳过虚拟网卡(如 docker, veth, lo) + if (iface.isLoopback() || iface.isVirtual() || !iface.isUp()) { + continue; + } + Enumeration 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> 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 swapMap(LinkedHashMap original) { + LinkedHashMap swapped = new LinkedHashMap<>(); + for (Map.Entry entry : original.entrySet()) { + if (entry.getValue() != null) { // 避免 null key + swapped.put(entry.getValue(), entry.getKey()); + } + } + return swapped; + } + + // 存储网关信息:接口名 -> 网关IP + private static Map gatewayMap = new HashMap<>(); + + // 正则表达式匹配:当前 IP:xxx.xxx.xxx.xxx 来自于:中国 江苏省 南京市 电信 + private static final String REGEX = "来自于:(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)"; + /** + * 收集所有 Ethernet 类型网卡信息 + */ + public static List collectNetworkInfo() throws Exception { + List 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 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 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 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 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; + } + + + +} diff --git a/src/main/java/com/tongran/agent/client/utils/AssertLog.java b/src/main/java/com/tongran/agent/client/utils/AssertLog.java new file mode 100644 index 0000000..95f1203 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/utils/AssertLog.java @@ -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); + } + } +} diff --git a/src/main/java/com/tongran/agent/client/utils/ClientExample.java b/src/main/java/com/tongran/agent/client/utils/ClientExample.java new file mode 100644 index 0000000..398d6ad --- /dev/null +++ b/src/main/java/com/tongran/agent/client/utils/ClientExample.java @@ -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 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 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 status = manager.getConnectionStatus(); + System.out.println("连接状态: " + status); + + Thread.sleep(5000); + + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } finally { +// manager.shutdown(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/utils/CpuUsageFromProcStat.java b/src/main/java/com/tongran/agent/client/utils/CpuUsageFromProcStat.java new file mode 100644 index 0000000..fb73efa --- /dev/null +++ b/src/main/java/com/tongran/agent/client/utils/CpuUsageFromProcStat.java @@ -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 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); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/utils/CrontabManager.java b/src/main/java/com/tongran/agent/client/utils/CrontabManager.java new file mode 100644 index 0000000..7b1311d --- /dev/null +++ b/src/main/java/com/tongran/agent/client/utils/CrontabManager.java @@ -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()); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/utils/EnhancedConnectionManager.java b/src/main/java/com/tongran/agent/client/utils/EnhancedConnectionManager.java new file mode 100644 index 0000000..f8f59cc --- /dev/null +++ b/src/main/java/com/tongran/agent/client/utils/EnhancedConnectionManager.java @@ -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 connectionConfigs; + + public EnhancedConnectionManager() { +// this.client = new MultiTargetNettyClient(); + this.connectionConfigs = new ConcurrentHashMap<>(); + } + + /** + * 批量创建连接 + */ + public Map createConnections(List configs) { + Map 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 getConnectionStatus() { + Map status = new HashMap<>(); +// connectionConfigs.forEach((key, config) -> { +// // 这里可以添加更详细的状态检查 +// status.put(key, client.sendMessage(key, "PING")); +// }); + return status; + } + +// public void shutdown() { +// client.shutdown(); +// } +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/utils/FileCleaner.java b/src/main/java/com/tongran/agent/client/utils/FileCleaner.java new file mode 100644 index 0000000..ea1fbe8 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/utils/FileCleaner.java @@ -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 deletedFiles = new ArrayList<>(); + List failedFiles = new ArrayList<>(); + + try (DirectoryStream 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 天以上的文件 + } +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/utils/MachineFingerprint.java b/src/main/java/com/tongran/agent/client/utils/MachineFingerprint.java new file mode 100644 index 0000000..56e1fa7 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/utils/MachineFingerprint.java @@ -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 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); + } +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/utils/NetworkUtil.java b/src/main/java/com/tongran/agent/client/utils/NetworkUtil.java new file mode 100644 index 0000000..0ca4130 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/utils/NetworkUtil.java @@ -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; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/utils/PublicIpFetcher.java b/src/main/java/com/tongran/agent/client/utils/PublicIpFetcher.java new file mode 100644 index 0000000..1fc6e4d --- /dev/null +++ b/src/main/java/com/tongran/agent/client/utils/PublicIpFetcher.java @@ -0,0 +1,86 @@ +package com.tongran.agent.client.utils; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; + +public class PublicIpFetcher { + + private static final String IP_SERVICE_URL = "https://myip.ipip.net"; + private static final int TIMEOUT_MS = 10_000; // 10秒超时 + + /** + * 获取公网 IP(包含归属地信息) + * + * @return IP 信息字符串,如 "当前 IP:112.96.15.145,来自于:中国 广东省 深圳市 联通" + * @throws IOException 如果网络请求失败 + */ + public static String getPublicIp() throws IOException { + URL url = new URL(IP_SERVICE_URL); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + + // 设置请求方法 + connection.setRequestMethod("GET"); + connection.setConnectTimeout(TIMEOUT_MS); + connection.setReadTimeout(TIMEOUT_MS); + + // 发起请求 + int responseCode = connection.getResponseCode(); + if (responseCode != 200) { + throw new IOException("HTTP " + responseCode + " from " + IP_SERVICE_URL); + } + + // 读取响应(注意:myip.ipip.net 返回的是 GBK 编码) + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(connection.getInputStream(), "UTF-8"))) { // 使用 GBK 解码 + StringBuilder response = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + response.append(line); + } + return response.toString().trim(); + } + } + + /** + * 从返回文本中提取纯 IP 地址(如 112.96.15.145) + * + * @param ipInfo 来自 getPublicIp() 的完整信息 + * @return 纯 IP 字符串,提取失败返回 null + */ + public static String extractIp(String ipInfo) { + if (ipInfo == null || ipInfo.isEmpty()) return null; + + // 匹配 IP 地址的正则 + java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})"); + java.util.regex.Matcher matcher = pattern.matcher(ipInfo); + return matcher.find() ? matcher.group(1) : null; + } + + // === 使用示例 === + public static void main(String[] args) { + try { + String ipInfo = getPublicIp(); + System.out.println("完整信息: " + ipInfo); + + String pureIp = extractIp(ipInfo); + if (pureIp != null) { + System.out.println("公网 IP: " + pureIp); + } else { + System.out.println("未能提取 IP 地址"); + } + String province = ""; + String city = ""; + String carrier = ""; + + System.out.println(ipInfo.substring(ipInfo.indexOf("来自于:")+4,ipInfo.length())); + + + } catch (IOException e) { + System.err.println("获取公网 IP 失败: " + e.getMessage()); + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/utils/R.java b/src/main/java/com/tongran/agent/client/utils/R.java new file mode 100644 index 0000000..b786ce9 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/utils/R.java @@ -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 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 R success(String msg, T t) { + R r = new R<>(); + r.setData(t); + r.setMsg(msg); + r.setCode(GlobalErrorCode.SUCCESS.getCode()); + return r; + } + + public static R success(T t) { + return R.success(GlobalErrorCode.SUCCESS.getMsg(), t); + } + + public static R success() { + return R.success(null); + } + + public static R error(String msg, Integer code) { + R r = new R<>(); + r.setMsg(msg); + r.setCode(code); + return r; + } + + public static R error(Integer code, String msg) { + R r = new R<>(); + r.setMsg(msg); + r.setCode(code); + return r; + } + + public static R error(ErrorCode err) { + return R.error(err.getMsg(), err.getCode()); + } + + public static R error() { + return R.error(GlobalErrorCode.ERROR.getMsg(), GlobalErrorCode.ERROR.getCode()); + } + + public static R error(String msg) { + return R.error(msg, GlobalErrorCode.ERROR.getCode()); + } + +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/utils/RoundMinutes.java b/src/main/java/com/tongran/agent/client/utils/RoundMinutes.java new file mode 100644 index 0000000..3823c7e --- /dev/null +++ b/src/main/java/com/tongran/agent/client/utils/RoundMinutes.java @@ -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); + + } +} \ No newline at end of file diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 0000000..9efad49 --- /dev/null +++ b/src/main/resources/application-dev.yml @@ -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 # 最大重连延迟(毫秒) \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..288a043 --- /dev/null +++ b/src/main/resources/application.yml @@ -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 diff --git a/src/main/resources/logback/logback-dev.xml b/src/main/resources/logback/logback-dev.xml new file mode 100644 index 0000000..f4abc1c --- /dev/null +++ b/src/main/resources/logback/logback-dev.xml @@ -0,0 +1,116 @@ + + + + + ${appName} + + + + + + + [requestId:%X{requestId}] [time:%d{yyyy-MM-dd HH:mm:ss.SSS}] [thread:%thread] [level:%-5level] [appName:${appName}] msg- %msg%n + + + + + + + ${LOG_HOME}/debug.log + + + + ${LOG_HOME}/debug.%d{yyyy-MM-dd}.%i.AssertLog.zip + + 30 + + 10GB + + + + 10MB + + + + + + [requestId:%X{requestId}] [time:%d{yyyy-MM-dd HH:mm:ss.SSS}] [thread:%thread] [level:%-5level] [appName:${appName}] msg- %msg%n + + + + + + + + INFO + ACCEPT + DENY + + + ${LOG_HOME}/info.log + + + + ${LOG_HOME}/info.%d{yyyy-MM-dd}.%i.AssertLog.zip + + 30 + + 10GB + + + + 10MB + + + + + + [requestId:%X{requestId}] [time:%d{yyyy-MM-dd HH:mm:ss.SSS,Asia/Shanghai}] [thread:%thread] [level:%-5level] [appName:${appName}] msg- %msg%n + + + + + + + + ERROR + ACCEPT + DENY + + ${LOG_HOME}/error.log + + + + ${LOG_HOME}/error.%d{yyyy-MM-dd}.%i.AssertLog.zip + + 30 + + 10GB + + + + 10MB + + + + + + [requestId:%X{requestId}] [time:%d{yyyy-MM-dd HH:mm:ss.SSS}] [thread:%thread] [level:%-5level] [appName:${appName}] msg- %msg%n + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/com/tongran/agent/client/TrAgentClientApplicationTests.java b/src/test/java/com/tongran/agent/client/TrAgentClientApplicationTests.java new file mode 100644 index 0000000..ce27208 --- /dev/null +++ b/src/test/java/com/tongran/agent/client/TrAgentClientApplicationTests.java @@ -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() { + } + +}