我们公司跨境电商那条业务线,年销几个亿,供应商三十多家,分布在深圳、东莞、义乌、青岛,SKU大几十个,一个SKU拆成四五种非标零部件分给不同工厂做。做跨境的都知道,非标供应链履约这一段是整个链条里最重的一块,几十家工厂同时在生产,每个批次都有十几个需要远程沟通和线下确认的节点,合同、发货、验货、入库,全靠微信对。
业务方找到我们技术中台的时候,需求很直接。能不能用OpenClaw把微信和供应链系统打通,状态变更自动推给供应商微信,供应商也能反向查待办。我一听觉得这活不重,不就是OpenClaw加微信插件搭个桥接服务嘛。结果上线两个月,踩的坑一个比一个离谱。
先说一下整体架构。OpenClaw是Gateway加Channel Plugin模式,微信插件是腾讯官方维护的@tencent-weixin/openclaw-weixin,底层走iLink协议,通信链路是用户微信到再到插件再到Gateway。我们加了一层桥接服务,一段Node.js进程systemd托管在腾讯云轻量服务器上,一边连OpenClaw拿消息和会话上下文,另一边调云函数读写业务数据库。架构三层,最外层是供应商和甲方员工的真实微信,中间层是桥接加OpenClaw加一个代收发微信号,最里层是云函数和云数据库。
OpenClaw支持多账号,理论上可以给每个供应商配一个独立微信号。但我们评估后放弃了。三十多个号,注册、实名、养号、防封,运维成本根本扛不住,而且多账号就是多套getUpdates长轮询,每个号独立占一个HTTP长连接,三十条长连接对服务器和iLink网关压力都不小。所以用了单代理账号方案,一个号跑在服务器上,其他人加好友绑定,零学习成本。
架构搭好之后,第一个问题就来了。
上线头两天监控面板开始告警,服务器CPU顶在100%。看了云函数调用面板,QPS和耗时正常。heap dump跑了两遍做对比分析,内存分配曲线平稳,GC正常。TCP连接数也没有异常增长。排查到第三天,开始一行一行翻OpenClaw配置文件,发现longpolling_timeout_ms被设成了5000。在本地写demo时为了快速看到推送效果随手改的,打完包忘了改回去。
这个参数控制getUpdates长轮询的超时间隔,5000毫秒就是每5秒往iLink网关发一次请求。微信后台对高频轮询有限流,每次被限流插件立即重试,重试再被限流,死循环把事件循环打满,CPU飞到100%。改回默认的35000之后,CPU从100%降到12%。一行配置,耗了三天。
CPU问题刚消停,第二周出了个更严重的。
那天下午供应商突然在群里集体反馈收不到消息了。我登服务器查日志,OpenClaw的Session Guard被触发了,代收发微信号的iLink会话过期。iLink协议里有一个错误码叫-14,表示当前会话凭据已失效,插件内置的Session Guard检测到这个错误码后会自动暂停该账号60分钟,等人工重新扫码登录。这个设计的初衷是安全保护,会话过期后如果继续发消息可能触发微信侧更严厉的风控,轻则限制功能,重则封号。
安全逻辑没毛病,但在业务场景里这60分钟是要命的。供应商那边几十份合同等着确认,发货单等着处理,验收结果等着同步,你一个技术组件自我保护就把整条业务链路停了一小时。生产环境不是实验室,你不能让业务等你的框架做完安全检查。
我们做了两层改造。桥接服务加独立心跳,每30秒调一次空的getUpdates请求,只确认会话存活。检测到Session Guard触发后,不干等60分钟,自动触发OpenClaw扫码重登流程,同时把重登期间的消息全部缓存到云数据库队列表,登录恢复后按序批量重放。业务最长不可用时间从60分钟压到了两分钟。
CPU炸了和会话过期这两件事解决之后,我以为后面能消停了。隔了一周,出了个更隐蔽的问题,差点让供应商多发一批货。
那天运维给云服务器打安全补丁,重启了机器。重启之后,OpenClaw的get_updates_buf游标文件被磁盘快照回滚机制恢复到了几个小时前的版本。这里解释一下游标机制。iLink协议的getUpdates接口每次响应都会返回一个get_updates_buf,这是一个不透明的游标字符串,你必须在下次getUpdates请求时原样带回去。服务器用这个游标判断已经给你推送过哪些消息,接下来该推什么。OpenClaw插件把这个游标持久化在本地状态文件里,路径是~/.openclaw/openclaw-weixin/accounts/{账号ID}.sync.json。重启时从文件恢复游标,继续增量拉取。
问题是这次重启后恢复的游标是几个小时前的旧版本。桥接服务拿着旧游标去拉消息,iLink服务器以为这期间的所有状态变更通知都没有送达,一股脑全部重新推了一遍。
供应商这边就收到了一条已经确认过的合同的待确认通知。他以为是份新的,差点又点了一遍确认。还好我们云函数里对合同确认操作做了业务级幂等校验,同一个合同编号加同一个确认动作只生效一次,数据库层面用唯一索引拦住了。但如果没有这层幂等校验,一个合同被重复确认两次可能就是重复下单、排产、付款,对跨境多币种结算场景是实打实的资金风险。
修复做了两层。一是游标状态文件用云存储COS做了定期快照备份,快照间隔设成一分钟,出问题可以精确回滚到最近一分钟的状态,而不是几个小时前的。二是每条通知写入消息队列时带一个哈希指纹,基于通知ID加用户ID加业务事件类型用SHA256生成,轮询推送前先查指纹是否已发送过,命中就跳过。这个指纹去重上线之后,消息重复的问题到现在没再出现过。
上面这几个都是链路层面的问题,下面这个更隐蔽,排查过程也更费劲。
有一次甲方跟单员反馈好几个供应商没收到合同确认通知,同时有个供应商收到了不属于自己的发货单。查推送日志,sendMessage返回ret为0,微信侧确认已送达,排除网络问题。顺着链路倒查,从通知生成到推送调用到消息投递,发现是context_token缓存失效了。iLink协议里每条入站消息都带一个context_token字段,这个token不是会话级别的,是每次对话交互级别的。你回复消息时必须原样带回去,微信服务器靠这个token把出站消息路由到正确的聊天窗口。不带token,消息发不出去。带过期token,消息要么被静默丢弃要么发到错误的会话。
问题出在我们的缓存策略上。供应商绑定时桥接服务一次性存了peer_id和当前的context_token,之后推送直接用缓存。但供应商绑定后如果再给代收发小号发消息,iLink协议就会更新token,我们拿到的旧缓存已失效,插件校验失败直接抛异常,消息被静默丢弃。
更麻烦的是极少数边界情况。旧token还没完全失效,iLink服务器接受了但关联的会话窗口已经不是和这个供应商的了。通知没发给工厂老板本人,发到了他之前跟代收发小号聊过的另一个会话里。
修法是在桥接服务里给每个用户的token记录增加了一个refreshed_at时间戳字段。每次从入站消息中提取到新的context_token时,更新这个时间戳。推送通知前先比较通知的生成时间和token的刷新时间,如果token比通知老,说明中间可能已经发生过token更新,先调一次空的getUpdates触发插件更新token,拿到新token之后再推送。这个逻辑加了之后,token失效导致的消息丢失和路由错误再没出现过。
还有个跟OpenClaw本身关系不大但跟微信集成深度绑定的事。
供应商验货的时候拍了照片直接发在微信群里或者私聊发给代收发小号。过几天甲方要在PC系统查这批货的历史验货记录,发现图片已经打不开了。微信聊天记录里的图片和视频有保存时效,没主动下载到本地的话过几天就过期。这对供应链管理来说是个致命问题,验货记录是合同纠纷和质量追溯的关键凭证。
我们做了一个图片自动归档管道。桥接服务收到图片消息后调OpenClaw的媒体下载接口。微信传给iLink的媒体文件是AES-128-ECB加密的,密钥在消息体的cdn_aes_key字段,OpenClaw插件下载层内置了解密,我们直接拿到解密后的原始图片异步上传到云存储,生成file_id挂到对应业务记录上。
这个管道的难点不在加解密,在异步上传的可靠性。同一个验货批次可能连续发来五六张图,并发上传如果有一张失败,验货记录就缺图片,质检场景里少一张图就可能错过某个质量缺陷。
我们改成了串行上传队列,按消息到达时间戳排序依次上传,每张失败重试三次,间隔递增。三次都失败就把这条记录标记为UPLOAD_FAILED,同时桥接服务往管理员推一条告警,说明哪个批次的哪张图归档失败,需要手动处理。上线以来这个管道跑了小一万张图片,触发过两次手动补传,一次是云存储服务商临时对上传频率做了限流,一次是供应商发了一张损坏的图片文件本身有问题。串行队列加告警兜底这个方案在生产环境里跑下来是够用的。
这两个月下来,我对OpenClaw的理解从最初觉得它就是微信消息管道,变成了它确实是消息管道,但生产环境里让一个消息管道可靠地跑起来,要解决的问题远比搭管道本身多得多。longpolling参数调错几毫秒CPU就上天,游标文件回滚几个小时就能引发业务事故,一个token缓存过期就能让消息跑偏。框架已经把80%的协议细节封装好了。剩下那20%,不在任何文档里,只能自己在生产环境一件一件踩出来。