Pre Merge pull request !806 from AprilWind/dev

This commit is contained in:
AprilWind 2026-03-04 08:35:02 +00:00 committed by Gitee
commit a0da9b4c61
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F

View File

@ -0,0 +1,116 @@
package com.aizuda.snailjob.common.core.alarm.strategy;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.net.url.UrlBuilder;
import cn.hutool.core.net.url.UrlPath;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.aizuda.snailjob.common.core.alarm.AlarmContext;
import com.aizuda.snailjob.common.core.alarm.attribute.WebhookAttribute;
import com.aizuda.snailjob.common.core.constant.SystemConstants;
import com.aizuda.snailjob.common.core.enums.AlarmTypeEnum;
import com.aizuda.snailjob.common.core.enums.ContentTypeEnum;
import com.aizuda.snailjob.common.core.util.JsonUtil;
import com.aizuda.snailjob.common.log.SnailJobLog;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
/**
* Webhook 告警策略
* <p>
* 设计原则
* 1. 框架只负责投递不关心业务协议
* 2. Payload 由使用者完全定义
* 3. 不绑定任何第三方 Webhook 规范
*
* @author AprilWind
*/
@Component
@RequiredArgsConstructor
public class WebhookAlarm extends AbstractAlarm<AlarmContext> {
/**
* 构建 Webhook 请求的 Payload
* <p>
* 设计原则
* 1. 用户完全自定义 Payload推荐做法
* - 如果 AlarmContext.notifyAttribute 中已经提供完整 JSON 字符串
* 框架会原样发送
* 2. 框架兜底结构保证最基本可用
* - title: 告警标题
* - text: 告警内容
* - timestamp: 当前时间戳
* 3. 支持根据 webhookUrl path query 自定义不同 Payload
* - /auth/webhook 纯文本发送
* - /markdown 使用 Markdown 格式
* - /third-party/alert 第三方自定义协议
* 4. 用户可扩展方式
* - notifyAttribute 中增加自定义字段例如 typelevel
* - 根据 webhookUrl 做路由发送不同 Payload
*
* @param context AlarmContext 对象包含 titletextnotifyAttribute 等信息
* @return JSON 字符串作为 Webhook 请求的 body
*/
private String buildPayload(AlarmContext context) {
// 解析 notifyAttribute获取 webhookUrl 等用户自定义信息
Map<String, Object> notifyAttribute = JsonUtil.parseHashMap(context.getNotifyAttribute());
String webhookUrl = Convert.toStr(notifyAttribute.get("webhookUrl"));
// 获取 URL path用于根据不同路径自定义 Payload
UrlPath path = UrlBuilder.ofHttp(webhookUrl).getPath();
if (path != null) {
// "/auth/webhook" "/markdown"
String fullPath = path.toString();
SnailJobLog.LOCAL.debug("Webhook URL path: [{}], 可以根据此路径自定义 payload", fullPath);
// TODO: 根据 fullPath 自定义不同的 payload
// 例如
// if (fullPath.endsWith("/markdown")) { 构建 Markdown payload }
// else if (fullPath.endsWith("/third-party/alert")) { 构建第三方协议 payload }
}
// 框架兜底 Payload保证最基本可用
Map<String, Object> defaultPayload = Map.of(
"title", context.getTitle(),
"text", context.getText(),
"timestamp", System.currentTimeMillis());
return JsonUtil.toJsonString(defaultPayload);
}
@Override
public Integer getAlarmType() {
return AlarmTypeEnum.WEBHOOK.getValue();
}
@Override
public boolean syncSendMessage(AlarmContext alarmContext) {
WebhookAttribute webhookAttribute = JsonUtil.parseObject(alarmContext.getNotifyAttribute(), WebhookAttribute.class);
try {
String webhookMessage = buildPayload(alarmContext);
HttpRequest post = HttpUtil.createPost(webhookAttribute.getWebhookUrl());
HttpRequest request = post.body(webhookMessage, ContentTypeEnum.valueOf(webhookAttribute.getContentType()).getMediaType().toString())
.header(SystemConstants.SECRET, webhookAttribute.getSecret());
HttpResponse execute = request.execute();
SnailJobLog.LOCAL.info("Sending Webhook alert result. webHook:[{}], result: [{}]", webhookAttribute.getWebhookUrl(), execute.body());
if (execute.isOk()) {
return true;
}
} catch (Exception e) {
SnailJobLog.LOCAL.error("Sending Webhook alert exception. webHook:[{}]", webhookAttribute, e);
return false;
}
return true;
}
@Override
public boolean asyncSendMessage(List<AlarmContext> alarmContexts) {
for (AlarmContext alarmContext : alarmContexts) {
asyncSendMessage(alarmContext);
}
return true;
}
}