以前做微信机器人,常见的方案是"挂机软件加轮询"——开一个安卓模拟器挂着微信,客户端定时拉消息。这种方案不稳定(模拟器容易崩)、延迟高(轮询间隔短的话接口压力大)、维护成本高。Webhook 架构从根本上解决了这几个问题。
一、Webhook vs 挂机轮询:架构差异
挂机轮询的核心问题是"你要主动去拉"——拉太频繁接口压力大,拉不频繁延迟就上来。Webhook 是"平台主动推给你"——有消息才推,没消息不浪费,延迟最低毫秒级。
还有一个差异:挂机软件本身不稳定,容易被风控,也会因为微信更新客户端版本就挂掉。Webhook 走的是 API 层面,客户端稳定性对它没那么大影响。
二、Webhook 实时回调的架构
三层:平台侧(微信 API 平台负责监听微信消息、打包成回调事件推给你)、传输层(HTTPS POST,带签名)、你的服务层(验签、去重、入队、ACK、业务处理)。
平台侧和传输层不用管,你要做的就是把服务层搭好。服务层的关键就是"收到就 ACK,处理放异步"——这条必须记牢,回调超时会被重推。
三、消息丢失检测:靠序号和重放
Webhook 也会丢消息——网络抖动、服务刚好挂了几秒钟,平台推过来没收到。做法是平台侧每条消息带一个递增序号,你的服务记录最后收到的序号,间隔超过阈值(比如 5 秒内没收到后续消息)就主动查一下有没有漏掉的序号。
有些平台提供了补拉接口——传起始序号返回这段时间内的消息。用好这个接口,丢消息的概率可以降到接近零。
四、重放机制:平台侧也会兜底
平台推送失败(没收到 ACK 或超时)会自动重推,通常三次,间隔递增。所以你的服务必须做幂等——同一条消息 ID 处理两次不会出问题。幂等用消息 ID 做键,处理前查一下,处理过就跳过。
Webhook vs 挂机轮询对照
维度 | 挂机轮询 | Webhook 架构 |
|---|---|---|
延迟 | 秒级 | 毫秒级 |
稳定性 | 模拟器易崩 | 服务端为主,稳定 |
接口压力 | 轮询频繁压力大 | 有消息才推 |
维护 | 客户端+服务端 | 只维护服务端 |
风控风险 | 高(模拟客户端) | 低(API 层面) |
丢消息恢复 | 靠补拉 | 序号检测+补拉接口 |
服务层核心实现
import queue, time, threading from flask import Flask, request import hashlib app = Flask(__name__) inbox = queue.Queue() processed = {} # msg_id -> 处理结果(持久化更好) last_seq = 0 # 最后收到的序号 seq_lock = threading.Lock() SECRET = "verify_secret" def verify(data): sign = data.get("signature", "") payload = "".join(f"{k}{v}" for k, v in sorted(data.items()) if k != "signature") payload += SECRET expect = hashlib.md5(payload.encode()).hexdigest() return sign == expect @app.route("/hook", methods=["POST"]) def webhook(): data = request.get_json(force=True) or {} # 1. 验签 if not verify(data): return {"code": -1}, 403 # 2. 去重(幂等) mid = data.get("msg_id") if mid in processed: return {"code": 0} # 3. 更新序号(检测丢消息) seq = data.get("seq", 0) global last_seq with seq_lock: if seq and last_seq and seq > last_seq + 1: print(f"[丢消息] seq {last_seq} -> {seq}") # 主动补拉:传 last_seq 到 seq-1 # fetch_missing(last_seq + 1, seq - 1) if seq > last_seq: last_seq = seq # 4. 入队立即 ACK processed[mid] = "pending" inbox.put(data) return {"code": 0} def worker(): while True: evt = inbox.get() try: process(evt) # 业务逻辑 processed[evt["msg_id"]] = "done" except Exception as e: processed[evt["msg_id"]] = f"fail:{e}"落地建议
Webhook 架构从根本上解决了挂机轮询的稳定性和延迟问题。服务层必须做三件事:验签、幂等去重、立即 ACK 后异步处理。消息丢失靠序号检测,发现间隔主动补拉。平台重推是常态,幂等一定要做,别假设消息只推一次。微信侧的 Webhook 推送由Eyun这类个人微信API平台 提供,服务层在自建服务实现,具体字段以平台开发文档为准。