7.6 KiB
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 把 Claude Agent SDK 的 query() 包一层。
模式 C:异步任务队列
AI 处理慢(深度研究、图像生成)时:
1. 收到消息 → 立即回复「收到,处理中」(可选:sendtyping)
2. 任务入队,worker 处理
3. 完成后用保存的最新 context_token 主动 sendmessage
⚠️ 注意 24h 窗口限制(见 06 文档):任务完成后主动推送必须在窗口内才送得到。慢任务一定要设计超时兜底文案。
模式 D:多通道网关(进阶)
如果以后还要接 Telegram/飞书/钉钉,把「规范化消息模型 + 统一网关」抽出来,iLink 只是其中一个 adapter。直接参考 OpenAkita 的架构: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 消息去重
三层兜底:
- 游标正确持久化(治本)
- 应用层按消息 ID 去重(
client_id/ 服务端消息 ID,存最近 N 条的 set) - 发送侧带唯一
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 恢复流程