msgId 是每条消息的唯一标识。它看起来只是一个字符串,但在接口开发的全流程里,从消息接收到发送再到撤回,msgId 贯穿了消息的完整生命周期。
一、接收时——幂等去重
同一条消息可能被推送多次(网络重试、服务重启后补推)。收到消息后第一步永远是检查 msgId 是否已处理——已处理的直接返回,不重复执行业务逻辑。
没有去重会导致:同一条消息触发两次自动回复(用户收到两条一样的)、消息被存两份(数据库脏数据)、统计计数翻倍。幂等去重是消息处理的地基。
二、存储时——关联键
消息表的主键是 msgId,所有和这条消息相关的数据都以 msgId 为关联键:消息内容存消息表、回复记录存回复表(reply_msgId 指向原消息)、处理日志存日志表(关联 msgId)、AI 分析结果存分析表(关联 msgId)。
一个 msgId 串起了"收到→处理→回复→分析"的完整链路。查某条消息的全部上下文时,以 msgId 为起点能找回所有关联数据。
三、发送时——撤回和引用的依赖
发送消息成功后接口返回新消息的 msgId。这个 msgId 的两个用途前面文章讲过:消息撤回依赖它(revoke 接口要传原 msgId),消息引用依赖它(引用回复要带原 msgId)。
不保存发送 msgId,撤回和引用都无法实现。发送消息后落库 msgId 是"用不用得上先存着"的典型场景——成本极低(多存一个字段),但缺失时的补救成本极高。
四、查询时——分页游标
聊天记录查询的分页通常用 msgId 做游标:第一页取最新 N 条,返回最后一条的 msgId 作为下一页的游标,下一页查"msgId 比游标更早的 N 条"。比按时间戳分页更精确——msgId 的唯一性保证了游标不会遗漏或重复。
msgId 用途对照
阶段 | 用途 | 不保存的后果 |
|---|---|---|
接收 | 幂等去重 | 重复处理、计数翻倍 |
存储 | 关联键 | 数据散落无法关联 |
发送 | 撤回/引用 | 无法撤回、无法引用 |
查询 | 分页游标 | 分页可能遗漏 |
msgId 全流程实现
@app.post("/webhook") def webhook(): d = request.json msg_id = d.get("msgId") # 1. 接收:幂等去重 if db.exists("messages", msgId=msg_id): return {"code": "1000"} # 已处理 # 2. 存储:以msgId为主键落库 db.save("messages", { "msgId": msg_id, "fromUser": d["fromUser"], "groupId": d.get("groupId", ""), "content": d["content"], "type": d["messageType"], "createTime": d["createTime"] }) # 处理后发送回复 reply = generate_reply(d) if reply: r = api("sendText", {"wId": WID, "toUser": d["fromUser"], "content": reply}) if r.get("code") == "1000": reply_msg_id = r["data"]["msgId"] # 3. 发送:保存回复msgId供撤回/引用 db.save("replies", { "origMsgId": msg_id, # 原消息 "replyMsgId": reply_msg_id, # 回复消息 "toUser": d["fromUser"], "content": reply, "time": now() }) return {"code": "1000"} def query_history(wxid, cursor_msgId=None, limit=20): """4. 查询:msgId做分页游标""" if cursor_msgId: return db.query_sql( """SELECT * FROM messages WHERE (fromUser=? OR toUser=?) AND msgId < ? ORDER BY createTime DESC LIMIT ?""", [wxid, wxid, cursor_msgId, limit]) return db.query_sql( """SELECT * FROM messages WHERE (fromUser=? OR toUser=?) ORDER BY createTime DESC LIMIT ?""", [wxid, wxid, limit])落地建议
msgId 的保存策略就一句话:"收到和发出的每条消息都存 msgId"。存储成本几乎为零(一个字符串字段),但缺失时的业务损失很大——不能撤回、不能引用、不能精确分页。msgId 的格式和生成规则参考 Eyun 开发文档,平台开通见 Eyun 官网。