Files
weixin_ClawBot_Bridge/docs/04-bridge-design.md
T

170 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 04 — Bridge(转接器)架构设计指南
> 目标读者:要把微信 ClawBot 接到自己已有 AI 项目的开发者。
> 本文档不绑定具体语言,给出模块划分、状态模型、对接模式和可靠性设计。
## 1. Bridge 的职责边界
Bridge 是一个**薄适配层**,职责只有:
```
iLink 长轮询 ──▶ 消息规范化 ──▶ 调用你的 AI 后端 ──▶ 回复格式化 ──▶ sendmessage
```
Bridge **不负责**:
- 对话历史/记忆管理(那是 AI 后端的事,除非后端无状态)
- 业务逻辑、工具调用编排
- 用户体系、计费
保持薄的好处:协议变了只改 Bridge;AI 后端可以独立演进、独立测试。
## 2. 推荐模块划分
```
bridge/
├── ilink/
│ ├── client.* # HTTP 封装:请求头、鉴权、X-WECHAT-UIN、超时
│ ├── auth.* # 扫码登录流程(get_bot_qrcode / get_qrcode_status)
│ ├── poller.* # 长轮询主循环、游标管理、退避重试
│ ├── sender.* # sendmessage / sendtyping / 分片发送
│ └── media.* # getuploadurl、CDN 上传下载、AES-128-ECB
├── store/
│ ├── account.* # bot_token / ilink_bot_id / baseurl 持久化
│ ├── cursor.* # get_updates_buf 持久化(每次更新立即落盘)
│ └── context.* # peer_id → context_token 映射持久化
├── normalize.* # WeixinMessage → 内部统一消息模型
├── backend.* # 你的 AI 项目的调用封装(HTTP / SDK / 进程)
└── main.* # 装配 + 启动 + 优雅退出
```
参考:官方包 `@tencent-weixin/openclaw-weixin` 的目录结构(`auth/ api/ cdn/ messaging/ monitor/ config/ storage/`)几乎就是这个划分;Java SDK 的门面模式(`OpenClawWeixinSdk` 统一封装 `QrLoginFlowService / WeixinApiClient / WeixinLongPollMonitor / MediaUploadService`)也值得对照。
## 3. 必须持久化的三类状态
| 状态 | 内容 | 更新时机 | 丢失后果 |
|---|---|---|---|
| 账号凭据 | `bot_token`、`ilink_bot_id`、`ilink_user_id`、`baseurl` | 扫码成功时 | 需重新扫码 |
| 同步游标 | `get_updates_buf` | **每次 getupdates 响应后立即** | 重复收到历史消息 |
| 上下文令牌 | `peer_id → context_token` | 每条入站消息 | 无法回复/主动消息送达不到 |
实现建议:
- 单机用 JSON 文件或 SQLite 就够(官方包就是文件存储,Java SDK 用 `FileAccountStore / FileContextTokenStore / FileSyncCursorStore`)
- 游标落盘要**同步写**(或至少 flush),崩溃丢游标 = 重启后消息轰炸
- 文件加个简易锁,防止多开实例写坏状态
## 4. 与 AI 后端的对接模式
按你的 AI 项目形态选一种:
### 模式 A:HTTP API 后端(最常见)
```
poller.onMessage(msg):
reply = http_post("http://127.0.0.1:8000/chat", {
"session_id": msg.peer_id, # 用微信用户 ID 当会话 ID
"text": msg.text,
"attachments": msg.media_paths
})
sender.send_text(msg.peer_id, reply.text, msg.context_token)
```
- 会话 ID 直接映射微信 `from_user_id`,天然一对一私聊
- 后端无状态的话,Bridge 也不存历史;有状态则后端自己管
### 模式 B:本地 SDK / 库调用
Bridge 与 AI 项目同进程(或同语言直接 import),把消息对象传给 Agent 函数,拿回字符串。参考 [cc-weixin](https://github.com/hao-ji-xing/cc-weixin) 把 Claude Agent SDK 的 `query()` 包一层。
### 模式 C:异步任务队列
AI 处理慢(深度研究、图像生成)时:
```
1. 收到消息 → 立即回复「收到,处理中」(可选:sendtyping)
2. 任务入队,worker 处理
3. 完成后用保存的最新 context_token 主动 sendmessage
```
⚠️ 注意 24h 窗口限制(见 06 文档):任务完成后主动推送必须在窗口内才送得到。慢任务一定要设计超时兜底文案。
### 模式 D:多通道网关(进阶)
如果以后还要接 Telegram/飞书/钉钉,把「规范化消息模型 + 统一网关」抽出来,iLink 只是其中一个 adapter。直接参考 [OpenAkita 的架构](https://github.com/openakita/openakita/blob/main/docs/im-channels.md):`Adapter → UnifiedMessage → Gateway → Agent`,发送路径反向。第一版不用做,但内部消息模型(见 03 文档第 7 节)建议一开始就用。
## 5. 可靠性设计
### 5.1 长轮询主循环
伪代码:
```
loop:
try:
resp = getupdates(cursor, timeout=server_timeout + 10s)
if resp.ret == 0:
if resp.get_updates_buf: cursor.save(resp.get_updates_buf)
for msg in resp.msgs: dispatch(msg)
backoff.reset()
elif resp.errcode == -14:
pause_account(1h); notify("需重新扫码")
else:
backoff.retry() # 2s 快速重试,连续 3 次后 30s
except HttpTimeout:
continue # 长轮询正常超时
except NetworkError:
backoff.retry()
```
### 5.2 消息去重
三层兜底:
1. 游标正确持久化(治本)
2. 应用层按消息 ID 去重(`client_id` / 服务端消息 ID,存最近 N 条的 set)
3. 发送侧带唯一 `client_id`(防自己重发)
### 5.3 回复超时
- AI 后端设硬超时(如 60s),超时回「处理超时,请重试」
- 有资料称 24h 窗口外消息送达不到——对「用户发问 → bot 回复」场景这不是问题(立即回复),只影响主动推送
### 5.4 优雅退出
- 捕获 SIGTERM/SIGINT:停 poller → 等正在处理的消息发完 → flush 状态 → 退出
- 不要在 dispatch 中途杀进程,游标已推进但回复没发 = 用户消息被吞
### 5.5 可观测性
- 结构化日志:收消息(peer、类型、长度)、发消息、游标推进、错误码、退避事件
- 关键指标:轮询错误率、-14 出现次数、AI 后端耗时分布
- token、context_token 不要打进日志(打 hash 前缀即可)
## 6. 安全注意
- `bot_token` 等于这个微信 bot 的全部权限:**不入 git、不进日志、文件权限 600**
- 谁扫码谁就是这个 bot 的「主人」,但任何拿到 token 的人都能收发消息——服务器上注意文件权限
- 入站消息内容视为不可信输入:如果 AI 后端有工具调用能力(执行命令、读写文件),提示词注入风险要自己评估
- 建议加白名单:只响应指定 `from_user_id`(比如就你自己),防止别人给 bot 发消息白嫖你的模型 API 额度
## 7. 容量与限制设计
- 微信侧不支持 Markdown:发送前转纯文本
- 长回复分片:按段落或固定字数(如 1500 字)切多条,注意保持顺序
- 媒体大小:入站大图/大文件设上限,超限回复提示而不是硬下载
- AI 后端并发:一个微信号只有一个 bot,但用户可能连发多条——dispatch 建议串行 per-peer(或按 peer 加锁),避免回复乱序
## 8. 测试策略
- 协议层:把 iLink client 抽象成接口,测试时用 mock server 回放抓包 JSON
- 规范化层:构造各类 WeixinMessage JSON(文本/图片/语音/图文混排/多 item)做单元测试
- 端到端:真机扫码,用另一个微信号发消息验证
- 回归重点:游标持久化(重启不重复收)、context_token 传递、-14 恢复流程
## 参考资料
- [openclaw-weixin-sdk 架构图与失败恢复策略](https://github.com/openclaw-java/openclaw-weixin-sdk)
- [OpenAkita 多通道网关架构](https://github.com/openakita/openakita/blob/main/docs/im-channels.md)
- [cc-weixin — 接入 Claude Code Agent 的完整范例](https://github.com/hao-ji-xing/cc-weixin)