Просмотр исходного кода

feat: 服务异常监控告警;

hanchaolong 1 неделя назад
Родитель
Сommit
98b297e601

+ 6 - 0
jd-logistics-visual/jd-logistics-monitor/pom.xml

@@ -53,6 +53,12 @@
             <artifactId>spring-boot-starter-security</artifactId>
         </dependency>
 		
+        <!-- Spring Boot Mail -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-mail</artifactId>
+        </dependency>
+		
     </dependencies>
 
     <build>

+ 44 - 0
jd-logistics-visual/jd-logistics-monitor/src/main/java/com/ruoyi/modules/monitor/config/AlarmEmailProperties.java

@@ -0,0 +1,44 @@
+package com.ruoyi.modules.monitor.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * 告警邮件配置属性
+ * 
+ * @author lydgt
+ */
+@Component
+@ConfigurationProperties(prefix = "alarm.email")
+public class AlarmEmailProperties
+{
+    /**
+     * 是否启用邮件告警
+     */
+    private boolean enabled = false;
+
+    /**
+     * 收件人列表,多个用逗号分隔
+     */
+    private String recipients;
+
+    public boolean isEnabled()
+    {
+        return enabled;
+    }
+
+    public void setEnabled(boolean enabled)
+    {
+        this.enabled = enabled;
+    }
+
+    public String getRecipients()
+    {
+        return recipients;
+    }
+
+    public void setRecipients(String recipients)
+    {
+        this.recipients = recipients;
+    }
+}

+ 153 - 0
jd-logistics-visual/jd-logistics-monitor/src/main/java/com/ruoyi/modules/monitor/listener/ServiceStatusListener.java

@@ -0,0 +1,153 @@
+package com.ruoyi.modules.monitor.listener;
+
+import de.codecentric.boot.admin.server.domain.entities.Instance;
+import de.codecentric.boot.admin.server.domain.entities.InstanceRepository;
+import de.codecentric.boot.admin.server.domain.events.InstanceEvent;
+import de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;
+import de.codecentric.boot.admin.server.notify.AbstractStatusChangeNotifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import com.ruoyi.modules.monitor.service.AlarmEmailService;
+import reactor.core.publisher.Mono;
+
+/**
+ * 服务状态变更通知器
+ * 通过继承AbstractStatusChangeNotifier实现自定义通知
+ * 
+ * @author lydgt
+ */
+@Component
+public class ServiceStatusListener extends AbstractStatusChangeNotifier
+{
+    private static final Logger log = LoggerFactory.getLogger(ServiceStatusListener.class);
+
+    private final AlarmEmailService alarmEmailService;
+
+    public ServiceStatusListener(InstanceRepository repository, AlarmEmailService alarmEmailService)
+    {
+        super(repository);
+        this.alarmEmailService = alarmEmailService;
+    }
+
+    /**
+     * 处理实例状态变更事件
+     */
+    @Override
+    protected Mono<Void> doNotify(InstanceEvent event, Instance instance)
+    {
+        return Mono.fromRunnable(() -> {
+            if (event instanceof InstanceStatusChangedEvent)
+            {
+                String status = ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus();
+                String serviceName = instance.getRegistration().getName();
+                String serviceUrl = instance.getRegistration().getServiceUrl();
+                
+                log.info("服务状态变更 - 服务: {}, URL: {}, 状态: {}", serviceName, serviceUrl, status);
+                
+                switch (status)
+                {
+                    // 健康检查没通过
+                    case "DOWN":
+                        log.warn("服务异常 - 服务: {}, 状态: DOWN", serviceName);
+                        sendAlarmEmail(serviceName, serviceUrl, status, "DOWN", event);
+                        break;
+                    // 服务离线
+                    case "OFFLINE":
+                        log.warn("服务离线 - 服务: {}, 状态: OFFLINE", serviceName);
+                        sendAlarmEmail(serviceName, serviceUrl, status, "OFFLINE", event);
+                        break;
+                    // 服务上线
+                    case "UP":
+                        log.info("服务恢复 - 服务: {}, 状态: UP", serviceName);
+                        sendRecoveryEmail(serviceName, serviceUrl, status);
+                        break;
+                    // 服务未知异常
+                    case "UNKNOWN":
+                        log.warn("服务未知异常 - 服务: {}, 状态: UNKNOWN", serviceName);
+                        sendAlarmEmail(serviceName, serviceUrl, status, "UNKNOWN", event);
+                        break;
+                    default:
+                        log.debug("服务状态变更 - 服务: {}, 状态: {}", serviceName, status);
+                        break;
+                }
+            }
+        });
+    }
+
+    /**
+     * 发送告警邮件
+     */
+    private void sendAlarmEmail(String serviceName, String serviceUrl, String status, String type, InstanceEvent event)
+    {
+        try
+        {
+            String subject = "【服务告警】服务异常通知 - " + serviceName;
+            String content = buildAlarmContent(serviceName, serviceUrl, status, type, event);
+            alarmEmailService.sendAlarmEmail(subject, content);
+            log.warn("已发送服务告警邮件 - 服务: {}, 类型: {}", serviceName, type);
+        }
+        catch (Exception e)
+        {
+            log.error("发送告警邮件失败 - 服务: {}", serviceName, e);
+        }
+    }
+
+    /**
+     * 发送恢复邮件
+     */
+    private void sendRecoveryEmail(String serviceName, String serviceUrl, String status)
+    {
+        try
+        {
+            String subject = "【服务恢复】服务恢复正常通知 - " + serviceName;
+            String content = buildRecoveryContent(serviceName, serviceUrl, status);
+            alarmEmailService.sendAlarmEmail(subject, content);
+            log.info("已发送服务恢复邮件 - 服务: {}", serviceName);
+        }
+        catch (Exception e)
+        {
+            log.error("发送恢复邮件失败 - 服务: {}", serviceName, e);
+        }
+    }
+
+    /**
+     * 构建告警邮件内容
+     */
+    private String buildAlarmContent(String serviceName, String serviceUrl, String status, String type, InstanceEvent event)
+    {
+        StringBuilder sb = new StringBuilder();
+        sb.append("<h2 style='color: red;'>服务异常告警</h2>");
+        sb.append("<p><strong>服务名称:</strong>").append(serviceName).append("</p>");
+        sb.append("<p><strong>服务地址:</strong>").append(serviceUrl).append("</p>");
+        sb.append("<p><strong>当前状态:</strong><span style='color: red;'>").append(status).append("</span></p>");
+        sb.append("<p><strong>告警类型:</strong>").append(type).append("</p>");
+        sb.append("<p><strong>告警时间:</strong>").append(java.time.LocalDateTime.now()).append("</p>");
+        
+        if (event instanceof InstanceStatusChangedEvent) {
+            Object details = ((InstanceStatusChangedEvent) event).getStatusInfo().getDetails();
+            if (details != null && !details.toString().isEmpty()) {
+                sb.append("<p><strong>详细信息:</strong></p><pre>").append(details.toString()).append("</pre>");
+            }
+        }
+        
+        sb.append("<hr><p style='color: gray;'>此邮件由监控系统自动发送,请勿回复。</p>");
+        return sb.toString();
+    }
+
+    /**
+     * 构建恢复邮件内容
+     */
+    private String buildRecoveryContent(String serviceName, String serviceUrl, String status)
+    {
+        StringBuilder sb = new StringBuilder();
+        sb.append("<h2 style='color: green;'>服务恢复正常</h2>");
+        sb.append("<p><strong>服务名称:</strong>").append(serviceName).append("</p>");
+        sb.append("<p><strong>服务地址:</strong>").append(serviceUrl).append("</p>");
+        sb.append("<p><strong>当前状态:</strong><span style='color: green;'>").append(status).append("</span></p>");
+        sb.append("<p><strong>恢复时间:</strong>").append(java.time.LocalDateTime.now()).append("</p>");
+        sb.append("<hr><p style='color: gray;'>此邮件由监控系统自动发送,请勿回复。</p>");
+        return sb.toString();
+    }
+}

+ 106 - 0
jd-logistics-visual/jd-logistics-monitor/src/main/java/com/ruoyi/modules/monitor/service/AlarmEmailService.java

@@ -0,0 +1,106 @@
+package com.ruoyi.modules.monitor.service;
+
+import java.util.Arrays;
+import java.util.List;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.mail.javamail.JavaMailSender;
+import org.springframework.mail.javamail.MimeMessageHelper;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import com.ruoyi.modules.monitor.config.AlarmEmailProperties;
+
+/**
+ * 告警邮件服务
+ * 
+ * @author lydgt
+ */
+@Service
+public class AlarmEmailService
+{
+    private static final Logger log = LoggerFactory.getLogger(AlarmEmailService.class);
+
+    @Autowired
+    private JavaMailSender mailSender;
+
+    @Autowired
+    private AlarmEmailProperties emailProperties;
+
+    @Value("${spring.mail.username}")
+    private String fromEmailAddress;
+
+    /**
+     * 发送告警邮件
+     * 
+     * @param subject 邮件主题
+     * @param content 邮件内容(HTML格式)
+     */
+    public void sendAlarmEmail(String subject, String content)
+    {
+        log.info("准备发送告警邮件 - 主题: {}", subject);
+        
+        // 检查是否启用邮件告警
+        if (!emailProperties.isEnabled())
+        {
+            log.info("邮件告警功能未启用,跳过发送");
+            return;
+        }
+
+        // 检查收件人配置
+        String recipients = emailProperties.getRecipients();
+        if (!StringUtils.hasText(recipients))
+        {
+            log.warn("收件人列表为空,无法发送告警邮件");
+            return;
+        }
+        
+        log.info("邮件配置 - 发件人: {}, 收件人: {}", fromEmailAddress, recipients);
+
+        try
+        {
+            MimeMessage mimeMessage = mailSender.createMimeMessage();
+            MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
+            
+            // 设置发件人
+            helper.setFrom(fromEmailAddress);
+            
+            // 设置收件人
+            List<String> recipientList = Arrays.asList(recipients.split(","));
+            String[] toAddresses = recipientList.stream()
+                    .map(String::trim)
+                    .filter(StringUtils::hasText)
+                    .toArray(String[]::new);
+            
+            if (toAddresses.length == 0) {
+                log.warn("有效的收件人列表为空,无法发送告警邮件");
+                return;
+            }
+            
+            helper.setTo(toAddresses);
+            
+            // 设置主题和内容
+            helper.setSubject(subject);
+            helper.setText(content, true); // true表示HTML内容
+            
+            // 发送邮件
+            log.info("开始发送邮件...");
+            mailSender.send(mimeMessage);
+            log.info("告警邮件发送成功 - 主题: {}, 收件人数量: {}", subject, toAddresses.length);
+        }
+        catch (MessagingException e)
+        {
+            log.error("发送告警邮件失败 - 主题: {}, 错误信息: {}", subject, e.getMessage(), e);
+        }
+        catch (Exception e)
+        {
+            log.error("发送告警邮件时发生未知错误 - 主题: {}, 错误信息: {}", subject, e.getMessage(), e);
+        }
+    }
+}