我最近把跑了几个月的 OpenClaw 实例从命令行挪到了微信生态里,中间最大的坎不是 Agent 本身,而是“微信怎么和一个本地 Agent 顺畅对话”。折腾完之后,我顺手把这一层抽出来做成了 weclaw-proxy 这个开源网关。标题写的“极简”不是营销话术——核心模块就两个文件,配置压缩到一个不到 100 行的 YAML,放在 Windows 老机器上跑,内存占用稳定在 20MB 左右。这篇文章把它的设计逻辑、部署过程、以及对 OpenClaw 对接时最容易踩的坑都摊开讲清楚,适合正在研究 Agent 接入渠道、或者想在微信里挂一个自建 Agent 的人参考。
1. 为什么 OpenClaw 这种 Agent 框架需要一个独立的微信接入网关
1.1 微信回调不是简单“POST 一个 JSON”就能搞定的事
很多人第一反应是:OpenClaw 不是有 HTTP API 吗?微信那边收到消息,转发给 OpenClaw 不就行了?真这么做,三天后你就会想砸键盘。
微信生态的回调链路比普通 REST API 啰嗦得多。以公众号/企业微信为例,回调 URL 要承受两类请求:一类是平台配置时的 GET 验证请求,带 signature、timestamp、nonce、echostr 四个参数,网关要做签名校验然后原样返回 echostr 明文;另一类是真实消息的 POST 请求,消息体是 XML,还可能带着 AES 加密,需要先用 EncodingAESKey 解密才能读到 FromUserName、Content、MsgType 这些字段。光把验签和加解密写对,就已经是一套独立的“微信协议适配层”了。
这还没完。微信对被动回复有 5 秒超时限制,超过 5 秒没响应,平台会重试推送。而 OpenClaw 这种 Agent 框架处理一条消息,内部要经历大模型推理、工具调用、多次循环,动辄 10 秒甚至更久。你不可能让微信直接等 Agent 跑完。所以网关必须立刻给微信回一个“200 收到”,然后走异步通道把 Agent 的回复主动推回去。这个“同步接收 + 异步回推”的模式,本身就是渠道适配的核心难题之一。
如果把这些逻辑全部塞进 OpenClaw 本体,Agent 的核心循环会被微信 SDK 的长连接、重连机制、消息协议彻底绑架。以后 OpenClaw 一升级,你就要担心微信模块是不是又要修一遍。
1.2 网关管的和不该管的,边界要清晰
我在做 weclaw-proxy 时给自己立了一条规矩:网关只做渠道适配,不碰 Agent 逻辑。
网关该管的是这些:
- 协议转换:微信 XML/加密报文 转成 OpenClaw 能理解的 JSON 对话请求,再把 Agent 回包转成微信要求的下发格式。
- 鉴权与验签:校验微信回调签名,防止别人伪造消息打你的 Agent 接口。
- 限流与幂等:微信重试机制会带来重复消息,网关需要用 MsgId 做幂等过滤;同时要对上游 OpenClaw 做超时控制,避免一个慢请求占满所有连接。
- 会话绑定:微信的 FromUserName 是天然的用户维度键,用它维护会话状态、对话历史 context,而不是让 Agent 裸奔处理无序请求。
网关不该管的是这些:
- 不写 Prompt
- 不维护长期记忆
- 不管工具调用和审批
- 不做 Agent 内部的编排
打个比方:网关是前台接待,OpenClaw 是办公室里的顾问。前台负责认人、登记、把访客领到正确的顾问办公室,但顾问怎么分析问题、用什么工具解决问题,前台一概不干预。这个边界划得越清楚,系统出问题时越好排查。
1.3 和“直接在 OpenClaw 里装微信 SDK”相比,代理层方案赢在哪
我在社区里看到不少人是把微信 SDK 直接集成进 Agent 进程的,短期看确实省事。但跑一段时间后,几个致命问题就出来了:
| 对比维度 | 直接集成微信 SDK | 独立接入网关 |
|---|---|---|
| 接入速度 | 快,但代码侵入性强 | 慢一两天,后续省心 |
| 升级维护 | OpenClaw 升级可能冲突 | 网关独立,几乎不受影响 |
| 故障隔离 | 微信长连接崩了,Agent 一起崩 | Agent 崩了,微信侧还是“已收到”,恢复后继续服务 |
| 多端复用 | 每加一个渠道改一次 Agent | 网关加一个 adapter 即可 |
| 回滚难度 | 需要回滚整个 Agent 版本 | 单独回滚网关配置 |
独立网关还有一个额外好处:你可以随时把网关流量切到 OpenClaw 的测试实例、灰度实例,甚至是另一套 Agent 框架上。生产环境出问题时,这种“渠道层可切换”的能力比什么都金贵。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. weclaw-proxy 的核心设计:把“极简”做到哪一步
2.1 一条微信消息从发出到回复,完整走了一条什么链路
先看最核心的消息流转路径:
code复制微信服务器
│ 1. POST /wechat/callback(XML加密报文)
▼
weclaw-proxy 网关
│ 2. 验签 → AES解密 → 解析消息
│ 3. MsgId 幂等检查(去重)
│ 4. 从内存会话表取出/创建会话上下文
│ 5. 调用 OpenClaw HTTP API(携带用户消息)
│
├── 若 Agent 在 4 秒内返回 → 组装微信被动回复包 → 同步返回给微信
│
└── 若 Agent 超时或需要更长时间 →
先返回 200 空包给微信(防止重试)
等 Agent 完成后 → 调用微信客服/群发接口主动下发
网关监听一个 HTTP 端口,微信回调打进来后,全部处理逻辑都在内存里完成,不依赖数据库。会话表是一个带 TTL 的并发安全字典,超过 30 分钟没有新消息,会话自动过期,下次对话重新建立 context。这个设计对个人使用场景完全够用,也把复杂度压到了最低。
2.2 为什么选 Go 而不是 Node.js 或 Python
weclaw-proxy 用 Go 写,核心考虑是 Windows 部署体验。Go 编译出来是单个静态二进制文件,不装运行时、不配环境变量、不依赖第三方包管理器,双击能跑。而 Python 需要解释器和一堆 pip 包,Node.js 要处理 node_modules,在 Windows 上部署一个给 Agent 用的网关,越少外部依赖越不容易挂。
更重要的是 Go 标准库自带 net/http、crypto/aes、crypto/sha1,实现微信验签和 AES 加解密完全不需要引第三方库。整个网关编译完不到 8MB,放在任意一台能联网的 Windows 机器上就能当常驻服务跑。
另一个被人忽略的点是 Go 的内存模型在并发场景下很稳。网关默认开 8 个 worker 并发处理回调,在低配机器上内存占用比 Python 版本低了将近一个量级。我实际观察下来,空载时内存稳定在 15MB 到 25MB,Agent 回复高峰期也不会超过 60MB。
2.3 目录结构和配置设计
源码仓库的目录结构保持了一个开源小项目该有的克制:
text复制weclaw-proxy/
├── main.go # 入口:HTTP 服务、路由注册、启动逻辑
├── wechat.go # 微信验签、AES 解密/加密、消息体解析
├── openclaw.go # 上游 OpenClaw 客户端、超时控制、错误处理
├── session.go # 内存会话管理、TTL 过期、并发安全
├── config.yaml # 配置文件
├── Dockerfile # 需要容器化部署时的备选方案
└── README.md
配置文件是全项目的核心,长这样:
yaml复制server:
listen: "0.0.0.0:8080"
wechat:
token: "your_wechat_callback_token"
encoding_aes_key: "your_43_char_encoding_aes_key"
app_id: "your_appid"
openclaw:
base_url: "http://127.0.0.1:3000"
api_key: "openclaw_api_key"
timeout_seconds: 120
session:
ttl_minutes: 30
max_conversation_messages: 20
security:
allow_users:
- "oXxxxx_wechat_openid_1"
- "oXxxxx_wechat_openid_2"
rate_limit_per_minute: 20
每个字段都有明确含义。allow_users 是白名单,只允许指定的微信用户触发 Agent,防止陌生人消耗你的 API 额度。rate_limit_per_minute 做令牌桶限流,防止有人恶意刷消息把 OpenClaw 打挂。
3. Windows 下从零部署:网络配置 + 二进制 + 验证回调
3.1 环境准备和仓库获取
在 Windows 上部署 weclaw-proxy 只需要两样东西:一个 release 二进制,和一个配置文件。
先去 GitHub Releases 页面下载最新版的 weclaw-proxy-windows-amd64.exe,如果你机器是 ARM 架构就下 arm64 版本。把文件放到一个干净目录,比如 C:\weclaw-proxy\,然后在这个目录下新建 config.yaml,把上面那份配置复制进去改成自己的值。
如果你对 Go 比较熟,也可以直接源码编译:
bash复制git clone https://github.com/yourname/weclaw-proxy.git
cd weclaw-proxy
go build -o weclaw-proxy.exe .
编译时注意 Windows 下路径不要带中文,我之前在一台用户名是中文的机器上编译,偶尔会因为 GOPATH 路径编码问题报错。建议把源码放在 C:\work\ 这种纯英文目录下。
3.2 配置逐项解读与推荐参数
这份配置左右着整个网关的行为,每一项都值得说明白。
先说 wechat 段。token 是你自己在微信公众平台/企业微信后台随意生成的一串字符,必须和网关配置完全一致;encoding_aes_key 是 43 位密钥,在后台生成后直接复制;app_id 是公众号或企业微信的应用 ID。这三样是微信验签和解密的基础,任何一个字符不对,回调验证都过不了。
再说 openclaw 段。base_url 指向 OpenClaw 服务地址,如果 OpenClaw 跑在同一台 Windows 机器上,用 http://127.0.0.1:3000 即可。这里有个参数容易被忽略:timeout_seconds。OpenClaw 处理复杂任务时可能超过 30 秒,这个值默认 120 是合理的。设太短会导致 Agent 还在思考,网关已经断开误报超时;设太长又会让网关线程被长时间占用。我最终的调参结果是:普通问答场景 60 秒,带复杂工具链的场景 120 秒。
最后是 security 段。allow_users 白名单强烈建议开启,尤其当你的 OpenClaw 连着本地文件系统、Shell 等能产生实际影响的工具时。没有白名单,等于任意一个知道你回调地址的人都能指挥你的 Agent 执行命令。
3.3 启动服务和健康检查
Windows 下启动很简单,打开命令行窗口进入目录,执行:
bash复制weclaw-proxy-windows-amd64.exe -config config.yaml
看到类似下面的日志说明启动成功:
text复制2025/01/12 10:23:45 [weclaw-proxy] listening on 0.0.0.0:8080
2025/01/12 10:23:45 [weclaw-proxy] openclaw config: http://127.0.0.1:3000
然后浏览器访问 http://localhost:8080/healthz,返回 OK 就代表网关活着。健康检查接口不需要鉴权,方便你放在探活系统里。
这里有一个特别提醒:Windows 命令行窗口一关,网关就停了。 我建议用 Windows 任务计划程序或者 NSSM 把网关注册成系统服务。代价很小,但能避免下次重启电脑后微信消息全部失败、你却找不到原因的情况。
3.4 微信公众平台侧的回调配置
登录微信公众平台后台,找到“服务器配置”页面,按下面填:
| 字段 | 填写内容 |
|---|---|
| 服务器地址(URL) | https://你的公网域名/wechat/callback |
| Token | 和 config.yaml 里的 wechat.token 一致 |
| EncodingAESKey | 和 config.yaml 里的 wechat.encoding_aes_key 一致 |
| 消息加解密方式 | 安全模式 |
注意,微信要求回调 URL 必须是公网可达的 HTTPS 地址,不能是 IP 加端口随便暴露。常见做法是在一台有公网 IP 的服务器上用 Nginx 反代到网关所在机器的 8080 端口,或者用内网映射方案把本机端口暴露出去。无论用哪种方式,务必确认你的公网链路稳定,因为微信服务器主动访问回调地址,如果连续失败多次,后台会直接停用配置。
填好后点提交,微信会发起一次 GET 验证请求。网关收到后会执行:
- 把 signature、timestamp、nonce、token 按字典序排序后拼接,做 SHA1 签名比对;
- 签名一致后,用
encoding_aes_key解密echostr; - 把解密后的明文返回给微信。
验证通过后,后台会显示“配置成功”。如果显示失败,九成是以下三个原因:
- Token 抄错了,一眼看不出来就两边复制粘贴后使用
fc命令比对文件。 - 时间戳偏差过大,确认服务器系统时间开了自动同步。
- 网关没起来,或者 Nginx 反代路径写错。
4. 对接 OpenClaw 最容易翻车的几个配置点
4.1 OpenClaw 侧到底需要开什么服务
OpenClaw 本身不以“随时待命”的方式常驻。要让网关能调用它,你需要把 OpenClaw 跑成一个带 HTTP API 的服务模式。以我在 Windows 上的实测为例,OpenClaw 启动后默认监听 127.0.0.1:3000,提供一个 JSON 格式的对话接口。网关把微信用户的消息 POST 到这个接口,OpenClaw 处理完以后返回最终答案。
最容易被忽略的是本地地址的 IPv6 坑。如果你的 OpenClaw 服务显示监听在 ::1 也就是 IPv6 的 localhost,而网关配置里写的是 http://127.0.0.1:3000,两者根本连不上。解决方案是在网关配置里改写成 http://[::1]:3000,或者启动 OpenClaw 时强制监听 0.0.0.0:3000。更省事的做法是设一个环境变量让 OpenClaw 绑定 IPv4,具体看你使用的版本文档。
4.2 工具自动审批和 exec-approvals.json 的关系
这是新手上路最容易卡死的环节,也是不少人看到“agent execution terminated due to error”这个报错时最摸不着头脑的地方。
OpenClaw 为了安全,在 Agent 准备执行本地命令、读写文件、调用外部工具之前,需要先获得审批。它把审批状态记录在一个 JSON 文件里,路径一般是 /root/.openclaw/exec-approvals.json。第一次运行时 OpenClaw 会提示你有一条或多条命令等审批,如果一直没批准,Agent 的工具调用请求就悬在那里,直到超时,最终整条执行链被终止,报出 agent execution terminated due to error。
通过网关接入微信后,消息是人发过来的,你不可能守在服务器前点“允许”。所以正确做法是预先处理好审批策略。OpenClaw 通常提供一个命令行工具,可以列出待审批项并手动批准:
bash复制openclaw approvals list
openclaw approvals approve --all
但要注意,approve --all 会把当前所有命令一次性放行,其中可能包含你不希望自动执行的高风险命令。我更推荐按白名单方式处理:
| 审批策略 | 适用场景 | 风险 |
|---|---|---|
| 全部自动批准 | 个人自用、Agent 只跑只读命令 | 高,一旦 Agent 被引导执行删除/写入命令会很危险 |
| 白名单审批 | 只放行 git status、ls、python 等固定命令 |
低,推荐 |
| 每次手动审批 | 不适用网关接入场景 | 完全不可用 |
在网关的 openclaw 配置里可以加一个 allowed_tools 列表,网关在转发前检查消息中是否涉及敏感命令,命中就拦截并返回“该操作未授权”。这种做法等于给 OpenClaw 自己的审批机制又加了一道闸,跑了一段时间下来,我认为这道闸非常值得加。
4.3 实测一条消息从发出到回复的完整日志
网关的日志设计得比较直白,每处理一条消息会输出一行结构化日志。实例如下:
text复制2025/01/12 11:02:17 [wechat] msgId=8941567230 fromUser=oXxx_Alice type=text content="帮我把今天的待办事项整理成列表"
2025/01/12 11:02:18 [openclaw] session=sess_8f3a21 upstream_latency=8.2s status=200
2025/01/12 11:02:18 [wechat] reply sent to wechat server, msgType=text
这几个字段以后排错时都很有用:
msgId:微信消息唯一 ID,排查重复消息时要靠它。fromUser:用户标识,能看出是谁触发了 Agent。upstream_latency:OpenClaw 处理耗时,超过 4 秒说明走了异步下发,超过 120 秒就要考虑是不是 Agent 卡死在工具循环里。status:OpenClaw 返回的状态码,非 200 时需要在 OpenClaw 侧日志里往下挖。
我建议部署后先不接微信,直接用命令行模拟一次会话,确认 openclaw 段配置正确了再接微信。这样能把问题定位范围缩小一半。
5. 实际运行后的踩坑清单和效果记录
5.1 我踩过的最深的五个坑
第一个坑是 5 秒超时导致的重复消息。微信在 5 秒内没收到响应会重试同一消息,如果不做幂等,OpenClaw 可能同一个问题被问了三四遍,浪费 API 额度不说,会话上下文也全乱了。网关用 msgId + 内存消息表解决,同一 msgId 只处理一次,后续重试直接返回上一次的回复结果。如果你发现 Agent 偶尔会重复回答同一个问题,第一件事就是查有没有漏掉幂等处理。
第二个坑是中文和 emoji 的编码问题。Windows 控制台默认代码页是 GBK,Go 程序输出日志里的 UTF-8 中文在部分老版本终端里会变成乱码。排查方法很简单,用 chcp 65001 切到 UTF-8 代码页再启动,日志就正常了。但注意这只是显示问题,不影响内部逻辑,微信发来的消息本身在 Go 里是 UTF-8 处理,所以不要为了“让终端不乱码”去改全局编码。
第三个坑是网关进程守护。一开始我直接在命令行窗口里跑网关,某天 Windows 自动更新重启后,微信消息全部失败,我在外面急了一头汗。后来用 NSSM 把 exe 注册成 Windows 服务,设了失败自动重启,才算一劳永逸。顺带一提,NSSM 配置里要把工作目录设成 exe 所在目录,否则它可能找不到 config.yaml。
第四个坑是 OpenClaw 上游超时和网关线程占用。曾经有一条消息让 Agent 去搜索某个冷门资料,Agent 来回调了好几个工具,花了将近三分钟。网关默认会阻塞等待,长时间占用一个 goroutine。遇到大量这类慢请求,网关的并发处理能力会直线下降。解决办法是给 OpenClaw 调用加上法定的超时上限,并按实际场景调整 worker 数量。
第五个坑是会话 Key 选错导致上下文串线。微信的 FromUserName 是最天然的用户标识,但如果你的公众号同时服务多个用户,千万不能用全局共享的会话表,否则用户 A 的上下文会被用户 B 的对话覆盖。网关默认按 fromUser 作为 session key,如果你自己改代码,一定记住这个原则。
5.2 网关的资源占用和延迟表现
我实测环境是 Windows 11 的旧笔记本(8GB 内存,i5-7300HQ),OpenClaw 和网关同时跑在本机,没有独立服务器。网关空载内存 16MB,高负载时不超过 60MB,CPU 占用几乎可以忽略。
延迟方面,端到端体验取决于 Agent 本身的速度。简单问答类消息 OpenClaw 返回时间普遍在 2 到 5 秒之间,网关同步回包,微信用户可以感觉到“正在输入”的状态,体感不错。复杂任务一旦超过 4 秒,网关返回空包后走异步下发,用户会在十几秒后收到推送回复,虽然不能像聊天那么即时,但比卡在“加载中”强太多。
如果你对延迟特别敏感,可以给 OpenClaw 接一个更快的推理后端,网关不需要做任何改动。这也是分层架构的好处之一。
5.3 从单聊到群聊、多 Agent 路由的演进
跑通微信单聊后,很自然的下一步是支持群聊场景。在群聊里,Agent 不能对每条消息都响应,否则会刷屏。常规做法是只处理 @Agent 的消息,或者在群里用特定前缀触发,比如“AI:查询一下……”
网关在解析消息时可以加一个判断:如果 fromUser 是群聊 ID,先看消息内容里是否包含 Agent 的名字或前缀,不匹配就直接返回 200 空包。群聊场景下还要注意会话 key 的粒度,建议用“群 ID + 发送者 ID”的组合,避免群里不同人的对话模糊成一段混乱的上下文。
再进一步,你可能会想给不同渠道、不同人群分配不同 Agent。比如工作群用的是偏严谨的 OpenClaw 实例,个人微信用的是偏闲聊的实例。网关的架构天然适合做这件事,在转发逻辑里加一个简单的路由规则就行:
yaml复制routes:
- match_channel: "wechat_group_work"
target_openclaw: "http://127.0.0.1:3001"
- match_channel: "wechat_personal"
target_openclaw: "http://127.0.0.1:3002"
按微信 OpenID 前缀或群聊 ID 匹配,不同的用户落在不同的 Agent 后端上。这一步做完,weclaw-proxy 就从一个简单的单渠道适配器变成了一个轻量级 Agent 流量网关。
最后分享一个调试技巧。微信后台的 GET 验证配置好之后,POST 消息调试总要等真实消息,特别麻烦。我一般在本地用 curl 直接模拟一次 POST,把加密报文用 Python 脚本生成一下,打到网关接口上:
bash复制curl -X POST "http://127.0.0.1:8080/wechat/callback" \
-H "Content-Type: text/xml" \
-d "@test_message.xml"
test_message.xml 里放一份微信格式的加密报文,跑通了再放到公网环境验证。这样一来不回后台、不发消息,也能快速把链路调通。自打把微信接入这层独立出来以后,我再也不怕 OpenClaw 升级把回调弄挂了,想接新渠道也只是给网关加 adapter 的事。微信接入网关的价值,不在于代码量多少,而在于把渠道和 Agent 的边界划得干干净净,这一刀划下去,后续的运维和扩展都轻松得多。
