← 返回文章列表

Dify 插件开发实验(06):通知渠道插件——如何把 Dify 推送到钉钉/企业微信等渠道?

1. 业务场景

先讲一个我们实际遇到的场景。

客服工单 SaaS 的通知场景:用户提交工单后,客服要收到「新工单待处理」的提醒;工单状态变了,用户要收到「您的工单已更新」的消息。团队内部用企业微信,对外通知走钉钉风格 webhook,还可能对接自建的消息网关——同一个通知内容,要按渠道适配格式发出去,发失败了还要自动重试、能查回执。

我们第一次做通知时,第一反应也是「POST 一下 webhook 不就行了」。真正动手才发现——「发出去」和「确保送达」,是两回事:企微和钉钉的 payload 格式不一样,混着用直接发不出去;webhook 静默失败没人知道,工单晾几个小时没人处理;网络抖动失败不重试就丢通知,4xx 的错重试一百遍也没用。

这不是个例。任何「系统要主动触达用户/内部协作」的场景都是这个模式:工单通知、告警推送、审批提醒、营销触达——渠道多、格式杂、发送还可能失败,通知能力必须是一个「带渠道校验/模板/重试/回执的完整能力」,而不是一段裸 http 调用。

2. 场景痛点

这个流程的痛点,在通知链路上体现得最直接:

本质上,通知的难点不在「发出一条消息」,而在「多发、可重试、可回执」——渠道要抽象、失败要降级、状态要可查。

3. 方案:为什么是通知工具插件

选通知工具插件,我们实际对比过:

这篇文章我们就用它把工单流程里的 http 通知节点升级为正式插件:notify 工具(channel 枚举 wecom/dingtalk/mock + title/body/priority),跑通「渠道适配 → 重试 → 回执 → 降级」的完整通知链路。

4. 整体架构

graph TD subgraph app["【验证应用】"] start["开始(channel/title/body)"] --> notify["发送通知(notify)"] --> check{"回执判断(IF-ELSE contains 「sent」: false)"} check -- "否" --> ok["发送成功(end_sent)"] check -- "是" --> deg["发送失败降级(end_degraded)"] end subgraph inner["【插件内部】notify"] v1["渠道校验(白名单式)"] --> v2["模板渲染(priority 拼装前缀)"] --> v3["渠道 payload 适配"] --> post["POST webhook(timeout=5;4xx 不重试 / 5xx·超时重试 3 次×2s)"] --> receipt["回执"] end

链路很清晰:收通知参数 → 渠道校验 → 模板渲染 → payload 适配 → 带重试发送 → 回执分流。关键设计是「失败显式化」——回执里带 sent/reason,下游永远知道这次通知到底发出去没有。

5. 模块设计

5.1 渠道适配与重试(tools/notify.py)

同一消息按渠道输出不同 payload,重试只对可重试错误:

CHANNELS = ("wecom", "dingtalk", "mock")

RETRY_TIMES = 3

RETRY_INTERVAL = 2

TIMEOUT = 5

def adapt_payload(channel, title, body, priority):

    content = f"[{priority}] {title}\n{body}" if priority and priority != "normal" else f"{title}\n{body}"

    if channel == "wecom":

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

    if channel == "dingtalk":

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

    return {"channel": channel, "title": title, "body": body, "priority": priority}

def _send_with_retry(self, url, payload):

    last_reason = ""

    for attempt in range(RETRY_TIMES):

        try:

            resp = requests.post(url, json=payload, timeout=TIMEOUT)

            if resp.status_code < 400:

                return {"sent": True, "message_id": mid}   # 成功回执

            if 400 <= resp.status_code < 500:

                # 4xx 不重试:URL 错/鉴权错,重试无意义

                return {"sent": False, "reason": f"http {resp.status_code} (not retried)"}

            last_reason = f"http {resp.status_code}"

        except requests.exceptions.Timeout:

            last_reason = "timeout"

        except requests.exceptions.RequestException as e:

            last_reason = f"network error: {type(e).__name__}"

        if attempt < RETRY_TIMES - 1:

            time.sleep(RETRY_INTERVAL)

    return {"sent": False, "reason": f"{last_reason} (retried {RETRY_TIMES}x)"}

5.2 渠道 URL 走凭证(provider/notify_tool.yaml)

wecom_url/dingtalk_url/mock_url 三个 credential(环境差异),模板在代码(业务差异)——换环境只改凭证。注意 manifest tags:1.16 daemon 合法枚举没有 communication,用 utilities(详见采坑点)。

6. 运行验证

输入 预期 结果
三渠道各发一条(mock 端核对) payload 与平台格式一致(wecom/dingtalk text 结构) ✅ 一致
priority=high / normal high 拼 [high] 前缀,normal 不拼 ✅ 一致
mock 配置 ?fail=2 前两次 500 第三次成功,回执 sent=true ✅ 实测耗时 4.0s(2 次间隔)
mock 持续 500 重试耗尽 sent=false + reason ✅ 4.0s;4xx 不重试 0.0s 直接失败
非法 channel / 缺 title 参数错误 param_invalid ✅ 一致
workflow 注入 fail=99 回执 contains "sent": false → 降级分支 ✅ 双出口正确

环境:Dify 1.16.1(Docker Compose),mock webhook 服务 tmp/mock_webhook.py(127.0.0.1:8004,?fail=N 模拟失败 / ?delay=秒 模拟慢响应 / GET /received 查看),凭证指向 host.docker.internal:8004。

7. 实战坑

现象 修复
plugin_tag 枚举 manifest tags 写 communication → 打包报错 plugin_tag 校验失败 用合法枚举 utilities(1.16 合法集:search/image/videos/weather/finance/design/travel/social/news/medical/productivity/education/business/entertainment/utilities/other/agent/rag/trigger)
4xx 也重试 URL 错/凭证错重试无意义还拖时间 4xx 不重试直接失败,仅 5xx/超时/网络重试 3 次×2s
降级静默 发送失败不告知下游,业务无感知 回执 sent=false 显式返回,IF-ELSE contains "sent": false 分流降级分支
渠道 payload 差异 企微与钉钉格式混用发不出去 每渠道 adapt_payload 函数,新增渠道加函数不破坏现有
webhook URL 写死 换环境就要改代码 渠道 URL 在 credentials(环境差异),模板在代码(业务差异)
通知超时拖慢主流程 webhook 慢响应卡住整个流程 requests timeout=5,超时归重试路径
mock 未知渠道不报错 测不出 4xx 不重试路径 mock 对白名单外渠道返回 404(模拟真实 URL 错)

8. 实验文档及源码获取

文章聚焦核心配置与采坑点,完整分步操作与渠道 payload 对照表见实验文档原文。

联系我

15088711270

手机端点击号码可直接拨打 · 桌面端可复制

微信二维码

扫码加微信 · 备注「门户」更快通过