0d0cd0c510
通过 Bot API sendMessage 投递渲染后的通知,MarkdownV2 自动转义标题与正文。 Co-authored-by: Cursor <cursoragent@cursor.com>
3.7 KiB
3.7 KiB
SafeW 渠道设计
Date: 2026-08-14
Status: Approved
Goal
新增出站渠道 safew:规则命中并渲染模板后,通过 SafeW Bot API sendMessage 把通知发到指定会话。
Non-goals
- 接收 SafeW webhook / 入站更新
- 全局 bot token(
config.yaml不加 safew 段) - 自定义 API base URL
- 钉钉式按分钟限流排队
- 远程 e2e 真实发送(无现成测试 bot 凭据)
- 管理 API 对 channel
type做白名单校验(现有渠道也没有)
Architecture
沿用现有扩展点:实现 adapter.ChannelSender,在 adapter.NewSender 注册 "safew"。
不改表结构。channel.type 为字符串,channel.config 为 JSON。
发送仍走:
POST /api/v1/notify
→ 匹配规则、渲染模板
→ Router 按 ch.Type 取 sender
→ 异步 Send(title, content, config)
→ 失败重试 1s / 5s / 30s
→ 写入 message_log
title 沿用现有值:{source.name}: {event}。content 为模板渲染结果。
Channel config
创建 channel 时:
{
"name": "safew-ops",
"type": "safew",
"config": {
"token": "<bot token>",
"chat_id": "123456789"
},
"status": 1
}
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
token |
string | 是 | SafeW Bot Token,每个 channel 独立 |
chat_id |
string 或 number | 是 | 目标会话。JSON 里数字 ID(123)或字符串("123" / "@name")都接受,sender 归一成字符串再发给 SafeW |
缺 token 或空 chat_id:立即返回 error,不发 HTTP。token / chat_id 读写前 strings.TrimSpace。
Send contract
- URL:
POST https://api.safew.bot/bot{token}/sendMessage - Header:
Content-Type: application/json - Body:
{
"chat_id": "<chat_id>",
"text": "*<escaped title>*\n<escaped content>",
"parse_mode": "MarkdownV2"
}
MarkdownV2 转义
标题和正文都自动转义后再组装。模板里的 markdown 会变成字面量,避免特殊字符导致 400。
转义字符:_ * [ ] ( ) ~ \ > # + - = | { } . ! 规则:每个字符前加`。
组装:text = "*" + escape(title) + "*\n" + escape(content)。
Error handling
| 情况 | 行为 |
|---|---|
config JSON 非法、缺 token / chat_id |
返回 error(router 仍会按现有策略重试,每次同样失败) |
| 网络错误、HTTP status ≥ 400 | 返回 error,触发重试 |
HTTP 2xx 但 body {"ok":false} |
视为失败;error 带上 SafeW 的 description |
HTTP 2xx 且 ok: true |
成功 |
不单独处理 429。不引入新的限流器。
Components
| 文件 | 改动 |
|---|---|
internal/adapter/safew.go |
新增 SafeWSender |
internal/adapter/safew_test.go |
httptest mock 单元测试 |
internal/adapter/adapter.go |
NewSender 增加 case "safew" |
README.md |
渠道列表与创建示例 |
docs/httpie/curls.md |
创建 SafeW channel 的 curl |
Tests
internal/adapter/safew_test.go(httptest.Server 模拟 SafeW):
- 转义:
hello_world.→hello\_world\.;标题包在*...*中 - 成功请求:URL 含
/bot{token}/sendMessage;body 含chat_id、parse_mode=MarkdownV2、转义后的text ok: false或 HTTP 4xx → error,且含description(若有)- 缺
token/chat_id→ 失败且不发 HTTP chat_id为 JSON number(123456789)时仍能发出,请求里是字符串"123456789"
不修改 test/e2e/。手工验证用 docs/httpie/curls.md 里的创建示例。
Success criteria
- 可创建
type=safew的 channel,并绑到规则 - 命中规则后异步调用 SafeW
sendMessage - 含 MarkdownV2 特殊字符的标题/正文能发出(已转义)
- SafeW 业务失败(
ok: false)记入失败日志,而不是当成成功