mirror of
https://gitee.com/dromara/RuoYi-Vue-Plus.git
synced 2026-09-19 09:55:16 +08:00
Pre Merge pull request !568 from AprilWind/dev-monitor
This commit is contained in:
commit
951b550f9f
@ -50,6 +50,11 @@
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-mail</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@ -0,0 +1,90 @@
|
||||
package org.dromara.monitor.admin.config.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 通知配置
|
||||
*
|
||||
* @author AprilWind
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "notify")
|
||||
@Configuration
|
||||
public class NotifyProperties {
|
||||
|
||||
/**
|
||||
* 邮件设置
|
||||
*/
|
||||
private Mail mail;
|
||||
|
||||
/**
|
||||
* WebHooks 设置
|
||||
*/
|
||||
private WebHook webHook;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public static class Mail {
|
||||
|
||||
/**
|
||||
* 发送开关
|
||||
*/
|
||||
private Boolean enabled = false;
|
||||
|
||||
/**
|
||||
* 收件人
|
||||
*/
|
||||
private String to;
|
||||
|
||||
/**
|
||||
* 主题
|
||||
*/
|
||||
private String subject;
|
||||
|
||||
/**
|
||||
* 邮件模版
|
||||
*/
|
||||
private String template;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public static class WebHook {
|
||||
|
||||
/**
|
||||
* 发送开关
|
||||
*/
|
||||
private Boolean enabled = false;
|
||||
|
||||
/**
|
||||
* 0默认 1密码 2签名密钥
|
||||
*/
|
||||
private String type = "0";
|
||||
|
||||
/**
|
||||
* 签名密钥
|
||||
*/
|
||||
private String secret;
|
||||
|
||||
/**
|
||||
* 关键词
|
||||
*/
|
||||
private String keywords;
|
||||
|
||||
/**
|
||||
* Post地址
|
||||
*/
|
||||
private String url;
|
||||
|
||||
/**
|
||||
* WebHook发送模版
|
||||
*/
|
||||
private String template;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
package org.dromara.monitor.admin.event;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 通知事件
|
||||
*
|
||||
* @author AprilWind
|
||||
*/
|
||||
@Data
|
||||
public class NotifierEvent implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 实例注册名称
|
||||
*/
|
||||
private String registName;
|
||||
|
||||
/**
|
||||
* 实例状态名称
|
||||
*/
|
||||
private String statusName;
|
||||
|
||||
/**
|
||||
* 实例ID
|
||||
*/
|
||||
private String instanceId;
|
||||
|
||||
/**
|
||||
* 实例状态
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 服务URL
|
||||
*/
|
||||
private String serviceUrl;
|
||||
|
||||
}
|
||||
@ -6,6 +6,8 @@ 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.AbstractEventNotifier;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.monitor.admin.event.NotifierEvent;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@ -49,6 +51,13 @@ public class CustomNotifier extends AbstractEventNotifier {
|
||||
};
|
||||
log.info("Instance Status Change: 状态名称【{}】, 注册名称【{}】, 实例ID【{}】, 状态【{}】, 服务URL【{}】",
|
||||
statusName, registName, instanceId, status, serviceUrl);
|
||||
NotifierEvent notifier = new NotifierEvent();
|
||||
notifier.setRegistName(registName);
|
||||
notifier.setStatusName(statusName);
|
||||
notifier.setInstanceId(instanceId);
|
||||
notifier.setStatus(status);
|
||||
notifier.setServiceUrl(serviceUrl);
|
||||
SpringUtils.context().publishEvent(notifier);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -0,0 +1,206 @@
|
||||
package org.dromara.monitor.admin.notifier;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.utils.DateUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.mail.utils.MailUtils;
|
||||
import org.dromara.monitor.admin.config.properties.NotifyProperties;
|
||||
import org.dromara.monitor.admin.event.NotifierEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 信息通知
|
||||
*
|
||||
* @author AprilWind
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
@Service
|
||||
public class InfoNotifier {
|
||||
private final NotifyProperties notifyProperties;
|
||||
|
||||
/**
|
||||
* 处理服务通知事件
|
||||
* <p>
|
||||
* 此方法会在服务状态发生变化时触发,并且会异步地发送 WebHook 通知
|
||||
* 适用于服务的正常状态变更,不包括异常情况
|
||||
*
|
||||
* @param notifier 通知事件对象,包含服务的状态和其他相关信息
|
||||
*/
|
||||
@Async
|
||||
@EventListener
|
||||
public void infoNotification(NotifierEvent notifier) {
|
||||
sendWebHook(notifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理服务下线,离线或异常的通知事件
|
||||
* <p>
|
||||
* 当服务的状态为“OFFLINE”、“DOWN”或“UNKNOWN”时,会触发此方法
|
||||
* 该方法会异步发送邮件通知,以便及时处理服务问题
|
||||
*
|
||||
* @param notifier 通知事件对象,包含服务的状态和其他相关信息
|
||||
*/
|
||||
@Async
|
||||
@EventListener(condition = "#notifier.status == 'OFFLINE' || #notifier.status == 'DOWN' || #notifier.status == 'UNKNOWN'")
|
||||
public void errNotification(NotifierEvent notifier) {
|
||||
sendMail(notifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送邮件通知
|
||||
*
|
||||
* @param notifier 包含通知信息的对象
|
||||
*/
|
||||
public void sendMail(NotifierEvent notifier) {
|
||||
NotifyProperties.Mail mail = notifyProperties.getMail();
|
||||
if (mail.getEnabled()) {
|
||||
String message = StringUtils.format(mail.getTemplate(), notifier.getRegistName(), notifier.getInstanceId(),
|
||||
notifier.getStatusName(), notifier.getStatus(), notifier.getServiceUrl(), DateUtils.getTime());
|
||||
try {
|
||||
MailUtils.sendHtml(mail.getTo(), notifier.getRegistName() + notifier.getStatusName(), message);
|
||||
log.info("邮件已发送至: {}", mail.getTo());
|
||||
} catch (Exception e) {
|
||||
log.error("邮件发送失败: ", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送WebHook通知
|
||||
*
|
||||
* @param notifier 包含通知信息的对象
|
||||
*/
|
||||
public void sendWebHook(NotifierEvent notifier) {
|
||||
NotifyProperties.WebHook webHook = notifyProperties.getWebHook();
|
||||
if (webHook.getEnabled()) {
|
||||
String title = notifier.getRegistName() + notifier.getStatusName();
|
||||
String message = StringUtils.format(webHook.getTemplate(), title, notifier.getRegistName(),
|
||||
notifier.getInstanceId(), notifier.getStatusName(), notifier.getStatus(), notifier.getServiceUrl(), DateUtils.getTime());
|
||||
try {
|
||||
sendWebHookMessage(webHook, title, message);
|
||||
log.info("WebHook消息已发送至: {}", webHook.getUrl());
|
||||
} catch (Exception e) {
|
||||
log.error("WebHook消息发送失败: ", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送WebHook消息
|
||||
*
|
||||
* @param webHook WebHook配置信息
|
||||
* @param title 消息标题
|
||||
* @param markdownMessage 消息内容
|
||||
* @throws Exception 处理过程中可能抛出的异常
|
||||
*/
|
||||
private void sendWebHookMessage(NotifyProperties.WebHook webHook, String title, String markdownMessage) throws Exception {
|
||||
String jsonMessage = createJsonMessage(title, markdownMessage);
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
HttpRequest request = createHttpRequest(webHook, jsonMessage);
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
handleResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建消息体的JSON字符串
|
||||
*
|
||||
* @param title 消息标题
|
||||
* @param markdownMessage 消息内容
|
||||
* @return JSON格式的消息体
|
||||
* @throws Exception 处理过程中可能抛出的异常
|
||||
*/
|
||||
private String createJsonMessage(String title, String markdownMessage) throws Exception {
|
||||
Map<String, Object> messageBody = new HashMap<>();
|
||||
messageBody.put("msgtype", "markdown");
|
||||
Map<String, String> markdownContent = new HashMap<>();
|
||||
markdownContent.put("title", title + "通知");
|
||||
markdownContent.put("text", markdownMessage);
|
||||
messageBody.put("markdown", markdownContent);
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
return objectMapper.writeValueAsString(messageBody);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据WebHook配置创建HTTP请求
|
||||
*
|
||||
* @param webHook WebHook配置信息
|
||||
* @param jsonMessage JSON格式的消息体
|
||||
* @return 创建的HTTP请求
|
||||
* @throws Exception 处理过程中可能抛出的异常
|
||||
*/
|
||||
private HttpRequest createHttpRequest(NotifyProperties.WebHook webHook, String jsonMessage) throws Exception {
|
||||
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
|
||||
.header("Content-Type", "application/json")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(jsonMessage));
|
||||
|
||||
if ("2".equals(webHook.getType())) {
|
||||
String signedUrl = generateSignedUrl(webHook);
|
||||
requestBuilder.uri(new URI(signedUrl));
|
||||
} else {
|
||||
requestBuilder.uri(new URI(webHook.getUrl()));
|
||||
}
|
||||
|
||||
return requestBuilder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成带签名的URL
|
||||
*
|
||||
* @param webHook WebHook配置信息
|
||||
* @return 带签名的URL
|
||||
* @throws Exception 处理过程中可能抛出的异常
|
||||
*/
|
||||
private String generateSignedUrl(NotifyProperties.WebHook webHook) throws Exception {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
String secret = webHook.getSecret();
|
||||
String stringToSign = timestamp + "\n" + secret;
|
||||
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
|
||||
byte[] signData = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
|
||||
String sign = URLEncoder.encode(Base64.getEncoder().encodeToString(signData), StandardCharsets.UTF_8);
|
||||
|
||||
return String.format("%s×tamp=%d&sign=%s", webHook.getUrl(), timestamp, sign);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理HTTP响应
|
||||
*
|
||||
* @param response HTTP响应
|
||||
*/
|
||||
private void handleResponse(HttpResponse<String> response) {
|
||||
try {
|
||||
// 解析响应体
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
JsonNode jsonNode = objectMapper.readTree(response.body());
|
||||
int errcode = jsonNode.has("errcode") ? jsonNode.get("errcode").asInt() : -1;
|
||||
String errmsg = jsonNode.has("errmsg") ? jsonNode.get("errmsg").asText() : "Unknown error";
|
||||
if (errcode == 0) {
|
||||
log.info("WebHook消息发送成功");
|
||||
} else {
|
||||
log.error("WebHook消息发送失败: errcode={}, errmsg={}", errcode, errmsg);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("处理WebHook响应时发生异常: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -46,3 +46,51 @@ spring.boot.admin.client:
|
||||
userpassword: ${spring.boot.admin.client.password}
|
||||
username: ruoyi
|
||||
password: 123456
|
||||
|
||||
--- # mail 邮件发送
|
||||
mail:
|
||||
enabled: false
|
||||
host: smtp.163.com
|
||||
port: 465
|
||||
# 是否需要用户名密码验证
|
||||
auth: true
|
||||
# 发送方,遵循RFC-822标准
|
||||
from: xxx@163.com
|
||||
# 用户名(注意:如果使用foxmail邮箱,此处user为qq号)
|
||||
user: xxx@163.com
|
||||
# 密码(注意,某些邮箱需要为SMTP服务单独设置密码,详情查看相关帮助)
|
||||
pass: xxxxxxxxxx
|
||||
# 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。
|
||||
starttlsEnable: true
|
||||
# 使用SSL安全连接
|
||||
sslEnable: true
|
||||
# SMTP超时时长,单位毫秒,缺省值不超时
|
||||
timeout: 0
|
||||
# Socket连接超时值,单位毫秒,缺省值不超时
|
||||
connectionTimeout: 0
|
||||
|
||||
# 服务通知
|
||||
notify:
|
||||
mail:
|
||||
enabled: false
|
||||
to:
|
||||
subject: 系统通知
|
||||
template: "<html><body>
|
||||
<p><b>服务名称:</b> {}</p >
|
||||
<p><b>实例编号:</b> {}</p >
|
||||
<p><b>服务状态:</b> {}({})</p >
|
||||
<p><b>服务地址:</b> {}</p >
|
||||
<p><b>发送时间:</b> {}</p >
|
||||
</body></html>"
|
||||
web-hook:
|
||||
enabled: true
|
||||
type: 2
|
||||
secret: SEC4d035df24951f00445ea16022f0db2d442fdc83b34c6ebe15a427572264f6798
|
||||
url: https://oapi.dingtalk.com/robot/send?access_token=81a122dbbfbcbe237acdd9e20e421059fc2e0d80bf83ded4099b0fef9f9800ef
|
||||
template: |
|
||||
#### **{}**
|
||||
- **服务名称**: {}
|
||||
- **实例编号**: {}
|
||||
- **服务状态**: {}({})
|
||||
- **服务地址**: {}
|
||||
- **发送时间**: {}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user