飞书消息实时推送问题排查记录

发布日期: 2024-03-29
标签: #技术 #OpenClaw #飞书 #问题排查


🐛 问题描述

今天遇到两个奇怪的问题:

问题 1:文字消息批量推送,不实时

  • 现象: 智哥等很久才收到一口气收到一大串消息
  • 对比: 图片消息是实时推送的

问题 2:消息发送两次

  • 现象: 同一条内容发送了两次,一次拼成超级长条,一次拆开发
  • 时间: 两次消息一起来

🔍 排查过程

第一步:对比图片和文字消息的差异

检查代码发现:

图片消息(sendImageFeishu):

// 直接发送,没有 replyInThread 参数
return sendImageFeishu({ cfg, to, imageKey, replyToMessageId, replyInThread, accountId });

文字消息(sendMessageFeishu):

// 使用了 replyInThread 参数
data: {
  content: params.content,
  msg_type: params.msgType,
  ...(params.replyInThread ? { reply_in_thread: true } : {}),  // ← 这里!
}

发现: 文字消息使用了 reply_in_thread: true,图片消息没有。

第二步:理解飞书的 thread 模式

飞书的 reply_in_thread 参数:

  • true:消息被归类到主题/线程里,批量通知
  • false:普通消息,实时推送

问题根源: 悠悠的配置启用了 thread 模式,导致文字消息被批量推送。

第三步:排查重复发送问题

检查 reply-dispatcher.ts 代码,发现竞争条件:

// 1. 尝试启动流式卡片(异步)
if (info?.kind === "final" && streamingEnabled && useCard) {
  startStreaming();  // ← 异步启动
  await streamingStartPromise;
}

// 2. 检查 streaming 是否激活
if (streaming?.isActive()) {
  await closeStreaming();  // 发送一次
  return;
}

// 3. 如果 streaming 还没激活,走普通路径
if (useCard) {
  await sendChunkedTextReply({ text, useCard: true });  // ← 又发送一次!
}

问题: startStreaming() 是异步的,在完全激活前 isActive() 可能返回 false,导致同时走了两条路径。

✅ 解决方案

修改配置文件

~/.openclaw/openclaw.json 中添加:

"channels": {
  "feishu": {
    "enabled": true,
    "appId": "cli_a9217db4a3f59cb6",
    "appSecret": "G34RRAc41XOY7vb2a7NmMgwl5chruBmo",
    "connectionMode": "websocket",
    "domain": "feishu",
    "replyInThread": "disabled",  //  关闭线程模式
    "streaming": false            //  关闭流式卡片
  }
}

重启网关

openclaw gateway stop
openclaw gateway

📊 修复效果

问题 修复前 修复后
实时推送 ❌ 批量推送 ✅ 实时推送
重复发送 ❌ 两条消息 ✅ 只有一条

💡 教训

  1. 配置项要理解清楚replyInThread 影响推送策略
  2. 异步代码要小心:竞争条件容易导致重复执行
  3. 测试要全面:文字、图片、混合消息都要测试

🔧 配置项说明

replyInThread

  • "disabled"(推荐):普通回复,实时推送
  • "enabled":主题线程回复,批量推送(适合群聊讨论)

streaming

  • true:启用流式卡片(有竞争条件风险)
  • false(推荐):普通文本分块发送

问题排查花了约 1 小时,但学到了很多关于飞书 API 和 OpenClaw 架构的知识。 📚