优化SSH线程池

This commit is contained in:
dengqichen 2025-12-09 10:00:36 +08:00
parent 81f1c6407b
commit 1a135d26cf
12 changed files with 886 additions and 173 deletions

View File

@ -62,6 +62,28 @@ public class ThreadPoolConfig {
return executor;
}
/**
* 服务器监控线程池 - 使用虚拟线程Java 21+
*
* 为什么使用虚拟线程
* 1. SSH连接 + 远程命令执行是**网络I/O密集型**任务
* 2. 等待SSH响应时线程会长时间阻塞
* 3. 虚拟线程在阻塞时不占用OS线程资源消耗极低
* 4. 支持数百台服务器并发监控无需担心线程池耗尽
*
* 💡 场景
* - 定时采集服务器CPU内存磁盘网络指标
* - 并发检测服务器在线状态
* - SSH命令执行topfreedf等
*/
@Bean("serverMonitorExecutor")
public SimpleAsyncTaskExecutor serverMonitorExecutor() {
SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor("server-monitor-virtual-");
executor.setVirtualThreads(true);
executor.setConcurrencyLimit(-1); // 无限制支持大量并发
return executor;
}
/**
* 通用应用任务线程池 - 保留平台线程不使用虚拟线程
*

View File

@ -36,8 +36,11 @@ public class ServerMonitorDataDTO {
@Schema(description = "各分区磁盘使用率")
private List<DiskUsageInfo> diskUsage;
@Schema(description = "网络使用率(MB/s)")
private BigDecimal networkUsage;
@Schema(description = "网络接收流量(KB/s)")
private Long networkRx;
@Schema(description = "网络发送流量(KB/s)")
private Long networkTx;
@Schema(description = "采集时间")
private LocalDateTime collectTime;

View File

@ -11,7 +11,7 @@ import com.qqchen.deploy.backend.deploy.repository.IServerRepository;
import com.qqchen.deploy.backend.deploy.service.IServerAlertService;
import com.qqchen.deploy.backend.deploy.service.IServerMonitorService;
import com.qqchen.deploy.backend.deploy.service.IServerService;
import com.qqchen.deploy.backend.framework.dto.DiskUsageInfo;
import com.qqchen.deploy.backend.framework.dto.MonitorData;
import com.qqchen.deploy.backend.framework.ssh.ISSHCommandService;
import com.qqchen.deploy.backend.framework.ssh.SSHCommandServiceFactory;
import com.qqchen.deploy.backend.notification.dto.SendNotificationRequest;
@ -19,14 +19,15 @@ import com.qqchen.deploy.backend.notification.service.INotificationService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import net.schmizz.sshj.SSHClient;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;
/**
@ -58,6 +59,10 @@ public class ServerMonitorScheduler {
@Resource
private IServerService serverService;
@Resource
@Qualifier("serverMonitorExecutor")
private Executor serverMonitorExecutor;
/**
* 采集所有在线服务器的监控数据
* 此方法由定时任务管理系统调用
@ -101,7 +106,8 @@ public class ServerMonitorScheduler {
final ServerMonitorNotificationConfig finalConfig = config;
List<CompletableFuture<ServerMonitorDataDTO>> futures = allServers.stream()
.map(server -> CompletableFuture.supplyAsync(() ->
collectSingleServerWithStatusCheck(server, finalConfig)))
collectSingleServerWithStatusCheck(server, finalConfig),
serverMonitorExecutor)) // 使用专用虚拟线程池
.collect(Collectors.toList());
// 3. 等待所有任务完成
@ -155,39 +161,65 @@ public class ServerMonitorScheduler {
}
/**
* 检测服务器连接状态并采集监控数据
* 统一使用 ServerService.testConnection() 方法进行连接测试和状态更新
* 检测服务器连接状态并采集监控数据一次SSH连接完成所有任务
*
* 优化点
* 1. 只创建一次SSH连接避免之前的双重连接问题
* 2. 不采集硬件信息hostnameCPU核心数等减少不必要的开销
* 3. 只更新状态和最后连接时间
*/
private ServerMonitorDataDTO collectSingleServerWithStatusCheck(Server server, ServerMonitorNotificationConfig config) {
SSHClient sshClient = null;
ISSHCommandService sshService = null;
try {
// 1. 调用统一的连接测试方法会自动更新服务器状态硬件信息等
ServerInfoDTO info = serverService.testConnection(server.getId());
// 1. 获取对应OS的SSH服务
sshService = sshCommandServiceFactory.getService(server.getOsType());
// 2. 检查连接状态
if (!info.getConnected()) {
// 连接失败离线发送离线通知
log.error("服务器连接失败(离线): serverId={}, name={}, ip={}, error={}",
server.getId(), server.getServerName(), server.getHostIp(), info.getErrorMessage());
// 2. 创建SSH连接
String password = null;
String privateKey = null;
String passphrase = null;
if (config != null && config.getNotificationChannelId() != null && config.getServerOfflineTemplateId() != null) {
try {
sendServerOfflineNotification(server, config);
} catch (Exception notifyError) {
log.error("发送服务器离线通知失败: serverId={}", server.getId(), notifyError);
}
}
return null;
switch (server.getAuthType()) {
case PASSWORD:
password = server.getSshPassword();
break;
case KEY:
privateKey = server.getSshPrivateKey();
passphrase = server.getSshPassphrase();
break;
}
// 3. 连接成功采集监控数据
return collectServerMonitorData(server);
sshClient = sshService.createConnection(
server.getHostIp(),
server.getSshPort(),
server.getSshUser(),
password,
privateKey,
passphrase
);
// 3. 连接成功更新服务器状态
server.setStatus(ServerStatusEnum.ONLINE);
server.setLastConnectTime(LocalDateTime.now());
serverRepository.save(server);
log.debug("服务器连接成功: serverId={}, name={}, ip={}",
server.getId(), server.getServerName(), server.getHostIp());
// 4. 采集监控数据复用同一个SSH连接
return collectServerMonitorData(server, sshClient, sshService);
} catch (Exception e) {
// 异常情况发送离线通知
log.error("服务器连接测试异常: serverId={}, name={}, ip={}, error={}",
// 连接失败更新服务器状态为离线
log.error("服务器连接失败: serverId={}, name={}, ip={}, error={}",
server.getId(), server.getServerName(), server.getHostIp(), e.getMessage());
server.setStatus(ServerStatusEnum.OFFLINE);
serverRepository.save(server);
// 发送离线通知
if (config != null && config.getNotificationChannelId() != null && config.getServerOfflineTemplateId() != null) {
try {
sendServerOfflineNotification(server, config);
@ -197,6 +229,12 @@ public class ServerMonitorScheduler {
}
return null;
} finally {
// 5. 关闭SSH连接
if (sshService != null && sshClient != null) {
sshService.closeConnection(sshClient);
}
}
}
@ -230,79 +268,61 @@ public class ServerMonitorScheduler {
}
/**
* 采集服务器监控数据CPU内存磁盘使用率
* 注意此方法仅负责采集监控数据不负责连接测试和状态更新
* 采集服务器监控数据CPU内存磁盘网络使用率
*
* 性能优化
* 1. 每个OS实现类内部使用 executeCommands 批量执行命令1次网络往返代替5次
* 2. 自动适配不同操作系统Linux用bashWindows用PowerShell
*
* @param server 服务器实体
* @param sshClient 已建立的SSH连接由调用方管理生命周期
* @param sshService SSH命令服务
* @return 监控数据
*/
private ServerMonitorDataDTO collectServerMonitorData(Server server) throws Exception {
SSHClient sshClient = null;
ISSHCommandService sshService = null;
private ServerMonitorDataDTO collectServerMonitorData(Server server, SSHClient sshClient, ISSHCommandService sshService) {
try {
// 1. 获取对应OS的SSH服务
sshService = sshCommandServiceFactory.getService(server.getOsType());
// 1. 调用框架层的统一采集方法内部已批量执行命令
MonitorData monitorData = sshService.collectMonitorData(sshClient);
// 2. 创建SSH连接
String password = null;
String privateKey = null;
String passphrase = null;
switch (server.getAuthType()) {
case PASSWORD:
password = server.getSshPassword();
break;
case KEY:
privateKey = server.getSshPrivateKey();
passphrase = server.getSshPassphrase();
break;
if (monitorData == null) {
log.warn("监控数据采集失败: serverId={}, 返回null", server.getId());
return null;
}
sshClient = sshService.createConnection(
server.getHostIp(),
server.getSshPort(),
server.getSshUser(),
password,
privateKey,
passphrase
);
// 3. 采集监控数据
BigDecimal cpuUsage = sshService.getCpuUsage(sshClient);
BigDecimal memoryUsage = sshService.getMemoryUsage(sshClient);
List<DiskUsageInfo> diskUsage = sshService.getDiskUsage(sshClient);
// 4. 计算已用内存基于内存使用率和总内存
// 2. 计算已用内存基于内存使用率和总内存
Integer memoryUsed = null;
if (memoryUsage != null && server.getMemorySize() != null) {
memoryUsed = memoryUsage.multiply(new BigDecimal(server.getMemorySize()))
if (monitorData.getMemoryUsage() != null && server.getMemorySize() != null) {
memoryUsed = monitorData.getMemoryUsage().multiply(new BigDecimal(server.getMemorySize()))
.divide(new BigDecimal(100), 0, BigDecimal.ROUND_HALF_UP)
.intValue();
}
// 5. 构建监控数据
// 3. 转换为业务层DTO
ServerMonitorDataDTO data = ServerMonitorDataDTO.builder()
.serverId(server.getId())
.cpuUsage(cpuUsage)
.memoryUsage(memoryUsage)
.cpuUsage(monitorData.getCpuUsage())
.memoryUsage(monitorData.getMemoryUsage())
.memoryUsed(memoryUsed)
.diskUsage(diskUsage)
.diskUsage(monitorData.getDiskUsage())
.networkRx(monitorData.getNetworkRx())
.networkTx(monitorData.getNetworkTx())
.collectTime(LocalDateTime.now())
.build();
log.debug("服务器监控数据采集成功: serverId={}, cpu={}%, mem={}%, diskCount={}",
server.getId(), cpuUsage, memoryUsage,
diskUsage != null ? diskUsage.size() : 0);
log.debug("服务器监控数据采集成功: serverId={}, cpu={}%, mem={}%, diskCount={}, netRx={}KB/s, netTx={}KB/s",
server.getId(),
monitorData.getCpuUsage(),
monitorData.getMemoryUsage(),
monitorData.getDiskUsage() != null ? monitorData.getDiskUsage().size() : 0,
monitorData.getNetworkRx(),
monitorData.getNetworkTx());
return data;
} catch (Exception e) {
log.error("采集服务器监控数据失败: serverId={}, serverName={}, error={}",
server.getId(), server.getServerName(), e.getMessage());
throw e; // 抛出异常让上层处理
} finally {
// 6. 关闭SSH连接
if (sshService != null && sshClient != null) {
sshService.closeConnection(sshClient);
}
return null; // 返回null由调用方处理
}
}

View File

@ -70,6 +70,8 @@ public class ServerMonitorServiceImpl implements IServerMonitorService {
.memoryUsage(dto.getMemoryUsage())
.memoryUsed(dto.getMemoryUsed())
.diskUsage(diskUsageJson)
.networkRx(dto.getNetworkRx())
.networkTx(dto.getNetworkTx())
.collectTime(dto.getCollectTime())
.build();
}

View File

@ -0,0 +1,53 @@
package com.qqchen.deploy.backend.framework.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* SSH命令执行结果
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CommandResult {
/**
* 命令是否执行成功
*/
private boolean success;
/**
* 命令输出成功时为标准输出失败时为错误输出
*/
private String output;
/**
* 退出码0表示成功
*/
private Integer exitCode;
/**
* 创建成功结果
*/
public static CommandResult success(String output) {
return CommandResult.builder()
.success(true)
.output(output)
.exitCode(0)
.build();
}
/**
* 创建失败结果
*/
public static CommandResult failure(String error, Integer exitCode) {
return CommandResult.builder()
.success(false)
.output(error)
.exitCode(exitCode)
.build();
}
}

View File

@ -0,0 +1,45 @@
package com.qqchen.deploy.backend.framework.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.util.List;
/**
* 监控数据Framework层
* 用于一次性返回所有监控指标
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MonitorData {
/**
* CPU使用率(%)
*/
private BigDecimal cpuUsage;
/**
* 内存使用率(%)
*/
private BigDecimal memoryUsage;
/**
* 磁盘使用情况列表
*/
private List<DiskUsageInfo> diskUsage;
/**
* 网络接收流量(KB/s)
*/
private Long networkRx;
/**
* 网络发送流量(KB/s)
*/
private Long networkTx;
}

View File

@ -1,5 +1,6 @@
package com.qqchen.deploy.backend.framework.ssh;
import com.qqchen.deploy.backend.framework.dto.CommandResult;
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
import com.qqchen.deploy.backend.framework.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
@ -10,6 +11,8 @@ import net.schmizz.sshj.userauth.keyprovider.KeyProvider;
import net.schmizz.sshj.userauth.password.PasswordUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
@ -99,20 +102,23 @@ public abstract class AbstractSSHCommandService implements ISSHCommandService {
}
@Override
public String executeCommand(SSHClient sshClient, String command) throws Exception {
public CommandResult executeCommand(SSHClient sshClient, String command) throws Exception {
try (Session session = sshClient.startSession()) {
Session.Command cmd = session.exec(command);
cmd.join(COMMAND_TIMEOUT_SECONDS, TimeUnit.SECONDS);
if (cmd.getExitStatus() == 0) {
Integer exitStatus = cmd.getExitStatus();
if (exitStatus == 0) {
// 命令执行成功
String output = new String(cmd.getInputStream().readAllBytes()).trim();
log.debug("命令执行成功: {} -> {}", command, output);
return output;
return CommandResult.success(output);
} else {
// 命令执行失败
String error = new String(cmd.getErrorStream().readAllBytes()).trim();
log.warn("命令执行失败: {} -> {}", command, error);
throw new BusinessException(ResponseCode.ERROR,
new Object[]{"命令执行失败: " + error});
log.warn("命令执行失败: exitCode={}, command={}, error={}", exitStatus, command, error);
return CommandResult.failure(error, exitStatus);
}
} catch (IOException e) {
log.error("执行SSH命令异常: {}", command, e);
@ -121,6 +127,44 @@ public abstract class AbstractSSHCommandService implements ISSHCommandService {
}
}
@Override
public List<CommandResult> executeCommands(SSHClient sshClient, List<String> commands) throws Exception {
if (commands == null || commands.isEmpty()) {
return new ArrayList<>();
}
log.debug("批量执行{}个命令", commands.size());
List<CommandResult> results = new ArrayList<>();
// 循环执行每个命令每个命令独立执行一个失败不影响其他
for (int i = 0; i < commands.size(); i++) {
String command = commands.get(i);
try {
CommandResult result = executeCommand(sshClient, command);
results.add(result);
if (result.isSuccess()) {
log.debug("命令[{}/{}]执行成功: {}", i + 1, commands.size(), command);
} else {
log.warn("命令[{}/{}]执行失败: exitCode={}, command={}",
i + 1, commands.size(), result.getExitCode(), command);
}
} catch (Exception e) {
// 命令执行异常连接问题等记录失败结果
log.error("命令[{}/{}]执行异常: {}", i + 1, commands.size(), command, e);
results.add(CommandResult.failure(e.getMessage(), -1));
}
}
log.debug("批量命令执行完成: 总数={}, 成功={}, 失败={}",
commands.size(),
results.stream().filter(CommandResult::isSuccess).count(),
results.stream().filter(r -> !r.isSuccess()).count());
return results;
}
@Override
public void closeConnection(SSHClient sshClient) {
if (sshClient != null) {
@ -137,11 +181,12 @@ public abstract class AbstractSSHCommandService implements ISSHCommandService {
/**
* 安全执行命令子类使用
* 出错时返回null而不是抛异常
* 返回命令输出失败时返回null
*/
protected String safeExecute(SSHClient sshClient, String command) {
try {
return executeCommand(sshClient, command);
CommandResult result = executeCommand(sshClient, command);
return result.isSuccess() ? result.getOutput() : null;
} catch (Exception e) {
log.warn("执行命令失败: {}", command, e);
return null;

View File

@ -1,7 +1,9 @@
package com.qqchen.deploy.backend.framework.ssh;
import com.qqchen.deploy.backend.framework.dto.CommandResult;
import com.qqchen.deploy.backend.framework.dto.DiskInfo;
import com.qqchen.deploy.backend.framework.dto.DiskUsageInfo;
import com.qqchen.deploy.backend.framework.dto.MonitorData;
import com.qqchen.deploy.backend.framework.enums.OsTypeEnum;
import net.schmizz.sshj.SSHClient;
@ -30,14 +32,25 @@ public interface ISSHCommandService {
String password, String privateKey, String passphrase) throws Exception;
/**
* 执行命令并返回结果
* 执行单个命令并返回结果包含成功/失败状态
*
* @param sshClient SSH客户端
* @param command 要执行的命令
* @return 命令输出结果
* @throws Exception 执行失败时抛出
* @return 命令执行结果包含成功/失败状态输出和退出码
* @throws Exception 连接异常时抛出
*/
String executeCommand(SSHClient sshClient, String command) throws Exception;
CommandResult executeCommand(SSHClient sshClient, String command) throws Exception;
/**
* 批量执行多个命令性能优化一次性执行多个命令减少网络往返
* 每个命令独立执行一个命令失败不影响其他命令
*
* @param sshClient SSH客户端
* @param commands 要执行的命令列表
* @return 命令执行结果列表顺序与输入commands对应包含成功/失败状态和输出
* @throws Exception 连接异常时抛出
*/
List<CommandResult> executeCommands(SSHClient sshClient, List<String> commands) throws Exception;
/**
* 关闭SSH连接
@ -111,9 +124,35 @@ public interface ISSHCommandService {
List<DiskUsageInfo> getDiskUsage(SSHClient sshClient);
/**
* 获取支持的操作系统类型
* 获取网络接收流量(KB/s)
*
* @return OS类型枚举
* @param sshClient SSH客户端
* @return 网络接收流量如果无法获取则返回null
*/
Long getNetworkRx(SSHClient sshClient);
/**
* 获取网络发送流量(KB/s)
*
* @param sshClient SSH客户端
* @return 网络发送流量如果无法获取则返回null
*/
Long getNetworkTx(SSHClient sshClient);
/**
* 一次性采集所有监控数据性能优化批量执行命令减少网络往返
* 每个OS实现类内部会使用 executeCommands 批量执行所有命令
* 推荐在定时监控场景使用此方法而不是分别调用单个方法
*
* @param sshClient SSH客户端
* @return 监控数据包含CPU内存磁盘网络
*/
MonitorData collectMonitorData(SSHClient sshClient);
/**
* 获取当前服务支持的操作系统类型
*
* @return 操作系统类型
*/
OsTypeEnum getSupportedOsType();
}

View File

@ -1,7 +1,9 @@
package com.qqchen.deploy.backend.framework.ssh.impl;
import com.qqchen.deploy.backend.framework.dto.CommandResult;
import com.qqchen.deploy.backend.framework.dto.DiskInfo;
import com.qqchen.deploy.backend.framework.dto.DiskUsageInfo;
import com.qqchen.deploy.backend.framework.dto.MonitorData;
import com.qqchen.deploy.backend.framework.enums.OsTypeEnum;
import com.qqchen.deploy.backend.framework.ssh.AbstractSSHCommandService;
import lombok.extern.slf4j.Slf4j;
@ -9,10 +11,7 @@ import net.schmizz.sshj.SSHClient;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
/**
* Linux SSH命令服务实现Framework层
@ -21,6 +20,42 @@ import java.util.Set;
@Service
public class LinuxSSHCommandServiceImpl extends AbstractSSHCommandService {
// ==================== 命令常量 ====================
/**
* CPU使用率命令
*/
private static final String CMD_CPU_USAGE =
"top -bn1 | grep 'Cpu(s)' | awk '{print $2}' | sed 's/%us,//'";
/**
* 内存使用率命令
*/
private static final String CMD_MEMORY_USAGE =
"free | grep Mem | awk '{printf \"%.2f\", ($2-$7)/$2 * 100}'";
/**
* 磁盘使用率命令格式设备|挂载点|文件系统|总容量|已用|使用率
*/
private static final String CMD_DISK_USAGE =
"df -BG -T | grep -E '^/dev/(sd|vd|nvme|mapper|xvd)' | " +
"awk '{print $1 \"|\" $7 \"|\" $2 \"|\" $3 \"|\" $4 \"|\" $6}' | " +
"sed 's/G//g; s/%//'";
/**
* 网络接收流量命令(KB/s)
*/
private static final String CMD_NETWORK_RX =
"cat /proc/net/dev | grep -vE 'lo:|face' | awk '{sum+=$2} END {print int(sum/1024)}'";
/**
* 网络发送流量命令(KB/s)
*/
private static final String CMD_NETWORK_TX =
"cat /proc/net/dev | grep -vE 'lo:|face' | awk '{sum+=$10} END {print int(sum/1024)}'";
// ==================== 实现方法 ====================
@Override
public String getHostname(SSHClient sshClient) {
return safeExecute(sshClient, "hostname");
@ -95,28 +130,19 @@ public class LinuxSSHCommandServiceImpl extends AbstractSSHCommandService {
@Override
public BigDecimal getCpuUsage(SSHClient sshClient) {
// 使用 top 命令获取CPU使用率
String command = "top -bn1 | grep 'Cpu(s)' | awk '{print $2}' | sed 's/%us,//'";
String result = safeExecute(sshClient, command);
String result = safeExecute(sshClient, CMD_CPU_USAGE);
return parseBigDecimal(result);
}
@Override
public BigDecimal getMemoryUsage(SSHClient sshClient) {
// 计算内存使用率(总内存 - 可用内存) / 总内存 * 100
String command = "free | grep Mem | awk '{printf \"%.2f\", ($2-$7)/$2 * 100}'";
String result = safeExecute(sshClient, command);
String result = safeExecute(sshClient, CMD_MEMORY_USAGE);
return parseBigDecimal(result);
}
@Override
public List<DiskUsageInfo> getDiskUsage(SSHClient sshClient) {
// 获取磁盘使用率
// 格式: 设备|挂载点|文件系统|总容量|已用|使用率
String command = "df -BG -T | grep -E '^/dev/(sd|vd|nvme|mapper|xvd)' | " +
"awk '{print $1 \"|\" $7 \"|\" $2 \"|\" $3 \"|\" $4 \"|\" $6}' | " +
"sed 's/G//g; s/%//'";
String result = safeExecute(sshClient, command);
String result = safeExecute(sshClient, CMD_DISK_USAGE);
List<DiskUsageInfo> diskUsageList = new ArrayList<>();
Set<String> seenDevices = new HashSet<>();
@ -157,6 +183,137 @@ public class LinuxSSHCommandServiceImpl extends AbstractSSHCommandService {
return diskUsageList;
}
@Override
public Long getNetworkRx(SSHClient sshClient) {
String result = safeExecute(sshClient, CMD_NETWORK_RX);
return parseLong(result);
}
@Override
public Long getNetworkTx(SSHClient sshClient) {
String result = safeExecute(sshClient, CMD_NETWORK_TX);
return parseLong(result);
}
/**
* 解析 Long 类型结果
*/
private Long parseLong(String result) {
if (result == null || result.trim().isEmpty()) {
return null;
}
try {
return Long.parseLong(result.trim());
} catch (NumberFormatException e) {
log.warn("解析Long失败: {}", result, e);
return null;
}
}
@Override
public MonitorData collectMonitorData(SSHClient sshClient) {
try {
// 1. 准备5个监控命令使用统一的命令常量
List<String> commands = Arrays.asList(
CMD_CPU_USAGE,
CMD_MEMORY_USAGE,
CMD_DISK_USAGE,
CMD_NETWORK_RX,
CMD_NETWORK_TX
);
// 2. 批量执行命令只需1次网络往返
List<CommandResult> results = executeCommands(sshClient, commands);
// 3. 解析结果
BigDecimal cpuUsage = parseCommandResultToBigDecimal(results.get(0));
BigDecimal memoryUsage = parseCommandResultToBigDecimal(results.get(1));
List<DiskUsageInfo> diskUsage = parseDiskUsageFromCommand(results.get(2));
Long networkRx = parseCommandResultToLong(results.get(3));
Long networkTx = parseCommandResultToLong(results.get(4));
// 4. 构建监控数据
return MonitorData.builder()
.cpuUsage(cpuUsage)
.memoryUsage(memoryUsage)
.diskUsage(diskUsage)
.networkRx(networkRx)
.networkTx(networkTx)
.build();
} catch (Exception e) {
log.error("批量采集Linux监控数据失败", e);
return null;
}
}
/**
* 从CommandResult解析BigDecimal
*/
private BigDecimal parseCommandResultToBigDecimal(CommandResult result) {
if (result == null || !result.isSuccess() || result.getOutput() == null || result.getOutput().trim().isEmpty()) {
return null;
}
return parseBigDecimal(result.getOutput());
}
/**
* 从CommandResult解析Long
*/
private Long parseCommandResultToLong(CommandResult result) {
if (result == null || !result.isSuccess() || result.getOutput() == null || result.getOutput().trim().isEmpty()) {
return null;
}
return parseLong(result.getOutput());
}
/**
* 从CommandResult解析磁盘使用率格式设备|挂载点|文件系统|总容量|已用|使用率
*/
private List<DiskUsageInfo> parseDiskUsageFromCommand(CommandResult result) {
List<DiskUsageInfo> diskUsageList = new ArrayList<>();
if (result == null || !result.isSuccess() || result.getOutput() == null || result.getOutput().trim().isEmpty()) {
return diskUsageList;
}
Set<String> seenDevices = new HashSet<>();
String[] lines = result.getOutput().trim().split("\\n");
for (String line : lines) {
String[] parts = line.split("\\|");
if (parts.length >= 6) {
try {
String device = parts[0].trim();
String mountPoint = parts[1].trim();
String fileSystem = parts[2].trim();
String totalStr = parts[3].trim();
String usedStr = parts[4].trim();
String usageStr = parts[5].trim();
// 去重
if (seenDevices.contains(device)) {
continue;
}
seenDevices.add(device);
DiskUsageInfo diskUsage = DiskUsageInfo.builder()
.mountPoint(mountPoint)
.fileSystem(fileSystem)
.totalSize(new BigDecimal(totalStr))
.usedSize(new BigDecimal(usedStr))
.usagePercent(new BigDecimal(usageStr))
.build();
diskUsageList.add(diskUsage);
} catch (NumberFormatException e) {
log.warn("解析磁盘使用率失败: {}", line, e);
}
}
}
return diskUsageList;
}
@Override
public OsTypeEnum getSupportedOsType() {
return OsTypeEnum.LINUX;

View File

@ -1,7 +1,9 @@
package com.qqchen.deploy.backend.framework.ssh.impl;
import com.qqchen.deploy.backend.framework.dto.CommandResult;
import com.qqchen.deploy.backend.framework.dto.DiskInfo;
import com.qqchen.deploy.backend.framework.dto.DiskUsageInfo;
import com.qqchen.deploy.backend.framework.dto.MonitorData;
import com.qqchen.deploy.backend.framework.enums.OsTypeEnum;
import com.qqchen.deploy.backend.framework.ssh.AbstractSSHCommandService;
import lombok.extern.slf4j.Slf4j;
@ -9,10 +11,7 @@ import net.schmizz.sshj.SSHClient;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
/**
* MacOS SSH命令服务实现Framework层
@ -21,6 +20,40 @@ import java.util.Set;
@Service
public class MacOSSSHCommandServiceImpl extends AbstractSSHCommandService {
// ==================== 命令常量 ====================
/**
* CPU使用率命令
*/
private static final String CMD_CPU_USAGE =
"top -l 1 -s 0 | grep 'CPU usage' | awk '{print $3}' | sed 's/%//'";
/**
* 内存使用率命令
*/
private static final String CMD_MEMORY_USAGE =
"vm_stat | awk '/Pages active/ {active=$3} /Pages wired/ {wired=$4} /Pages free/ {free=$3} END {used=active+wired; total=used+free; printf \"%.2f\", (used/total)*100}'";
/**
* 磁盘使用率命令格式设备|挂载点|容量|已用|可用|使用率
*/
private static final String CMD_DISK_USAGE =
"df -h | grep -E '^/dev/disk' | awk '{print $1 \"|\" $9 \"|\" $2 \"|\" $3 \"|\" $4 \"|\" $5}' | sed 's/Gi//g; s/%//'";
/**
* 网络接收流量命令(KB/s) - 使用netstat统计
*/
private static final String CMD_NETWORK_RX =
"netstat -ib | awk '/en[0-9]/ && !/Link/ {sum+=$7} END {print int(sum/1024)}'";
/**
* 网络发送流量命令(KB/s) - 使用netstat统计
*/
private static final String CMD_NETWORK_TX =
"netstat -ib | awk '/en[0-9]/ && !/Link/ {sum+=$10} END {print int(sum/1024)}'";
// ==================== 实现方法 ====================
@Override
public String getHostname(SSHClient sshClient) {
return safeExecute(sshClient, "hostname");
@ -96,27 +129,19 @@ public class MacOSSSHCommandServiceImpl extends AbstractSSHCommandService {
@Override
public BigDecimal getCpuUsage(SSHClient sshClient) {
// MacOS CPU使用率使用top命令获取
String command = "top -l 1 -s 0 | grep 'CPU usage' | awk '{print $3}' | sed 's/%//'";
String result = safeExecute(sshClient, command);
String result = safeExecute(sshClient, CMD_CPU_USAGE);
return parseBigDecimal(result);
}
@Override
public BigDecimal getMemoryUsage(SSHClient sshClient) {
// MacOS内存使用率使用vm_stat命令计算
// vm_stat输出的是页数需要计算实际使用率
String command = "vm_stat | awk '/Pages active/ {active=$3} /Pages wired/ {wired=$4} /Pages free/ {free=$3} END {used=active+wired; total=used+free; printf \"%.2f\", (used/total)*100}'";
String result = safeExecute(sshClient, command);
String result = safeExecute(sshClient, CMD_MEMORY_USAGE);
return parseBigDecimal(result);
}
@Override
public List<DiskUsageInfo> getDiskUsage(SSHClient sshClient) {
// MacOS磁盘使用率使用df命令
// 格式: 设备|挂载点|容量|已用|可用|使用率
String command = "df -h | grep -E '^/dev/disk' | awk '{print $1 \"|\" $9 \"|\" $2 \"|\" $3 \"|\" $4 \"|\" $5}' | sed 's/Gi//g; s/%//'";
String result = safeExecute(sshClient, command);
String result = safeExecute(sshClient, CMD_DISK_USAGE);
List<DiskUsageInfo> diskUsageList = new ArrayList<>();
Set<String> seenDevices = new HashSet<>();
@ -159,6 +184,139 @@ public class MacOSSSHCommandServiceImpl extends AbstractSSHCommandService {
return diskUsageList;
}
@Override
public Long getNetworkRx(SSHClient sshClient) {
String result = safeExecute(sshClient, CMD_NETWORK_RX);
return parseLong(result);
}
@Override
public Long getNetworkTx(SSHClient sshClient) {
String result = safeExecute(sshClient, CMD_NETWORK_TX);
return parseLong(result);
}
/**
* 解析 Long 类型结果
*/
private Long parseLong(String result) {
if (result == null || result.trim().isEmpty()) {
return null;
}
try {
return Long.parseLong(result.trim());
} catch (NumberFormatException e) {
log.warn("解析Long失败: {}", result, e);
return null;
}
}
@Override
public MonitorData collectMonitorData(SSHClient sshClient) {
try {
// 1. 准备5个监控命令使用统一的命令常量
List<String> commands = Arrays.asList(
CMD_CPU_USAGE,
CMD_MEMORY_USAGE,
CMD_DISK_USAGE,
CMD_NETWORK_RX,
CMD_NETWORK_TX
);
// 2. 批量执行命令只需1次网络往返
List<CommandResult> results = executeCommands(sshClient, commands);
// 3. 解析结果
BigDecimal cpuUsage = parseCommandResultToBigDecimal(results.get(0));
BigDecimal memoryUsage = parseCommandResultToBigDecimal(results.get(1));
List<DiskUsageInfo> diskUsage = parseDiskUsageFromCommand(results.get(2));
Long networkRx = parseCommandResultToLong(results.get(3));
Long networkTx = parseCommandResultToLong(results.get(4));
// 4. 构建监控数据
return MonitorData.builder()
.cpuUsage(cpuUsage)
.memoryUsage(memoryUsage)
.diskUsage(diskUsage)
.networkRx(networkRx)
.networkTx(networkTx)
.build();
} catch (Exception e) {
log.error("批量采集macOS监控数据失败", e);
return null;
}
}
/**
* 从CommandResult解析BigDecimal
*/
private BigDecimal parseCommandResultToBigDecimal(CommandResult result) {
if (result == null || !result.isSuccess() || result.getOutput() == null || result.getOutput().trim().isEmpty()) {
return null;
}
return parseBigDecimal(result.getOutput());
}
/**
* 从CommandResult解析Long
*/
private Long parseCommandResultToLong(CommandResult result) {
if (result == null || !result.isSuccess() || result.getOutput() == null || result.getOutput().trim().isEmpty()) {
return null;
}
return parseLong(result.getOutput());
}
/**
* 从CommandResult解析macOS磁盘使用率格式设备|挂载点|容量|已用|可用|使用率
*/
private List<DiskUsageInfo> parseDiskUsageFromCommand(CommandResult result) {
List<DiskUsageInfo> diskUsageList = new ArrayList<>();
if (result == null || !result.isSuccess() || result.getOutput() == null || result.getOutput().trim().isEmpty()) {
return diskUsageList;
}
Set<String> seenDevices = new HashSet<>();
String[] lines = result.getOutput().trim().split("\\n");
for (String line : lines) {
String[] parts = line.split("\\|");
if (parts.length >= 6) {
try {
String device = parts[0].trim();
String mountPoint = parts[1].trim();
String totalStr = parts[2].trim();
String usedStr = parts[3].trim();
String usageStr = parts[5].trim();
// 去重
if (seenDevices.contains(device)) {
continue;
}
seenDevices.add(device);
// 简化的文件系统类型
String fileSystem = "APFS";
DiskUsageInfo diskUsage = DiskUsageInfo.builder()
.mountPoint(mountPoint)
.fileSystem(fileSystem)
.totalSize(parseBigDecimal(totalStr))
.usedSize(parseBigDecimal(usedStr))
.usagePercent(parseBigDecimal(usageStr))
.build();
diskUsageList.add(diskUsage);
} catch (Exception e) {
log.warn("解析macOS磁盘使用率失败: {}", line, e);
}
}
}
return diskUsageList;
}
@Override
public OsTypeEnum getSupportedOsType() {
return OsTypeEnum.MACOS;

View File

@ -1,7 +1,9 @@
package com.qqchen.deploy.backend.framework.ssh.impl;
import com.qqchen.deploy.backend.framework.dto.CommandResult;
import com.qqchen.deploy.backend.framework.dto.DiskInfo;
import com.qqchen.deploy.backend.framework.dto.DiskUsageInfo;
import com.qqchen.deploy.backend.framework.dto.MonitorData;
import com.qqchen.deploy.backend.framework.enums.OsTypeEnum;
import com.qqchen.deploy.backend.framework.ssh.AbstractSSHCommandService;
import lombok.extern.slf4j.Slf4j;
@ -9,10 +11,7 @@ import net.schmizz.sshj.SSHClient;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
/**
* Windows SSH命令服务实现Framework层
@ -22,6 +21,61 @@ import java.util.Set;
@Service
public class WindowsSSHCommandServiceImpl extends AbstractSSHCommandService {
// ==================== 命令常量 ====================
/**
* CPU使用率命令
*/
private static final String CMD_CPU_USAGE =
"powershell \"Get-Counter '\\Processor(_Total)\\% Processor Time' | " +
"Select-Object -ExpandProperty CounterSamples | " +
"Select-Object -ExpandProperty CookedValue\"";
/**
* 内存使用率命令
*/
private static final String CMD_MEMORY_USAGE =
"powershell \"$os = Get-CimInstance Win32_OperatingSystem; " +
"$total = $os.TotalVisibleMemorySize; " +
"$free = $os.FreePhysicalMemory; " +
"$used = $total - $free; " +
"[math]::Round(($used / $total) * 100, 2)\"";
/**
* 磁盘使用率命令格式驱动器号|文件系统|总容量|已用|使用率
*/
private static final String CMD_DISK_USAGE =
"powershell \"Get-Volume | " +
"Where-Object {$_.DriveLetter -ne $null -and $_.Size -gt 0} | " +
"ForEach-Object {" +
" $used = [math]::Round(($_.Size - $_.SizeRemaining)/1GB, 2); " +
" $total = [math]::Round($_.Size/1GB, 2); " +
" $percent = [math]::Round((($_.Size - $_.SizeRemaining)/$_.Size)*100, 2); " +
" $_.DriveLetter + '|' + $_.FileSystemType + '|' + $total + '|' + $used + '|' + $percent" +
"}\"";
/**
* 网络接收流量命令(KB)
*/
private static final String CMD_NETWORK_RX =
"powershell \"Get-NetAdapterStatistics | " +
"Where-Object {$_.Name -notlike '*Loopback*' -and $_.Name -notlike '*Virtual*'} | " +
"Measure-Object -Property ReceivedBytes -Sum | " +
"Select-Object -ExpandProperty Sum | " +
"ForEach-Object {[math]::Round($_ / 1KB)}\"";
/**
* 网络发送流量命令(KB)
*/
private static final String CMD_NETWORK_TX =
"powershell \"Get-NetAdapterStatistics | " +
"Where-Object {$_.Name -notlike '*Loopback*' -and $_.Name -notlike '*Virtual*'} | " +
"Measure-Object -Property SentBytes -Sum | " +
"Select-Object -ExpandProperty Sum | " +
"ForEach-Object {[math]::Round($_ / 1KB)}\"";
// ==================== 实现方法 ====================
@Override
public String getHostname(SSHClient sshClient) {
return safeExecute(sshClient, "hostname");
@ -101,39 +155,19 @@ public class WindowsSSHCommandServiceImpl extends AbstractSSHCommandService {
@Override
public BigDecimal getCpuUsage(SSHClient sshClient) {
// Windows CPU使用率使用PowerShell获取
String command = "powershell \"Get-Counter '\\Processor(_Total)\\% Processor Time' | " +
"Select-Object -ExpandProperty CounterSamples | " +
"Select-Object -ExpandProperty CookedValue\"";
String result = safeExecute(sshClient, command);
String result = safeExecute(sshClient, CMD_CPU_USAGE);
return parseBigDecimal(result);
}
@Override
public BigDecimal getMemoryUsage(SSHClient sshClient) {
// Windows内存使用率使用PowerShell计算
String command = "powershell \"$os = Get-CimInstance Win32_OperatingSystem; " +
"$total = $os.TotalVisibleMemorySize; " +
"$free = $os.FreePhysicalMemory; " +
"$used = $total - $free; " +
"[math]::Round(($used / $total) * 100, 2)\"";
String result = safeExecute(sshClient, command);
String result = safeExecute(sshClient, CMD_MEMORY_USAGE);
return parseBigDecimal(result);
}
@Override
public List<DiskUsageInfo> getDiskUsage(SSHClient sshClient) {
// Windows磁盘使用率使用PowerShell获取
// 输出格式: 驱动器号|文件系统|总容量|已用|使用率
String command = "powershell \"Get-Volume | " +
"Where-Object {$_.DriveLetter -ne $null -and $_.Size -gt 0} | " +
"ForEach-Object {" +
" $used = [math]::Round(($_.Size - $_.SizeRemaining)/1GB, 2); " +
" $total = [math]::Round($_.Size/1GB, 2); " +
" $percent = [math]::Round((($_.Size - $_.SizeRemaining)/$_.Size)*100, 2); " +
" $_.DriveLetter + '|' + $_.FileSystemType + '|' + $total + '|' + $used + '|' + $percent" +
"}\"";
String result = safeExecute(sshClient, command);
String result = safeExecute(sshClient, CMD_DISK_USAGE);
List<DiskUsageInfo> diskUsageList = new ArrayList<>();
Set<String> seenDrives = new HashSet<>();
@ -178,6 +212,141 @@ public class WindowsSSHCommandServiceImpl extends AbstractSSHCommandService {
return diskUsageList;
}
@Override
public Long getNetworkRx(SSHClient sshClient) {
String result = safeExecute(sshClient, CMD_NETWORK_RX);
return parseLong(result);
}
@Override
public Long getNetworkTx(SSHClient sshClient) {
String result = safeExecute(sshClient, CMD_NETWORK_TX);
return parseLong(result);
}
/**
* 解析 Long 类型结果
*/
private Long parseLong(String value) {
if (value == null || value.trim().isEmpty()) {
return null;
}
try {
return Long.parseLong(value.trim());
} catch (NumberFormatException e) {
log.warn("解析Long失败: {}", value, e);
return null;
}
}
@Override
public MonitorData collectMonitorData(SSHClient sshClient) {
try {
// 1. 准备5个PowerShell监控命令使用统一的命令常量
List<String> commands = Arrays.asList(
CMD_CPU_USAGE,
CMD_MEMORY_USAGE,
CMD_DISK_USAGE,
CMD_NETWORK_RX,
CMD_NETWORK_TX
);
// 2. 批量执行PowerShell命令只需1次网络往返
List<CommandResult> results = executeCommands(sshClient, commands);
// 3. 解析结果
BigDecimal cpuUsage = parseCommandResultToBigDecimal(results.get(0));
BigDecimal memoryUsage = parseCommandResultToBigDecimal(results.get(1));
List<DiskUsageInfo> diskUsage = parseDiskUsageFromCommand(results.get(2));
Long networkRx = parseCommandResultToLong(results.get(3));
Long networkTx = parseCommandResultToLong(results.get(4));
// 4. 构建监控数据
return MonitorData.builder()
.cpuUsage(cpuUsage)
.memoryUsage(memoryUsage)
.diskUsage(diskUsage)
.networkRx(networkRx)
.networkTx(networkTx)
.build();
} catch (Exception e) {
log.error("批量采集Windows监控数据失败", e);
return null;
}
}
/**
* 从CommandResult解析BigDecimal
*/
private BigDecimal parseCommandResultToBigDecimal(CommandResult result) {
if (result == null || !result.isSuccess() || result.getOutput() == null || result.getOutput().trim().isEmpty()) {
return null;
}
return parseBigDecimal(result.getOutput());
}
/**
* 从CommandResult解析Long
*/
private Long parseCommandResultToLong(CommandResult result) {
if (result == null || !result.isSuccess() || result.getOutput() == null || result.getOutput().trim().isEmpty()) {
return null;
}
return parseLong(result.getOutput());
}
/**
* 从CommandResult解析Windows磁盘使用率格式驱动器号|文件系统|总容量|已用|使用率
*/
private List<DiskUsageInfo> parseDiskUsageFromCommand(CommandResult result) {
List<DiskUsageInfo> diskUsageList = new ArrayList<>();
if (result == null || !result.isSuccess() || result.getOutput() == null || result.getOutput().trim().isEmpty()) {
return diskUsageList;
}
Set<String> seenDrives = new HashSet<>();
String[] lines = result.getOutput().trim().split("\\n");
for (String line : lines) {
String[] parts = line.split("\\|");
if (parts.length >= 5) {
try {
String driveLetter = parts[0].trim();
String fileSystem = parts[1].trim();
String totalStr = parts[2].trim();
String usedStr = parts[3].trim();
String usageStr = parts[4].trim();
// 去重
if (seenDrives.contains(driveLetter)) {
continue;
}
seenDrives.add(driveLetter);
// 处理空的文件系统类型
if (fileSystem == null || fileSystem.isEmpty()) {
fileSystem = "NTFS";
}
DiskUsageInfo diskUsage = DiskUsageInfo.builder()
.mountPoint(driveLetter + ":")
.fileSystem(fileSystem)
.totalSize(parseBigDecimal(totalStr))
.usedSize(parseBigDecimal(usedStr))
.usagePercent(parseBigDecimal(usageStr))
.build();
diskUsageList.add(diskUsage);
} catch (Exception e) {
log.warn("解析Windows磁盘使用率失败: {}", line, e);
}
}
}
return diskUsageList;
}
@Override
public OsTypeEnum getSupportedOsType() {
return OsTypeEnum.WINDOWS;

View File

@ -769,19 +769,19 @@ INSERT INTO `deploy-ease-platform`.`schedule_job_category` (`id`, `create_by`, `
INSERT INTO `deploy-ease-platform`.`schedule_job_category` (`id`, `create_by`, `create_time`, `update_by`, `update_time`, `version`, `deleted`, `code`, `name`, `description`, `icon`, `color`, `enabled`, `sort`) VALUES (5, 'system', NOW(), 'system', NOW(), 1, b'0', 'BACKUP', '备份任务', '定期备份数据库和重要文件', 'DatabaseOutlined', '#722ed1', b'1', 5);
INSERT INTO `deploy-ease-platform`.`schedule_job` (`id`, `create_by`, `create_time`, `update_by`, `update_time`, `version`, `deleted`, `job_name`, `job_description`, `category_id`, `bean_name`, `method_name`, `form_definition_id`, `method_params`, `cron_expression`, `status`, `concurrent`, `last_execute_time`, `next_execute_time`, `execute_count`, `success_count`, `fail_count`, `timeout_seconds`, `retry_count`, `alert_email`) VALUES (2, 'admin', NOW(), 'admin', NOW(), 27, b'0', '链宇Git仓库组同步', '定期同步Git仓库组信息每天凌晨2点执行', 2, 'repositoryGroupServiceImpl', 'syncGroups', NULL, '{\"externalSystemId\": 2}', '0 0 2 * * ?', 'ENABLED', b'0', NOW(), NOW(), 0, 0, 0, 3600, 2, '');
INSERT INTO `deploy-ease-platform`.`schedule_job` (`id`, `create_by`, `create_time`, `update_by`, `update_time`, `version`, `deleted`, `job_name`, `job_description`, `category_id`, `bean_name`, `method_name`, `form_definition_id`, `method_params`, `cron_expression`, `status`, `concurrent`, `last_execute_time`, `next_execute_time`, `execute_count`, `success_count`, `fail_count`, `timeout_seconds`, `retry_count`, `alert_email`) VALUES (3, 'admin', NOW(), 'admin', NOW(), 29, b'0', '链宇Git项目同步', '定期同步Git项目信息每天凌晨3点执行', 2, 'repositoryProjectServiceImpl', 'syncProjects', NULL, '{\"externalSystemId\": 2}', '0 0 3 * * ?', 'ENABLED', b'0', NOW(), NOW(), 0, 0, 0, 3600, 2, '');
INSERT INTO `deploy-ease-platform`.`schedule_job` (`id`, `create_by`, `create_time`, `update_by`, `update_time`, `version`, `deleted`, `job_name`, `job_description`, `category_id`, `bean_name`, `method_name`, `form_definition_id`, `method_params`, `cron_expression`, `status`, `concurrent`, `last_execute_time`, `next_execute_time`, `execute_count`, `success_count`, `fail_count`, `timeout_seconds`, `retry_count`, `alert_email`) VALUES (4, 'admin', NOW(), 'system', NOW(), 5725, b'0', '链宇Git分支同步', '定期同步Git仓库分支信息每5分钟执行一次', 2, 'repositoryBranchServiceImpl', 'syncBranches', NULL, '{\"externalSystemId\": 2}', '0 */5 * * * ?', 'ENABLED', b'0', NOW(), NOW(), 7, 7, 0, 3600, 2, '');
INSERT INTO `deploy-ease-platform`.`schedule_job` (`id`, `create_by`, `create_time`, `update_by`, `update_time`, `version`, `deleted`, `job_name`, `job_description`, `category_id`, `bean_name`, `method_name`, `form_definition_id`, `method_params`, `cron_expression`, `status`, `concurrent`, `last_execute_time`, `next_execute_time`, `execute_count`, `success_count`, `fail_count`, `timeout_seconds`, `retry_count`, `alert_email`) VALUES (8, 'admin', NOW(), 'system', NOW(), 1209, b'0', '链宇Jenkins视图同步', '', 2, 'jenkinsViewServiceImpl', 'syncViews', NULL, '{\"externalSystemId\": 1}', '0 */5 * * * ?', 'ENABLED', b'0', NOW(), NOW(), 7, 7, 0, 300, 0, '');
INSERT INTO `deploy-ease-platform`.`schedule_job` (`id`, `create_by`, `create_time`, `update_by`, `update_time`, `version`, `deleted`, `job_name`, `job_description`, `category_id`, `bean_name`, `method_name`, `form_definition_id`, `method_params`, `cron_expression`, `status`, `concurrent`, `last_execute_time`, `next_execute_time`, `execute_count`, `success_count`, `fail_count`, `timeout_seconds`, `retry_count`, `alert_email`) VALUES (9, 'admin', NOW(), 'system', NOW(), 1209, b'0', '链宇Jenkins任务同步', '', 2, 'jenkinsJobServiceImpl', 'syncJobs', NULL, '{\"externalSystemId\": 1}', '0 */5 * * * ?', 'ENABLED', b'0', NOW(), NOW(), 7, 7, 0, 300, 0, '');
INSERT INTO `deploy-ease-platform`.`schedule_job` (`id`, `create_by`, `create_time`, `update_by`, `update_time`, `version`, `deleted`, `job_name`, `job_description`, `category_id`, `bean_name`, `method_name`, `form_definition_id`, `method_params`, `cron_expression`, `status`, `concurrent`, `last_execute_time`, `next_execute_time`, `execute_count`, `success_count`, `fail_count`, `timeout_seconds`, `retry_count`, `alert_email`) VALUES (10, 'admin', NOW(), 'system', NOW(), 10156, b'0', '链宇Jenkins构建同步', '', 2, 'jenkinsBuildServiceImpl', 'syncBuilds', NULL, '{\"externalSystemId\": 1}', '0 */2 * * * ?', 'ENABLED', b'0', NOW(), NOW(), 18, 18, 0, 300, 0, '');
INSERT INTO `deploy-ease-platform`.`schedule_job` (`id`, `create_by`, `create_time`, `update_by`, `update_time`, `version`, `deleted`, `job_name`, `job_description`, `category_id`, `bean_name`, `method_name`, `form_definition_id`, `method_params`, `cron_expression`, `status`, `concurrent`, `last_execute_time`, `next_execute_time`, `execute_count`, `success_count`, `fail_count`, `timeout_seconds`, `retry_count`, `alert_email`) VALUES (11, 'admin', NOW(), 'system', NOW(), 1210, b'0', '隆基Jenkins视图同步', '', 2, 'jenkinsViewServiceImpl', 'syncViews', NULL, '{\"externalSystemId\": 3}', '0 */5 * * * ?', 'ENABLED', b'0', NOW(), NOW(), 7, 7, 0, 300, 0, '');
INSERT INTO `deploy-ease-platform`.`schedule_job` (`id`, `create_by`, `create_time`, `update_by`, `update_time`, `version`, `deleted`, `job_name`, `job_description`, `category_id`, `bean_name`, `method_name`, `form_definition_id`, `method_params`, `cron_expression`, `status`, `concurrent`, `last_execute_time`, `next_execute_time`, `execute_count`, `success_count`, `fail_count`, `timeout_seconds`, `retry_count`, `alert_email`) VALUES (12, 'admin', NOW(), 'system', NOW(), 1210, b'0', '隆基Jenkins任务同步', '', 2, 'jenkinsJobServiceImpl', 'syncJobs', NULL, '{\"externalSystemId\": 3}', '0 */5 * * * ?', 'ENABLED', b'0', NOW(), NOW(), 7, 7, 0, 300, 0, '');
INSERT INTO `deploy-ease-platform`.`schedule_job` (`id`, `create_by`, `create_time`, `update_by`, `update_time`, `version`, `deleted`, `job_name`, `job_description`, `category_id`, `bean_name`, `method_name`, `form_definition_id`, `method_params`, `cron_expression`, `status`, `concurrent`, `last_execute_time`, `next_execute_time`, `execute_count`, `success_count`, `fail_count`, `timeout_seconds`, `retry_count`, `alert_email`) VALUES (13, 'admin', NOW(), 'system', NOW(), 10180, b'0', '隆基Jenkins构建同步', '', 2, 'jenkinsBuildServiceImpl', 'syncBuilds', NULL, '{\"externalSystemId\": 3}', '0 */2 * * * ?', 'ENABLED', b'0', NOW(), NOW(), 18, 18, 0, 300, 0, '');
INSERT INTO `deploy-ease-platform`.`schedule_job` (`id`, `create_by`, `create_time`, `update_by`, `update_time`, `version`, `deleted`, `job_name`, `job_description`, `category_id`, `bean_name`, `method_name`, `form_definition_id`, `method_params`, `cron_expression`, `status`, `concurrent`, `last_execute_time`, `next_execute_time`, `execute_count`, `success_count`, `fail_count`, `timeout_seconds`, `retry_count`, `alert_email`) VALUES (14, 'admin', NOW(), 'admin', NOW(), 26, b'0', '隆基Git仓库组同步', '定期同步Git仓库组信息每天凌晨2点执行', 2, 'repositoryGroupServiceImpl', 'syncGroups', NULL, '{\"externalSystemId\": 4}', '0 0 3 * * ?', 'ENABLED', b'0', NOW(), NOW(), 0, 0, 0, 3600, 2, '');
INSERT INTO `deploy-ease-platform`.`schedule_job` (`id`, `create_by`, `create_time`, `update_by`, `update_time`, `version`, `deleted`, `job_name`, `job_description`, `category_id`, `bean_name`, `method_name`, `form_definition_id`, `method_params`, `cron_expression`, `status`, `concurrent`, `last_execute_time`, `next_execute_time`, `execute_count`, `success_count`, `fail_count`, `timeout_seconds`, `retry_count`, `alert_email`) VALUES (15, 'admin', NOW(), 'system', NOW(), 1211, b'0', '隆基Git项目同步', '定期同步Git项目信息每天凌晨3点执行', 2, 'repositoryProjectServiceImpl', 'syncProjects', NULL, '{\"externalSystemId\": 4}', '0 */5 * * * ?', 'ENABLED', b'0', NOW(), NOW(), 7, 7, 0, 3600, 2, '');
INSERT INTO `deploy-ease-platform`.`schedule_job` (`id`, `create_by`, `create_time`, `update_by`, `update_time`, `version`, `deleted`, `job_name`, `job_description`, `category_id`, `bean_name`, `method_name`, `form_definition_id`, `method_params`, `cron_expression`, `status`, `concurrent`, `last_execute_time`, `next_execute_time`, `execute_count`, `success_count`, `fail_count`, `timeout_seconds`, `retry_count`, `alert_email`) VALUES (16, 'admin', NOW(), 'system', NOW(), 5726, b'0', '隆基Git分支同步', '定期同步Git仓库分支信息每5分钟执行一次', 2, 'repositoryBranchServiceImpl', 'syncBranches', NULL, '{\"externalSystemId\": 4}', '0 */5 * * * ?', 'ENABLED', b'0', NOW(), NOW(), 7, 7, 0, 3600, 2, '');
INSERT INTO `deploy-ease-platform`.`schedule_job` (`id`, `create_by`, `create_time`, `update_by`, `update_time`, `version`, `deleted`, `job_name`, `job_description`, `category_id`, `bean_name`, `method_name`, `form_definition_id`, `method_params`, `cron_expression`, `status`, `concurrent`, `last_execute_time`, `next_execute_time`, `execute_count`, `success_count`, `fail_count`, `timeout_seconds`, `retry_count`, `alert_email`) VALUES (17, 'admin', NOW(), 'system', NOW(), 19, b'0', '服务器预警', '', 4, 'serverMonitorScheduler', 'collectServerMetrics', NULL, '{\"notificationChannelId\": 5, \"serverOfflineTemplateId\": 12, \"resourceAlertTemplateId\": 11}', '0 */5 * * * ?', 'ENABLED', b'0', NOW(), NOW(), 15, 15, 0, 300, 0, '');
INSERT INTO `deploy-ease-platform`.`schedule_job` (`id`, `create_by`, `create_time`, `update_by`, `update_time`, `version`, `deleted`, `job_name`, `job_description`, `category_id`, `bean_name`, `method_name`, `form_definition_id`, `method_params`, `cron_expression`, `status`, `concurrent`, `last_execute_time`, `next_execute_time`, `execute_count`, `success_count`, `fail_count`, `timeout_seconds`, `retry_count`, `alert_email`) VALUES (2, 'admin', NOW(), 'admin', NOW(), 28, b'0', '链宇Git仓库组同步', '定期同步Git仓库组信息每天凌晨2点执行', 2, 'repositoryGroupServiceImpl', 'syncGroups', NULL, '{\"externalSystemId\": 2}', '0 0 2 * * ?', 'DISABLED', b'0', NOW(), NOW(), 0, 0, 0, 3600, 2, '');
INSERT INTO `deploy-ease-platform`.`schedule_job` (`id`, `create_by`, `create_time`, `update_by`, `update_time`, `version`, `deleted`, `job_name`, `job_description`, `category_id`, `bean_name`, `method_name`, `form_definition_id`, `method_params`, `cron_expression`, `status`, `concurrent`, `last_execute_time`, `next_execute_time`, `execute_count`, `success_count`, `fail_count`, `timeout_seconds`, `retry_count`, `alert_email`) VALUES (3, 'admin', NOW(), 'admin', NOW(), 30, b'0', '链宇Git项目同步', '定期同步Git项目信息每天凌晨3点执行', 2, 'repositoryProjectServiceImpl', 'syncProjects', NULL, '{\"externalSystemId\": 2}', '0 0 3 * * ?', 'DISABLED', b'0', NOW(), NOW(), 0, 0, 0, 3600, 2, '');
INSERT INTO `deploy-ease-platform`.`schedule_job` (`id`, `create_by`, `create_time`, `update_by`, `update_time`, `version`, `deleted`, `job_name`, `job_description`, `category_id`, `bean_name`, `method_name`, `form_definition_id`, `method_params`, `cron_expression`, `status`, `concurrent`, `last_execute_time`, `next_execute_time`, `execute_count`, `success_count`, `fail_count`, `timeout_seconds`, `retry_count`, `alert_email`) VALUES (4, 'admin', NOW(), 'admin', NOW(), 5726, b'0', '链宇Git分支同步', '定期同步Git仓库分支信息每5分钟执行一次', 2, 'repositoryBranchServiceImpl', 'syncBranches', NULL, '{\"externalSystemId\": 2}', '0 */5 * * * ?', 'DISABLED', b'0', NOW(), NOW(), 7, 7, 0, 3600, 2, '');
INSERT INTO `deploy-ease-platform`.`schedule_job` (`id`, `create_by`, `create_time`, `update_by`, `update_time`, `version`, `deleted`, `job_name`, `job_description`, `category_id`, `bean_name`, `method_name`, `form_definition_id`, `method_params`, `cron_expression`, `status`, `concurrent`, `last_execute_time`, `next_execute_time`, `execute_count`, `success_count`, `fail_count`, `timeout_seconds`, `retry_count`, `alert_email`) VALUES (8, 'admin', NOW(), 'admin', NOW(), 1210, b'0', '链宇Jenkins视图同步', '', 2, 'jenkinsViewServiceImpl', 'syncViews', NULL, '{\"externalSystemId\": 1}', '0 */5 * * * ?', 'DISABLED', b'0', NOW(), NOW(), 7, 7, 0, 300, 0, '');
INSERT INTO `deploy-ease-platform`.`schedule_job` (`id`, `create_by`, `create_time`, `update_by`, `update_time`, `version`, `deleted`, `job_name`, `job_description`, `category_id`, `bean_name`, `method_name`, `form_definition_id`, `method_params`, `cron_expression`, `status`, `concurrent`, `last_execute_time`, `next_execute_time`, `execute_count`, `success_count`, `fail_count`, `timeout_seconds`, `retry_count`, `alert_email`) VALUES (9, 'admin', NOW(), 'admin', NOW(), 1210, b'0', '链宇Jenkins任务同步', '', 2, 'jenkinsJobServiceImpl', 'syncJobs', NULL, '{\"externalSystemId\": 1}', '0 */5 * * * ?', 'DISABLED', b'0', NOW(), NOW(), 7, 7, 0, 300, 0, '');
INSERT INTO `deploy-ease-platform`.`schedule_job` (`id`, `create_by`, `create_time`, `update_by`, `update_time`, `version`, `deleted`, `job_name`, `job_description`, `category_id`, `bean_name`, `method_name`, `form_definition_id`, `method_params`, `cron_expression`, `status`, `concurrent`, `last_execute_time`, `next_execute_time`, `execute_count`, `success_count`, `fail_count`, `timeout_seconds`, `retry_count`, `alert_email`) VALUES (10, 'admin', NOW(), 'admin', NOW(), 10157, b'0', '链宇Jenkins构建同步', '', 2, 'jenkinsBuildServiceImpl', 'syncBuilds', NULL, '{\"externalSystemId\": 1}', '0 */2 * * * ?', 'DISABLED', b'0', NOW(), NOW(), 18, 18, 0, 300, 0, '');
INSERT INTO `deploy-ease-platform`.`schedule_job` (`id`, `create_by`, `create_time`, `update_by`, `update_time`, `version`, `deleted`, `job_name`, `job_description`, `category_id`, `bean_name`, `method_name`, `form_definition_id`, `method_params`, `cron_expression`, `status`, `concurrent`, `last_execute_time`, `next_execute_time`, `execute_count`, `success_count`, `fail_count`, `timeout_seconds`, `retry_count`, `alert_email`) VALUES (11, 'admin', NOW(), 'admin', NOW(), 1211, b'0', '隆基Jenkins视图同步', '', 2, 'jenkinsViewServiceImpl', 'syncViews', NULL, '{\"externalSystemId\": 3}', '0 */5 * * * ?', 'DISABLED', b'0', NOW(), NOW(), 7, 7, 0, 300, 0, '');
INSERT INTO `deploy-ease-platform`.`schedule_job` (`id`, `create_by`, `create_time`, `update_by`, `update_time`, `version`, `deleted`, `job_name`, `job_description`, `category_id`, `bean_name`, `method_name`, `form_definition_id`, `method_params`, `cron_expression`, `status`, `concurrent`, `last_execute_time`, `next_execute_time`, `execute_count`, `success_count`, `fail_count`, `timeout_seconds`, `retry_count`, `alert_email`) VALUES (12, 'admin', NOW(), 'admin', NOW(), 1211, b'0', '隆基Jenkins任务同步', '', 2, 'jenkinsJobServiceImpl', 'syncJobs', NULL, '{\"externalSystemId\": 3}', '0 */5 * * * ?', 'DISABLED', b'0', NOW(), NOW(), 7, 7, 0, 300, 0, '');
INSERT INTO `deploy-ease-platform`.`schedule_job` (`id`, `create_by`, `create_time`, `update_by`, `update_time`, `version`, `deleted`, `job_name`, `job_description`, `category_id`, `bean_name`, `method_name`, `form_definition_id`, `method_params`, `cron_expression`, `status`, `concurrent`, `last_execute_time`, `next_execute_time`, `execute_count`, `success_count`, `fail_count`, `timeout_seconds`, `retry_count`, `alert_email`) VALUES (13, 'admin', NOW(), 'admin', NOW(), 10181, b'0', '隆基Jenkins构建同步', '', 2, 'jenkinsBuildServiceImpl', 'syncBuilds', NULL, '{\"externalSystemId\": 3}', '0 */2 * * * ?', 'DISABLED', b'0', NOW(), NOW(), 18, 18, 0, 300, 0, '');
INSERT INTO `deploy-ease-platform`.`schedule_job` (`id`, `create_by`, `create_time`, `update_by`, `update_time`, `version`, `deleted`, `job_name`, `job_description`, `category_id`, `bean_name`, `method_name`, `form_definition_id`, `method_params`, `cron_expression`, `status`, `concurrent`, `last_execute_time`, `next_execute_time`, `execute_count`, `success_count`, `fail_count`, `timeout_seconds`, `retry_count`, `alert_email`) VALUES (14, 'admin', NOW(), 'admin', NOW(), 27, b'0', '隆基Git仓库组同步', '定期同步Git仓库组信息每天凌晨2点执行', 2, 'repositoryGroupServiceImpl', 'syncGroups', NULL, '{\"externalSystemId\": 4}', '0 0 3 * * ?', 'DISABLED', b'0', NOW(), NOW(), 0, 0, 0, 3600, 2, '');
INSERT INTO `deploy-ease-platform`.`schedule_job` (`id`, `create_by`, `create_time`, `update_by`, `update_time`, `version`, `deleted`, `job_name`, `job_description`, `category_id`, `bean_name`, `method_name`, `form_definition_id`, `method_params`, `cron_expression`, `status`, `concurrent`, `last_execute_time`, `next_execute_time`, `execute_count`, `success_count`, `fail_count`, `timeout_seconds`, `retry_count`, `alert_email`) VALUES (15, 'admin', NOW(), 'admin', NOW(), 1212, b'0', '隆基Git项目同步', '定期同步Git项目信息每天凌晨3点执行', 2, 'repositoryProjectServiceImpl', 'syncProjects', NULL, '{\"externalSystemId\": 4}', '0 */5 * * * ?', 'DISABLED', b'0', NOW(), NOW(), 7, 7, 0, 3600, 2, '');
INSERT INTO `deploy-ease-platform`.`schedule_job` (`id`, `create_by`, `create_time`, `update_by`, `update_time`, `version`, `deleted`, `job_name`, `job_description`, `category_id`, `bean_name`, `method_name`, `form_definition_id`, `method_params`, `cron_expression`, `status`, `concurrent`, `last_execute_time`, `next_execute_time`, `execute_count`, `success_count`, `fail_count`, `timeout_seconds`, `retry_count`, `alert_email`) VALUES (16, 'admin', NOW(), 'admin', NOW(), 5727, b'0', '隆基Git分支同步', '定期同步Git仓库分支信息每5分钟执行一次', 2, 'repositoryBranchServiceImpl', 'syncBranches', NULL, '{\"externalSystemId\": 4}', '0 */5 * * * ?', 'DISABLED', b'0', NOW(), NOW(), 7, 7, 0, 3600, 2, '');
INSERT INTO `deploy-ease-platform`.`schedule_job` (`id`, `create_by`, `create_time`, `update_by`, `update_time`, `version`, `deleted`, `job_name`, `job_description`, `category_id`, `bean_name`, `method_name`, `form_definition_id`, `method_params`, `cron_expression`, `status`, `concurrent`, `last_execute_time`, `next_execute_time`, `execute_count`, `success_count`, `fail_count`, `timeout_seconds`, `retry_count`, `alert_email`) VALUES (17, 'admin', NOW(), 'admin', NOW(), 20, b'0', '服务器预警', '', 4, 'serverMonitorScheduler', 'collectServerMetrics', NULL, '{\"notificationChannelId\": 5, \"serverOfflineTemplateId\": 12, \"resourceAlertTemplateId\": 11}', '0 */5 * * * ?', 'DISABLED', b'0', NOW(), NOW(), 15, 15, 0, 300, 0, '');