Cloudflare 通知转发钉钉机器人

今天想着把 Cloudflare 账单通知打开,然后把通知推到钉钉群里方便查看。 结果发现邮件通知正常收到了,钉钉那边却一点反应都没有。 排查之后发现是 Cloudflare 发出的 Webhook payload 和钉钉机器人要求的格式对不上。 于是搓了个 Cloudflare Worker 做中...
Cloudflare 通知转发钉钉机器人
Cloudflare 通知转发钉钉机器人

今天想着把 Cloudflare 账单通知打开,然后把通知推到钉钉群里方便查看。

结果发现邮件通知正常收到了,钉钉那边却一点反应都没有。

排查之后发现是 Cloudflare 发出的 Webhook payload 和钉钉机器人要求的格式对不上。

于是搓了个 Cloudflare Worker 做中间层转换,把 Cloudflare 的 payload 转换成钉钉能识别的格式。

1. 前置条件

  • 一个 Cloudflare 账号
  • 一个钉钉群,已创建自定义机器人,安全设置选择 加签模式

问题分析:为什么直接填 Webhook 没反应

Cloudflare 发送的 payload 格式:官方文档

{
  "name": "string",
  "text": "string",
  "data": {},
  "ts": 1136214245
}

钉钉机器人文本格式:官方文档

{
  "msgtype": "text",
  "text": {
    "content": "xxxx"
  }
}

2. 创建 Worker

在 Cloudflare 控制台进入 Workers & Pages,选择 Create Worker

  1. 选择 “Hello World” Worker
  2. 给 Worker 起个名字,比如 dingtalk-notify
  3. 点击 部署
  4. 部署完成后点击编辑代码

把默认代码全部删掉,替换为下面这段:

// 生成钉钉加签
async function generateSign (timestamp, secret) {
  const encoder = new TextEncoder ();
  const keyData = encoder.encode (secret);
  const message = encoder.encode (`${timestamp}\n${secret}`);

  const cryptoKey = await crypto.subtle.importKey (
    "raw",
    keyData,
    { name: "HMAC", hash: "SHA-256" },
    false,
    ["sign"]
  );

  const signature = await crypto.subtle.sign ("HMAC", cryptoKey, message);
  const sign = btoa (String.fromCharCode (...new Uint8Array (signature)));
  return encodeURIComponent (sign);
}

export default {
  async fetch (request, env, ctx) {
    if (request.method !== "POST") {
      return new Response (
        "This Worker only accepts POST requests from Cloudflare notifications.",
        { status: 405, headers: { "Content-Type": "text/plain" } }
      );
    }

    try {
      const text = await request.text ();
      if (!text) {
        return new Response (JSON.stringify ({ error: "Empty request body" }), {
          status: 400,
          headers: { "Content-Type": "application/json" },
        });
      }

      const cfPayload = JSON.parse (text);
      const title = cfPayload.name || "Cloudflare 通知";
      const content = cfPayload.text || "";

      const dingtalkBody = {
        msgtype: "text",
        text: { content: `${title}\n${content}` },
      };

      const timestamp = Date.now ();
      const sign = await generateSign (timestamp, env.DINGTALK_SECRET);

      const webhookUrl = new URL (env.DINGTALK_WEBHOOK_URL);
      webhookUrl.searchParams.set ("timestamp", timestamp);
      webhookUrl.searchParams.set ("sign", sign);

      const res = await fetch (webhookUrl.toString (), {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify (dingtalkBody),
      });

      const result = await res.text ();
      return new Response (result, { status: res.status });
    } catch (err) {
      return new Response (JSON.stringify ({ error: err.message }), {
        status: 500,
        headers: { "Content-Type": "application/json" },
      });
    }
  },
};

改完后点击 ** 部署 **。

3. 设置环境变量

在 Worker 页面,点击顶部 设置 标签,然后点击左侧 变量和机密

添加两个变量:

变量名 值 类型 DINGTALK_WEBHOOK_URL 你的钉钉 Webhook 地址,如 https://oapi.dingtalk.com/robot/send?access_token=xxx 密钥 DINGTALK_SECRET 钉钉机器人后台加签 SECxxxxxx 密钥

4. 修改 Cloudflare 通知目标

  1. 回到 Cloudflare 控制台的通知 Webhook 页面 目的地 -----> 创建
  2. 把 Webhook URL 改成你的 Worker 地址(格式类似 https://dingtalk-notify.xxx.workers.dev
  3. Secret 留空
  4. 点击 保存并测试

此时钉钉群里会收到一条测试通知。

5. 效果

配置完成后,Cloudflare 的通知就会通过 Worker 转发到钉钉群了

image

常见问题

  • 钉钉机器人后台的安全设置必须是加签模式(不是关键词模式),SECxxxxxx 才能生效。如果是关键词模式,要么改成加签,要么把 Worker 代码改成不带加签的版本。
  • 浏览器访问 Worker 地址返回 405? 这是正常的,Worker 只接受 POST 请求,需要用 Cloudflare 的通知去触发。

1 个帖子 - 1 位参与者

阅读完整话题

来源: LinuxDo 最新话题查看原文