接口调通只是入门,系统在生产环境跑得稳才是真本事。我们这套 Eyun 企业微信 API 的对接跑了两年,线上事故复盘下来,80% 集中在三类问题:异常返回理解错了、重复请求没防住、多步操作的数据对不上。这篇不讲怎么发第一条消息,专门讲这三个最烧时间的坑,以及我们最终落地的处理方式。
一、异常处理:先看懂两套完全不同的错误码
这是新手最容易栽的第一坑。平台响应封套是 {code, data, message, …},但 code 有两套语义,混为一谈一定写错判断:
|
业务失败 |
数字 -1 |
message 形如 `-3004 |
参数错误` |
请求到达了业务层,业务没办成 |
|
网关/鉴权失败 |
字符串 |
invalid_token |
请求根本没进业务层 |
也就是说,业务错误码不在 code 字段里,而在 message 竖线前面。看到 code=-1 不能直接当一种错误处理,要从 message 里把数字码切出来再分支。我们封装了统一解析函数,全项目只允许用它判断错误:
def parse_error(resp):
if resp["code"] == 0:
return None # 成功
if isinstance(resp["code"], str):
return ("gateway", resp["code"]) # 网关错误,原样保留字符串码
biz = resp["message"].split("|", 1)[0] # 业务错误,取竖线前数字
return ("biz", int(biz))
顺便注意:登录、重连、测试推送这几个接口,code=0 也不代表操作成功,还要核对 data 里的实际结果字段,成功判断以文档说明为准。
二、常见业务错误码的正确姿势
我们高频遇到的就那几个,处理方式完全不同,背下来能省掉大量排障时间:
|
-11001 |
连接断开 |
调重连接口,成功后重试原请求 |
无脑重试发送,越试越错 |
|
-11002 |
账号在别处登录 |
置离线 + 告警,等人确认 |
自动重连(会反复互踢) |
|
-12007 |
二维码过期 |
重新取码开始新会话 |
拿旧 uuid 继续轮询 |
|
-4014 |
uin 已失效 |
先刷新账号信息再重试 |
直接判定客户不存在 |
|
-3004 |
参数错误 |
查参数格式,不重试 |
重试一百遍还是错 |
|
-3020 |
会话错误 |
校验 conversationId 取值与类型 |
拿单聊 ID 发群聊 |
|
-2003 |
无增量数据 |
正常结束同步循环 |
当成失败告警 |
这里有个非常隐蔽的坑:conversationId 必须是 JSON 数字,传字符串会报类型反序列化错误;单聊传对方 userId,群聊传 roomId,给自己发只能用当前账号 uin。参数错和会话错都属于"请求本身有问题",重试没有任何意义,还会污染监控数据。
三、重试要分类:三种失败,三种策略
所有错误统一"重试三次"是偷懒,正确做法按性质分三类:
永不重试:参数错误(-3004)、会话错误(-3020)、无权限。重试只会刷屏日志,要立即失败并告警给开发修 bug。
有限退避重试:限流、网关超时、连接断开(-11001,先重连再重试)。指数退避 10s、30s、60s,最多 3 次,仍失败转死信人工处理。
立即人工介入:挤号(-11002)、凭证失效(invalid_token)。这类不是程序能自愈的,自动乱搞反而扩大事故。
重试前还有一个必做动作:确认操作是否其实已经成功。超时异常最暧昧——请求可能到了服务器、业务执行了、只是响应丢了。直接重试就可能重复发消息、重复建群。这就引出下一个大坑。
四、重复请求:网络会重试,系统必须幂等
重复来源有四个:我方超时重试、消息队列重投、平台回调重推、用户连点按钮。防重的核心是业务幂等键,针对不同操作选天然唯一键:
|
回调处理 |
投递 ID / 消息 ID |
|
发送消息 |
业务侧生成的 clientMsgId(或 业务单号+场景) |
|
建群、建联 |
业务流水号 |
|
状态变更 |
事件 ID |
发送类操作的模式是:执行前用 Redis SETNX 占位(带过期时间防死键),占位失败直接认为已处理;占位成功才调用接口。回调侧同理——同一条消息回调推两次,第二次直接返回 2xx 跳过,否则客户会收到两条一模一样的回复。
还有个隐蔽的重复场景:重连后的补发。-11001 重连后,平台可能补推断线期间的消息,所以重连恢复消费时幂等检查不能关,这恰恰是最容易漏防的窗口期。
五、数据一致性:多步操作别相信"都会成功"
企微业务很少是一步:加好友要"搜索拿票据 → 发起申请 → 等通过回调 → 更新档案 → 发欢迎语";改归属要"更新关系 → 转群 → 通知双方"。任何一步失败,两边数据就对不上。三种实战模式:
1. 本地消息表(事务性发件箱)
"改数据库 + 调接口"无法放进一个事务。做法是:业务数据和一条"待执行任务"在同一个本地事务里写库,后台任务轮询待执行表去调接口,成功才标记完成。这样最坏情况只是任务延迟,绝不会出现"库里说发了、实际没发"。
本地事务{ 更新业务状态; 写入outbox任务 } → 异步worker执行接口 → 成功标记/失败重试
2. 状态机驱动,不跳步
多步流程用显式状态机推进,每一步完成才落下一步状态。中途崩溃重启,从数据库读出当前状态接着跑,而不是从头再来一遍。我们早期靠"内存里记流程进度",一次发布重启让几十个加好友流程卡在半路,之后全改成了持久化状态机。
3. 对账兜底
再严密的机制也挡不住所有意外(比如对方手动删好友、平台侧操作没回调)。必须有反向对账:定时用查询类接口核对关键数据——好友关系是否还存在、群成员是否一致、消息是否真的发出。以平台侧数据为准修正本地状态。对账是低频但救命的最后一道防线,频率按业务重要程度定,小时级或天级即可。
六、几个小但值钱的细节
-
回调快速 2xx:业务异步处理,回调端点超时会触发平台重推,重推风暴会放大任何一个小故障。
-
写操作和高风险操作分级:删除联系人、解散群、退群这类不可逆接口,调用前必须有业务侧二次确认,代码里禁止被自动化任务直接调用。
-
日志记录原始报文:异常时把完整请求和响应落日志(敏感字段脱敏)。线上最怕"报了个错但没留现场"。
-
接口字段变更留缓冲:解析响应用宽松模式,未知字段容忍、缺失字段给默认值,平台加字段不会把老服务搞崩。
-
时钟与时间戳:依赖消息时间做统计时用报文里的原始时间,不用本地接收时间,补偿拉取的历史消息会乱序到达。
写在最后
稳定的系统不是不犯错,而是假设错误一定会发生——错误码分两套语义、失败按性质分类处理、每个写操作都有幂等键、多步操作靠本地消息表和状态机、再用对账兜底。这些模式在 Eyun 企业微信 API 的开发文档里都能找到对应的接口说明和错误码表,但把它们串成工程纪律,得靠线上事故一遍一遍教。希望这篇能让你少交几笔学费。
网硕互联帮助中心




评论前必须登录!
注册