Skip to content

租户任务附件管理 - (IFeishuTenantV2TaskAttachments)

功能描述

任务附件管理接口,支持为任务上传、查询、获取详情和删除附件。附件可以是任意类型的文件,如图片、PDF文档、zip文件等。附件不可单独存在,必须与任务资源产生关联关系。为新任务添加附件时,必须先调用创建任务接口完成任务创建,再调用上传附件接口上传文件并关联到新建的任务上。该接口使用租户令牌进行认证。

参考文档

https://open.feishu.cn/document/task-v2/attachment/attachment-feature-overview

函数列表

函数名称功能描述认证方式HTTP 方法
UploadAttachmentAsync为特定资源上传附件,支持一次上传多个附件,最多5个租户令牌POST
GetAttachmentPageListAsync列取一个资源的所有附件,支持分页,按照附件上传时间排序租户令牌GET
GetAttachmentByIdAsync获取附件的详细信息,包括GUID、名称、大小、上传时间、临时可下载链接等租户令牌GET
DeleteAttachmentByIdAsync删除指定附件,删除后不可恢复租户令牌DELETE

函数详细内容

上传附件

函数签名

csharp
Task<FeishuApiResult<TaskAttachmentsUploadResult>?> UploadAttachmentAsync(
     [Body] UploadTaskAttachmentsRequest uploadFileRequest,
     [Query("user_id_type")] string user_id_type = Consts.User_Id_Type,
     CancellationToken cancellationToken = default);

认证:租户令牌

参数

  • uploadFileRequest (UploadTaskAttachmentsRequest): 上传附件请求体,包含文件信息和资源关联信息
  • user_id_type (string): 用户ID类型,默认值为Consts.User_Id_Type
  • cancellationToken (CancellationToken): 取消操作令牌对象

响应

json
{
  "code": 0,
  "data": {
    "attachments": [
      {
        "attachment_guid": "b59aa7a3-e98c-4830-8273-cbb29f89b837",
        "name": "项目文档.pdf",
        "size": 1024000,
        "content_type": "application/pdf",
        "upload_time": "2024-01-01T10:00:00Z",
        "resource_id": "d300a75f-c56a-4be9-80d1-e47653028ceb",
        "resource_type": "task"
      }
    ]
  },
  "msg": "success"
}

说明:为特定资源上传附件。本接口可以支持一次上传多个附件,最多5个。每个附件尺寸不超过50MB,格式不限。

代码示例

typescript
const uploadFileRequest = {
  resource_id: "d300a75f-c56a-4be9-80d1-e47653028ceb",
  resource_type: "task",
  files: [
    {
      name: "项目需求文档.pdf",
      content: base64EncodedFileContent,
      content_type: "application/pdf"
    },
    {
      name: "项目图片.jpg", 
      content: base64EncodedImageContent,
      content_type: "image/jpeg"
    }
  ]
};

const response = await feishuTenantClient.uploadAttachment(uploadFileRequest);
console.log("附件上传成功:", response.data.attachments);

// 检查上传结果
const uploadedAttachments = response.data.attachments;
uploadedAttachments.forEach((attachment, index) => {
  console.log(`附件 ${index + 1}:`);
  console.log(`- 文件名: ${attachment.name}`);
  console.log(`- 大小: ${attachment.size} bytes`);
  console.log(`- GUID: ${attachment.attachment_guid}`);
});

获取附件列表

函数签名

csharp
Task<FeishuApiPageListResult<AttachmentResultInfo>?> GetAttachmentPageListAsync(
     [Query("resource_id")] string? resource_id = null,
     [Query("resource_type")] string? resource_type = "task",
     [Query("page_size")] int page_size = 10,
     [Query("page_token")] string? page_token = null,
     [Query("user_id_type")] string user_id_type = Consts.User_Id_Type,
     CancellationToken cancellationToken = default);

认证:租户令牌

参数

  • resource_id (string): 要获取附件的资源ID,例如任务全局唯一ID,示例值:"d300a75f-c56a-4be9-80d1-e47653028ceb"
  • resource_type (string): 要获取附件列表的资源类型,目前只支持"task",默认为"task",示例值:"task"
  • page_size (int): 分页大小,默认值:10
  • page_token (string): 分页标记,第一次请求不填
  • user_id_type (string): 用户ID类型,默认值为Consts.User_Id_Type
  • cancellationToken (CancellationToken): 取消操作令牌对象

响应

json
{
  "code": 0,
  "data": {
    "items": [
      {
        "attachment_guid": "b59aa7a3-e98c-4830-8273-cbb29f89b837",
        "name": "项目文档.pdf",
        "size": 1024000,
        "content_type": "application/pdf",
        "upload_time": "2024-01-01T10:00:00Z",
        "temp_url": "https://example.com/download/attachment?token=xxx",
        "temp_url_expire": "2024-01-01T10:03:00Z",
        "resource_id": "d300a75f-c56a-4be9-80d1-e47653028ceb",
        "resource_type": "task"
      }
    ],
    "page_token": "next_page_token",
    "has_more": true
  },
  "msg": "success"
}

说明:列取一个资源的所有附件。返回的附件列表支持分页,按照附件上传时间排序。每个附件会返回一个可供下载的临时url,有效期为3分钟,最多可以支持3次下载。如果超过使用限制,需要通过本接口获取新的临时url。

代码示例

typescript
// 获取特定任务的所有附件
const response = await feishuTenantClient.getAttachmentPageList({
  resource_id: "d300a75f-c56a-4be9-80d1-e47653028ceb",
  resource_type: "task",
  page_size: 20
});

const attachments = response.data.items;
console.log("任务附件列表:", attachments);

// 计算附件总大小
const totalSize = attachments.reduce((sum, attachment) => sum + attachment.size, 0);
console.log(`附件总大小: ${totalSize} bytes (${(totalSize / 1024 / 1024).toFixed(2)} MB)`);

// 按文件类型分组统计
const attachmentsByType = attachments.reduce((acc, attachment) => {
  const type = attachment.content_type.split('/')[0];
  if (!acc[type]) acc[type] = [];
  acc[type].push(attachment);
  return acc;
}, {});

console.log("按文件类型统计:");
Object.keys(attachmentsByType).forEach(type => {
  console.log(`${type}: ${attachmentsByType[type].length} 个文件`);
});

// 分页获取所有附件
let pageToken: string | undefined = undefined;
let allAttachments = [];

do {
  const pageResponse = await feishuTenantClient.getAttachmentPageList({
    resource_id: "d300a75f-c56a-4be9-80d1-e47653028ceb",
    page_size: 100,
    page_token: pageToken
  });
  
  allAttachments.push(...pageResponse.data.items);
  pageToken = pageResponse.data.page_token;
} while (pageToken);

console.log(`获取到全部 ${allAttachments.length} 个附件`);

获取附件详情

函数签名

csharp
Task<FeishuApiResult<GetAttachmentsInfoResult>?> GetAttachmentByIdAsync(
    [Path] string attachment_guid,
    [Query("user_id_type")] string user_id_type = Consts.User_Id_Type,
    CancellationToken cancellationToken = default);

认证:租户令牌

参数

  • attachment_guid (string): 获取详情的附件GUID,示例值:"b59aa7a3-e98c-4830-8273-cbb29f89b837"
  • user_id_type (string): 用户ID类型,默认值为Consts.User_Id_Type
  • cancellationToken (CancellationToken): 取消操作令牌对象

响应

json
{
  "code": 0,
  "data": {
    "attachment": {
      "attachment_guid": "b59aa7a3-e98c-4830-8273-cbb29f89b837",
      "name": "项目文档.pdf",
      "size": 1024000,
      "content_type": "application/pdf",
      "upload_time": "2024-01-01T10:00:00Z",
      "temp_url": "https://example.com/download/attachment?token=xxx",
      "temp_url_expire": "2024-01-01T10:03:00Z",
      "resource_id": "d300a75f-c56a-4be9-80d1-e47653028ceb",
      "resource_type": "task",
      "upload_user": {
        "user_id": "user_123",
        "name": "张三",
        "email": "zhangsan@example.com"
      }
    }
  },
  "msg": "success"
}

说明:提供一个附件GUID,返回附件的详细信息,包括GUID,名称,大小,上传时间,临时可下载链接等。

代码示例

typescript
const response = await feishuTenantClient.getAttachmentById("b59aa7a3-e98c-4830-8273-cbb29f89b837");
const attachment = response.data.attachment;

console.log("附件详情:");
console.log(`文件名: ${attachment.name}`);
console.log(`文件大小: ${attachment.size} bytes`);
console.log(`文件类型: ${attachment.content_type}`);
console.log(`上传时间: ${attachment.upload_time}`);
console.log(`上传者: ${attachment.upload_user.name}`);
console.log(`下载链接: ${attachment.temp_url}`);

// 检查下载链接是否有效
const now = new Date();
const expireTime = new Date(attachment.temp_url_expire);
if (now < expireTime) {
  console.log("下载链接有效,可以使用");
  
  // 计算剩余有效时间
  const remainingTime = expireTime.getTime() - now.getTime();
  const remainingMinutes = Math.floor(remainingTime / (1000 * 60));
  console.log(`剩余有效时间: ${remainingMinutes} 分钟`);
} else {
  console.log("下载链接已过期,需要重新获取");
  
  // 重新获取附件列表以获得新的下载链接
  const newAttachmentsResponse = await feishuTenantClient.getAttachmentPageList({
    resource_id: attachment.resource_id
  });
  
  const updatedAttachment = newAttachmentsResponse.data.items.find(
    item => item.attachment_guid === attachment.attachment_guid
  );
  
  if (updatedAttachment && updatedAttachment.temp_url) {
    console.log("新的下载链接:", updatedAttachment.temp_url);
  }
}

删除附件

函数签名

csharp
Task<FeishuNullDataApiResult?> DeleteAttachmentByIdAsync(
      [Path] string attachment_guid,
      [Query("user_id_type")] string user_id_type = Consts.User_Id_Type,
      CancellationToken cancellationToken = default);

认证:租户令牌

参数

  • attachment_guid (string): 删除的附件GUID,示例值:"b59aa7a3-e98c-4830-8273-cbb29f89b837"
  • user_id_type (string): 用户ID类型,默认值为Consts.User_Id_Type
  • cancellationToken (CancellationToken): 取消操作令牌对象

响应

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

说明:提供一个附件GUID,删除该附件。删除后该附件不可再恢复,请谨慎操作。

代码示例

typescript
const attachmentGuid = "b59aa7a3-e98c-4830-8273-cbb29f89b837";

// 安全删除:先获取附件信息确认
try {
  const detailResponse = await feishuTenantClient.getAttachmentById(attachmentGuid);
  const attachment = detailResponse.data.attachment;
  
  console.log(`准备删除附件: ${attachment.name}`);
  console.log(`文件大小: ${attachment.size} bytes`);
  console.log(`上传时间: ${attachment.upload_time}`);
  
  // 确认删除
  const deleteResponse = await feishuTenantClient.deleteAttachmentById(attachmentGuid);
  
  if (deleteResponse.code === 0) {
    console.log(`附件 "${attachment.name}" 删除成功`);
  } else {
    console.log(`删除失败: ${deleteResponse.msg}`);
  }
  
} catch (error) {
  console.log(`附件不存在或已被删除: ${error.message}`);
}

// 批量删除附件(示例:删除超过30天的旧附件)
async function cleanupOldAttachments(taskId: string, daysThreshold: number = 30) {
  const thresholdDate = new Date();
  thresholdDate.setDate(thresholdDate.getDate() - daysThreshold);
  
  const attachmentsResponse = await feishuTenantClient.getAttachmentPageList({
    resource_id: taskId,
    page_size: 100
  });
  
  const oldAttachments = attachmentsResponse.data.items.filter(
    attachment => new Date(attachment.upload_time) < thresholdDate
  );
  
  console.log(`发现 ${oldAttachments.length} 个超过 ${daysThreshold} 天的旧附件`);
  
  for (const oldAttachment of oldAttachments) {
    try {
      await feishuTenantClient.deleteAttachmentById(oldAttachment.attachment_guid);
      console.log(`已删除旧附件: ${oldAttachment.name}`);
    } catch (error) {
      console.log(`删除附件失败: ${oldAttachment.name}, 错误: ${error.message}`);
    }
  }
}

// 使用批量清理功能
await cleanupOldAttachments("d300a75f-c56a-4be9-80d1-e47653028ceb", 30);

附件管理最佳实践

完整的附件上传流程

typescript
async function uploadAttachmentsToTask(taskId: string, files: File[]) {
  try {
    // 1. 验证文件大小和数量
    if (files.length > 5) {
      throw new Error("一次最多只能上传5个文件");
    }
    
    const oversizedFiles = files.filter(file => file.size > 50 * 1024 * 1024);
    if (oversizedFiles.length > 0) {
      throw new Error("文件大小不能超过50MB");
    }
    
    // 2. 转换文件为base64
    const fileData = await Promise.all(
      files.map(async (file) => {
        const base64 = await fileToBase64(file);
        return {
          name: file.name,
          content: base64.split(',')[1], // 移除data:image/...;base64,前缀
          content_type: file.type
        };
      })
    );
    
    // 3. 上传附件
    const uploadRequest = {
      resource_id: taskId,
      resource_type: "task",
      files: fileData
    };
    
    const response = await feishuTenantClient.uploadAttachment(uploadRequest);
    
    console.log(`成功上传 ${response.data.attachments.length} 个附件`);
    return response.data.attachments;
    
  } catch (error) {
    console.error("附件上传失败:", error);
    throw error;
  }
}

// 辅助函数:将文件转换为base64
function fileToBase64(file: File): Promise<string> {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = () => resolve(reader.result as string);
    reader.onerror = error => reject(error);
  });
}

// 使用示例
const files = [file1, file2, file3]; // 来自文件选择的File对象
const uploadedAttachments = await uploadAttachmentsToTask("task_123", files);

附件监控和统计

typescript
// 获取任务的附件统计信息
async function getAttachmentStats(taskId: string) {
  const allAttachments = [];
  let pageToken: string | undefined = undefined;
  
  // 分页获取所有附件
  do {
    const response = await feishuTenantClient.getAttachmentPageList({
      resource_id: taskId,
      page_size: 100,
      page_token: pageToken
    });
    
    allAttachments.push(...response.data.items);
    pageToken = response.data.page_token;
  } while (pageToken);
  
  // 统计信息
  const stats = {
    totalAttachments: allAttachments.length,
    totalSize: allAttachments.reduce((sum, att) => sum + att.size, 0),
    fileTypes: {},
    uploadDates: {},
    oldestAttachment: null,
    newestAttachment: null
  };
  
  // 按文件类型统计
  allAttachments.forEach(att => {
    const type = att.content_type;
    stats.fileTypes[type] = (stats.fileTypes[type] || 0) + 1;
  });
  
  // 按上传日期统计
  allAttachments.forEach(att => {
    const date = new Date(att.upload_time).toLocaleDateString();
    stats.uploadDates[date] = (stats.uploadDates[date] || 0) + 1;
  });
  
  // 找出最旧和最新的附件
  if (allAttachments.length > 0) {
    stats.oldestAttachment = allAttachments.reduce((oldest, current) =>
      new Date(current.upload_time) < new Date(oldest.upload_time) ? current : oldest
    );
    
    stats.newestAttachment = allAttachments.reduce((newest, current) =>
      new Date(current.upload_time) > new Date(newest.upload_time) ? current : newest
    );
  }
  
  return stats;
}

// 使用统计功能
const stats = await getAttachmentStats("d300a75f-c56a-4be9-80d1-e47653028ceb");
console.log("任务附件统计:");
console.log(`- 总数: ${stats.totalAttachments}`);
console.log(`- 总大小: ${(stats.totalSize / 1024 / 1024).toFixed(2)} MB`);
console.log(`- 文件类型分布:`, stats.fileTypes);
console.log(`- 上传日期分布:`, stats.uploadDates);
console.log(`- 最旧附件:`, stats.oldestAttachment?.name);
console.log(`- 最新附件:`, stats.newestAttachment?.name);