diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/ServerAlertRuleDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/ServerAlertRuleDTO.java index f528405d..b306542b 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/ServerAlertRuleDTO.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/ServerAlertRuleDTO.java @@ -1,6 +1,6 @@ package com.qqchen.deploy.backend.deploy.dto; -import com.qqchen.deploy.backend.deploy.enums.AlertTypeEnum; +import com.qqchen.deploy.backend.framework.enums.MonitorMetricEnum; import com.qqchen.deploy.backend.framework.dto.BaseDTO; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.*; @@ -25,9 +25,9 @@ public class ServerAlertRuleDTO extends BaseDTO { @Size(max = 100, message = "规则名称长度不能超过100个字符") private String ruleName; - @Schema(description = "告警类型: CPU/MEMORY/DISK", required = true) - @NotNull(message = "告警类型不能为空") - private AlertTypeEnum alertType; + @Schema(description = "监控指标类型: CPU/MEMORY/DISK/NETWORK", required = true) + @NotNull(message = "监控指标类型不能为空") + private MonitorMetricEnum alertType; @Schema(description = "警告阈值(%)", required = true, example = "80.00") @NotNull(message = "警告阈值不能为空") @@ -48,13 +48,6 @@ public class ServerAlertRuleDTO extends BaseDTO { @Schema(description = "是否启用") private Boolean enabled; - @Schema(description = "通知方式: EMAIL/SMS/WEBHOOK") - @Size(max = 50, message = "通知方式长度不能超过50个字符") - private String notifyMethod; - - @Schema(description = "通知联系人(JSON格式)") - private String notifyContacts; - @Schema(description = "规则描述") @Size(max = 500, message = "描述长度不能超过500个字符") private String description; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/ServerMonitorDataDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/ServerMonitorDataDTO.java index 4065dea8..db657ffc 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/ServerMonitorDataDTO.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/ServerMonitorDataDTO.java @@ -36,6 +36,9 @@ public class ServerMonitorDataDTO { @Schema(description = "各分区磁盘使用率") private List diskUsage; + @Schema(description = "网络使用率(MB/s)") + private BigDecimal networkUsage; + @Schema(description = "采集时间") private LocalDateTime collectTime; } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/entity/ServerAlertLog.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/entity/ServerAlertLog.java index a9a06886..342aeb21 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/entity/ServerAlertLog.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/entity/ServerAlertLog.java @@ -1,7 +1,7 @@ package com.qqchen.deploy.backend.deploy.entity; -import com.qqchen.deploy.backend.deploy.enums.AlertLevelEnum; -import com.qqchen.deploy.backend.deploy.enums.AlertTypeEnum; +import com.qqchen.deploy.backend.framework.enums.MonitorAlertLevelEnum; +import com.qqchen.deploy.backend.framework.enums.MonitorMetricEnum; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; @@ -39,18 +39,18 @@ public class ServerAlertLog { private Long ruleId; /** - * 告警类型 + * 监控指标类型 */ @Enumerated(EnumType.STRING) @Column(name = "alert_type", nullable = false, length = 20) - private AlertTypeEnum alertType; + private MonitorMetricEnum alertType; /** * 告警级别 */ @Enumerated(EnumType.STRING) @Column(name = "alert_level", nullable = false, length = 20) - private AlertLevelEnum alertLevel; + private MonitorAlertLevelEnum alertLevel; /** * 当前值 diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/entity/ServerAlertRule.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/entity/ServerAlertRule.java index 11a32928..89caccd5 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/entity/ServerAlertRule.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/entity/ServerAlertRule.java @@ -1,7 +1,7 @@ package com.qqchen.deploy.backend.deploy.entity; -import com.qqchen.deploy.backend.deploy.enums.AlertTypeEnum; import com.qqchen.deploy.backend.framework.annotation.LogicDelete; +import com.qqchen.deploy.backend.framework.enums.MonitorMetricEnum; import com.qqchen.deploy.backend.framework.domain.Entity; import jakarta.persistence.*; import lombok.AllArgsConstructor; @@ -36,11 +36,11 @@ public class ServerAlertRule extends Entity { private String ruleName; /** - * 告警类型 + * 监控指标类型 */ @Enumerated(EnumType.STRING) @Column(name = "alert_type", nullable = false, length = 20) - private AlertTypeEnum alertType; + private MonitorMetricEnum alertType; /** * 警告阈值(%) @@ -66,18 +66,6 @@ public class ServerAlertRule extends Entity { @Column(name = "enabled") private Boolean enabled = true; - /** - * 通知方式: EMAIL/SMS/WEBHOOK - */ - @Column(name = "notify_method", length = 50) - private String notifyMethod; - - /** - * 通知联系人(JSON格式) - */ - @Column(name = "notify_contacts", columnDefinition = "JSON") - private String notifyContacts; - /** * 规则描述 */ diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/enums/AlertTypeEnum.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/enums/AlertTypeEnum.java deleted file mode 100644 index 760c999a..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/enums/AlertTypeEnum.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.qqchen.deploy.backend.deploy.enums; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -/** - * 告警类型枚举 - */ -@Getter -@AllArgsConstructor -public enum AlertTypeEnum { - /** - * CPU告警 - */ - CPU("CPU", "CPU使用率"), - - /** - * 内存告警 - */ - MEMORY("MEMORY", "内存使用率"), - - /** - * 磁盘告警 - */ - DISK("DISK", "磁盘使用率"); - - private final String code; - private final String description; -} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/impl/BaseExternalSystemIntegration.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/impl/BaseExternalSystemIntegration.java index 8ada2067..043cd6e1 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/impl/BaseExternalSystemIntegration.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/impl/BaseExternalSystemIntegration.java @@ -1,7 +1,7 @@ package com.qqchen.deploy.backend.deploy.integration.impl; import com.qqchen.deploy.backend.deploy.entity.ExternalSystem; -import com.qqchen.deploy.backend.framework.util.SensitiveDataEncryptor; +import com.qqchen.deploy.backend.framework.utils.SensitiveDataEncryptor; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/monitor/ServerMonitorThresholdCheckerFactory.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/monitor/ServerMonitorThresholdCheckerFactory.java new file mode 100644 index 00000000..299243ac --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/monitor/ServerMonitorThresholdCheckerFactory.java @@ -0,0 +1,52 @@ +package com.qqchen.deploy.backend.deploy.monitor; + +import com.qqchen.deploy.backend.deploy.dto.ServerMonitorDataDTO; +import com.qqchen.deploy.backend.framework.enums.MonitorMetricEnum; +import com.qqchen.deploy.backend.framework.monitor.ServerThresholdCheckerFactory; +import com.qqchen.deploy.backend.framework.monitor.impl.CpuThresholdChecker; +import com.qqchen.deploy.backend.framework.monitor.impl.DiskThresholdChecker; +import com.qqchen.deploy.backend.framework.monitor.impl.MemoryThresholdChecker; +import com.qqchen.deploy.backend.framework.monitor.impl.NetworkThresholdChecker; +import jakarta.annotation.PostConstruct; +import org.springframework.stereotype.Component; + +import java.math.BigDecimal; + +/** + * 服务器监控阈值检测器工厂 + * Deploy 业务层使用 Framework 提供的检测器能力 + * + * @author qqchen + * @since 2025-12-07 + */ +@Component +public class ServerMonitorThresholdCheckerFactory extends ServerThresholdCheckerFactory { + + @PostConstruct + @Override + protected void registerCheckers() { + // 注册 CPU 检测器 + checkers.put(MonitorMetricEnum.CPU, + new CpuThresholdChecker<>(ServerMonitorDataDTO::getCpuUsage)); + + // 注册内存检测器 + checkers.put(MonitorMetricEnum.MEMORY, + new MemoryThresholdChecker<>(ServerMonitorDataDTO::getMemoryUsage)); + + // 注册磁盘检测器(取所有分区中使用率最高的) + checkers.put(MonitorMetricEnum.DISK, + new DiskThresholdChecker<>(data -> { + if (data == null || data.getDiskUsage() == null || data.getDiskUsage().isEmpty()) { + return null; + } + return data.getDiskUsage().stream() + .map(disk -> disk.getUsagePercent()) + .max(BigDecimal::compareTo) + .orElse(null); + })); + + // 注册网络检测器 + checkers.put(MonitorMetricEnum.NETWORK, + new NetworkThresholdChecker<>(ServerMonitorDataDTO::getNetworkUsage)); + } +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/query/ServerAlertRuleQuery.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/query/ServerAlertRuleQuery.java index bc2fb070..cfebbe89 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/query/ServerAlertRuleQuery.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/query/ServerAlertRuleQuery.java @@ -1,6 +1,6 @@ package com.qqchen.deploy.backend.deploy.query; -import com.qqchen.deploy.backend.deploy.enums.AlertTypeEnum; +import com.qqchen.deploy.backend.framework.enums.MonitorMetricEnum; import com.qqchen.deploy.backend.framework.annotation.QueryField; import com.qqchen.deploy.backend.framework.enums.QueryType; import com.qqchen.deploy.backend.framework.query.BaseQuery; @@ -24,9 +24,9 @@ public class ServerAlertRuleQuery extends BaseQuery { @QueryField(field = "ruleName", type = QueryType.LIKE) private String ruleName; - @Schema(description = "告警类型") - @QueryField(field = "alertType") - private AlertTypeEnum alertType; + @Schema(description = "监控指标类型") + @QueryField(type = QueryType.EQUAL) + private MonitorMetricEnum alertType; @Schema(description = "是否启用") @QueryField(field = "enabled") diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/scheduler/ServerMonitorScheduler.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/scheduler/ServerMonitorScheduler.java index fd12f977..1c5f196d 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/scheduler/ServerMonitorScheduler.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/scheduler/ServerMonitorScheduler.java @@ -52,32 +52,44 @@ public class ServerMonitorScheduler { * 采集所有在线服务器的监控数据 * 此方法由定时任务管理系统调用 * - * @param config 通知配置(可选,为null则只记录数据不发通知) + * @param notificationChannelId 通知渠道ID(可选,为null则不发送通知) + * @param serverOfflineTemplateId 服务器离线通知模板ID(可选) + * @param resourceAlertTemplateId 资源告警通知模板ID(可选) */ - public void collectServerMetrics(ServerMonitorNotificationConfig config) { - if (config != null) { + public void collectServerMetrics(Long notificationChannelId, + Long serverOfflineTemplateId, + Long resourceAlertTemplateId) { + // 构建通知配置对象 + ServerMonitorNotificationConfig config = null; + if (notificationChannelId != null) { + config = new ServerMonitorNotificationConfig(); + config.setNotificationChannelId(notificationChannelId); + config.setServerOfflineTemplateId(serverOfflineTemplateId); + config.setResourceAlertTemplateId(resourceAlertTemplateId); + log.info("========== 开始采集服务器监控数据 ========== channelId={}, offlineTemplateId={}, alertTemplateId={}", - config.getNotificationChannelId(), config.getServerOfflineTemplateId(), config.getResourceAlertTemplateId()); + notificationChannelId, serverOfflineTemplateId, resourceAlertTemplateId); } else { log.info("========== 开始采集服务器监控数据(不发送通知) =========="); } long startTime = System.currentTimeMillis(); try { - // 1. 查询所有应该在线的服务器(状态为ONLINE) - List shouldBeOnlineServers = serverRepository - .findByStatusAndDeleted(ServerStatusEnum.ONLINE, false); + // 1. 查询所有服务器(不管当前状态),准备检测在线状态 + List allServers = serverRepository.findAll(); - if (shouldBeOnlineServers.isEmpty()) { + if (allServers.isEmpty()) { log.debug("没有需要监控的服务器,跳过监控采集"); return; } - log.info("发现 {} 台应在线服务器,开始检测状态", shouldBeOnlineServers.size()); + log.info("发现 {} 台服务器,开始检测在线状态并采集监控数据", allServers.size()); - // 2. 并发检测服务器连接状态并采集监控数据 + // 2. 并发检测所有服务器的连接状态并采集监控数据 + // - 连接失败 → 发送离线通知 + // - 连接成功 → 采集数据,检查阈值告警 final ServerMonitorNotificationConfig finalConfig = config; - List> futures = shouldBeOnlineServers.stream() + List> futures = allServers.stream() .map(server -> CompletableFuture.supplyAsync(() -> collectSingleServerWithStatusCheck(server, finalConfig))) .collect(Collectors.toList()); @@ -95,8 +107,8 @@ public class ServerMonitorScheduler { .collect(Collectors.toList()); long duration = System.currentTimeMillis() - startTime; - log.info("========== 监控数据采集完成: 成功={}/{}, 耗时={}ms ==========", - monitorDataList.size(), shouldBeOnlineServers.size(), duration); + log.info("========== 监控数据采集完成: 在线={}/{}, 耗时={}ms ==========", + monitorDataList.size(), allServers.size(), duration); // 5. 批量保存监控数据到数据库 if (!monitorDataList.isEmpty()) { @@ -152,7 +164,8 @@ public class ServerMonitorScheduler { Map templateParams = new HashMap<>(); templateParams.put("serverName", server.getServerName()); templateParams.put("serverIp", server.getHostIp()); - templateParams.put("offlineTime", LocalDateTime.now().toString()); + templateParams.put("offlineTime", LocalDateTime.now().format( + java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); // 2. 构建SendNotificationRequest SendNotificationRequest request = new SendNotificationRequest(); diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/ExternalSystemServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/ExternalSystemServiceImpl.java index b9d350c1..efbc26e3 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/ExternalSystemServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/ExternalSystemServiceImpl.java @@ -14,14 +14,13 @@ import com.qqchen.deploy.backend.deploy.service.IJenkinsBuildService; import com.qqchen.deploy.backend.deploy.service.IJenkinsJobService; import com.qqchen.deploy.backend.deploy.service.IJenkinsViewService; import com.qqchen.deploy.backend.system.enums.ExternalSystemAuthTypeEnum; -import com.qqchen.deploy.backend.system.enums.ExternalSystemSyncStatusEnum; import com.qqchen.deploy.backend.system.enums.ExternalSystemTypeEnum; import com.qqchen.deploy.backend.framework.annotation.ServiceType; import com.qqchen.deploy.backend.framework.enums.ResponseCode; import com.qqchen.deploy.backend.framework.exception.BusinessException; import com.qqchen.deploy.backend.framework.exception.UniqueConstraintException; import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; -import com.qqchen.deploy.backend.framework.util.SensitiveDataEncryptor; +import com.qqchen.deploy.backend.framework.utils.SensitiveDataEncryptor; import com.qqchen.deploy.backend.deploy.integration.IExternalSystemIntegration; import com.qqchen.deploy.backend.system.model.ExternalSystemDTO; import com.qqchen.deploy.backend.deploy.repository.IExternalSystemRepository; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/ServerAlertServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/ServerAlertServiceImpl.java index e3666cb1..1b4b89d9 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/ServerAlertServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/ServerAlertServiceImpl.java @@ -5,8 +5,8 @@ import com.qqchen.deploy.backend.deploy.dto.ServerMonitorNotificationConfig; import com.qqchen.deploy.backend.deploy.entity.Server; import com.qqchen.deploy.backend.deploy.entity.ServerAlertLog; import com.qqchen.deploy.backend.deploy.entity.ServerAlertRule; -import com.qqchen.deploy.backend.deploy.enums.AlertLevelEnum; -import com.qqchen.deploy.backend.deploy.enums.AlertTypeEnum; +import com.qqchen.deploy.backend.framework.enums.MonitorAlertLevelEnum; +import com.qqchen.deploy.backend.framework.enums.MonitorMetricEnum; import com.qqchen.deploy.backend.deploy.repository.IServerAlertLogRepository; import com.qqchen.deploy.backend.deploy.repository.IServerAlertRuleRepository; import com.qqchen.deploy.backend.deploy.repository.IServerRepository; @@ -98,14 +98,14 @@ public class ServerAlertServiceImpl implements IServerAlertService { } // 判断告警级别 - AlertLevelEnum alertLevel = null; + MonitorAlertLevelEnum alertLevel = null; BigDecimal threshold = null; if (currentValue.compareTo(rule.getCriticalThreshold()) >= 0) { - alertLevel = AlertLevelEnum.CRITICAL; + alertLevel = MonitorAlertLevelEnum.CRITICAL; threshold = rule.getCriticalThreshold(); } else if (currentValue.compareTo(rule.getWarningThreshold()) >= 0) { - alertLevel = AlertLevelEnum.WARNING; + alertLevel = MonitorAlertLevelEnum.WARNING; threshold = rule.getWarningThreshold(); } @@ -117,6 +117,7 @@ public class ServerAlertServiceImpl implements IServerAlertService { /** * 检查磁盘告警 + * 策略:计算所有分区的总使用率 = (所有已用空间总和) / (所有总容量) * 100% */ private void checkDiskAlert(Long serverId, ServerMonitorDataDTO monitorData, ServerAlertRule rule, ServerMonitorNotificationConfig config) { @@ -125,34 +126,50 @@ public class ServerAlertServiceImpl implements IServerAlertService { return; } - for (DiskUsageInfo diskUsage : diskUsageList) { - BigDecimal usagePercent = diskUsage.getUsagePercent(); - if (usagePercent == null) { - continue; - } - - AlertLevelEnum alertLevel = null; - BigDecimal threshold = null; - - if (usagePercent.compareTo(rule.getCriticalThreshold()) >= 0) { - alertLevel = AlertLevelEnum.CRITICAL; - threshold = rule.getCriticalThreshold(); - } else if (usagePercent.compareTo(rule.getWarningThreshold()) >= 0) { - alertLevel = AlertLevelEnum.WARNING; - threshold = rule.getWarningThreshold(); - } - - if (alertLevel != null) { - String resourceInfo = "磁盘[" + diskUsage.getMountPoint() + "]"; - triggerAlert(serverId, rule, alertLevel, usagePercent, threshold, resourceInfo, config); + // 计算所有分区的总容量和已用空间 + BigDecimal totalCapacity = BigDecimal.ZERO; // 总容量(GB) + BigDecimal totalUsed = BigDecimal.ZERO; // 已用空间(GB) + + for (DiskUsageInfo disk : diskUsageList) { + if (disk.getTotalSize() != null && disk.getUsedSize() != null) { + totalCapacity = totalCapacity.add(disk.getTotalSize()); + totalUsed = totalUsed.add(disk.getUsedSize()); } } + + if (totalCapacity.compareTo(BigDecimal.ZERO) == 0) { + return; + } + + // 计算总磁盘使用率 + BigDecimal totalUsagePercent = totalUsed + .multiply(new BigDecimal(100)) + .divide(totalCapacity, 2, BigDecimal.ROUND_HALF_UP); + + // 判断是否超过阈值 + MonitorAlertLevelEnum alertLevel = null; + BigDecimal threshold = null; + + if (totalUsagePercent.compareTo(rule.getCriticalThreshold()) >= 0) { + alertLevel = MonitorAlertLevelEnum.CRITICAL; + threshold = rule.getCriticalThreshold(); + } else if (totalUsagePercent.compareTo(rule.getWarningThreshold()) >= 0) { + alertLevel = MonitorAlertLevelEnum.WARNING; + threshold = rule.getWarningThreshold(); + } + + // 触发告警 + if (alertLevel != null) { + String resourceInfo = String.format("总磁盘(%d个分区,总容量%.0fGB,已用%.0fGB)", + diskUsageList.size(), totalCapacity.doubleValue(), totalUsed.doubleValue()); + triggerAlert(serverId, rule, alertLevel, totalUsagePercent, threshold, resourceInfo, config); + } } /** * 触发告警 */ - private void triggerAlert(Long serverId, ServerAlertRule rule, AlertLevelEnum level, + private void triggerAlert(Long serverId, ServerAlertRule rule, MonitorAlertLevelEnum level, BigDecimal currentValue, BigDecimal threshold, String resourceInfo, ServerMonitorNotificationConfig config) { // 1. 记录告警日志到数据库 @@ -199,7 +216,7 @@ public class ServerAlertServiceImpl implements IServerAlertService { /** * 发送告警通知 */ - private void sendAlertNotification(Long serverId, ServerAlertRule rule, AlertLevelEnum level, + private void sendAlertNotification(Long serverId, ServerAlertRule rule, MonitorAlertLevelEnum level, BigDecimal currentValue, BigDecimal threshold, String resourceInfo, ServerMonitorNotificationConfig config) { try { @@ -220,7 +237,8 @@ public class ServerAlertServiceImpl implements IServerAlertService { templateParams.put("resourceInfo", resourceInfo); templateParams.put("currentValue", String.format("%.2f", currentValue)); templateParams.put("threshold", String.format("%.2f", threshold)); - templateParams.put("alertTime", LocalDateTime.now().toString()); + templateParams.put("alertTime", LocalDateTime.now().format( + java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); // 3. 构建SendNotificationRequest SendNotificationRequest request = new SendNotificationRequest(); diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/enums/AlertLevelEnum.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/enums/MonitorAlertLevelEnum.java similarity index 59% rename from backend/src/main/java/com/qqchen/deploy/backend/deploy/enums/AlertLevelEnum.java rename to backend/src/main/java/com/qqchen/deploy/backend/framework/enums/MonitorAlertLevelEnum.java index 88a382df..50b1bc5a 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/enums/AlertLevelEnum.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/enums/MonitorAlertLevelEnum.java @@ -1,14 +1,18 @@ -package com.qqchen.deploy.backend.deploy.enums; +package com.qqchen.deploy.backend.framework.enums; import lombok.AllArgsConstructor; import lombok.Getter; /** - * 告警级别枚举 + * 监控告警级别枚举 + * Framework 提供的服务器监控告警级别 + * + * @author qqchen + * @since 2025-12-07 */ @Getter @AllArgsConstructor -public enum AlertLevelEnum { +public enum MonitorAlertLevelEnum { /** * 警告 */ diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/enums/MonitorMetricEnum.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/enums/MonitorMetricEnum.java new file mode 100644 index 00000000..7dc434d2 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/enums/MonitorMetricEnum.java @@ -0,0 +1,43 @@ +package com.qqchen.deploy.backend.framework.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 监控指标枚举 + * Framework 提供的服务器监控指标类型 + * + * @author qqchen + * @since 2025-12-07 + */ +@Getter +@AllArgsConstructor +public enum MonitorMetricEnum { + /** + * CPU告警(百分比) + */ + CPU("CPU", "CPU使用率", "%"), + + /** + * 内存告警(百分比) + */ + MEMORY("MEMORY", "内存使用率", "%"), + + /** + * 磁盘告警(百分比) + */ + DISK("DISK", "磁盘使用率", "%"), + + /** + * 网络告警(MB/s) + */ + NETWORK("NETWORK", "网络使用率", "MB/s"); + + private final String code; + private final String description; + + /** + * 度量单位 + */ + private final String unit; +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/monitor/AbstractServerThresholdChecker.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/monitor/AbstractServerThresholdChecker.java new file mode 100644 index 00000000..5511efd7 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/monitor/AbstractServerThresholdChecker.java @@ -0,0 +1,37 @@ +package com.qqchen.deploy.backend.framework.monitor; + +import com.qqchen.deploy.backend.framework.enums.MonitorAlertLevelEnum; + +import java.math.BigDecimal; + +/** + * 服务器阈值检测器抽象基类 + * 提供通用的阈值检测逻辑 + * + * @author qqchen + * @since 2025-12-07 + */ +public abstract class AbstractServerThresholdChecker implements IServerThresholdChecker { + + @Override + public MonitorAlertLevelEnum checkThreshold(BigDecimal currentValue, + BigDecimal warningThreshold, + BigDecimal criticalThreshold) { + if (currentValue == null) { + return null; + } + + // 检查是否超过严重阈值 + if (criticalThreshold != null && currentValue.compareTo(criticalThreshold) >= 0) { + return MonitorAlertLevelEnum.CRITICAL; + } + + // 检查是否超过警告阈值 + if (warningThreshold != null && currentValue.compareTo(warningThreshold) >= 0) { + return MonitorAlertLevelEnum.WARNING; + } + + // 未超过阈值 + return null; + } +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/monitor/IServerThresholdChecker.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/monitor/IServerThresholdChecker.java new file mode 100644 index 00000000..0f849ace --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/monitor/IServerThresholdChecker.java @@ -0,0 +1,43 @@ +package com.qqchen.deploy.backend.framework.monitor; + +import com.qqchen.deploy.backend.framework.enums.MonitorAlertLevelEnum; + +import java.math.BigDecimal; + +/** + * 服务器阈值检测器接口 + * Framework 提供的服务器监控指标阈值检测能力 + * + * 每种监控指标(CPU/内存/磁盘/网络)实现自己的阈值检测逻辑 + * + * @author qqchen + * @since 2025-12-07 + */ +public interface IServerThresholdChecker { + + /** + * 获取当前指标值 + * + * @param monitorData 监控数据 + * @return 当前指标值 + */ + BigDecimal getCurrentValue(T monitorData); + + /** + * 检查是否超过阈值并触发告警 + * + * @param currentValue 当前值 + * @param warningThreshold 警告阈值 + * @param criticalThreshold 严重阈值 + * @return 告警级别(null表示未超过阈值) + */ + MonitorAlertLevelEnum checkThreshold(BigDecimal currentValue, BigDecimal warningThreshold, BigDecimal criticalThreshold); + + /** + * 获取资源信息描述(用于通知消息) + * + * @param monitorData 监控数据 + * @return 资源描述,如 "CPU使用率", "内存使用率", "磁盘(/data)使用率" + */ + String getResourceInfo(T monitorData); +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/monitor/ServerMonitorService.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/monitor/ServerMonitorService.java new file mode 100644 index 00000000..321dd946 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/monitor/ServerMonitorService.java @@ -0,0 +1,55 @@ +package com.qqchen.deploy.backend.framework.monitor; + +import com.qqchen.deploy.backend.framework.enums.MonitorAlertLevelEnum; +import com.qqchen.deploy.backend.framework.enums.MonitorMetricEnum; +import com.qqchen.deploy.backend.framework.monitor.dto.ThresholdCheckResult; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; + +/** + * 服务器监控服务 + * Framework 提供的阈值检测能力 + * + * SSH采集由业务层自行处理(ServerMonitorScheduler) + * + * @author qqchen + * @since 2025-12-07 + */ +@Slf4j +@Service +public class ServerMonitorService { + + /** + * 检查阈值 + * 使用检测器检查指标是否超过阈值 + * + * @param metric 监控指标类型 + * @param monitorData 监控数据 + * @param warningThreshold 警告阈值 + * @param criticalThreshold 严重阈值 + * @param checker 阈值检测器 + * @return 检测结果 + */ + public ThresholdCheckResult checkThreshold(MonitorMetricEnum metric, + T monitorData, + BigDecimal warningThreshold, + BigDecimal criticalThreshold, + IServerThresholdChecker checker) { + // 获取当前值 + BigDecimal currentValue = checker.getCurrentValue(monitorData); + + // 检查阈值 + MonitorAlertLevelEnum alertLevel = checker.checkThreshold( + currentValue, warningThreshold, criticalThreshold); + + // 构建结果 + return ThresholdCheckResult.builder() + .metric(metric) + .currentValue(currentValue) + .alertLevel(alertLevel) + .resourceInfo(checker.getResourceInfo(monitorData)) + .build(); + } +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/monitor/ServerThresholdCheckerFactory.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/monitor/ServerThresholdCheckerFactory.java new file mode 100644 index 00000000..f58b9118 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/monitor/ServerThresholdCheckerFactory.java @@ -0,0 +1,39 @@ +package com.qqchen.deploy.backend.framework.monitor; + +import com.qqchen.deploy.backend.framework.enums.MonitorMetricEnum; + +import java.util.HashMap; +import java.util.Map; + +/** + * 服务器阈值检测器工厂抽象类 + * Framework 提供的工厂能力 + * + * @author qqchen + * @since 2025-12-07 + */ +public abstract class ServerThresholdCheckerFactory { + + protected Map> checkers = new HashMap<>(); + + /** + * 获取指定监控指标的检测器 + * + * @param metric 监控指标类型 + * @return 对应的检测器 + * @throws IllegalArgumentException 如果找不到对应的检测器 + */ + public IServerThresholdChecker getChecker(MonitorMetricEnum metric) { + IServerThresholdChecker checker = checkers.get(metric); + if (checker == null) { + throw new IllegalArgumentException("未找到监控指标的检测器: " + metric); + } + return checker; + } + + /** + * 注册检测器 + * 子类需要实现此方法来注册所有检测器 + */ + protected abstract void registerCheckers(); +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/monitor/dto/ThresholdCheckResult.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/monitor/dto/ThresholdCheckResult.java new file mode 100644 index 00000000..a18d1c8c --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/monitor/dto/ThresholdCheckResult.java @@ -0,0 +1,44 @@ +package com.qqchen.deploy.backend.framework.monitor.dto; + +import com.qqchen.deploy.backend.framework.enums.MonitorAlertLevelEnum; +import com.qqchen.deploy.backend.framework.enums.MonitorMetricEnum; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; + +/** + * 阈值检测结果 + * Framework 提供的检测结果数据结构 + * + * @author qqchen + * @since 2025-12-07 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ThresholdCheckResult { + + /** + * 监控指标类型 + */ + private MonitorMetricEnum metric; + + /** + * 当前值 + */ + private BigDecimal currentValue; + + /** + * 告警级别(null表示未超过阈值) + */ + private MonitorAlertLevelEnum alertLevel; + + /** + * 资源信息描述 + */ + private String resourceInfo; +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/monitor/impl/CpuThresholdChecker.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/monitor/impl/CpuThresholdChecker.java new file mode 100644 index 00000000..fe798469 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/monitor/impl/CpuThresholdChecker.java @@ -0,0 +1,35 @@ +package com.qqchen.deploy.backend.framework.monitor.impl; + +import com.qqchen.deploy.backend.framework.monitor.AbstractServerThresholdChecker; +import lombok.extern.slf4j.Slf4j; + +import java.math.BigDecimal; + +/** + * CPU阈值检测器 + * Framework 提供的CPU使用率检测能力 + * + * 通过函数式接口传入取值逻辑,实现通用性 + * + * @author qqchen + * @since 2025-12-07 + */ +@Slf4j +public class CpuThresholdChecker extends AbstractServerThresholdChecker { + + private final java.util.function.Function valueExtractor; + + public CpuThresholdChecker(java.util.function.Function valueExtractor) { + this.valueExtractor = valueExtractor; + } + + @Override + public BigDecimal getCurrentValue(T monitorData) { + return monitorData != null ? valueExtractor.apply(monitorData) : null; + } + + @Override + public String getResourceInfo(T monitorData) { + return "CPU使用率"; + } +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/monitor/impl/DiskThresholdChecker.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/monitor/impl/DiskThresholdChecker.java new file mode 100644 index 00000000..e2ffa622 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/monitor/impl/DiskThresholdChecker.java @@ -0,0 +1,35 @@ +package com.qqchen.deploy.backend.framework.monitor.impl; + +import com.qqchen.deploy.backend.framework.monitor.AbstractServerThresholdChecker; +import lombok.extern.slf4j.Slf4j; + +import java.math.BigDecimal; + +/** + * 磁盘阈值检测器 + * Framework 提供的磁盘使用率检测能力 + * + * 通过函数式接口传入取值逻辑,实现通用性 + * + * @author qqchen + * @since 2025-12-07 + */ +@Slf4j +public class DiskThresholdChecker extends AbstractServerThresholdChecker { + + private final java.util.function.Function valueExtractor; + + public DiskThresholdChecker(java.util.function.Function valueExtractor) { + this.valueExtractor = valueExtractor; + } + + @Override + public BigDecimal getCurrentValue(T monitorData) { + return monitorData != null ? valueExtractor.apply(monitorData) : null; + } + + @Override + public String getResourceInfo(T monitorData) { + return "磁盘使用率"; + } +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/monitor/impl/MemoryThresholdChecker.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/monitor/impl/MemoryThresholdChecker.java new file mode 100644 index 00000000..1c99d1ae --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/monitor/impl/MemoryThresholdChecker.java @@ -0,0 +1,35 @@ +package com.qqchen.deploy.backend.framework.monitor.impl; + +import com.qqchen.deploy.backend.framework.monitor.AbstractServerThresholdChecker; +import lombok.extern.slf4j.Slf4j; + +import java.math.BigDecimal; + +/** + * 内存阈值检测器 + * Framework 提供的内存使用率检测能力 + * + * 通过函数式接口传入取值逻辑,实现通用性 + * + * @author qqchen + * @since 2025-12-07 + */ +@Slf4j +public class MemoryThresholdChecker extends AbstractServerThresholdChecker { + + private final java.util.function.Function valueExtractor; + + public MemoryThresholdChecker(java.util.function.Function valueExtractor) { + this.valueExtractor = valueExtractor; + } + + @Override + public BigDecimal getCurrentValue(T monitorData) { + return monitorData != null ? valueExtractor.apply(monitorData) : null; + } + + @Override + public String getResourceInfo(T monitorData) { + return "内存使用率"; + } +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/monitor/impl/NetworkThresholdChecker.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/monitor/impl/NetworkThresholdChecker.java new file mode 100644 index 00000000..13485326 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/monitor/impl/NetworkThresholdChecker.java @@ -0,0 +1,35 @@ +package com.qqchen.deploy.backend.framework.monitor.impl; + +import com.qqchen.deploy.backend.framework.monitor.AbstractServerThresholdChecker; +import lombok.extern.slf4j.Slf4j; + +import java.math.BigDecimal; + +/** + * 网络阈值检测器 + * Framework 提供的网络使用率检测能力 + * + * 通过函数式接口传入取值逻辑,实现通用性 + * + * @author qqchen + * @since 2025-12-07 + */ +@Slf4j +public class NetworkThresholdChecker extends AbstractServerThresholdChecker { + + private final java.util.function.Function valueExtractor; + + public NetworkThresholdChecker(java.util.function.Function valueExtractor) { + this.valueExtractor = valueExtractor; + } + + @Override + public BigDecimal getCurrentValue(T monitorData) { + return monitorData != null ? valueExtractor.apply(monitorData) : null; + } + + @Override + public String getResourceInfo(T monitorData) { + return "网络使用率"; + } +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/util/EncryptionUtils.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/utils/EncryptionUtils.java similarity index 99% rename from backend/src/main/java/com/qqchen/deploy/backend/framework/util/EncryptionUtils.java rename to backend/src/main/java/com/qqchen/deploy/backend/framework/utils/EncryptionUtils.java index 6d2256f7..179a9130 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/framework/util/EncryptionUtils.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/utils/EncryptionUtils.java @@ -1,4 +1,4 @@ -package com.qqchen.deploy.backend.framework.util; +package com.qqchen.deploy.backend.framework.utils; import org.springframework.security.crypto.encrypt.Encryptors; import org.springframework.security.crypto.encrypt.TextEncryptor; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/util/SensitiveDataEncryptor.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/utils/SensitiveDataEncryptor.java similarity index 98% rename from backend/src/main/java/com/qqchen/deploy/backend/framework/util/SensitiveDataEncryptor.java rename to backend/src/main/java/com/qqchen/deploy/backend/framework/utils/SensitiveDataEncryptor.java index e2cdf570..6cddce78 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/framework/util/SensitiveDataEncryptor.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/utils/SensitiveDataEncryptor.java @@ -1,4 +1,4 @@ -package com.qqchen.deploy.backend.framework.util; +package com.qqchen.deploy.backend.framework.utils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/notification/service/impl/NotificationChannelServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/notification/service/impl/NotificationChannelServiceImpl.java index 7251302c..b241fe41 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/notification/service/impl/NotificationChannelServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/notification/service/impl/NotificationChannelServiceImpl.java @@ -40,6 +40,8 @@ public class NotificationChannelServiceImpl extends BaseServiceImpl new BusinessException(ResponseCode.DATA_NOT_FOUND)); - log.info("开始测试通知渠道连接: id={}, name={}, type={}", + log.info("开始测试通知渠道: id={}, name={}, type={}", id, channel.getName(), channel.getChannelType()); - // 2. 获取对应的适配器 - INotificationChannelAdapter adapter; - try { - adapter = adapterFactory.getAdapter(channel.getChannelType()); - } catch (IllegalArgumentException e) { - log.error("获取通知渠道适配器失败: {}", e.getMessage()); - throw new BusinessException(ResponseCode.ERROR, new Object[] {"不支持的渠道类型: " + channel.getChannelType()}); - } + // 2. 创建测试消息 + BaseSendNotificationRequest testRequest = createTestRequest(channel); - // 3. 转换配置 - BaseNotificationConfig config = convertConfig(channel); - - // 4. 执行连接测试 + // 3. 发送测试消息(复用 send 逻辑,测试完整的发送流程) try { - adapter.testConnection(config); - log.info("通知渠道连接测试成功: id={}, name={}", id, channel.getName()); + notificationSendService.send(channel, testRequest); + log.info("通知渠道测试成功: id={}, name={}", id, channel.getName()); return true; } catch (Exception e) { - log.error("通知渠道连接测试失败: id={}, name={}, 错误: {}", + log.error("通知渠道测试失败: id={}, name={}, 错误: {}", id, channel.getName(), e.getMessage(), e); - throw new BusinessException(ResponseCode.ERROR, new Object[] {"连接测试失败: " + e.getMessage()}); + throw new BusinessException(ResponseCode.ERROR, new Object[] {"测试失败: " + e.getMessage()}); + } + } + + /** + * 创建测试消息 + */ + private BaseSendNotificationRequest createTestRequest(NotificationChannel channel) { + String testContent = String.format( + "【通知渠道测试】\n" + + "渠道名称:%s\n" + + "渠道类型:%s\n" + + "测试时间:%s\n\n" + + "如果您收到此消息,说明通知渠道配置正确!", + channel.getName(), + channel.getChannelType().getDescription(), + java.time.LocalDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + ); + + switch (channel.getChannelType()) { + case WEWORK -> { + com.qqchen.deploy.backend.notification.dto.WeworkSendNotificationRequest request = + new com.qqchen.deploy.backend.notification.dto.WeworkSendNotificationRequest(); + request.setContent(testContent); + request.setTitle("【测试消息】通知渠道连接测试"); + request.setMessageType(com.qqchen.deploy.backend.notification.enums.WeworkMessageTypeEnum.TEXT); + return request; + } + case EMAIL -> { + com.qqchen.deploy.backend.notification.dto.EmailSendNotificationRequest request = + new com.qqchen.deploy.backend.notification.dto.EmailSendNotificationRequest(); + request.setContent(testContent); + request.setTitle("【测试消息】通知渠道连接测试"); + request.setToReceivers(java.util.Arrays.asList("admin@company.com")); + return request; + } + default -> throw new BusinessException(ResponseCode.ERROR, + new Object[]{"不支持的渠道类型: " + channel.getChannelType()}); } } diff --git a/backend/src/main/resources/db/changelog/changes/v1.0.0-schema.sql b/backend/src/main/resources/db/changelog/changes/v1.0.0-schema.sql index 3f237fcf..34f39a75 100644 --- a/backend/src/main/resources/db/changelog/changes/v1.0.0-schema.sql +++ b/backend/src/main/resources/db/changelog/changes/v1.0.0-schema.sql @@ -1212,10 +1212,6 @@ CREATE TABLE deploy_server_alert_rule -- 是否启用 enabled BOOLEAN DEFAULT TRUE COMMENT '是否启用', - -- 通知方式 - notify_method VARCHAR(50) NULL COMMENT '通知方式: EMAIL/SMS/WEBHOOK', - notify_contacts JSON NULL COMMENT '通知联系人', - -- 描述 description VARCHAR(500) NULL COMMENT '规则描述',