入站到出站的表面路径很短:收到消息 → 业务判断 → 再投一句。连发却经常出现。值班会说机器人太勤。开发去改话术,句子越改越短,双份还在。多数不是生成太热情,而是同一入站被处理两次,或同一出站被提交两次。两件事的修复点不同,合成一个「降温」解决不了。
回调不具备「只达一次」的保证
网络抖动、接收超时、进程重启都会导致 Webhook 重放。消费侧若每次都出站,对端就会看到重复回复。缓解办法是入站幂等,而不是把句子写短。幂等键须标识节点与消息,采用通道侧唯一消息标识。用正文哈希会把两条真实的相同文本误判为一条,该回的「好的」会被吃掉。
本账号发出的消息若同样回调,不加过滤就会形成自反馈:你回一句,这句再进规则,再回一句。须先判定是否自身发送,是则退出自动回复。群里这个判断更绕,因为接收方是群,不能只用「是不是发给我」。
入站先去重再决策。键用通道消息标识,不要用正文。下面是消费侧示意,回调字段名以你们实际推送为准,不确定的不要写进请求里:
python
SEEN = set() # 生产应落库;进程一重启内存表就空了
def on_message(node_id, msg_id, is_self, text):
key = f"{node_id}:{msg_id}"
if key in SEEN:
return # 重放,不再出站
SEEN.add(key)
if is_self:
return # 自己发的,不进自动回复
# 再匹配规则、再投递
先记账再回复。先回复后记账,崩溃窗口里重推仍会再出一条。SEEN 必须进数据库或带 TTL 的缓存,单进程集合挡不住双实例。
回调要在短时限内返回。业务很重就先落事件表再异步处理。回调线程里同步调规则再同步发送,超时会诱发重推,未完成的处理会再跑一遍。
出站超时会在幂等之后再制造一条
入站去重正确,出站仍可能双写。回复接口读超时,HTTP 层自动重试,对端已落库。超时只否定「等到了回执」,不否定「已经写入」。投递路径禁止套用查询接口那套失败重试。不确定态只能按业务键核对。键已成功就停。
群会话会把该问题放大。闲聊全量回复叠加回调重放,短时间即可刷屏,随后被静音或移出。移出之后调用仍可能成功,观察点却空了,故障会被登记成通道损坏。群侧默认应以点名或口令为开口条件,未命中只记账。
两段拆开,开关才能拆开
入站消费负责校验、去重、落事件、决策。出站只投已决定的最终文本并携带业务键。两段不得共用总开关:关闭自动回复,不应切断工单类通知。入站失败也不应自动把出站再打一遍。规则没跑完,和话没送出去,是两件事。
日志里留下原文、是否去重命中、命中哪条规则、有没有出站。只有成功计数,连发时你看不见是重放还是超时重试。联调时用同一条消息标识打两次回调,出站应仍一句。再把读超时压短,会话仍一句。两步都过了,再去调话术。
查看企业微信 API 文档: