Skip to content

接口名称

批量消息管理 -(IFeishuTenantV1BatchMessage)

功能描述

批量消息管理API用于向多个用户或多个部门批量发送各类消息,支持文本、富文本、图片、群名片等多种消息类型。该接口特别适用于企业通知、营销推送、系统公告等需要同时向大量用户发送相同消息的场景。使用租户令牌访问,适用于企业应用场景,提供消息发送、撤回、进度查询和已读状态统计等完整功能。

参考文档

https://open.feishu.cn/document/server-docs/im-v1/batch_message/send-messages-in-batches

函数列表

函数名称功能描述认证方式HTTP 方法
BatchSendTextMessageAsync批量发送文本消息租户令牌POST
BatchSendRichTextMessageAsync批量发送富文本消息租户令牌POST
BatchSendImageMessageAsync批量发送图片消息租户令牌POST
BatchSendGroupShareMessageAsync批量发送群分享消息租户令牌POST
RevokeMessageAsync撤回批量消息租户令牌DELETE
GetUserReadMessageInfosAsync查询消息已读状态租户令牌GET
GetBatchMessageProgressAsync查询消息处理进度租户令牌GET

函数详细内容


函数名称:批量发送文本消息

函数签名

csharp
Task<FeishuApiResult<BatchMessageResult>?> BatchSendTextMessageAsync(
  [Body] BatchSenderTextMessageRequest sendMessageRequest,
  CancellationToken cancellationToken = default);

认证:租户令牌

参数

参数名类型必填说明示例值
sendMessageRequestBatchSenderTextMessageRequest✅ 必填批量发送文本消息请求体见下方结构
sendMessageRequest.OpenIdsList<string>⚪ 可选用户open_id列表["ou_18eac85d35a26f989317ad4f02e8bbbb", "ou_461cf042d9eedaa60d445f26dc747d5e"]
sendMessageRequest.UserIdsList<string>⚪ 可选用户user_id列表["7cdcc7c2", "ca51d83b"]
sendMessageRequest.UnionIdsList<string>⚪ 可选用户union_id列表["on_cad4860e7af114fb4ff6c5d496d1dd76"]
sendMessageRequest.DepartmentIdsList<string>⚪ 可选部门ID列表["3dceba33a33226", "od-5b91c9affb665451a16b90b4be367efa"]
sendMessageRequest.MsgTypestring✅ 必填消息类型,固定值"text""text"
sendMessageRequest.ContentMessageTextContent✅ 必填消息内容见下方结构
sendMessageRequest.Content.Textstring✅ 必填文本消息内容"明天上午10点召开部门例会"
cancellationTokenCancellationToken⚪ 可选取消操作令牌-

响应

成功响应示例:

json
{
  "code": 0,
  "msg": "success",
  "data": {
    "message_id": "om_dc13264520392913993dd051dba21dcf",
    "invalid_open_ids": [],
    "invalid_user_ids": [],
    "invalid_union_ids": [],
    "invalid_department_ids": ["invalid_dept_123"]
  }
}

常见错误响应示例:

json
{
  "code": 400,
  "msg": "请求参数错误",
  "data": null
}
json
{
  "code": 403,
  "msg": "无权限向该用户发送消息",
  "data": null
}

说明

  • 支持同时向多个用户和多个部门发送文本消息
  • 至少需要提供一种类型的接收者(用户或部门)
  • 返回message_id可用于后续的消息撤回或状态查询
  • 无效ID列表帮助识别发送失败的原因

代码示例

javascript
// 发送部门例会通知
async function sendDepartmentMeetingNotification() {
  const meetingInfo = {
    title: "部门例会通知",
    content: "各位同事好,明天(周三)上午10:00在会议室A召开部门例会,请准时参加。",
    targetUsers: [
      "ou_18eac85d35a26f989317ad4f02e8bbbb", // 张三
      "ou_461cf042d9eedaa60d445f26dc747d5e", // 李四
      "ou_5g2h3j4k5l6m7n8o9p0q1r2s3t4u5v6"  // 王五
    ],
    targetDepartments: [
      "od-5b91c9affb665451a16b90b4be367efa", // 研发部
      "od-6a7b8c9d0e1f2a3b4c5d6e7f8g9h0i1"  // 产品部
    ]
  };

  try {
    const response = await feishuClient.tenantV1BatchMessage.batchSendTextMessageAsync({
      open_ids: meetingInfo.targetUsers,
      department_ids: meetingInfo.targetDepartments,
      msg_type: "text",
      content: {
        text: meetingInfo.content
      }
    });

    if (response.code === 0) {
      const result = response.data;
      console.log(`消息发送成功,消息ID: ${result.message_id}`);
      
      // 分析发送结果
      const analysis = analyzeBatchSendResult(result, meetingInfo);
      console.log(`成功发送到 ${analysis.successfulSends} 个接收者`);
      
      if (analysis.hasInvalidRecipients) {
        console.warn(`发现无效接收者: ${JSON.stringify(analysis.invalidRecipients)}`);
        // 记录无效接收者以便后续清理
        await logInvalidRecipients(analysis.invalidRecipients);
      }
      
      // 设置消息发送状态监控
      await setupMessageMonitoring(result.message_id);
      
      return result;
    } else {
      console.error(`发送失败: ${response.msg}`);
      throw new Error(`批量发送文本消息失败: ${response.msg}`);
    }
  } catch (error) {
    console.error("发送部门例会通知时发生错误:", error.message);
    throw error;
  }
}

// 分析批量发送结果
function analyzeBatchSendResult(result, meetingInfo) {
  const totalRequested = 
    meetingInfo.targetUsers.length + 
    meetingInfo.targetDepartments.length;
  
  const invalidCount = 
    (result.invalid_open_ids?.length || 0) + 
    (result.invalid_user_ids?.length || 0) + 
    (result.invalid_union_ids?.length || 0) + 
    (result.invalid_department_ids?.length || 0);
  
  const successfulSends = totalRequested - invalidCount;
  
  const analysis = {
    totalRequested,
    invalidCount,
    successfulSends,
    hasInvalidRecipients: invalidCount > 0
  };
  
  if (analysis.hasInvalidRecipients) {
    analysis.invalidRecipients = {
      invalidOpenIds: result.invalid_open_ids || [],
      invalidUserIds: result.invalid_user_ids || [],
      invalidUnionIds: result.invalid_union_ids || [],
      invalidDepartmentIds: result.invalid_department_ids || []
    };
  }
  
  return analysis;
}

// 设置消息监控
async function setupMessageMonitoring(messageId) {
  // 5分钟后检查发送进度
  setTimeout(async () => {
    const progress = await feishuClient.tenantV1BatchMessage.getBatchMessageProgressAsync(messageId);
    console.log(`消息 ${messageId} 发送进度:`, progress.data);
    
    // 1小时后检查已读状态
    setTimeout(async () => {
      const readStatus = await feishuClient.tenantV1BatchMessage.getUserReadMessageInfosAsync(messageId);
      console.log(`消息 ${messageId} 已读状态:`, readStatus.data);
    }, 60 * 60 * 1000);
  }, 5 * 60 * 1000);
}

函数名称:批量发送富文本消息

函数签名

csharp
Task<FeishuApiResult<BatchMessageResult>?> BatchSendRichTextMessageAsync(
  [Body] BatchSenderRichTextMessageRequest sendMessageRequest,
  CancellationToken cancellationToken = default);

认证:租户令牌

参数

参数名类型必填说明示例值
sendMessageRequestBatchSenderRichTextMessageRequest✅ 必填批量发送富文本消息请求体见下方结构
sendMessageRequest.OpenIdsList<string>⚪ 可选用户open_id列表["ou_18eac85d35a26f989317ad4f02e8bbbb"]
sendMessageRequest.DepartmentIdsList<string>⚪ 可选部门ID列表["od-5b91c9affb665451a16b90b4be367efa"]
sendMessageRequest.MsgTypestring✅ 必填消息类型,固定值"post""post"
sendMessageRequest.ContentMessageRichTextContent✅ 必填富文本消息内容见下方结构
cancellationTokenCancellationToken⚪ 可选取消操作令牌-

响应

成功响应示例:

json
{
  "code": 0,
  "msg": "success",
  "data": {
    "message_id": "om_dc13264520392913993dd051dba21dcf",
    "invalid_open_ids": [],
    "invalid_department_ids": []
  }
}

说明

  • 支持发送格式丰富的文本消息,包含标题、列表、链接等
  • 适用于发送通知公告、产品介绍等需要格式化的内容
  • 富文本内容需要符合飞书的富文本格式规范

代码示例

javascript
// 发送产品发布富文本公告
async function sendProductReleaseAnnouncement() {
  const announcement = {
    title: "🎉 新产品发布通知",
    content: {
      post: {
        zh_cn: {
          title: "🎉 智能办公助手 v2.0 正式发布",
          content: [
            [
              {
                tag: "text",
                text: "亲爱的各位同事,\n\n我们很高兴地宣布,"
              },
              {
                tag: "text",
                text: "智能办公助手 v2.0",
                style: {
                  bold: true,
                  color: "#FF0000"
                }
              },
              {
                tag: "text",
                text: "今天正式发布!\n\n"
              }
            ],
            [
              {
                tag: "text",
                text: "🚀 "
              },
              {
                tag: "text",
                text: "主要特性:",
                style: {
                  bold: true
                }
              }
            ],
            [
              {
                tag: "bullet",
                bullet: "1"
              },
              {
                tag: "text",
                text: "智能日程管理,自动安排会议时间"
              }
            ],
            [
              {
                tag: "bullet",
                bullet: "2"
              },
              {
                tag: "text",
                text: "AI文档生成,提升工作效率300%"
              }
            ],
            [
              {
                tag: "bullet",
                bullet: "3"
              },
              {
                tag: "text",
                text: "团队协作优化,实时同步工作进度"
              }
            ],
            [
              {
                tag: "a",
                text: "📖 点击查看详细文档",
                href: "https://docs.company.com/assistant-v2"
              }
            ],
            [
              {
                tag: "text",
                text: "\n\n感谢大家的支持!",
                style: {
                  italic: true
                }
              }
            ]
          ]
        }
      }
    }
  };

  try {
    const response = await feishuClient.tenantV1BatchMessage.batchSendRichTextMessageAsync({
      open_ids: await getAllEmployeeIds(),
      department_ids: ["od-5b91c9affb665451a16b90b4be367efa"],
      msg_type: "post",
      content: announcement.content
    });

    if (response.code === 0) {
      console.log(`产品发布公告发送成功,消息ID: ${response.data.message_id}`);
      
      // 创建发布记录
      await createAnnouncementRecord({
        messageId: response.data.message_id,
        title: announcement.title,
        type: "product_release",
        sentAt: new Date().toISOString()
      });
      
      // 设置后续跟踪
      await trackAnnouncementEngagement(response.data.message_id);
      
      return response.data;
    } else {
      console.error(`发送失败: ${response.msg}`);
      throw new Error(`批量发送富文本消息失败: ${response.msg}`);
    }
  } catch (error) {
    console.error("发送产品发布公告时发生错误:", error.message);
    throw error;
  }
}

// 获取所有员工ID
async function getAllEmployeeIds() {
  // 这里应该调用获取所有员工ID的接口
  return [
    "ou_18eac85d35a26f989317ad4f02e8bbbb",
    "ou_461cf042d9eedaa60d445f26dc747d5e"
  ];
}

// 跟踪公告参与度
async function trackAnnouncementEngagement(messageId) {
  // 定期检查已读状态
  const checkInterval = setInterval(async () => {
    try {
      const readStatus = await feishuClient.tenantV1BatchMessage.getUserReadMessageInfosAsync(messageId);
      const readCount = parseInt(readStatus.data.read_user.read_count);
      const totalCount = parseInt(readStatus.data.read_user.total_count);
      
      const readRate = totalCount > 0 ? (readCount / totalCount * 100).toFixed(2) : 0;
      console.log(`公告已读进度: ${readCount}/${totalCount} (${readRate}%)`);
      
      // 如果大部分人都已阅读,停止检查
      if (readRate >= 80) {
        clearInterval(checkInterval);
        console.log("公告阅读率达到80%,停止跟踪");
      }
    } catch (error) {
      console.error("检查公告阅读状态时发生错误:", error.message);
    }
  }, 30 * 60 * 1000); // 每30分钟检查一次
  
  // 24小时后自动停止跟踪
  setTimeout(() => {
    clearInterval(checkInterval);
  }, 24 * 60 * 60 * 1000);
}

函数名称:批量发送图片消息

函数签名

csharp
Task<FeishuApiResult<BatchMessageResult>?> BatchSendImageMessageAsync(
  [Body] BatchSenderMessageImageRequest sendMessageRequest,
  CancellationToken cancellationToken = default);

认证:租户令牌

参数

参数名类型必填说明示例值
sendMessageRequestBatchSenderMessageImageRequest✅ 必填批量发送图片消息请求体见下方结构
sendMessageRequest.OpenIdsList<string>⚪ 可选用户open_id列表["ou_18eac85d35a26f989317ad4f02e8bbbb"]
sendMessageRequest.DepartmentIdsList<string>⚪ 可选部门ID列表["od-5b91c9affb665451a16b90b4be367efa"]
sendMessageRequest.MsgTypestring✅ 必填消息类型,固定值"image""image"
sendMessageRequest.ContentMessageImageContent✅ 必填图片消息内容见下方结构
sendMessageRequest.Content.ImageKeystring✅ 必填图片的唯一标识符"img_v2_041b8e4a-1234-5678-90ab-cdef12345678g"
cancellationTokenCancellationToken⚪ 可选取消操作令牌-

响应

成功响应示例:

json
{
  "code": 0,
  "msg": "success",
  "data": {
    "message_id": "om_dc13264520392913993dd051dba21dcf",
    "invalid_open_ids": []
  }
}

说明

  • image_key需要通过图片上传接口获取
  • 支持常见图片格式:JPG、PNG、GIF等
  • 图片大小限制通常为10MB以内

代码示例

javascript
// 发送公司活动照片
async function sendCompanyActivityPhotos() {
  const activityInfo = {
    title: "团队建设活动照片分享",
    description: "上周五的团队建设活动圆满结束,以下是精彩瞬间:",
    photos: [
      {
        image_key: "img_v2_041b8e4a-1234-5678-90ab-cdef12345678g",
        description: "团队合影"
      },
      {
        image_key: "img_v2_041b8e4a-8765-4321-ba98-fedcba987654h",
        description: "游戏环节"
      }
    ]
  };

  try {
    // 先发送文字说明
    const textResponse = await feishuClient.tenantV1BatchMessage.batchSendTextMessageAsync({
      open_ids: await getActivityParticipantIds(),
      msg_type: "text",
      content: {
        text: activityInfo.description
      }
    });

    if (textResponse.code !== 0) {
      throw new Error(`发送文字说明失败: ${textResponse.msg}`);
    }

    // 然后发送照片
    for (const photo of activityInfo.photos) {
      const response = await feishuClient.tenantV1BatchMessage.batchSendImageMessageAsync({
        open_ids: await getActivityParticipantIds(),
        department_ids: ["od-5b91c9affb665451a16b90b4be367efa"],
        msg_type: "image",
        content: {
          image_key: photo.image_key
        }
      });

      if (response.code === 0) {
        console.log(`照片发送成功: ${photo.description}, 消息ID: ${response.data.message_id}`);
        
        // 记录照片发送记录
        await recordPhotoSend({
          messageId: response.data.message_id,
          imageKey: photo.image_key,
          description: photo.description,
          sentAt: new Date().toISOString()
        });
      } else {
        console.error(`发送照片失败: ${photo.description}, 错误: ${response.msg}`);
      }
    }

    return {
      success: true,
      message: "活动照片发送完成"
    };
  } catch (error) {
    console.error("发送活动照片时发生错误:", error.message);
    throw error;
  }
}

// 获取活动参与者ID
async function getActivityParticipantIds() {
  // 这里应该调用获取活动参与者的接口
  return [
    "ou_18eac85d35a26f989317ad4f02e8bbbb",
    "ou_461cf042d9eedaa60d445f26dc747d5e"
  ];
}

// 记录照片发送记录
async function recordPhotoSend(photoInfo) {
  // 这里应该调用保存照片发送记录的接口
  console.log("记录照片发送:", photoInfo);
}

函数名称:批量发送群分享消息

函数签名

csharp
Task<FeishuApiResult<BatchMessageResult>?> BatchSendGroupShareMessageAsync(
  [Body] BatchSenderMessageGroupShareRequest sendMessageRequest,
  CancellationToken cancellationToken = default);

认证:租户令牌

参数

参数名类型必填说明示例值
sendMessageRequestBatchSenderMessageGroupShareRequest✅ 必填批量发送群分享消息请求体见下方结构
sendMessageRequest.OpenIdsList<string>⚪ 可选用户open_id列表["ou_18eac85d35a26f989317ad4f02e8bbbb"]
sendMessageRequest.DepartmentIdsList<string>⚪ 可选部门ID列表["od-5b91c9affb665451a16b90b4be367efa"]
sendMessageRequest.MsgTypestring✅ 必填消息类型,固定值"share_chat""share_chat"
sendMessageRequest.ContentMessageGroupShareContent✅ 必填群分享消息内容见下方结构
sendMessageRequest.Content.ShareChatIdstring✅ 必填分享的群聊ID"oc_5d9c3e8f7b6a5c4d3e2f1a9b8c7d6e5f"
cancellationTokenCancellationToken⚪ 可选取消操作令牌-

响应

成功响应示例:

json
{
  "code": 0,
  "msg": "success",
  "data": {
    "message_id": "om_dc13264520392913993dd051dba21dcf",
    "invalid_open_ids": []
  }
}

说明

  • share_chat_id是要分享的群聊的唯一标识
  • 接收者点击后可以查看并加入该群聊
  • 适用于推广重要群聊或引导用户加入特定讨论组

代码示例

javascript
// 推广技术交流群
async function promoteTechnicalDiscussionGroup() {
  const promotionInfo = {
    targetGroup: {
      share_chat_id: "oc_5d9c3e8f7b6a5c4d3e2f1a9b8c7d6e5f",
      name: "前端技术交流群",
      description: "分享前端开发经验,讨论最新技术趋势"
    },
    targetAudience: {
      departments: ["od-5b91c9affb665451a16b90b4be367efa"], // 研发部
      individuals: [
        "ou_18eac85d35a26f989317ad4f02e8bbbb"  // 张三(前端负责人)
      ]
    }
  };

  try {
    // 先发送邀请说明
    const inviteText = `🚀 ${promotionInfo.targetGroup.name}\n\n${promotionInfo.targetGroup.description}\n\n点击下方链接加入群聊,与大家一起交流学习!`;
    
    const textResponse = await feishuClient.tenantV1BatchMessage.batchSendTextMessageAsync({
      open_ids: promotionInfo.targetAudience.individuals,
      department_ids: promotionInfo.targetAudience.departments,
      msg_type: "text",
      content: {
        text: inviteText
      }
    });

    if (textResponse.code !== 0) {
      throw new Error(`发送邀请说明失败: ${textResponse.msg}`);
    }

    // 发送群聊分享卡片
    const response = await feishuClient.tenantV1BatchMessage.batchSendGroupShareMessageAsync({
      open_ids: promotionInfo.targetAudience.individuals,
      department_ids: promotionInfo.targetAudience.departments,
      msg_type: "share_chat",
      content: {
        share_chat_id: promotionInfo.targetGroup.share_chat_id
      }
    });

    if (response.code === 0) {
      console.log(`群聊推广成功,消息ID: ${response.data.message_id}`);
      
      // 记录推广活动
      await recordPromotionActivity({
        messageId: response.data.message_id,
        groupId: promotionInfo.targetGroup.share_chat_id,
        groupName: promotionInfo.targetGroup.name,
        targetCount: promotionInfo.targetAudience.individuals.length + 1, // +1 for department
        promotedAt: new Date().toISOString()
      });
      
      // 设置3天后检查加入情况
      setTimeout(async () => {
        await checkGroupJoinStatus(promotionInfo.targetGroup.share_chat_id);
      }, 3 * 24 * 60 * 60 * 1000);
      
      return response.data;
    } else {
      console.error(`群聊推广失败: ${response.msg}`);
      throw new Error(`批量发送群分享消息失败: ${response.msg}`);
    }
  } catch (error) {
    console.error("推广技术交流群时发生错误:", error.message);
    throw error;
  }
}

// 检查群聊加入状态
async function checkGroupJoinStatus(groupId) {
  try {
    // 这里应该调用检查群聊成员的接口来了解新加入情况
    const currentMembers = await getGroupMemberCount(groupId);
    console.log(`群聊 ${groupId} 当前成员数: ${currentMembers}`);
    
    // 记录加入情况统计
    await recordJoinStatistics({
      groupId,
      memberCount: currentMembers,
      checkDate: new Date().toISOString()
    });
  } catch (error) {
    console.error("检查群聊加入状态时发生错误:", error.message);
  }
}

// 获取群聊成员数
async function getGroupMemberCount(groupId) {
  // 这里应该调用飞书获取群聊信息的接口
  return 25; // 模拟返回
}

// 记录推广活动
async function recordPromotionActivity(promotionData) {
  // 这里应该调用保存推广记录的接口
  console.log("记录推广活动:", promotionData);
}

函数名称:撤回批量消息

函数签名

csharp
Task<FeishuNullDataApiResult?> RevokeMessageAsync(
    [Path] string batch_message_id,
    CancellationToken cancellationToken = default);

认证:租户令牌

参数

参数名类型必填说明示例值
batch_message_idstring✅ 必填批量消息任务ID"om_dc13264520392913993dd051dba21dcf"
cancellationTokenCancellationToken⚪ 可选取消操作令牌-

响应

成功响应示例:

json
{
  "code": 0,
  "msg": "success",
  "data": null
}

常见错误响应示例:

json
{
  "code": 404,
  "msg": "消息不存在或已过期",
  "data": null
}
json
{
  "code": 403,
  "msg": "消息发送时间超过24小时,无法撤回",
  "data": null
}

说明

  • 只能撤回通过批量发送接口发送的消息
  • 撤回有时间限制(通常是24小时内)
  • 撤回操作不可逆,请谨慎使用

代码示例

javascript
// 紧急通知撤回系统
class EmergencyNotificationSystem {
  constructor() {
    this.pendingRevokes = new Map(); // 存储待撤回的消息
    this.revokeReasons = new Map();  // 存储撤回原因
  }

  // 发送可撤回的紧急通知
  async sendEmergencyNotification(content, recipients, maxRetentionHours = 2) {
    try {
      const response = await feishuClient.tenantV1BatchMessage.batchSendTextMessageAsync({
        open_ids: recipients.users || [],
        department_ids: recipients.departments || [],
        msg_type: "text",
        content: {
          text: `🚨 紧急通知(${maxRetentionHours}小时后自动撤回):\n\n${content}`
        }
      });

      if (response.code === 0) {
        const messageId = response.data.message_id;
        
        // 记录待撤回消息
        this.pendingRevokes.set(messageId, {
          content,
          recipients,
          sentAt: new Date(),
          maxRetentionHours,
          autoRevoke: true
        });

        // 设置自动撤回定时器
        const revokeTime = maxRetentionHours * 60 * 60 * 1000;
        setTimeout(async () => {
          await this.autoRevokeMessage(messageId);
        }, revokeTime);

        console.log(`紧急通知发送成功,将在${maxRetentionHours}小时后自动撤回`);
        return messageId;
      } else {
        throw new Error(`发送紧急通知失败: ${response.msg}`);
      }
    } catch (error) {
      console.error("发送紧急通知时发生错误:", error.message);
      throw error;
    }
  }

  // 手动撤回消息
  async revokeMessage(messageId, reason = "手动撤回") {
    try {
      const response = await feishuClient.tenantV1BatchMessage.revokeMessageAsync(messageId);

      if (response.code === 0) {
        console.log(`消息 ${messageId} 撤回成功: ${reason}`);
        
        // 记录撤回操作
        await this.recordRevokeOperation({
          messageId,
          reason,
          timestamp: new Date().toISOString(),
          operator: "system"
        });

        // 清理待撤回记录
        this.pendingRevokes.delete(messageId);
        this.revokeReasons.delete(messageId);

        return true;
      } else {
        console.error(`撤回消息失败: ${response.msg}`);
        return false;
      }
    } catch (error) {
      console.error("撤回消息时发生错误:", error.message);
      return false;
    }
  }

  // 自动撤回消息
  async autoRevokeMessage(messageId) {
    const messageInfo = this.pendingRevokes.get(messageId);
    if (!messageInfo) {
      return;
    }

    const reason = `消息已超过${messageInfo.maxRetentionHours}小时,自动撤回`;
    const success = await this.revokeMessage(messageId, reason);

    if (success) {
      // 发送撤回通知
      await this.sendRevokeNotification(messageInfo, reason);
    }
  }

  // 批量撤回过期消息
  async revokeExpiredMessages() {
    const now = new Date();
    const expiredMessages = [];

    for (const [messageId, messageInfo] of this.pendingRevokes) {
      const elapsedHours = (now - messageInfo.sentAt) / (1000 * 60 * 60);
      if (elapsedHours >= messageInfo.maxRetentionHours) {
        expiredMessages.push(messageId);
      }
    }

    console.log(`发现 ${expiredMessages.length} 条过期消息需要撤回`);

    for (const messageId of expiredMessages) {
      await this.autoRevokeMessage(messageId);
    }

    return expiredMessages.length;
  }

  // 发送撤回通知
  async sendRevokeNotification(messageInfo, reason) {
    try {
      const notification = `⚠️ 通知撤回\n\n原通知内容:${messageInfo.content.substring(0, 50)}...\n撤回原因:${reason}`;

      await feishuClient.tenantV1BatchMessage.batchSendTextMessageAsync({
        open_ids: messageInfo.recipients.users || [],
        department_ids: messageInfo.recipients.departments || [],
        msg_type: "text",
        content: {
          text: notification
        }
      });

      console.log("撤回通知发送成功");
    } catch (error) {
      console.error("发送撤回通知时发生错误:", error.message);
    }
  }

  // 记录撤回操作
  async recordRevokeOperation(operation) {
    // 这里应该调用保存撤回记录的接口
    console.log("记录撤回操作:", operation);
  }
}

// 使用示例
const emergencySystem = new EmergencyNotificationSystem();

// 发送紧急系统维护通知
async function sendSystemMaintenanceNotification() {
  const maintenanceContent = `
系统将于今晚22:00-24:00进行维护升级,期间可能影响正常使用。
请提前保存重要工作,给您带来的不便敬请谅解。

维护内容:
1. 数据库优化
2. 安全补丁更新
3. 性能提升
  `;

  const recipients = {
    users: ["ou_18eac85d35a26f989317ad4f02e8bbbb"],
    departments: ["od-5b91c9affb665451a16b90b4be367efa"]
  };

  try {
    const messageId = await emergencySystem.sendEmergencyNotification(
      maintenanceContent,
      recipients,
      3 // 3小时后自动撤回
    );

    console.log(`系统维护通知发送成功,消息ID: ${messageId}`);
    return messageId;
  } catch (error) {
    console.error("发送系统维护通知时发生错误:", error.message);
    throw error;
  }
}

函数名称:查询消息已读状态

函数签名

csharp
Task<FeishuApiResult<BatchMessageReadStatusResult>?> GetUserReadMessageInfosAsync(
    [Path] string batch_message_id,
    CancellationToken cancellationToken = default);

认证:租户令牌

参数

参数名类型必填说明示例值
batch_message_idstring✅ 必填批量消息任务ID"om_dc13264520392913993dd051dba21dcf"
cancellationTokenCancellationToken⚪ 可选取消操作令牌-

响应

成功响应示例:

json
{
  "code": 0,
  "msg": "success",
  "data": {
    "read_user": {
      "read_count": "15",
      "total_count": "20"
    }
  }
}

说明

  • 返回消息的已读用户数和总用户数
  • 可用于统计消息的阅读效果
  • 适用于重要通知的阅读跟踪

代码示例

javascript
// 消息阅读效果分析系统
class MessageAnalyticsSystem {
  constructor() {
    this.messageStats = new Map(); // 存储消息统计数据
    this.analyticsReports = [];    // 存储分析报告
  }

  // 跟踪消息阅读情况
  async trackMessageReadStatus(messageId, options = {}) {
    const {
      checkInterval = 30 * 60 * 1000,  // 30分钟检查一次
      maxCheckTime = 24 * 60 * 60 * 1000, // 最多检查24小时
      targetReadRate = 80  // 目标阅读率
    } = options;

    const startTime = Date.now();
    const stats = {
      messageId,
      startTime: new Date().toISOString(),
      checkCount: 0,
      readings: []
    };

    this.messageStats.set(messageId, stats);

    const checkIntervalId = setInterval(async () => {
      const elapsed = Date.now() - startTime;
      
      // 超过最大检查时间,停止跟踪
      if (elapsed >= maxCheckTime) {
        clearInterval(checkIntervalId);
        await this.finalizeMessageTracking(messageId);
        return;
      }

      await this.performReadCheck(messageId);
    }, checkInterval);

    // 10分钟后进行第一次检查
    setTimeout(async () => {
      await this.performReadCheck(messageId);
    }, 10 * 60 * 1000);

    return checkIntervalId;
  }

  // 执行阅读检查
  async performReadCheck(messageId) {
    try {
      const response = await feishuClient.tenantV1BatchMessage.getUserReadMessageInfosAsync(messageId);

      if (response.code === 0) {
        const readData = response.data.read_user;
        const readCount = parseInt(readData.read_count);
        const totalCount = parseInt(readData.total_count);
        const readRate = totalCount > 0 ? (readCount / totalCount * 100).toFixed(2) : 0;

        const reading = {
          timestamp: new Date().toISOString(),
          readCount,
          totalCount,
          readRate: parseFloat(readRate)
        };

        const stats = this.messageStats.get(messageId);
        if (stats) {
          stats.checkCount++;
          stats.readings.push(reading);

          console.log(`消息 ${messageId} 阅读情况: ${readCount}/${totalCount} (${readRate}%)`);

          // 检查是否达到目标阅读率
          if (reading.readRate >= 80) {
            console.log(`消息 ${messageId} 已达到80%阅读率,停止跟踪`);
            clearInterval(stats.checkIntervalId);
            await this.finalizeMessageTracking(messageId);
          }

          // 检查阅读增长情况
          if (stats.readings.length >= 2) {
            const prevReading = stats.readings[stats.readings.length - 2];
            const growthRate = ((reading.readCount - prevReading.readCount) / prevReading.totalCount * 100).toFixed(2);
            console.log(`阅读增长率: ${growthRate}%`);
          }
        }

        return reading;
      } else {
        console.error(`查询阅读状态失败: ${response.msg}`);
        return null;
      }
    } catch (error) {
      console.error("检查消息阅读状态时发生错误:", error.message);
      return null;
    }
  }

  // 完成消息跟踪
  async finalizeMessageTracking(messageId) {
    const stats = this.messageStats.get(messageId);
    if (!stats) {
      return;
    }

    const finalReading = stats.readings[stats.readings.length - 1];
    
    const report = {
      messageId,
      trackingPeriod: {
        start: stats.startTime,
        end: new Date().toISOString(),
        checkCount: stats.checkCount
      },
      finalStats: {
        readCount: finalReading.readCount,
        totalCount: finalReading.totalCount,
        readRate: finalReading.readRate
      },
      growthAnalysis: this.analyzeGrowth(stats.readings),
      generatedAt: new Date().toISOString()
    };

    this.analyticsReports.push(report);
    
    // 保存分析报告
    await this.saveAnalyticsReport(report);

    // 清理跟踪数据
    this.messageStats.delete(messageId);

    console.log(`消息 ${messageId} 跟踪完成,最终阅读率: ${finalReading.readRate}%`);
    return report;
  }

  // 分析阅读增长情况
  analyzeGrowth(readings) {
    if (readings.length < 2) {
      return null;
    }

    const first = readings[0];
    const last = readings[readings.length - 1];
    
    const totalGrowth = last.readCount - first.readCount;
    const totalGrowthRate = (totalGrowth / first.totalCount * 100).toFixed(2);
    
    // 计算平均增长率
    let totalIntervalGrowth = 0;
    for (let i = 1; i < readings.length; i++) {
      totalIntervalGrowth += readings[i].readCount - readings[i-1].readCount;
    }
    const avgGrowthPerInterval = (totalIntervalGrowth / (readings.length - 1)).toFixed(2);

    return {
      totalGrowth,
      totalGrowthRate: parseFloat(totalGrowthRate),
      avgGrowthPerInterval: parseFloat(avgGrowthPerInterval),
      timeToReach50Percent: this.getTimeToTarget(readings, 50),
      timeToReach80Percent: this.getTimeToTarget(readings, 80)
    };
  }

  // 计算达到目标阅读率的时间
  getTimeToTarget(readings, targetRate) {
    for (const reading of readings) {
      if (reading.readRate >= targetRate) {
        const startTime = new Date(readings[0].timestamp);
        const targetTime = new Date(reading.timestamp);
        return Math.round((targetTime - startTime) / (1000 * 60)); // 返回分钟数
      }
    }
    return null; // 未达到目标
  }

  // 生成阅读效果报告
  generateReadingReport(messageId) {
    const report = this.analyticsReports.find(r => r.messageId === messageId);
    if (!report) {
      return null;
    }

    const reportText = `
📊 消息阅读效果分析报告

📋 基本信息
- 消息ID: ${report.messageId}
- 跟踪时间: ${new Date(report.trackingPeriod.start).toLocaleString()} - ${new Date(report.trackingPeriod.end).toLocaleString()}
- 检查次数: ${report.trackingPeriod.checkCount}

📈 最终统计
- 总接收人数: ${report.finalStats.totalCount}
- 已读人数: ${report.finalStats.readCount}
- 阅读率: ${report.finalStats.readRate}%

📊 增长分析
- 总增长人数: ${report.growthAnalysis?.totalGrowth || 0}
- 总增长率: ${report.growthAnalysis?.totalGrowthRate || 0}%
- 平均每次检查增长: ${report.growthAnalysis?.avgGrowthPerInterval || 0}
- 达到50%阅读率用时: ${report.growthAnalysis?.timeToReach50Percent ? report.growthAnalysis.timeToReach50Percent + '分钟' : '未达到'}
- 达到80%阅读率用时: ${report.growthAnalysis?.timeToReach80Percent ? report.growthAnalysis.timeToReach80Percent + '分钟' : '未达到'}

💡 建议
${this.generateSuggestions(report)}

📅 报告生成时间: ${new Date(report.generatedAt).toLocaleString()}
    `;

    return reportText.trim();
  }

  // 生成建议
  generateSuggestions(report) {
    const suggestions = [];
    const readRate = report.finalStats.readRate;

    if (readRate >= 90) {
      suggestions.push("✅ 阅读率优秀,消息内容吸引力强");
    } else if (readRate >= 80) {
      suggestions.push("👍 阅读率良好,继续保持");
    } else if (readRate >= 60) {
      suggestions.push("⚠️ 阅读率一般,建议优化消息发送时间或内容");
    } else {
      suggestions.push("❌ 阅读率偏低,建议检查接收者列表或消息内容");
    }

    if (report.growthAnalysis?.timeToReach50Percent > 120) {
      suggestions.push("🕐 建议在用户活跃时间段发送消息以提高阅读率");
    }

    return suggestions.join("\n");
  }

  // 保存分析报告
  async saveAnalyticsReport(report) {
    // 这里应该调用保存分析报告的接口
    console.log("保存分析报告:", report);
  }
}

// 使用示例
const analyticsSystem = new MessageAnalyticsSystem();

// 发送重要通知并跟踪阅读情况
async function sendImportantNotificationWithTracking() {
  const messageId = "om_dc13264520392913993dd051dba21dcf"; // 假设已发送的消息ID

  // 开始跟踪
  await analyticsSystem.trackMessageReadStatus(messageId, {
    checkInterval: 15 * 60 * 1000, // 15分钟检查一次
    maxCheckTime: 12 * 60 * 60 * 1000, // 12小时
    targetReadRate: 85 // 目标阅读率85%
  });

  console.log(`开始跟踪消息 ${messageId} 的阅读情况`);
}

// 生成并查看阅读报告
async function viewMessageReport(messageId) {
  const report = analyticsSystem.generateReadingReport(messageId);
  if (report) {
    console.log(report);
    
    // 可以将报告发送给管理员
    await sendReportToAdmin(report);
  } else {
    console.log("未找到该消息的分析报告");
  }
}

// 发送报告给管理员
async function sendReportToAdmin(reportText) {
  // 这里应该调用发送消息给管理员的接口
  console.log("发送报告给管理员:", reportText);
}

函数名称:查询消息处理进度

函数签名

csharp
Task<FeishuApiResult<BatchMessageProgressResult>?> GetBatchMessageProgressAsync(
    [Path] string batch_message_id,
    CancellationToken cancellationToken = default);

认证:租户令牌

参数

参数名类型必填说明示例值
batch_message_idstring✅ 必填批量消息任务ID"om_dc13264520392913993dd051dba21dcf"
cancellationTokenCancellationToken⚪ 可选取消操作令牌-

响应

成功响应示例:

json
{
  "code": 0,
  "msg": "success",
  "data": {
    "batch_message_send_progress": {
      "total_count": 100,
      "success_count": 85,
      "failed_count": 10,
      "pending_count": 5,
      "progress_rate": 85
    },
    "batch_message_recall_progress": {
      "total_count": 50,
      "success_count": 45,
      "failed_count": 5,
      "pending_count": 0,
      "progress_rate": 90
    }
  }
}

说明

  • 同时返回发送进度和撤回进度(如果进行了撤回操作)
  • 包含成功、失败、待处理的详细统计
  • 可用于监控大批量消息的处理状态

代码示例

javascript
// 批量消息进度监控系统
class BatchMessageMonitor {
  constructor() {
    this.monitoringTasks = new Map(); // 存储监控任务
    this.progressHistory = new Map(); // 存储进度历史
  }

  // 开始监控消息进度
  async startMonitoring(messageId, options = {}) {
    const {
      checkInterval = 10 * 1000,     // 10秒检查一次
      maxMonitorTime = 5 * 60 * 1000, // 最多监控5分钟
      onProgress = null,             // 进度回调
      onComplete = null,              // 完成回调
      onError = null                 // 错误回调
    } = options;

    const monitorTask = {
      messageId,
      startTime: Date.now(),
      checkCount: 0,
      checkInterval,
      maxMonitorTime,
      callbacks: { onProgress, onComplete, onError },
      status: 'running'
    };

    this.monitoringTasks.set(messageId, monitorTask);

    const intervalId = setInterval(async () => {
      await this.checkProgress(messageId, intervalId);
    }, checkInterval);

    // 设置最大监控时间
    const timeoutId = setTimeout(() => {
      this.stopMonitoring(messageId, 'timeout');
    }, maxMonitorTime);

    monitorTask.intervalId = intervalId;
    monitorTask.timeoutId = timeoutId;

    console.log(`开始监控消息 ${messageId} 的处理进度`);
    return intervalId;
  }

  // 检查进度
  async checkProgress(messageId, intervalId) {
    const task = this.monitoringTasks.get(messageId);
    if (!task || task.status !== 'running') {
      return;
    }

    try {
      task.checkCount++;
      
      const response = await feishuClient.tenantV1BatchMessage.getBatchMessageProgressAsync(messageId);
      
      if (response.code === 0) {
        const progressData = response.data;
        const analysis = this.analyzeProgress(progressData);
        
        // 记录进度历史
        this.recordProgressHistory(messageId, analysis);
        
        console.log(`消息 ${messageId} 进度更新:`, analysis);
        
        // 调用进度回调
        if (task.callbacks.onProgress) {
          await task.callbacks.onProgress(analysis);
        }
        
        // 检查是否完成
        if (analysis.sendProgress.isComplete || analysis.recallProgress?.isComplete) {
          this.stopMonitoring(messageId, 'completed');
        }
        
        // 检查异常情况
        if (analysis.hasErrors) {
          if (task.callbacks.onError) {
            await task.callbacks.onError(analysis);
          }
        }
        
      } else {
        console.error(`查询进度失败: ${response.msg}`);
        if (task.callbacks.onError) {
          await task.callbacks.onError({ error: response.msg });
        }
      }
    } catch (error) {
      console.error("检查消息进度时发生错误:", error.message);
      if (task.callbacks.onError) {
        await task.callbacks.onError({ error: error.message });
      }
    }
  }

  // 分析进度数据
  analyzeProgress(progressData) {
    const analysis = {
      timestamp: new Date().toISOString(),
      sendProgress: this.analyzeProgressItem(progressData.batch_message_send_progress),
      recallProgress: progressData.batch_message_recall_progress ? 
                     this.analyzeProgressItem(progressData.batch_message_recall_progress) : null,
      hasErrors: false,
      isComplete: false
    };

    // 检查是否有错误
    if (analysis.sendProgress.failedCount > 0 || 
        (analysis.recallProgress && analysis.recallProgress.failedCount > 0)) {
      analysis.hasErrors = true;
    }

    // 检查是否完成
    if (analysis.sendProgress.isComplete && 
        (!analysis.recallProgress || analysis.recallProgress.isComplete)) {
      analysis.isComplete = true;
    }

    return analysis;
  }

  // 分析单个进度项
  analyzeProgressItem(progressItem) {
    if (!progressItem) {
      return null;
    }

    const totalCount = progressItem.total_count || 0;
    const successCount = progressItem.success_count || 0;
    const failedCount = progressItem.failed_count || 0;
    const pendingCount = progressItem.pending_count || 0;
    const progressRate = progressItem.progress_rate || 0;

    const isComplete = pendingCount === 0 && (successCount + failedCount === totalCount);
    const successRate = totalCount > 0 ? (successCount / totalCount * 100).toFixed(2) : 0;
    const failureRate = totalCount > 0 ? (failedCount / totalCount * 100).toFixed(2) : 0;

    return {
      totalCount,
      successCount,
      failedCount,
      pendingCount,
      progressRate,
      successRate: parseFloat(successRate),
      failureRate: parseFloat(failureRate),
      isComplete
    };
  }

  // 记录进度历史
  recordProgressHistory(messageId, analysis) {
    if (!this.progressHistory.has(messageId)) {
      this.progressHistory.set(messageId, []);
    }
    
    const history = this.progressHistory.get(messageId);
    history.push(analysis);
    
    // 保留最近50条记录
    if (history.length > 50) {
      history.shift();
    }
  }

  // 停止监控
  stopMonitoring(messageId, reason = 'manual') {
    const task = this.monitoringTasks.get(messageId);
    if (!task) {
      return;
    }

    task.status = reason;

    // 清理定时器
    if (task.intervalId) {
      clearInterval(task.intervalId);
    }
    if (task.timeoutId) {
      clearTimeout(task.timeoutId);
    }

    // 调用完成回调
    if (reason === 'completed' && task.callbacks.onComplete) {
      const history = this.progressHistory.get(messageId) || [];
      const finalProgress = history[history.length - 1];
      task.callbacks.onComplete(finalProgress, reason);
    }

    console.log(`停止监控消息 ${messageId},原因: ${reason}`);
    
    // 清理任务
    this.monitoringTasks.delete(messageId);
  }

  // 生成进度报告
  generateProgressReport(messageId) {
    const history = this.progressHistory.get(messageId);
    const task = this.monitoringTasks.get(messageId);
    
    if (!history || history.length === 0) {
      return null;
    }

    const firstProgress = history[0];
    const finalProgress = history[history.length - 1];
    const duration = new Date(finalProgress.timestamp) - new Date(firstProgress.timestamp);

    const report = {
      messageId,
      monitoringDuration: Math.round(duration / 1000), // 秒
      checkCount: task ? task.checkCount : history.length,
      sendProgress: {
        initial: firstProgress.sendProgress,
        final: finalProgress.sendProgress,
        improvement: this.calculateImprovement(firstProgress.sendProgress, finalProgress.sendProgress)
      },
      recallProgress: finalProgress.recallProgress ? {
        initial: history.find(h => h.recallProgress)?.recallProgress,
        final: finalProgress.recallProgress,
        improvement: finalProgress.recallProgress ? 
                     this.calculateImprovement(
                       history.find(h => h.recallProgress)?.recallProgress, 
                       finalProgress.recallProgress
                     ) : null
      } : null,
      timeline: history.map(h => ({
        timestamp: h.timestamp,
        sendProgress: h.sendProgress.progressRate,
        recallProgress: h.recallProgress?.progressRate || 0
      }))
    };

    return report;
  }

  // 计算改进情况
  calculateImprovement(initial, final) {
    if (!initial || !final) {
      return null;
    }

    return {
      successRateImprovement: final.successRate - initial.successRate,
      processedCountImprovement: (final.successCount + final.failedCount) - (initial.successCount + initial.failedCount),
      completionTime: final.isComplete ? "已完成" : "进行中"
    };
  }
}

// 使用示例
const messageMonitor = new BatchMessageMonitor();

// 监控重要通知的发送进度
async function monitorImportantNotification() {
  const messageId = "om_dc13264520392913993dd051dba21dcf"; // 假设的消息ID

  await messageMonitor.startMonitoring(messageId, {
    checkInterval: 5 * 1000,      // 5秒检查一次
    maxMonitorTime: 10 * 60 * 1000, // 监控10分钟
    onProgress: async (progress) => {
      console.log(`发送进度: ${progress.sendProgress.progressRate}% (${progress.sendProgress.successCount}/${progress.sendProgress.totalCount})`);
      
      // 如果发送失败率过高,发送警告
      if (progress.sendProgress.failureRate > 10) {
        console.warn(`⚠️ 发送失败率过高: ${progress.sendProgress.failureRate}%`);
        await sendAlertNotification(progress);
      }
    },
    onComplete: async (finalProgress, reason) => {
      console.log(`监控完成,原因: ${reason}`);
      
      // 生成进度报告
      const report = messageMonitor.generateProgressReport(messageId);
      if (report) {
        await saveProgressReport(report);
        await sendProgressReportToAdmin(report);
      }
    },
    onError: async (error) => {
      console.error("监控过程中发生错误:", error);
      await sendErrorAlert(error);
    }
  });
}

// 发送警告通知
async function sendAlertNotification(progress) {
  const alertMessage = `
⚠️ 批量消息发送异常警告

消息ID: ${progress.messageId || 'Unknown'}
发送进度: ${progress.sendProgress.progressRate}%
失败率: ${progress.sendProgress.failureRate}%
失败数量: ${progress.sendProgress.failedCount}

请及时检查处理!
  `;

  // 这里应该调用发送警告消息的接口
  console.log("发送警告通知:", alertMessage);
}

// 保存进度报告
async function saveProgressReport(report) {
  // 这里应该调用保存报告的接口
  console.log("保存进度报告:", report);
}

// 发送进度报告给管理员
async function sendProgressReportToAdmin(report) {
  const reportText = `
📊 批量消息处理进度报告

📋 基本信息
- 消息ID: ${report.messageId}
- 监控时长: ${Math.round(report.monitoringDuration / 60)}分钟
- 检查次数: ${report.checkCount}

📤 发送进度
- 初始进度: ${report.sendProgress.initial?.progressRate || 0}%
- 最终进度: ${report.sendProgress.final.progressRate}%
- 成功率改进: ${report.sendProgress.improvement?.successRateImprovement || 0}%
- 处理数量改进: ${report.sendProgress.improvement?.processedCountImprovement || 0}

🔙 撤回进度${report.recallProgress ? `
- 最终进度: ${report.recallProgress.final.progressRate}%
- 成功率改进: ${report.recallProgress.improvement?.successRateImprovement || 0}%` : ': 无撤回操作'}

📈 时间线
${report.timeline.map(t => `- ${new Date(t.timestamp).toLocaleTimeString()}: 发送${t.sendProgress}%${t.recallProgress > 0 ? ` 撤回${t.recallProgress}%` : ''}`).join('\n')}

📅 生成时间: ${new Date().toLocaleString()}
  `;

  // 这里应该调用发送报告给管理员的接口
  console.log("发送进度报告:", reportText);
}