From f64fe50108b6b47985babe1ea0604ae240251648 Mon Sep 17 00:00:00 2001 From: AprilWind <2100166581@qq.com> Date: Mon, 15 Dec 2025 17:11:25 +0800 Subject: [PATCH] =?UTF-8?q?add=20=E6=B7=BB=E5=8A=A0Webhook=20=E5=91=8A?= =?UTF-8?q?=E8=AD=A6=E7=AD=96=E7=95=A5=EF=BC=8C=E6=89=A9=E5=B1=95=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E8=87=AA=E5=AE=9A=E4=B9=89Webhook=E6=8A=95=E9=80=81?= =?UTF-8?q?=E5=8D=8F=E8=AE=AE=E6=96=87=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/alarm/strategy/WebhookAlarm.java | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 ruoyi-extend/ruoyi-snailjob-server/src/main/java/com/aizuda/snailjob/common/core/alarm/strategy/WebhookAlarm.java diff --git a/ruoyi-extend/ruoyi-snailjob-server/src/main/java/com/aizuda/snailjob/common/core/alarm/strategy/WebhookAlarm.java b/ruoyi-extend/ruoyi-snailjob-server/src/main/java/com/aizuda/snailjob/common/core/alarm/strategy/WebhookAlarm.java new file mode 100644 index 000000000..d4c03d028 --- /dev/null +++ b/ruoyi-extend/ruoyi-snailjob-server/src/main/java/com/aizuda/snailjob/common/core/alarm/strategy/WebhookAlarm.java @@ -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 告警策略 + *

+ * 设计原则: + * 1. 框架只负责投递,不关心业务协议 + * 2. Payload 由使用者完全定义 + * 3. 不绑定任何第三方 Webhook 规范 + * + * @author AprilWind + */ +@Component +@RequiredArgsConstructor +public class WebhookAlarm extends AbstractAlarm { + + /** + * 构建 Webhook 请求的 Payload + *

+ * 设计原则: + * 1. 用户完全自定义 Payload(推荐做法): + * - 如果 AlarmContext.notifyAttribute 中已经提供完整 JSON 字符串, + * 框架会原样发送。 + * 2. 框架兜底结构(保证最基本可用): + * - title: 告警标题 + * - text: 告警内容 + * - timestamp: 当前时间戳 + * 3. 支持根据 webhookUrl 的 path 或 query 自定义不同 Payload: + * - /auth/webhook → 纯文本发送 + * - /markdown → 使用 Markdown 格式 + * - /third-party/alert → 第三方自定义协议 + * 4. 用户可扩展方式: + * - 在 notifyAttribute 中增加自定义字段,例如 type、level 等 + * - 根据 webhookUrl 做路由,发送不同 Payload + * + * @param context AlarmContext 对象,包含 title、text、notifyAttribute 等信息 + * @return JSON 字符串,作为 Webhook 请求的 body + */ + private String buildPayload(AlarmContext context) { + // 解析 notifyAttribute,获取 webhookUrl 等用户自定义信息 + Map 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 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 alarmContexts) { + for (AlarmContext alarmContext : alarmContexts) { + asyncSendMessage(alarmContext); + } + return true; + } +}