From 4330ffdff11da93c56ef7b418c43fdcaa8defd6e Mon Sep 17 00:00:00 2001 From: qiminbao Date: Wed, 10 Sep 2025 15:36:49 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 149 ++++++ .../client/TrAgentClientApplication.java | 17 + .../core/config/ApplicationProperties.java | 29 ++ .../client/core/config/GlobalConfig.java | 140 +++++ .../agent/client/core/enums/MsgEnum.java | 73 +++ .../client/core/eo/AgentVersionUpdateEO.java | 35 ++ .../agent/client/core/eo/CollectEO.java | 16 + .../agent/client/core/eo/ScriptPolicyEO.java | 58 +++ .../agent/client/core/session/Session.java | 85 +++ .../client/core/session/SessionManager.java | 137 +++++ .../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 ++ .../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/AgentNettyConfig.java | 22 + .../agent/client/netty/AgentNettyServer.java | 57 +++ .../agent/client/netty/BaseNettyServer.java | 117 +++++ .../netty/annotation/AgentDispatcher.java | 47 ++ .../netty/basics/AgentDispatcherManager.java | 48 ++ .../client/netty/basics/AgentHandler.java | 21 + .../client/netty/config/BaseNettyConfig.java | 58 +++ .../client/netty/enpoint/AgentEndpoint.java | 484 ++++++++++++++++++ .../netty/handler/AgentDecoderHandler.java | 141 +++++ .../netty/handler/AgentDispatcherHandler.java | 38 ++ .../netty/handler/AgentEncoderHandler.java | 49 ++ .../netty/handler/TCPListenHandler.java | 78 +++ .../netty/handler/UDPListenHandler.java | 24 + .../agent/client/netty/model/Message.java | 37 ++ .../client/netty/model/UpMsgResponse.java | 24 + .../scheduler/config/SchedulerConfig.java | 37 ++ .../service/AdvancedAsyncDownloader.java | 121 +++++ .../scheduler/service/AppInitializer.java | 98 ++++ .../service/AsyncCommandExecutor.java | 273 ++++++++++ .../scheduler/service/BusinessTasks.java | 269 ++++++++++ .../scheduler/service/CompensatedTrigger.java | 57 +++ .../scheduler/service/DynamicTaskService.java | 147 ++++++ .../service/DynamicTcpPortService.java | 181 +++++++ .../service/Java8FileDownloader.java | 182 +++++++ .../service/SchedulerController.java | 63 +++ .../scheduler/task/SpecificTimeRequest.java | 53 ++ .../task/SpecificTimeTaskConfig.java | 114 +++++ .../task/SpecificTimeTaskService.java | 116 +++++ .../agent/client/service/AgentService.java | 11 + .../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 | 11 + .../agent/client/service/SystemService.java | 9 + .../client/service/impl/AgentServiceImpl.java | 381 ++++++++++++++ .../client/service/impl/CPUServiceImpl.java | 98 ++++ .../client/service/impl/DiskServiceImpl.java | 108 ++++ .../service/impl/DockerServiceImpl.java | 135 +++++ .../service/impl/MemoryServiceImpl.java | 74 +++ .../client/service/impl/NetServiceImpl.java | 123 +++++ .../service/impl/SwitchBoardServiceImpl.java | 468 +++++++++++++++++ .../service/impl/SystemServiceImpl.java | 401 +++++++++++++++ .../agent/client/utils/AgentDataUtil.java | 26 + .../tongran/agent/client/utils/AgentUtil.java | 220 ++++++++ .../tongran/agent/client/utils/AssertLog.java | 73 +++ .../com/tongran/agent/client/utils/R.java | 76 +++ .../agent/client/utils/RoundMinutes.java | 53 ++ src/main/resources/application-dev.yml | 22 + src/main/resources/application.yml | 31 ++ src/main/resources/logback/logback-dev.xml | 116 +++++ .../client/TrAgentClientApplicationTests.java | 13 + 75 files changed, 6674 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/CollectEO.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/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/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/AgentNettyConfig.java create mode 100644 src/main/java/com/tongran/agent/client/netty/AgentNettyServer.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/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/BaseNettyConfig.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/handler/TCPListenHandler.java create mode 100644 src/main/java/com/tongran/agent/client/netty/handler/UDPListenHandler.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/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/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/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/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..4609be9 --- /dev/null +++ b/pom.xml @@ -0,0 +1,149 @@ + + + 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 + + + + + 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..573a738 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/TrAgentClientApplication.java @@ -0,0 +1,17 @@ +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 java.time.LocalDateTime; + +@SpringBootApplication +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..3929b2c --- /dev/null +++ b/src/main/java/com/tongran/agent/client/core/config/ApplicationProperties.java @@ -0,0 +1,29 @@ +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; + + // 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; + } +} \ 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..a1e75d2 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/core/config/GlobalConfig.java @@ -0,0 +1,140 @@ +package com.tongran.agent.client.core.config; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 全局配置类 + */ + +public class GlobalConfig { + + /** + * 服务启动时间 + */ + public static long startupTime; + + + /** + * 客户端ID - 对应平台唯一SN + */ + public static String CLIENT_ID; + + /** + * 交换机 + */ + public static String SWITCH_COMMUNITY; //团名 + public static String SWITCH_IP; //交换机IP + public static int SWITCH_PORT; //交换机端口 + public static LinkedHashMap SWITCH_NET_OID; //交换机网络端口发现OID + public static LinkedHashMap SWITCH_MODULE_OID; //交换机光模块发现OID + public static LinkedHashMap SWITCH_MPU_OID; //交换机MPU发现OID + public static LinkedHashMap SWITCH_PWR_OID; //交换机电源发现OID + public static LinkedHashMap SWITCH_FAN_OID; //交换机风扇发现OID + public static LinkedHashMap SWITCH_OTHER_OID; //交换机系统其他OID + + /** + * 采集标识 + */ + 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 switchNetCollect = false; //交换机网络采集 + public static long switchNetInterval = 300; //交换机网络采集间隔 + public static boolean switchModuleCollect = false; //光模块采集 + public static long switchModuleInterval = 300; //光模块采集间隔 + public static boolean switchMpuCollect = false; //MPU采集 + public static long switchMpuInterval = 300; //MPU采集间隔 + public static boolean switchPwrCollect = false; //电源采集 + public static long switchPwrInterval = 300; //电源采集间隔 + public static boolean switchFanCollect = false; //风扇采集 + public static long switchFanInterval = 300; //风扇采集间隔 + /** + * 交换机其他监控 + */ + public static boolean switchSysDescrCollect = false; //系统描述采集 + public static long switchSysDescrInterval = 300; //系统描述采集间隔 + public static boolean switchSysObjectIDCollect = false; //系统Object ID采集 + public static long switchSysObjectIDInterval = 300; //系统Object ID采集间隔 + public static boolean switchSysUpTimeCollect = false; //系统运行时间采集 + public static long switchSysUpTimeInterval = 300; //系统运行时间采集间隔 + public static boolean switchSysContactCollect = false; //系统联系信息采集 + public static long switchSysContactInterval = 300; //系统联系信息采集间隔 + public static boolean switchSysNameCollect = false; //系统名称采集 + public static long switchSysNameInterval = 300; //系统名称采集间隔 + public static boolean switchSysLocationCollect = false; //系统位置采集 + public static long switchSysLocationInterval = 300; //系统位置采集间隔 + public static boolean switchHwStackSystemMacCollect = false; //系统MAC地址采集 + public static long switchHwStackSystemMacInterval = 300; //系统MAC地址采集间隔 + public static boolean switchEntIndexCollect = false; //设备索引采集 + public static long switchEntIndexInterval = 300; //设备索引采集间隔 + public static boolean switchEntPhysicalNameCollect = false; //设备名称采集 + public static long switchEntPhysicalNameInterval = 300; //设备名称采集间隔 + public static boolean switchEntPhysicalSoftwareRevCollect = false; //设备软件版本采集 + public static long switchEntPhysicalSoftwareRevInterval = 300; //设备软件版本采集间隔 + public static boolean switchHwEntityCpuUsageCollect = false; //设备CPU使用率(%)采集 + public static long switchHwEntityCpuUsageInterval = 300; //设备CPU使用率(%)采集间隔 + public static boolean switchHwEntityMemUsageCollect = false; //设备内存使用率(%)采集 + public static long switchHwEntityMemUsageInterval = 300; //设备内存使用率(%)采集间隔 + public static boolean switchHwAveragePowerCollect = false; //系统平均功率(mW)采集 + public static long switchHwAveragePowerInterval = 300; //系统平均功率(mW)采集间隔 + public static boolean switchHwCurrentPowerCollect = false; //系统实时功率(mW)采集 + public static long switchHwCurrentPowerInterval = 300; //系统实时功率(mW)采集间隔 + + + + +} 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..55cb040 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/core/enums/MsgEnum.java @@ -0,0 +1,73 @@ +package com.tongran.agent.client.core.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +/** + * 控制码 + */ +@Getter +@AllArgsConstructor +@NoArgsConstructor +public enum MsgEnum { + + 注册("REGISTER"), + + 注册应答("REGISTER_RSP"), + + 断开("DISCONNECT"), + + 断开应答("DISCONNECT_RSP"), + + 心跳上报("HEARTBEAT"), + + CPU上报("CPU"), + + 磁盘上报("DISK"), + + 容器上报("DOCKER"), + + 内存上报("MEMORY"), + + 网络上报("NET"), + + 挂载上报("POINT"), + + 交换机上报("SWITCHBOARD"), + + 系统其他上报("OTHER_SYSTEM"), + + 开启系统采集("SYSTEM_COLLECT_START"), + + 开启系统采集应答("SYSTEM_COLLECT_START_RSP"), + + 关闭系统采集("SYSTEM_COLLECT_STOP"), + + 关闭系统采集应答("SYSTEM_COLLECT_STOP_RSP"), + + 开启交换机采集("SWITCH_COLLECT_START"), + + 开启交换机采集应答("SWITCH_COLLECT_START_RSP"), + + 关闭交换机采集("SWITCH_COLLECT_STOP"), + + 关闭交换机采集应答("SWITCH_COLLECT_STOP_RSP"), + + 告警设置("ALARM_SET"), + + 告警设置应答("ALARM_SET_RSP"), + + 执行脚本策略("SCRIPT_POLICY"), + + 执行脚本策略应答("SCRIPT_POLICY_RSP"), + + Agent版本更新("AGENT_VERSION_UPDATE"), + + Agent版本更新应答("AGENT_VERSION_UPDATE_RSP"); + + 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..6c5b328 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/core/eo/AgentVersionUpdateEO.java @@ -0,0 +1,35 @@ +package com.tongran.agent.client.core.eo; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.List; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class AgentVersionUpdateEO { + + //文件地址,外网HTTP(S)地址 + private String fileUrl; + + //保存文件地址 + private String filePath; + + //命令 + private List commands; + + //执行方式:0、立即执行;1、定时执行; + private int method; + + //定时时间,执行方式为1、定时执行时该字段必传 + private LocalDateTime policyTime; + + //时间戳 + private long timestamp; + +} 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/ScriptPolicyEO.java b/src/main/java/com/tongran/agent/client/core/eo/ScriptPolicyEO.java new file mode 100644 index 0000000..8222969 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/core/eo/ScriptPolicyEO.java @@ -0,0 +1,58 @@ +package com.tongran.agent.client.core.eo; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.List; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ScriptPolicyEO implements Serializable { + + private static final long serialVersionUID = -1267013167162440612L; + + /** + * 文件 + */ + private List files; + + //目标路径地址 + private String filePath; + + //命令 + private List commands; + + //执行方式:0、立即执行;1、定时执行; + private int method; + + //执行方式为1、定时执行时必传-指定时间 + private LocalDateTime policyTime; + + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class ScriptFile{ + + //文件类型:0、平台文件地址;1、外网HTTP(S) + private int fileType; + + //文件类型为1、外网HTTP(S)时必传 + private String fileUrl; + + //文件类型为0、平台文件地址时必传(文件名) + private String fileName; + + //文件类型为0、平台文件地址时必传(文件流) + private String fileData; + + } + +} 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/CpuVO.java b/src/main/java/com/tongran/agent/client/core/vo/CpuVO.java new file mode 100644 index 0000000..3ed0b47 --- /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 long interrupt; + + @Schema(description = "CPU使用率%") + private double uti; + + @Schema(description = "CPU数量") + private int num; + + @Schema(description = "CPU正常运行时间/秒") + private long normal; + + @Schema(description = "CPU空闲时间") + private long idle; + + @Schema(description = "CPU等待响应时间") + private double iowait; + + @Schema(description = "CPU系统时间") + private long system; + + @Schema(description = "CPU软件无响应时间") + private double noresp; + + @Schema(description = "CPU用户进程所花费的时间") + private long 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..30c6494 --- /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/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/AgentNettyConfig.java b/src/main/java/com/tongran/agent/client/netty/AgentNettyConfig.java new file mode 100644 index 0000000..7c6a06e --- /dev/null +++ b/src/main/java/com/tongran/agent/client/netty/AgentNettyConfig.java @@ -0,0 +1,22 @@ +package com.tongran.agent.client.netty; + +import com.tongran.agent.client.netty.config.BaseNettyConfig; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * Netty配置 + */ +@Data +@Configuration +@ToString(callSuper = true) +@EqualsAndHashCode(callSuper = true) +@ConfigurationProperties(prefix = "tcp.netty.charge") +public class AgentNettyConfig extends BaseNettyConfig { + + private static final long serialVersionUID = -4284214721383107291L; + +} diff --git a/src/main/java/com/tongran/agent/client/netty/AgentNettyServer.java b/src/main/java/com/tongran/agent/client/netty/AgentNettyServer.java new file mode 100644 index 0000000..16f6b8c --- /dev/null +++ b/src/main/java/com/tongran/agent/client/netty/AgentNettyServer.java @@ -0,0 +1,57 @@ +package com.tongran.agent.client.netty; + + +import com.tongran.agent.client.netty.handler.AgentDecoderHandler; +import com.tongran.agent.client.netty.handler.AgentDispatcherHandler; +import com.tongran.agent.client.netty.handler.AgentEncoderHandler; +import com.tongran.agent.client.netty.handler.TCPListenHandler; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.timeout.IdleStateHandler; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.Resource; + +@Configuration +public class AgentNettyServer extends BaseNettyServer { + + @Resource + private AgentNettyConfig config; + + @Resource + private TCPListenHandler tcpListenHandler; + + @Resource + private AgentDecoderHandler decoderHandler; + + @Resource + private AgentEncoderHandler encoderHandler; + + @Resource + private AgentDispatcherHandler dispatcherHandler; + + + protected AgentNettyServer(AgentNettyConfig config) { + super(config); + } + + @Bean(name = "NettyCharge", initMethod = "start", destroyMethod = "stop") + BaseNettyServer run() { + config.setHander(new ChannelInitializer() { + @Override + public void initChannel(NioSocketChannel ch) throws Exception { + ch.pipeline().addLast(new IdleStateHandler(config.readerIdleTime, config.writerIdleTime, config.allIdleTime));// 心跳 + ch.pipeline().addLast(tcpListenHandler);// 监听器 + //入栈 + ch.pipeline().addLast(decoderHandler);//解码器 + //出栈 + ch.pipeline().addLast(encoderHandler);//加码器 + // 业务分发是入站最后一个处理器, 出站第一个处理器, 位置要放在最后 + ch.pipeline().addLast(businessGroup, dispatcherHandler);//业务分发 这里使用业务线程组 + } + }); + return new AgentNettyServer(config); + } + +} \ No newline at end of file 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/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/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/enpoint/AgentEndpoint.java b/src/main/java/com/tongran/agent/client/netty/enpoint/AgentEndpoint.java new file mode 100644 index 0000000..616a6ad --- /dev/null +++ b/src/main/java/com/tongran/agent/client/netty/enpoint/AgentEndpoint.java @@ -0,0 +1,484 @@ +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.GlobalConfig; +import com.tongran.agent.client.core.enums.MsgEnum; +import com.tongran.agent.client.core.eo.AgentVersionUpdateEO; +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.Message; +import com.tongran.agent.client.netty.model.UpMsgResponse; +import com.tongran.agent.client.scheduler.service.AdvancedAsyncDownloader; +import com.tongran.agent.client.scheduler.service.AppInitializer; +import com.tongran.agent.client.scheduler.service.AsyncCommandExecutor; +import com.tongran.agent.client.scheduler.service.Java8FileDownloader; +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.AgentUtil; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +@Component +public class AgentEndpoint { + + @Resource + private AppInitializer appInitializer; + + @Resource + private SpecificTimeTaskService taskService; + + @Resource + private AgentService agentService; + + + @AgentDispatcher(msgId = MsgEnum.注册) + public class RegisterHandler implements AgentHandler { + @Override + public UpMsgResponse upHandle(String data, String clientId) { + //更改全局变量 + JSONObject jsonObject = JSONObject.parseObject(data); + if(jsonObject.containsKey("switchBoard")){ + String switchBoard = jsonObject.getString("switchBoard"); + jsonObject = JSONObject.parseObject(switchBoard); + if(jsonObject.containsKey("community")){ + GlobalConfig.SWITCH_COMMUNITY = jsonObject.getString("community"); + } + if(jsonObject.containsKey("ip")){ + GlobalConfig.SWITCH_IP = jsonObject.getString("ip"); + } + if(jsonObject.containsKey("port")){ + GlobalConfig.SWITCH_PORT = jsonObject.getInteger("port"); + } + if(jsonObject.containsKey("netOID")){ + String netOID = jsonObject.getString("netOID"); + LinkedHashMap map = JSON.parseObject(netOID, new TypeReference>() {}); + GlobalConfig.SWITCH_NET_OID = map; + } + if(jsonObject.containsKey("moduleOID")){ + String moduleOID = jsonObject.getString("moduleOID"); + LinkedHashMap map = JSON.parseObject(moduleOID, new TypeReference>() {}); + GlobalConfig.SWITCH_MODULE_OID = map; + } + if(jsonObject.containsKey("mpuOID")){ + String mpuOID = jsonObject.getString("mpuOID"); + LinkedHashMap map = JSON.parseObject(mpuOID, new TypeReference>() {}); + GlobalConfig.SWITCH_MPU_OID = map; + } + if(jsonObject.containsKey("pwrOID")){ + String pwrOID = jsonObject.getString("pwrOID"); + LinkedHashMap map = JSON.parseObject(pwrOID, new TypeReference>() {}); + GlobalConfig.SWITCH_PWR_OID = map; + } + if(jsonObject.containsKey("fanOID")){ + String fanOID = jsonObject.getString("fanOID"); + LinkedHashMap map = JSON.parseObject(fanOID, new TypeReference>() {}); + GlobalConfig.SWITCH_FAN_OID = map; + } + if(jsonObject.containsKey("otherOID")){ + String otherOID = jsonObject.getString("otherOID"); + LinkedHashMap map = JSON.parseObject(otherOID, new TypeReference>() {}); + GlobalConfig.SWITCH_OTHER_OID = map; + } + } + GlobalConfig.isCollect = true; + GlobalConfig.CLIENT_ID = clientId; + //调用采集任务 + try { + appInitializer.run(); + } catch (Exception e) { + e.printStackTrace(); + } + 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 DisconnectHandler implements AgentHandler { + @Override + public UpMsgResponse upHandle(String data, String clientId) { + //更改全局变量 + GlobalConfig.isCollect = false; + //调用采集任务 + try { + appInitializer.run(); + } catch (Exception e) { + e.printStackTrace(); + } + long timestamp = System.currentTimeMillis(); + timestamp = Math.round(timestamp / 1000.0); + JSONObject json = new JSONObject(); + json.put("resCode",1); + json.put("resMag",""); + json.put("timestamp",timestamp); + return UpMsgResponse.builder().clientId(clientId).dataType(MsgEnum.断开应答.getValue()) + .content(json.toString()).build(); + } + } + + @AgentDispatcher(msgId = MsgEnum.开启系统采集) + public class SysCollectStartHandler implements AgentHandler { + @Override + public UpMsgResponse upHandle(String data, String clientId) { + //更改全局变量 + agentService.systemCollectStart(data); + long timestamp = System.currentTimeMillis(); + timestamp = Math.round(timestamp / 1000.0); + JSONObject json = new JSONObject(); + json.put("resCode",1); + json.put("resMag",""); + json.put("timestamp",timestamp); + return UpMsgResponse.builder().dataType(MsgEnum.开启系统采集应答.getValue()).content(json.toString()).build(); + } + } + + @AgentDispatcher(msgId = MsgEnum.关闭系统采集) + public class SysCollectStopHandler implements AgentHandler { + @Override + public UpMsgResponse upHandle(String data, String clientId) { + //更改全局变量 + agentService.systemCollectStop(); + long timestamp = System.currentTimeMillis(); + timestamp = Math.round(timestamp / 1000.0); + JSONObject json = new JSONObject(); + json.put("resCode",1); + json.put("resMag",""); + json.put("timestamp",timestamp); + return UpMsgResponse.builder().dataType(MsgEnum.关闭系统采集应答.getValue()).content(json.toString()).build(); + } + } + + @AgentDispatcher(msgId = MsgEnum.开启交换机采集) + public class SwitchCollectStartHandler implements AgentHandler { + @Override + public UpMsgResponse upHandle(String data, String clientId) { + //更改全局变量 + agentService.switchCollectStart(data); + long timestamp = System.currentTimeMillis(); + timestamp = Math.round(timestamp / 1000.0); + JSONObject json = new JSONObject(); + json.put("resCode",1); + json.put("resMag",""); + json.put("timestamp",timestamp); + return UpMsgResponse.builder().dataType(MsgEnum.开启交换机采集应答.getValue()).content(json.toString()).build(); + } + } + + @AgentDispatcher(msgId = MsgEnum.关闭交换机采集) + public class SwitchCollectStopHandler implements AgentHandler { + @Override + public UpMsgResponse upHandle(String data, String clientId) { + //更改全局变量 + agentService.switchCollectStop(); + long timestamp = System.currentTimeMillis(); + timestamp = Math.round(timestamp / 1000.0); + JSONObject json = new JSONObject(); + json.put("resCode",1); + json.put("resMag",""); + json.put("timestamp",timestamp); + return UpMsgResponse.builder().dataType(MsgEnum.关闭交换机采集应答.getValue()).content(json.toString()).build(); + } + } + + @AgentDispatcher(msgId = MsgEnum.告警设置) + public class AlarmSetHandler implements AgentHandler { + @Override + public UpMsgResponse upHandle(String data, String clientId) { +// //更改全局变量 +// JSONObject jsonObject = JSONObject.parseObject(data); +// if(jsonObject.containsKey("switchBoard")){ +// String switchBoard = jsonObject.getString("switchBoard"); +// jsonObject = JSONObject.parseObject(switchBoard); +// if(jsonObject.containsKey("community")){ +// GlobalConfig.SWITCH_COMMUNITY = jsonObject.getString("community"); +// } +// if(jsonObject.containsKey("ip")){ +// GlobalConfig.SWITCH_IP = jsonObject.getString("ip"); +// } +// if(jsonObject.containsKey("port")){ +// GlobalConfig.SWITCH_PORT = jsonObject.getInteger("port"); +// } +// if(jsonObject.containsKey("netOID")){ +// String netOID = jsonObject.getString("netOID"); +// LinkedHashMap map = JSON.parseObject(netOID, new TypeReference>() {}); +// GlobalConfig.SWITCH_NET_OID = map; +// } +// if(jsonObject.containsKey("moduleOID")){ +// String moduleOID = jsonObject.getString("moduleOID"); +// LinkedHashMap map = JSON.parseObject(moduleOID, new TypeReference>() {}); +// GlobalConfig.SWITCH_MODULE_OID = map; +// } +// if(jsonObject.containsKey("mpuOID")){ +// String mpuOID = jsonObject.getString("mpuOID"); +// LinkedHashMap map = JSON.parseObject(mpuOID, new TypeReference>() {}); +// GlobalConfig.SWITCH_MPU_OID = map; +// } +// if(jsonObject.containsKey("pwrOID")){ +// String pwrOID = jsonObject.getString("pwrOID"); +// LinkedHashMap map = JSON.parseObject(pwrOID, new TypeReference>() {}); +// GlobalConfig.SWITCH_PWR_OID = map; +// } +// if(jsonObject.containsKey("fanOID")){ +// String fanOID = jsonObject.getString("fanOID"); +// LinkedHashMap map = JSON.parseObject(fanOID, new TypeReference>() {}); +// GlobalConfig.SWITCH_FAN_OID = map; +// } +// if(jsonObject.containsKey("otherOID")){ +// String otherOID = jsonObject.getString("otherOID"); +// LinkedHashMap map = JSON.parseObject(otherOID, new TypeReference>() {}); +// GlobalConfig.SWITCH_OTHER_OID = map; +// } +// } +// GlobalConfig.isCollect = true; +// GlobalConfig.CLIENT_ID = clientId; +// //调用采集任务 +// try { +// appInitializer.run(); +// } catch (Exception e) { +// e.printStackTrace(); +// } +// JSONObject json = new JSONObject(); +// json.put("resCode",1); + return UpMsgResponse.builder().clientId(clientId).dataType(MsgEnum.告警设置应答.getValue()) + .content("").build(); + } + } + + + @AgentDispatcher(msgId = MsgEnum.执行脚本策略) + public class ScriptPolicyHandler implements AgentHandler { + @Override + public UpMsgResponse upHandle(String data, String clientId) { + ScriptPolicyEO policy = JSON.parseObject(data, ScriptPolicyEO.class); + boolean isFalse = false; + if(StringUtils.isNotBlank(policy.getFilePath())){ + if(AdvancedAsyncDownloader.createSingleDirectoryIfNotExists(policy.getFilePath())){ + if(CollectionUtil.isNotEmpty(policy.getFiles())){ + isFalse = true; + for (ScriptPolicyEO.ScriptFile f : policy.getFiles()) { + String finalSaveDir = policy.getFilePath(); + //文件类型:0、平台文件地址;1、外网HTTP(S) + if(f.getFileType() == 0){ + String filePath = policy.getFilePath() + "/" + f.getFileName(); + // 移除Base64 URL前缀(如果存在) +// if (f.getFileData().contains(",")) { +// f.setFileData(f.getFileData().split(",")[1]); +// } + try { + AgentUtil.base64ToFile(f.getFileData(), filePath); + System.out.println("文件保存成功: " + filePath); + isFalse = true; + } catch (IOException e) { + isFalse = false; + System.err.println("文件保存失败: " + e.getMessage()); + } + }else{ + Java8FileDownloader.DownloadResult result = Java8FileDownloader.downloadFile(f.getFileUrl(), finalSaveDir, f.getFileName()); + // 验证下载是否完成 + if (result.isSuccess()) { + boolean isComplete = Java8FileDownloader.verifyFileCompletion( + result.getFilePath(), result.getExpectedSize()); + if(isComplete){ + isFalse = true; + System.out.println("下载完成: " + finalSaveDir+"/"+f.getFileName()); + }else{ + isFalse = false; + System.err.println("下载失败"); + } + } + } + } + } + } + } + //判断文件是否保存成功 + if(isFalse){ + if(CollectionUtil.isNotEmpty(policy.getCommands())){ + //执行方式:0、立即执行;1、定时执行; + if(policy.getMethod() == 1){ + SpecificTimeRequest request = SpecificTimeRequest.builder() + .taskId("") + .taskName("") + .taskData(policy.getCommands()) + .specificDateTime(policy.getPolicyTime()) + .clientId(clientId) + .build(); + try { + taskService.createSpecificTimeTask(request); + JSONObject json = new JSONObject(); + json.put("resCode",1); + json.put("resMsg", "执行脚本策略定时任务保存成功"); + Message message = Message.builder().clientId(clientId).dataType(MsgEnum.执行脚本策略应答.getValue()).data(json.toString()).build(); + return UpMsgResponse.builder().clientId(clientId).dataType(MsgEnum.执行脚本策略应答.getValue()).content(json.toString()).build(); + } catch (Exception e) { + JSONObject json = new JSONObject(); + json.put("resCode",0); + json.put("resMsg", "执行脚本策略定时任务保存失败"); + Message message = Message.builder().clientId(clientId).dataType(MsgEnum.执行脚本策略应答.getValue()).data(json.toString()).build(); + return UpMsgResponse.builder().clientId(clientId).dataType(MsgEnum.执行脚本策略应答.getValue()).content(json.toString()).build(); + } + }else{ + List> list = new ArrayList<>(); + for (String command : policy.getCommands()) { + Map map = new HashMap<>(); + List cmd = Arrays.asList(command.split("\\s+")); + CompletableFuture future = + AsyncCommandExecutor.executeCommandAsync( + cmd, + 60, TimeUnit.SECONDS); + future.thenAccept(result -> { + if (result.isSuccess()) { + System.out.println("脚本执行成功"); + System.out.println("[成功resOut] " + result.getOutput()); + map.put("command",command); + map.put("resOut",result.getOutput()); + } else { + System.out.println("脚本执行失败"); + System.out.println("[失败resOut] " + result.getOutput()); + map.put("command",command); + map.put("resOut",result.getOutput()); + } + }).exceptionally(ex -> { + System.err.println("执行失败: " + ex.getMessage()); + map.put("command",command); + map.put("resOut","Policy execute filed"); + return null; + }); + list.add(map); + } + if(CollectionUtil.isNotEmpty(list)){ + JSONObject json = new JSONObject(); + json.put("resCode",1); + json.put("resMsg", ""); + json.put("result", AgentUtil.toJsonString(list)); + return UpMsgResponse.builder().clientId(clientId).dataType(MsgEnum.执行脚本策略应答.getValue()).content(json.toString()).build(); + } + } + } + }else{ + JSONObject json = new JSONObject(); + json.put("resCode",0); + json.put("resMsg", "A执行脚本策略文件保存失败"); + return UpMsgResponse.builder().clientId(clientId).dataType(MsgEnum.执行脚本策略应答.getValue()).content(json.toString()).build(); + } + return null; + } + } + + @AgentDispatcher(msgId = MsgEnum.Agent版本更新) + public class VersionUpdateHandler implements AgentHandler { + @Override + public UpMsgResponse upHandle(String data, String clientId) { + AgentVersionUpdateEO versionUpdateEO = JSON.parseObject(data, AgentVersionUpdateEO.class); + boolean isFalse = false; + if(StringUtils.isNotBlank(versionUpdateEO.getFileUrl()) && StringUtils.isNotBlank(versionUpdateEO.getFilePath())){ + Java8FileDownloader.DownloadResult result = Java8FileDownloader.downloadFile(versionUpdateEO.getFileUrl(), versionUpdateEO.getFilePath(), ""); + // 验证下载是否完成 + if (result.isSuccess()) { + boolean isComplete = Java8FileDownloader.verifyFileCompletion( + result.getFilePath(), result.getExpectedSize()); + if(isComplete){ + isFalse = true; + System.out.println("Agent版本更新下载完成"); + }else{ + isFalse = false; + System.err.println("Agent版本更新下载失败"); + } + } + } + //判断文件是否保存成功 + if(isFalse){ + if(CollectionUtil.isNotEmpty(versionUpdateEO.getCommands())){ + //执行方式:0、立即执行;1、定时执行; + if(versionUpdateEO.getMethod() == 1){ + SpecificTimeRequest request = SpecificTimeRequest.builder() + .taskId("") + .taskName("") + .taskData(versionUpdateEO.getCommands()) + .specificDateTime(versionUpdateEO.getPolicyTime()) + .clientId(clientId) + .dataType(MsgEnum.Agent版本更新应答.getValue()) + .build(); + try { + taskService.createSpecificTimeTask(request); + JSONObject json = new JSONObject(); + json.put("resCode",1); + json.put("resMsg", "Agent版本更新定时任务保存成功"); +// Message message = Message.builder().clientId(clientId).dataType(MsgEnum.Agent更新应答.getValue()).data(json.toString()).build(); + return UpMsgResponse.builder().clientId(clientId).dataType(MsgEnum.Agent版本更新应答.getValue()).content(json.toString()).build(); + } catch (Exception e) { + JSONObject json = new JSONObject(); + json.put("resCode",0); + json.put("resMsg", "Agent版本更新定时任务保存失败"); +// Message message = Message.builder().clientId(clientId).dataType(MsgEnum.Agent更新应答.getValue()).data(json.toString()).build(); + return UpMsgResponse.builder().clientId(clientId).dataType(MsgEnum.Agent版本更新应答.getValue()).content(json.toString()).build(); + } + }else{ + List> list = new ArrayList<>(); + for (String command : versionUpdateEO.getCommands()) { + Map map = new HashMap<>(); + List cmd = Arrays.asList(command.split("\\s+")); + CompletableFuture future = + AsyncCommandExecutor.executeCommandAsync( + cmd, + 60, TimeUnit.SECONDS); + future.thenAccept(result -> { + if (result.isSuccess()) { + System.out.println("脚本执行成功"); + System.out.println("[成功resOut] " + result.getOutput()); + map.put("command",command); + map.put("resOut",result.getOutput()); + } else { + System.out.println("脚本执行失败"); + System.out.println("[失败resOut] " + result.getOutput()); + map.put("command",command); + map.put("resOut",result.getOutput()); + } + }).exceptionally(ex -> { + System.err.println("执行失败: " + ex.getMessage()); + map.put("command",command); + map.put("resOut","Policy execute filed"); + return null; + }); + list.add(map); + } + if(CollectionUtil.isNotEmpty(list)){ + JSONObject json = new JSONObject(); + json.put("resCode",1); + json.put("resMsg", ""); + json.put("result", AgentUtil.toJsonString(list)); +// Message message = Message.builder().clientId(clientId).dataType(MsgEnum.Agent版本更新应答.getValue()).data(json.toString()).build(); + return UpMsgResponse.builder().clientId(clientId).dataType(MsgEnum.Agent版本更新应答.getValue()).content(json.toString()).build(); + } + } + } + }else{ + JSONObject json = new JSONObject(); + json.put("resCode",0); + json.put("resMsg", "Agent版本更新文件保存失败"); +// Message message = Message.builder().clientId(clientId).dataType(MsgEnum.Agent版本更新应答.getValue()).data(json.toString()).build(); + 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..e001d42 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/netty/handler/AgentDecoderHandler.java @@ -0,0 +1,141 @@ +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 = ""; + 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; + content = arr_msg[0]+"@tong-ran"; + }else{ + lruCache.remove(sessionManager.client(ctx)); + tmpMsgSize = 0; + content = arr_msg[0]; + lruCache.put(sessionManager.client(ctx), content, DateUnit.SECOND.getMillis() * 3000); + } + } + //判定是否粘包 + if(arr_msg.length > 1){ + if(!endsWith){ + lruCache.remove(sessionManager.client(ctx)); + tmpMsgSize = 0; + content = arr_msg[0]+"@tong-ran"; + lruCache.put(sessionManager.client(ctx), arr_msg[1], DateUnit.SECOND.getMillis() * 3000); + }else{ + lruCache.remove(sessionManager.client(ctx)); + tmpMsgSize = 0; + content = String.join("@tong-ran", arr_msg); + } + } + } + if (tmpMsgSize > 0) { + content = tempMsg + messages; + endsWith = content.endsWith("@tong-ran"); + if(endsWith){ + isClear = true; + }else{ + lruCache.remove(sessionManager.client(ctx)); + tmpMsgSize = 0; + 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){ + String[] arr = content.split("@tong-ran"); + for (String message : arr) { + JSONObject jsonObject = JSONObject.parseObject(message); + String clientId = jsonObject.getString("clientId"); + String dataType = jsonObject.getString("dataType"); + AgentHandler msgHandler = agentDispatcherManager.getHandler(dataType + "&" + AgentDispatcher.VersionEnum.V1.value); + if (ObjectUtil.isNotEmpty(msgHandler)) { + UpMsgResponse response = msgHandler.upHandle(message, clientId ); + AssertLog.info("<<[up-after-handle]:clientId:{},type={},[handle-content]={}", response.getClientId(), dataType, message); + if(Objects.nonNull(response)){ + Message agentMessage = Message.builder().build(); + agentMessage.setClientId(response.getClientId()); + agentMessage.setDataType(response.getDataType()); + agentMessage.setData(response.getContent()); + ctx.fireChannelRead(agentMessage);//传递到下一个handler + } + } + } + } + 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/handler/TCPListenHandler.java b/src/main/java/com/tongran/agent/client/netty/handler/TCPListenHandler.java new file mode 100644 index 0000000..d364383 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/netty/handler/TCPListenHandler.java @@ -0,0 +1,78 @@ +package com.tongran.agent.client.netty.handler; + +import com.tongran.agent.client.core.config.GlobalConfig; +import com.tongran.agent.client.core.session.SessionManager; +import com.tongran.agent.client.scheduler.service.AppInitializer; +import com.tongran.agent.client.utils.AssertLog; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.handler.timeout.IdleState; +import io.netty.handler.timeout.IdleStateEvent; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.io.IOException; + +/** + * @Description: TCP消息适配器 + */ +@Component +@ChannelHandler.Sharable +public class TCPListenHandler extends ChannelInboundHandlerAdapter { + + @Resource + private AppInitializer appInitializer; + + private final SessionManager sessionManager; + + public TCPListenHandler() { + this.sessionManager = SessionManager.getInstance(); + } + + @Override + public void channelActive(ChannelHandlerContext ctx) { + AssertLog.info("<<<<<<[终端连接]{}", ctx.channel().remoteAddress()); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) { + AssertLog.info(">>>>>>[断开连接]{}", sessionManager.client(ctx)); + sessionManager.remove(ctx.channel()); + try { + GlobalConfig.isCollect = false; + appInitializer.run(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) { + if (e instanceof IOException) { + AssertLog.info("<<<<<<[终端断开连接]{} {}", sessionManager.client(ctx), e.getMessage()); + } else { + AssertLog.info(">>>>>>[消息处理异常]" + sessionManager.client(ctx), e); + } + } + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { + if (evt instanceof IdleStateEvent) { + IdleStateEvent event = (IdleStateEvent) evt; + IdleState state = event.state(); + if (state == IdleState.READER_IDLE || state == IdleState.WRITER_IDLE || state == IdleState.ALL_IDLE) { + AssertLog.warn(">>>>>>[终端心跳超时]{} {}", state, sessionManager.client(ctx)); + sessionManager.remove(ctx.channel()); + ctx.close(); + try { + GlobalConfig.isCollect = false; + appInitializer.run(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + +} diff --git a/src/main/java/com/tongran/agent/client/netty/handler/UDPListenHandler.java b/src/main/java/com/tongran/agent/client/netty/handler/UDPListenHandler.java new file mode 100644 index 0000000..9b4a9e1 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/netty/handler/UDPListenHandler.java @@ -0,0 +1,24 @@ +package com.tongran.agent.client.netty.handler; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.socket.DatagramPacket; +import org.springframework.stereotype.Component; + +/** + * @Description: UDP消息适配器 + */ +@Component +@ChannelHandler.Sharable +public class UDPListenHandler extends ChannelInboundHandlerAdapter { + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + DatagramPacket packet = (DatagramPacket) msg; + ByteBuf buf = packet.content(); + buf.clear();// 暂未实现 + } + +} 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..03095cb --- /dev/null +++ b/src/main/java/com/tongran/agent/client/scheduler/config/SchedulerConfig.java @@ -0,0 +1,37 @@ +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.ThreadPoolTaskScheduler; + +import java.util.concurrent.ThreadPoolExecutor; + +@Configuration +@EnableScheduling +@EnableAsync +public class SchedulerConfig { + + @Bean + public ThreadPoolTaskScheduler taskScheduler() { + ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); + scheduler.setPoolSize(10); // 核心线程数 + 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(10); // 工作线程数 + executor.setThreadNamePrefix("task-worker-"); + executor.setAwaitTerminationSeconds(30); + executor.setWaitForTasksToCompleteOnShutdown(true); + return executor; + } +} \ No newline at end of file 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..66bd47c --- /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); + } + + private 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..d66b8b0 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/scheduler/service/AppInitializer.java @@ -0,0 +1,98 @@ +package com.tongran.agent.client.scheduler.service; + +import com.tongran.agent.client.core.config.GlobalConfig; +import com.tongran.agent.client.utils.AssertLog; +import org.springframework.boot.CommandLineRunner; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +@Component +public class AppInitializer implements CommandLineRunner { + + private final BusinessTasks businessTasks; + private final DynamicTaskService dynamicTaskService; + + // 使用构造函数注入,避免循环依赖 + public AppInitializer(DynamicTaskService dynamicTaskService, + @Lazy BusinessTasks businessTasks) { + this.dynamicTaskService = dynamicTaskService; + this.businessTasks = businessTasks; + } + + @Override + public void run(String... args) throws Exception { + System.out.println("应用启动完成,开始初始化定时任务..."); + if(!GlobalConfig.isCollect){ + // 开启定时任务 + initScheduledTasks(); + }else{ + // 关闭定时任务 + cancelTasks(); + } + System.out.println("定时任务初始化完成"); + } + + private void initScheduledTasks() { + // 每30秒执行心跳任务 + AssertLog.info("初始化启动心跳定时任务 - 延迟: {}ms, 间隔: {}ms", 5000, 30000); + dynamicTaskService.scheduleTask("heartbeat", + businessTasks::heartbeatTask, 5000, 30000); + +// long milli = AgentUtil.getMillisToNextMinute() + 60000; +// // 每300秒执行CPU信息采集任务 +// AssertLog.info("初始化启动CPU信息采集定时任务 - 延迟: {}ms, 间隔: {}ms", milli, 300000); +// dynamicTaskService.scheduleTask("cpu", +// businessTasks::cpuTask, milli, 300000); // 25秒后开始,每300秒执行 +// +// // 每300秒执行磁盘信息采集任务 +// AssertLog.info("初始化启动磁盘信息采集定时任务 - 延迟: {}ms, 间隔: {}ms", milli, 300000); +// dynamicTaskService.scheduleTask("disk", +// businessTasks::diskTask, milli, 300000); // 35秒后开始,每300秒执行 +// +// // 每300秒执行系统信息采集任务 +// AssertLog.info("初始化启动系统信息采集定时任务 - 延迟: {}ms, 间隔: {}ms", milli, 300000); +// dynamicTaskService.scheduleTask("system", +// businessTasks::systemTask, milli, 300000); // 40秒后开始,每300秒执行 +// +// // 每300秒执行容器信息采集任务 +// AssertLog.info("初始化启动容器信息采集定时任务 - 延迟: {}ms, 间隔: {}ms", milli, 300000); +// dynamicTaskService.scheduleTask("docker", +// businessTasks::dockerTask, milli, 300000); // 45秒后开始,每300秒执行 +// +// // 每300秒执行内存信息采集任务 +// AssertLog.info("初始化启动内存信息采集定时任务 - 延迟: {}ms, 间隔: {}ms", milli, 300000); +// dynamicTaskService.scheduleTask("memory", +// businessTasks::memoryTask, milli, 300000); // 55秒后开始,每300秒执行 +// +// +// //获取当前时间距离下一个“分钟为 0 或 5”的时间点还差多少毫秒 +// long millis = AgentUtil.millisecondsToNext5Minute(); +// // 每300秒执行网络信息采集任务 +// AssertLog.info("初始化启动网络信息采集定时任务 - 延迟: {}ms, 间隔: {}ms", millis, 300000); +// dynamicTaskService.scheduleTask("net", +// businessTasks::netTask, millis, 300000); // 65秒后开始,每300秒执行 +// +// // 每300秒执行挂载信息采集任务 +// AssertLog.info("初始化启动挂载信息采集定时任务 - 延迟: {}ms, 间隔: {}ms", milli, 300000); +// dynamicTaskService.scheduleTask("point", +// businessTasks::pointTask, milli, 300000); // 75秒后开始,每300秒执行 +// +// // 每300秒执行交换机信息采集任务 +// AssertLog.info("初始化启动交换机信息采集定时任务 - 延迟: {}ms, 间隔: {}ms", millis, 300000); +// dynamicTaskService.scheduleTask("switchBoard", +// businessTasks::switchBoardTask, millis, 300000); // 85秒后开始,每300秒执行 + + } + + private void cancelTasks() { + dynamicTaskService.cancelTask("heartbeat"); +// dynamicTaskService.cancelTask("cpu"); +// dynamicTaskService.cancelTask("disk"); +// dynamicTaskService.cancelTask("system"); +// dynamicTaskService.cancelTask("docker"); +// dynamicTaskService.cancelTask("memory"); +// dynamicTaskService.cancelTask("net"); +// dynamicTaskService.cancelTask("point"); +// dynamicTaskService.cancelTask("switchBoard"); + } +} \ 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..7c73828 --- /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; + }); + + // 设置超时 + 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..c094670 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/scheduler/service/BusinessTasks.java @@ -0,0 +1,269 @@ +package com.tongran.agent.client.scheduler.service; + +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.model.Message; +import com.tongran.agent.client.service.*; +import com.tongran.agent.client.utils.AgentUtil; +import com.tongran.agent.client.utils.AssertLog; +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 task1Counter = new AtomicInteger(0); + private final AtomicInteger task2Counter = new AtomicInteger(0); + private final AtomicInteger task3Counter = new AtomicInteger(0); + private final AtomicInteger task4Counter = new AtomicInteger(0); + private final AtomicInteger task5Counter = new AtomicInteger(0); + private final AtomicInteger task6Counter = new AtomicInteger(0); + private final AtomicInteger task7Counter = new AtomicInteger(0); + private final AtomicInteger task8Counter = new AtomicInteger(0); + private final AtomicInteger task9Counter = 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 SwitchBoardService switchBoardService; + + @Resource + private SystemService systemService; + + @Resource + private ApplicationProperties properties; + + /** + * 任务1:心跳上报任务 + */ + @Async("taskExecutor") + public void heartbeatTask() { + long timestamp = System.currentTimeMillis(); + timestamp = Math.round(timestamp / 1000.0); + int count = task1Counter.incrementAndGet(); + AssertLog.info("心跳定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count); + // 业务处理 + // 判定客户端与服务端是否连接 + if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) { + // 发送心跳包 + JSONObject object = new JSONObject(); + object.put("strength","31"); + object.put("name", properties.getName()); + object.put("version", properties.getVersion()); + object.put("startupTime", GlobalConfig.startupTime); + object.put("timestamp",timestamp); + Message message = Message.builder().clientId(GlobalConfig.CLIENT_ID).dataType(MsgEnum.心跳上报.getValue()).data(object.toString()).build(); + sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message); + } + AssertLog.info("心跳定时任务执行 - task #{} completed", count); + } + + /** + * 任务2:cpu信息采集任务 + */ + @Async("taskExecutor") + public void cpuTask() { + long timestamp = System.currentTimeMillis(); + timestamp = Math.round(timestamp / 1000.0); + int count = task2Counter.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信息采集定时任务执行 - task #{} completed", count); + } + + /** + * 任务3:磁盘信息采集任务 + */ + @Async("taskExecutor") + public void diskTask() { + long timestamp = System.currentTimeMillis(); + timestamp = Math.round(timestamp / 1000.0); + int count = task3Counter.incrementAndGet(); + AssertLog.info("磁盘信息采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count); + // 判定客户端与服务端是否连接 + if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) { + // 发送磁盘信息包 + List list = diskService.diskList(timestamp); + String 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("磁盘信息采集定时任务执行 - task #{} completed", count); + } + + /** + * 任务4:容器信息采集任务 + */ + @Async("taskExecutor") + public void dockerTask() { + long timestamp = System.currentTimeMillis(); + timestamp = Math.round(timestamp / 1000.0); + int count = task4Counter.incrementAndGet(); + AssertLog.info("容器信息采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count); + // 判定客户端与服务端是否连接 + if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) { + // 发送容器包 + List list = dockerService.dockerList(timestamp); + String 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("容器信息采集定时任务执行 - task #{} completed", count); + } + + /** + * 任务5:内存信息采集任务 + */ + @Async("taskExecutor") + public void memoryTask() { + long timestamp = System.currentTimeMillis(); + timestamp = Math.round(timestamp / 1000.0); + int count = task5Counter.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("内存信息采集定时任务执行 - task #{} completed", count); + } + + /** + * 任务6:网络信息采集任务 + */ + @Async("taskExecutor") + public void netTask() { + long timestamp = AgentUtil.roundMinutes(); + int count = task6Counter.incrementAndGet(); + AssertLog.info("网络信息采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count); + // 判定客户端与服务端是否连接 + if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) { + // 发送网卡信息包 + List list = netService.netList(timestamp); + String 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("网络信息采集定时任务执行 - task #{} completed", count); + } + + /** + * 任务7:挂载信息采集任务 + */ + @Async("taskExecutor") + public void pointTask() { + long timestamp = System.currentTimeMillis(); + timestamp = Math.round(timestamp / 1000.0); + int count = task7Counter.incrementAndGet(); + AssertLog.info("挂载信息采集定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count); + // 判定客户端与服务端是否连接 + if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) { + // 发送挂载点信息包 + List list = diskService.pointList(timestamp); + String 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("挂载点信息采集定时任务执行 - task #{} completed", count); + } + + /** + * 任务8:交换机信息采集任务 + */ + @Async("taskExecutor") + public void switchBoardTask(String type) { + long timestamp = System.currentTimeMillis(); + timestamp = Math.round(timestamp / 1000.0); + 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(); + String data = switchBoardService.getSwitchDataByType(type); + JSONObject 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("交换机信息采集定时任务执行 - task #{} completed", count); + } + + /** + * 任务9:系统机信息采集任务 + */ + @Async("taskExecutor") + public void systemTask(String type) { + long timestamp = System.currentTimeMillis(); + 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(); + String data = systemService.otherSystem(type); + JSONObject 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("系统信息采集定时任务执行 - 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..1b3935d --- /dev/null +++ b/src/main/java/com/tongran/agent/client/scheduler/service/DynamicTaskService.java @@ -0,0 +1,147 @@ +package com.tongran.agent.client.scheduler.service; + +import com.tongran.agent.client.core.config.GlobalConfig; +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); + } + + /** + * 创建带补偿的任务 + */ + private Runnable createCompensatedTask(String taskId, Runnable originalTask) { + return () -> { + long startTime = System.currentTimeMillis(); + lastExecutionTimes.put(taskId, startTime); + + try { + originalTask.run(); + } catch (Exception e) { + 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()); + } + }; + } + + /** + * 取消任务 + */ + 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..14b3d37 --- /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; +import java.util.List; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SpecificTimeRequest { + private String taskId; + private String taskName; + private List taskData; + + 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..8f01f41 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/scheduler/task/SpecificTimeTaskService.java @@ -0,0 +1,116 @@ +package com.tongran.agent.client.scheduler.task; + +import cn.hutool.core.collection.CollectionUtil; +import com.alibaba.fastjson2.JSONObject; +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.AgentUtil; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.time.LocalDateTime; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +@Service +public class SpecificTimeTaskService { + + protected final SessionManager sessionManager; + + @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()); + + List> list = new ArrayList<>(); + for (String command : request.getTaskData()) { + Map map = new HashMap<>(); + List cmd = Arrays.asList(command.split("\\s+")); + CompletableFuture future = + AsyncCommandExecutor.executeCommandAsync( + cmd, + 60, TimeUnit.SECONDS); + future.thenAccept(result -> { + if (result.isSuccess()) { + System.out.println("脚本执行成功"); + System.out.println("[成功resOut] " + result.getOutput()); + map.put("command",command); + map.put("resOut",result.getOutput()); + } else { + System.out.println("脚本执行失败"); + System.out.println("[失败resOut] " + result.getOutput()); + map.put("command",command); + map.put("resOut",result.getOutput()); + } + }).exceptionally(ex -> { + System.err.println("执行失败: " + ex.getMessage()); + map.put("command",command); + map.put("resOut","Policy execute filed"); + return null; + }); + list.add(map); + } + if(CollectionUtil.isNotEmpty(list)){ + JSONObject json = new JSONObject(); + json.put("resCode",1); + json.put("resMsg", ""); + json.put("result", AgentUtil.toJsonString(list)); + Message message = Message.builder().clientId(request.getClientId()).dataType(request.getDataType()).data(json.toString()).build(); + if (Objects.nonNull(sessionManager.getSessionById(request.getClientId()))) { + sessionManager.writeAndFlush(sessionManager.getSessionById(request.getClientId()).getChannel(), message); + } + } + // 调用其他服务等 + } 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..fb3cbe3 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/service/AgentService.java @@ -0,0 +1,11 @@ +package com.tongran.agent.client.service; + +public interface AgentService { + void systemCollectStart(String data); + + void systemCollectStop(); + + void switchCollectStart(String data); + + void switchCollectStop(); +} 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..5fc1209 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/service/SwitchBoardService.java @@ -0,0 +1,11 @@ +package com.tongran.agent.client.service; + +import com.tongran.agent.client.core.vo.SwitchBoardVO; + +import java.util.List; + +public interface SwitchBoardService { + List switchBoardList(long timestamp); + + String getSwitchDataByType(String type); +} 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..1aff3b4 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/service/impl/AgentServiceImpl.java @@ -0,0 +1,381 @@ +package com.tongran.agent.client.service.impl; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.TypeReference; +import com.tongran.agent.client.core.config.GlobalConfig; +import com.tongran.agent.client.core.eo.CollectEO; +import com.tongran.agent.client.scheduler.service.BusinessTasks; +import com.tongran.agent.client.scheduler.service.DynamicTaskService; +import com.tongran.agent.client.service.AgentService; +import com.tongran.agent.client.utils.AgentUtil; +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 java.util.List; + +@Service +public class AgentServiceImpl implements AgentService { + + private final DynamicTaskService dynamicTaskService; + + private final BusinessTasks businessTasks; + + // 在注入点使用@Lazy + @Autowired + public AgentServiceImpl(DynamicTaskService dynamicTaskService, + @Lazy BusinessTasks businessTasks) { + this.dynamicTaskService = dynamicTaskService; + this.businessTasks = businessTasks; + } + + @Override + public void systemCollectStart(String data) { + //更改全局变量 + JSONObject jsonObject = JSONObject.parseObject(data); + if(jsonObject.containsKey("collects")){ + String collects = jsonObject.getString("collects"); + List configList = JSON.parseObject(collects, new TypeReference>() {}); + for (CollectEO c : configList) { + String type = c.getType(); + boolean collect = c.isCollect(); + switch(type) { + case "cpuCollect": //cpu采集 + GlobalConfig.cpuCollect = collect; + GlobalConfig.cpuInterval = c.getInterval(); + if(collect){ + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(c.getType(), + businessTasks::cpuTask, milli, GlobalConfig.cpuInterval*1000L); + } + break; + case "vfsCollect": //挂载采集 + GlobalConfig.vfsCollect = collect; + GlobalConfig.vfsInterval = c.getInterval(); + if(collect){ + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(c.getType(), + businessTasks::pointTask, milli, GlobalConfig.vfsInterval*1000L); + } + break; + case "netCollect": //网络采集 + GlobalConfig.netCollect = collect; + GlobalConfig.netInterval = c.getInterval(); + if(collect){ + long milli = AgentUtil.millisecondsToNext5Minute(); + dynamicTaskService.scheduleTask(c.getType(), + businessTasks::netTask, milli, GlobalConfig.netInterval*1000L); + } + break; + case "diskCollect": //磁盘采集 + GlobalConfig.diskCollect = collect; + GlobalConfig.diskInterval = c.getInterval(); + if(collect){ + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(c.getType(), + businessTasks::diskTask, milli, GlobalConfig.diskInterval*1000L); + } + break; + case "dockerCollect": //docker采集 + GlobalConfig.dockerCollect = collect; + GlobalConfig.dockerInterval = c.getInterval(); + if(collect){ + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(c.getType(), + businessTasks::dockerTask, milli, GlobalConfig.dockerInterval*1000L); + } + break; + default: //系统其他采集 + if(StringUtils.equals(type, "systemSwapSizeFreeCollect")){ + GlobalConfig.systemSwapSizeFreeCollect = collect; + GlobalConfig.systemSwapSizeFreeInterval = c.getInterval(); + } + if(StringUtils.equals(type, "memoryUtilizationCollect")){ + GlobalConfig.memoryUtilizationCollect = collect; + GlobalConfig.memoryUtilizationInterval = c.getInterval(); + } + if(StringUtils.equals(type, "systemSwapSizePercentCollect")){ + GlobalConfig.systemSwapSizePercentCollect = collect; + GlobalConfig.systemSwapSizePercentInterval = c.getInterval(); + } + if(StringUtils.equals(type, "memorySizeAvailableCollect")){ + GlobalConfig.memorySizeAvailableCollect = collect; + GlobalConfig.memorySizeAvailableInterval = c.getInterval(); + } + if(StringUtils.equals(type, "memorySizePercentCollect")){ + GlobalConfig.memorySizePercentCollect = collect; + GlobalConfig.memorySizePercentInterval = c.getInterval(); + } + if(StringUtils.equals(type, "memorySizeTotalCollect")){ + GlobalConfig.memorySizeTotalCollect = collect; + GlobalConfig.memorySizeTotalInterval = c.getInterval(); + } + if(StringUtils.equals(type, "systemSwOsCollect")){ + GlobalConfig.systemSwOsCollect = collect; + GlobalConfig.systemSwOsInterval = c.getInterval(); + } + if(StringUtils.equals(type, "systemSwArchCollect")){ + GlobalConfig.systemSwArchCollect = collect; + GlobalConfig.systemSwArchInterval = c.getInterval(); + } + if(StringUtils.equals(type, "kernelMaxprocCollect")){ + GlobalConfig.kernelMaxprocCollect = collect; + GlobalConfig.kernelMaxprocInterval = c.getInterval(); + } + if(StringUtils.equals(type, "procNumRunCollect")){ + GlobalConfig.procNumRunCollect = collect; + GlobalConfig.procNumRunInterval = c.getInterval(); + } + if(StringUtils.equals(type, "systemUsersNumCollect")){ + GlobalConfig.systemUsersNumCollect = collect; + GlobalConfig.systemUsersNumInterval = c.getInterval(); + } + if(StringUtils.equals(type, "systemDiskSizeTotalCollect")){ + GlobalConfig.systemDiskSizeTotalCollect = collect; + GlobalConfig.systemDiskSizeTotalInterval = c.getInterval(); + } + if(StringUtils.equals(type, "systemBoottimeCollect")){ + GlobalConfig.systemBoottimeCollect = collect; + GlobalConfig.systemBoottimeInterval = c.getInterval(); + } + if(StringUtils.equals(type, "systemUnameCollect")){ + GlobalConfig.systemUnameCollect = collect; + GlobalConfig.systemUnameInterval = c.getInterval(); + } + if(StringUtils.equals(type, "systemLocaltimeCollect")){ + GlobalConfig.systemLocaltimeCollect = collect; + GlobalConfig.systemLocaltimeInterval = c.getInterval(); + } + if(StringUtils.equals(type, "systemUptimeCollect")){ + GlobalConfig.systemUptimeCollect = collect; + GlobalConfig.systemUptimeInterval = c.getInterval(); + } + if(StringUtils.equals(type, "procNumCollect")){ + GlobalConfig.procNumCollect = collect; + GlobalConfig.procNumInterval = c.getInterval(); + } + long milli = AgentUtil.getMillisToNextMinute() + 60000; + dynamicTaskService.scheduleTask(c.getType(), + () -> businessTasks.systemTask(c.getType()), milli, c.getInterval()*1000L); + break; + } + } + } + } + + @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 switchCollectStart(String data) { + //更改全局变量 + JSONObject jsonObject = JSONObject.parseObject(data); + if(jsonObject.containsKey("collects")){ + String collects = jsonObject.getString("collects"); + List configList = JSON.parseObject(collects, new TypeReference>() {}); + for (CollectEO c : configList) { + String type = c.getType(); + boolean collect = c.isCollect(); + switch(type) { + case "switchNetCollect": //交换机网络采集 + GlobalConfig.switchNetCollect = collect; + GlobalConfig.switchNetInterval = c.getInterval(); + break; + case "switchModuleCollect": //光模块采集 + GlobalConfig.switchModuleCollect = collect; + GlobalConfig.switchModuleInterval = c.getInterval(); + break; + case "switchMpuCollect": //MPU采集 + GlobalConfig.switchMpuCollect = collect; + GlobalConfig.switchMpuInterval = c.getInterval(); + break; + case "switchPwrCollect": //电源采集 + GlobalConfig.switchPwrCollect = collect; + GlobalConfig.switchPwrInterval = c.getInterval(); + break; + case "dockerCollect": //风扇采集 + GlobalConfig.switchFanCollect = collect; + GlobalConfig.switchFanInterval = c.getInterval(); + break; + default: //系统其他采集 + if(StringUtils.equals(type, "switchSysDescrCollect")){ + GlobalConfig.switchSysDescrCollect = collect; + GlobalConfig.switchSysDescrInterval = c.getInterval(); + } + if(StringUtils.equals(type, "switchSysObjectIDCollect")){ + GlobalConfig.switchSysObjectIDCollect = collect; + GlobalConfig.switchSysObjectIDInterval = c.getInterval(); + } + if(StringUtils.equals(type, "switchSysUpTimeCollect")){ + GlobalConfig.switchSysUpTimeCollect = collect; + GlobalConfig.switchSysUpTimeInterval = c.getInterval(); + } + if(StringUtils.equals(type, "switchSysContactCollect")){ + GlobalConfig.switchSysContactCollect = collect; + GlobalConfig.switchSysContactInterval = c.getInterval(); + } + if(StringUtils.equals(type, "switchSysNameCollect")){ + GlobalConfig.switchSysNameCollect = collect; + GlobalConfig.switchSysNameInterval = c.getInterval(); + } + if(StringUtils.equals(type, "switchSysLocationCollect")){ + GlobalConfig.switchSysLocationCollect = collect; + GlobalConfig.switchSysLocationInterval = c.getInterval(); + } + if(StringUtils.equals(type, "switchHwStackSystemMacCollect")){ + GlobalConfig.switchHwStackSystemMacCollect = collect; + GlobalConfig.switchHwStackSystemMacInterval = c.getInterval(); + } + if(StringUtils.equals(type, "switchEntIndexCollect")){ + GlobalConfig.switchEntIndexCollect = collect; + GlobalConfig.switchEntIndexInterval = c.getInterval(); + } + if(StringUtils.equals(type, "switchEntPhysicalNameCollect")){ + GlobalConfig.switchEntPhysicalNameCollect = collect; + GlobalConfig.switchEntPhysicalNameInterval = c.getInterval(); + } + if(StringUtils.equals(type, "switchEntPhysicalSoftwareRevCollect")){ + GlobalConfig.switchEntPhysicalSoftwareRevCollect = collect; + GlobalConfig.switchEntPhysicalSoftwareRevInterval = c.getInterval(); + } + if(StringUtils.equals(type, "switchHwEntityCpuUsageCollect")){ + GlobalConfig.switchHwEntityCpuUsageCollect = collect; + GlobalConfig.switchHwEntityCpuUsageInterval = c.getInterval(); + } + if(StringUtils.equals(type, "switchHwEntityMemUsageCollect")){ + GlobalConfig.switchHwEntityMemUsageCollect = collect; + GlobalConfig.switchHwEntityMemUsageInterval = c.getInterval(); + } + if(StringUtils.equals(type, "switchHwAveragePowerCollect")){ + GlobalConfig.switchHwAveragePowerCollect = collect; + GlobalConfig.switchHwAveragePowerInterval = c.getInterval(); + } + if(StringUtils.equals(type, "switchHwCurrentPowerCollect")){ + GlobalConfig.switchHwCurrentPowerCollect = collect; + GlobalConfig.switchHwCurrentPowerInterval = c.getInterval(); + } + break; + } + if(collect){ + long milli = AgentUtil.millisecondsToNext5Minute(); + dynamicTaskService.scheduleTask(c.getType(), + () -> businessTasks.switchBoardTask(c.getType()), milli, GlobalConfig.switchNetInterval*1000L); + } + } + } + } + + @Override + public void switchCollectStop() { + String[] arr = {"switchNetCollect","switchModuleCollect","switchMpuCollect","switchPwrCollect","switchFanCollect","switchSysDescrCollect" + ,"switchSysObjectIDCollect","switchSysUpTimeCollect","switchSysContactCollect","switchSysNameCollect","switchSysLocationCollect" + ,"switchHwStackSystemMacCollect","switchEntIndexCollect","switchEntPhysicalNameCollect","switchEntPhysicalSoftwareRevCollect" + ,"switchHwEntityCpuUsageCollect","switchHwEntityMemUsageCollect","switchHwAveragePowerCollect","switchHwCurrentPowerCollect"}; + for (String taskId : arr) { + dynamicTaskService.cancelTask(taskId); + } + GlobalConfig.switchNetCollect = false; //交换机网络采集 + GlobalConfig.switchNetInterval = 300; //交换机网络采集间隔 + GlobalConfig.switchModuleCollect = false; //光模块采集 + GlobalConfig.switchModuleInterval = 300; //光模块采集间隔 + GlobalConfig.switchMpuCollect = false; //MPU采集 + GlobalConfig.switchMpuInterval = 300; //MPU采集间隔 + GlobalConfig.switchPwrCollect = false; //电源采集 + GlobalConfig.switchPwrInterval = 300; //电源采集间隔 + GlobalConfig.switchFanCollect = false; //风扇采集 + GlobalConfig.switchFanInterval = 300; //风扇采集间隔 + /** + * 交换机其他监控 + */ + GlobalConfig.switchSysDescrCollect = false; //系统描述采集 + GlobalConfig.switchSysDescrInterval = 300; //系统描述采集间隔 + GlobalConfig.switchSysObjectIDCollect = false; //系统Object ID采集 + GlobalConfig.switchSysObjectIDInterval = 300; //系统Object ID采集间隔 + GlobalConfig.switchSysUpTimeCollect = false; //系统运行时间采集 + GlobalConfig.switchSysUpTimeInterval = 300; //系统运行时间采集间隔 + GlobalConfig.switchSysContactCollect = false; //系统联系信息采集 + GlobalConfig.switchSysContactInterval = 300; //系统联系信息采集间隔 + GlobalConfig.switchSysNameCollect = false; //系统名称采集 + GlobalConfig.switchSysNameInterval = 300; //系统名称采集间隔 + GlobalConfig.switchSysLocationCollect = false; //系统位置采集 + GlobalConfig.switchSysLocationInterval = 300; //系统位置采集间隔 + GlobalConfig.switchHwStackSystemMacCollect = false; //系统MAC地址采集 + GlobalConfig.switchHwStackSystemMacInterval = 300; //系统MAC地址采集间隔 + GlobalConfig.switchEntIndexCollect = false; //设备索引采集 + GlobalConfig.switchEntIndexInterval = 300; //设备索引采集间隔 + GlobalConfig.switchEntPhysicalNameCollect = false; //设备名称采集 + GlobalConfig.switchEntPhysicalNameInterval = 300; //设备名称采集间隔 + GlobalConfig.switchEntPhysicalSoftwareRevCollect = false; //设备软件版本采集 + GlobalConfig.switchEntPhysicalSoftwareRevInterval = 300; //设备软件版本采集间隔 + GlobalConfig.switchHwEntityCpuUsageCollect = false; //设备CPU使用率(%)采集 + GlobalConfig.switchHwEntityCpuUsageInterval = 300; //设备CPU使用率(%)采集间隔 + GlobalConfig.switchHwEntityMemUsageCollect = false; //设备内存使用率(%)采集 + GlobalConfig.switchHwEntityMemUsageInterval = 300; //设备内存使用率(%)采集间隔 + GlobalConfig.switchHwAveragePowerCollect = false; //系统平均功率(mW)采集 + GlobalConfig.switchHwAveragePowerInterval = 300; //系统平均功率(mW)采集间隔 + GlobalConfig.switchHwCurrentPowerCollect = false; //系统实时功率(mW)采集 + GlobalConfig.switchHwCurrentPowerInterval = 300; //系统实时功率(mW)采集间隔 + } + + +} 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..af9b602 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/service/impl/CPUServiceImpl.java @@ -0,0 +1,98 @@ +package com.tongran.agent.client.service.impl; + +import com.tongran.agent.client.core.vo.CpuVO; +import com.tongran.agent.client.service.CPUService; +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使用率(需要两次采样) + System.out.println("\n=== CPU使用率 ==="); + long[] prevTicks = processor.getSystemCpuLoadTicks(); + TimeUnit.SECONDS.sleep(1); // 等待1秒 + // 计算使用率 + double cpuUsage = processor.getSystemCpuLoadBetweenTicks(prevTicks); + cpuVO.setUti(cpuUsage * 100); // CPU使用率 + 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("中断累计时间: " + + (allTicks[CentralProcessor.TickType.IRQ.getIndex()] + + allTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()])); + System.out.println("空闲累计时间: " + allTicks[CentralProcessor.TickType.IDLE.getIndex()]); + System.out.printf("I/O等待时间(CPU等待响应时间): %.2f%%\n", 100d * iowait / total); + System.out.println("系统累计时间: " + allTicks[CentralProcessor.TickType.SYSTEM.getIndex()]); + long softwareTime = sys - irq - softirq; + System.out.printf("软件相关时间(近似无响应时间): %.2f%%\n", 100d * softwareTime / total); + System.out.println("用户进程累计时间: " + allTicks[CentralProcessor.TickType.USER.getIndex()]); + + cpuVO.setInterrupt((allTicks[CentralProcessor.TickType.IRQ.getIndex()] + + allTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()])); // CPU硬件中断提供服务时间 + cpuVO.setIdle(allTicks[CentralProcessor.TickType.IDLE.getIndex()]);// CPU空闲时间 + cpuVO.setIowait(100d * iowait / total);// CPU等待响应时间 + cpuVO.setSystem(allTicks[CentralProcessor.TickType.SYSTEM.getIndex()]);// CPU系统时间 + cpuVO.setNoresp(100d * softwareTime / total);// CPU软件无响应时间 + cpuVO.setUser(allTicks[CentralProcessor.TickType.USER.getIndex()]);// CPU用户进程所花费的时间 + + // 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..52bb68f --- /dev/null +++ b/src/main/java/com/tongran/agent/client/service/impl/MemoryServiceImpl.java @@ -0,0 +1,74 @@ +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 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; + System.out.printf("总内存使用率: %.2f%%\n", totalUsage); + // 实际内存使用率 + long cached = memInfo.getOrDefault("Cached", 0L) + + memInfo.getOrDefault("SReclaimable", 0L); + long buffers = memInfo.getOrDefault("Buffers", 0L); + double actualUsage = (double)(total - available - cached - buffers) / total * 100; + System.out.printf("实际内存使用率: %.2f%%\n", actualUsage); + memoryVO.setUntilzation(actualUsage); //内存利用率 + } 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..2066874 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/service/impl/NetServiceImpl.java @@ -0,0 +1,123 @@ +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() - 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..f9d04df --- /dev/null +++ b/src/main/java/com/tongran/agent/client/service/impl/SwitchBoardServiceImpl.java @@ -0,0 +1,468 @@ +package com.tongran.agent.client.service.impl; + +import com.alibaba.fastjson2.JSONObject; +import com.tongran.agent.client.core.config.GlobalConfig; +import com.tongran.agent.client.core.vo.SwitchBoardVO; +import com.tongran.agent.client.service.SwitchBoardService; +import org.apache.commons.lang3.StringUtils; +import org.snmp4j.*; +import org.snmp4j.event.ResponseEvent; +import org.snmp4j.mp.SnmpConstants; +import org.snmp4j.smi.GenericAddress; +import org.snmp4j.smi.OID; +import org.snmp4j.smi.OctetString; +import org.snmp4j.smi.VariableBinding; +import org.snmp4j.transport.DefaultUdpTransportMapping; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; + +@Service +public class SwitchBoardServiceImpl implements SwitchBoardService { + @Override + public List switchBoardList(long timestamp) { + List list = new ArrayList<>(); + System.out.println("==================== 交换机流量信息 ===================="); + try { + // 1. 创建传输映射 + TransportMapping transport = new DefaultUdpTransportMapping(); + Snmp snmp = new Snmp(transport); + transport.listen(); + + // 2. 创建目标对象 + CommunityTarget target = new CommunityTarget(); + target.setCommunity(new OctetString(GlobalConfig.SWITCH_COMMUNITY)); + target.setAddress(GenericAddress.parse("udp:" + GlobalConfig.SWITCH_IP + "/" + GlobalConfig.SWITCH_PORT)); + target.setRetries(2); + target.setTimeout(5000); + target.setVersion(SnmpConstants.version2c); + + // 3. 获取交换机基本信息 + getSystemInfo(snmp, target); + + // 4. 获取接口信息 + list = getInterfaceInfo(snmp, target, timestamp); + + snmp.close(); + } catch (Exception e) { + e.printStackTrace(); + } + return list; + } + + @Override + public String getSwitchDataByType(String type) { + JSONObject json = new JSONObject(); + json.put("type",type); + switch(type){ + case "switchNetCollect": + json.put("value",handleSwitchNet()); + break; + case "switchModuleCollect": + json.put("value",handleSwitchModule()); + break; + case "switchMpuCollect": + json.put("value",handleSwitchMpu()); + break; + case "switchPwrCollect": + json.put("value",handleSwitchPwr()); + break; + case "switchFanCollect": + json.put("value",handleSwitchFan()); + break; + default: + json.put("value",handleSwitchOther(type)); + break; + } + return json.toString(); + } + + private String handleSwitchNet(){ + System.out.println("==================== 交换机网络信息 ===================="); + String result = ""; + try { + if(GlobalConfig.SWITCH_NET_OID.isEmpty()){ + return result; + } + // 1. 创建传输映射 + TransportMapping transport = new DefaultUdpTransportMapping(); + Snmp snmp = new Snmp(transport); + transport.listen(); + // 2. 创建目标对象 + CommunityTarget target = new CommunityTarget(); + target.setCommunity(new OctetString(GlobalConfig.SWITCH_COMMUNITY)); + target.setAddress(GenericAddress.parse("udp:" + GlobalConfig.SWITCH_IP + "/" + GlobalConfig.SWITCH_PORT)); + target.setRetries(2); + target.setTimeout(5000); + target.setVersion(SnmpConstants.version2c); + result = getInterfaceInfoByType(snmp, target, "switchNet", GlobalConfig.SWITCH_NET_OID); + snmp.close(); + } catch (Exception e) { + e.printStackTrace(); + } + return result; + } + + private String handleSwitchModule(){ + System.out.println("==================== 交换机光模块信息 ===================="); + String result = ""; + try { + if(GlobalConfig.SWITCH_MODULE_OID.isEmpty()){ + return result; + } + // 1. 创建传输映射 + TransportMapping transport = new DefaultUdpTransportMapping(); + Snmp snmp = new Snmp(transport); + transport.listen(); + // 2. 创建目标对象 + CommunityTarget target = new CommunityTarget(); + target.setCommunity(new OctetString(GlobalConfig.SWITCH_COMMUNITY)); + target.setAddress(GenericAddress.parse("udp:" + GlobalConfig.SWITCH_IP + "/" + GlobalConfig.SWITCH_PORT)); + target.setRetries(2); + target.setTimeout(5000); + target.setVersion(SnmpConstants.version2c); + result = getInterfaceInfoByType(snmp, target, "switchModule", GlobalConfig.SWITCH_MODULE_OID); + snmp.close(); + } catch (Exception e) { + e.printStackTrace(); + } + return result; + } + + private String handleSwitchMpu(){ + System.out.println("==================== 交换机MPU信息 ===================="); + String result = ""; + try { + if(GlobalConfig.SWITCH_MPU_OID.isEmpty()){ + return result; + } + // 1. 创建传输映射 + TransportMapping transport = new DefaultUdpTransportMapping(); + Snmp snmp = new Snmp(transport); + transport.listen(); + // 2. 创建目标对象 + CommunityTarget target = new CommunityTarget(); + target.setCommunity(new OctetString(GlobalConfig.SWITCH_COMMUNITY)); + target.setAddress(GenericAddress.parse("udp:" + GlobalConfig.SWITCH_IP + "/" + GlobalConfig.SWITCH_PORT)); + target.setRetries(2); + target.setTimeout(5000); + target.setVersion(SnmpConstants.version2c); + result = getInterfaceInfoByType(snmp, target, "switchMpu", GlobalConfig.SWITCH_MPU_OID); + snmp.close(); + } catch (Exception e) { + e.printStackTrace(); + } + return result; + } + + private String handleSwitchPwr(){ + System.out.println("==================== 交换机电源信息 ===================="); + String result = ""; + try { + if(GlobalConfig.SWITCH_PWR_OID.isEmpty()){ + return result; + } + // 1. 创建传输映射 + TransportMapping transport = new DefaultUdpTransportMapping(); + Snmp snmp = new Snmp(transport); + transport.listen(); + // 2. 创建目标对象 + CommunityTarget target = new CommunityTarget(); + target.setCommunity(new OctetString(GlobalConfig.SWITCH_COMMUNITY)); + target.setAddress(GenericAddress.parse("udp:" + GlobalConfig.SWITCH_IP + "/" + GlobalConfig.SWITCH_PORT)); + target.setRetries(2); + target.setTimeout(5000); + target.setVersion(SnmpConstants.version2c); + result = getInterfaceInfoByType(snmp, target, "switchPwr", GlobalConfig.SWITCH_PWR_OID); + snmp.close(); + } catch (Exception e) { + e.printStackTrace(); + } + return result; + } + + private String handleSwitchFan(){ + System.out.println("==================== 交换机风扇信息 ===================="); + String result = ""; + try { + if(GlobalConfig.SWITCH_FAN_OID.isEmpty()){ + return result; + } + // 1. 创建传输映射 + TransportMapping transport = new DefaultUdpTransportMapping(); + Snmp snmp = new Snmp(transport); + transport.listen(); + // 2. 创建目标对象 + CommunityTarget target = new CommunityTarget(); + target.setCommunity(new OctetString(GlobalConfig.SWITCH_COMMUNITY)); + target.setAddress(GenericAddress.parse("udp:" + GlobalConfig.SWITCH_IP + "/" + GlobalConfig.SWITCH_PORT)); + target.setRetries(2); + target.setTimeout(5000); + target.setVersion(SnmpConstants.version2c); + result = getInterfaceInfoByType(snmp, target, "switchFan", GlobalConfig.SWITCH_FAN_OID); + snmp.close(); + } catch (Exception e) { + e.printStackTrace(); + } + return result; + } + + private String handleSwitchOther(String type){ + System.out.println("==================== 交换机系统其他信息 ===================="); + String result = ""; + try { + if(GlobalConfig.SWITCH_OTHER_OID.isEmpty()){ + return result; + } + // 1. 创建传输映射 + TransportMapping transport = new DefaultUdpTransportMapping(); + Snmp snmp = new Snmp(transport); + transport.listen(); + // 2. 创建目标对象 + CommunityTarget target = new CommunityTarget(); + target.setCommunity(new OctetString(GlobalConfig.SWITCH_COMMUNITY)); + target.setAddress(GenericAddress.parse("udp:" + GlobalConfig.SWITCH_IP + "/" + GlobalConfig.SWITCH_PORT)); + target.setRetries(2); + target.setTimeout(5000); + target.setVersion(SnmpConstants.version2c); + result = getInterfaceInfoByType(snmp, target, type, GlobalConfig.SWITCH_OTHER_OID); + snmp.close(); + } catch (Exception e) { + e.printStackTrace(); + } + return result; + } + + private static void getSystemInfo(Snmp snmp, Target target) throws IOException { + // 系统描述 OID + String[] systemOIDs = { + "1.3.6.1.2.1.1.1.0", // sysDescr + "1.3.6.1.2.1.1.5.0", // sysName + "1.3.6.1.2.1.1.6.0", // sysLocation + "1.3.6.1.2.1.1.4.0", // sysContact + "1.3.6.1.2.1.1.3.0" // sysUpTime + }; + + PDU pdu = new PDU(); + for (String oid : systemOIDs) { + pdu.add(new VariableBinding(new OID(oid))); + } + pdu.setType(PDU.GET); + + ResponseEvent event = snmp.send(pdu, target); + if (event != null && event.getResponse() != null) { + System.out.println("\n=== 交换机基本信息 ==="); + for (VariableBinding vb : event.getResponse().getVariableBindings()) { + System.out.printf("%-30s: %s%n", + getOIDDescription(vb.getOid().toString()), + vb.getVariable()); + } + } + } + + private static List getInterfaceInfo(Snmp snmp, Target target, long timestamp) throws IOException { + // 获取接口数量 + String ifNumberOID = "1.3.6.1.2.1.2.1.0"; + PDU pdu = new PDU(); + pdu.add(new VariableBinding(new OID(ifNumberOID))); + pdu.setType(PDU.GET); + + ResponseEvent event = snmp.send(pdu, target); + if (event == null || event.getResponse() == null) { + System.err.println("无法获取接口数量"); + return null; + } + + int ifNumber = event.getResponse().get(0).getVariable().toInt(); + System.out.println("\n=== 接口数量: " + ifNumber + " ==="); + + // 获取每个接口的信息 + String[] ifOIDs = { + "1.3.6.1.2.1.2.2.1.2", // ifDescr + "1.3.6.1.2.1.2.2.1.3", // ifType + "1.3.6.1.2.1.2.2.1.5", // ifSpeed + "1.3.6.1.2.1.2.2.1.8", // ifOperStatus + "1.3.6.1.2.1.31.1.1.1.6", // ifInOctets + "1.3.6.1.2.1.31.1.1.1.10" // ifOutOctets +// "1.3.6.1.2.1.2.2.1.10", // ifInOctets +// "1.3.6.1.2.1.2.2.1.16" // ifOutOctets + }; + System.out.printf("%-5s %-15s %-10s %-15s %-8s %-12s %-12s%n", + "Index", "Name", "Type", "Speed", "Status", "InBytes", "OutBytes"); + List list = new ArrayList<>(); + for (int i = 1; i <= ifNumber; i++) { + pdu = new PDU(); + for (String baseOID : ifOIDs) { + pdu.add(new VariableBinding(new OID(baseOID + "." + i))); + } + pdu.setType(PDU.GET); + + event = snmp.send(pdu, target); + if (event != null && event.getResponse() != null) { + VariableBinding[] vbs = event.getResponse().getVariableBindings().toArray(new VariableBinding[0]); + String ifName = vbs[0].getVariable().toString(); + String ifType = getInterfaceType(vbs[1].getVariable().toInt()); + String speed = formatSpeed(vbs[2].getVariable().toInt()); + String status = getOperStatus(vbs[3].getVariable().toInt()); + long inBytes = vbs[4].getVariable().toLong(); + long outBytes = vbs[5].getVariable().toLong(); + SwitchBoardVO boardVO = SwitchBoardVO.builder() + .name(ifName) + .switchIp(GlobalConfig.SWITCH_IP) + .type(ifType) + .status(status) + .inBytes(inBytes) + .outBytes(outBytes) + .timestamp(timestamp) + .build(); + list.add(boardVO); + System.out.printf("%-5d %-15s %-10s %-15s %-8s %-12d %-12d%n", + i, ifName, ifType, speed, status, inBytes, outBytes); + } + } + return list; + } + + // 辅助方法 + private static String getOIDDescription(String oid) { + switch(oid) { + case "1.3.6.1.2.1.1.1.0": return "系统描述"; + case "1.3.6.1.2.1.1.5.0": return "系统名称"; + case "1.3.6.1.2.1.1.6.0": return "物理位置"; + case "1.3.6.1.2.1.1.4.0": return "联系人"; + case "1.3.6.1.2.1.1.3.0": return "运行时间"; + default: return oid; + } + } + + private static String getInterfaceType(int type) { + switch(type) { + case 6: return "Ethernet"; + case 23: return "PPP"; + case 24: return "Loopback"; + case 53: return "VLAN"; + case 131: return "Tunnel"; + default: return "其他("+type+")"; + } + } + + private static String formatSpeed(int speed) { + if (speed >= 1000000000) { + return (speed / 1000000000) + " Gbps"; + } else if (speed >= 1000000) { + return (speed / 1000000) + " Mbps"; + } else if (speed >= 1000) { + return (speed / 1000) + " Kbps"; + } + return speed + " bps"; + } + + private static String getOperStatus(int status) { + switch(status) { + case 1: return "Up"; + case 2: return "Down"; + case 3: return "Testing"; + case 4: return "Unknown"; + case 5: return "Dormant"; + case 6: return "NotPresent"; + case 7: return "LowerLayerDown"; + default: return "未知"; + } + } + + private static String getInterfaceInfoByType(Snmp snmp, Target target, String type, LinkedHashMap oidParams) throws IOException { + JSONObject json = new JSONObject(); + // 获取接口数量 + String ifNumberOID = "1.3.6.1.2.1.2.1.0"; + PDU pdu = new PDU(); + pdu.add(new VariableBinding(new OID(ifNumberOID))); + pdu.setType(PDU.GET); + + ResponseEvent event = snmp.send(pdu, target); + if (event == null || event.getResponse() == null) { + System.err.println("无法获取接口数量"); + return ""; + } + int ifNumber = event.getResponse().get(0).getVariable().toInt(); + System.out.println("\n=== 接口数量: " + ifNumber + " ==="); + if(oidParams.isEmpty()){ + return ""; + } + String[] ifOIDs = oidParams.keySet().toArray(new String[0]); + String[] params = oidParams.values().toArray(new String[0]); + // 获取每个接口的信息 +// String[] ifOIDs = { +// "1.3.6.1.2.1.2.2.1.2", // ifDescr +// "1.3.6.1.2.1.2.2.1.3", // ifType +// "1.3.6.1.2.1.2.2.1.5", // ifSpeed +// "1.3.6.1.2.1.2.2.1.8", // ifOperStatus +// "1.3.6.1.2.1.31.1.1.1.6", // ifInOctets +// "1.3.6.1.2.1.31.1.1.1.10" // ifOutOctets +//// "1.3.6.1.2.1.2.2.1.10", // ifInOctets +//// "1.3.6.1.2.1.2.2.1.16" // ifOutOctets +// }; + for (int i = 1; i <= ifNumber; i++) { + pdu = new PDU(); + for (String baseOID : ifOIDs) { + pdu.add(new VariableBinding(new OID(baseOID + "." + i))); + } + pdu.setType(PDU.GET); + + event = snmp.send(pdu, target); + if (event != null && event.getResponse() != null) { + VariableBinding[] vbs = event.getResponse().getVariableBindings().toArray(new VariableBinding[0]); + for (int m = 0; m < params.length; m++) { + if(StringUtils.isNotBlank(type)){ + if(StringUtils.equals(params[m], type)){ + json.put(params[m],vbs[m].getVariable().toString()); + System.out.println(params[m]+":"+vbs[m].getVariable().toString()); + } + }else{ + json.put(params[m],vbs[m].getVariable().toString()); + System.out.println(params[m]+":"+vbs[m].getVariable().toString()); + } + } + } + } + return json.toString(); + } + + /** + * 结果封装类 + */ + public static class MapArrays { + private final String[] keys; + private final String[] values; + + public MapArrays(String[] keys, String[] values) { + this.keys = keys != null ? keys : new String[0]; + this.values = values != null ? values : new String[0]; + } + + public String[] getKeys() { + return keys; + } + + public String[] getValues() { + return values; + } + + public int size() { + return keys.length; + } + + public boolean isEmpty() { + return keys.length == 0; + } + + @Override + public String toString() { + return "Keys: " + Arrays.toString(keys) + + "\nValues: " + Arrays.toString(values); + } + } +} 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..eeed138 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/service/impl/SystemServiceImpl.java @@ -0,0 +1,401 @@ +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 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)); + // 实际内存使用率 + 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); + 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()); + return os.getFamily(); + } + + private String handleSystemSwArch(){ + SystemInfo si = new SystemInfo(); + OperatingSystem os = si.getOperatingSystem(); + System.out.println("========================================================="); + String arch = os.getVersionInfo().getVersion() + " " + 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..e820eb9 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/utils/AgentDataUtil.java @@ -0,0 +1,26 @@ +package com.tongran.agent.client.utils; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.HashMap; +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; + } +} 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..a80283f --- /dev/null +++ b/src/main/java/com/tongran/agent/client/utils/AgentUtil.java @@ -0,0 +1,220 @@ +package com.tongran.agent.client.utils; + +import com.fasterxml.jackson.databind.ObjectMapper; +import oshi.hardware.NetworkIF; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.temporal.ChronoUnit; +import java.util.Base64; +import java.util.Enumeration; +import java.util.List; +import java.util.Map; + +public class AgentUtil { + + 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(); + // 四舍五入到最近的5分钟 + LocalDateTime roundedTime = now + .truncatedTo(ChronoUnit.MINUTES) // 先去掉秒和纳秒 + .withMinute((int) (Math.round(now.getMinute() / 5.0) * 5)) + .withSecond(0) + .withNano(0); + // 处理分钟进位的情况 + if (roundedTime.getMinute() == 60) { + roundedTime = roundedTime.plusHours(1).withMinute(0); + } + // 转换为10位时间戳 + long timestamp = roundedTime.atZone(ZoneId.systemDefault()).toEpochSecond(); + return timestamp; + } + +} 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/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..6ddc6fe --- /dev/null +++ b/src/main/resources/application-dev.yml @@ -0,0 +1,22 @@ +server: + port: 7010 + servlet: + context-path: /agent-client + +# 接口文档配置 +knife4j: + enable: true + production: false # 开启屏蔽文档资源 + +# 日志配置 +logging: + file: + path: /data/agent-client/logs + +tcp: + netty: + charge: + enable: true + name: AGENT-TCP-服务 + port: 6610 + readerIdleTime: 300 \ 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..28291d8 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,31 @@ +spring: + profiles: + active: dev + mvc: + pathmatch: + matching-strategy: ant_path_matcher + application: + name: agent-client + version: 1.0 + 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: AGENTTCP服务 + description: AGENTTCP服务接口文档 + contact: AGENTTCP + email: + version: ${spring.application.version} + group-configs: + - group: 'AGENTTCP服务' + 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..cf1e7db --- /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}] [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() { + } + +}