Skip to content

用户任务附件管理 - (IFeishuUserV2TaskAttachments)

功能描述

个人任务附件管理接口,支持为用户自己的任务上传、查询、获取详情和删除附件。附件可以是任意类型的文件,如图片、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": 512000,
        "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: "工作计划.docx", 
      content: base64EncodedDocContent,
      content_type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
    }
  ]
};

const response = await feishuUserClient.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(`- 类型: ${attachment.content_type}`);
  console.log(`- GUID: ${attachment.attachment_guid}`);
});

// 计算总上传大小
const totalSize = uploadedAttachments.reduce((sum, attachment) => sum + attachment.size, 0);
console.log(`本次上传总大小: ${(totalSize / 1024).toFixed(2)} KB`);

获取附件列表

函数签名

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": 512000,
        "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 feishuUserClient.getAttachmentPageList({
  resource_id: "d300a75f-c56a-4be9-80d1-e47653028ceb",
  resource_type: "task",
  page_size: 20
});

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

// 分析个人附件使用情况
if (attachments.length > 0) {
  const totalSize = attachments.reduce((sum, attachment) => sum + attachment.size, 0);
  const avgSize = totalSize / attachments.length;
  const largestAttachment = attachments.reduce((max, current) => 
    current.size > max.size ? current : max
  );
  const smallestAttachment = attachments.reduce((min, current) => 
    current.size < min.size ? current : min
  );
  
  console.log("个人附件统计:");
  console.log(`- 附件总数: ${attachments.length}`);
  console.log(`- 总大小: ${(totalSize / 1024).toFixed(2)} KB`);
  console.log(`- 平均大小: ${(avgSize / 1024).toFixed(2)} KB`);
  console.log(`- 最大文件: ${largestAttachment.name} (${(largestAttachment.size / 1024).toFixed(2)} KB)`);
  console.log(`- 最小文件: ${smallestAttachment.name} (${(smallestAttachment.size / 1024).toFixed(2)} KB)`);
  
  // 按文件类型分类
  const attachmentsByType = attachments.reduce((acc, attachment) => {
    const type = attachment.content_type.split('/')[0] || 'unknown';
    if (!acc[type]) acc[type] = [];
    acc[type].push(attachment);
    return acc;
  }, {});
  
  console.log("按文件类型分布:");
  Object.keys(attachmentsByType).forEach(type => {
    console.log(`- ${type}: ${attachmentsByType[type].length} 个文件`);
  });
}

// 分页获取所有个人附件
async function getAllPersonalAttachments(taskId: string) {
  let pageToken: string | undefined = undefined;
  let allAttachments = [];
  
  do {
    const pageResponse = await feishuUserClient.getAttachmentPageList({
      resource_id: taskId,
      page_size: 100,
      page_token: pageToken
    });
    
    allAttachments.push(...pageResponse.data.items);
    pageToken = pageResponse.data.page_token;
    
    console.log(`已获取 ${allAttachments.length} 个附件...`);
  } while (pageToken);
  
  return allAttachments;
}

// 使用分页获取功能
const allPersonalAttachments = await getAllPersonalAttachments("d300a75f-c56a-4be9-80d1-e47653028ceb");
console.log(`个人任务共有 ${allPersonalAttachments.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": 512000,
      "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_self",
        "name": "张三",
        "email": "zhangsan@example.com"
      }
    }
  },
  "msg": "success"
}

说明:提供一个附件GUID,返回个人附件的详细信息,包括GUID,名称,大小,上传时间,临时可下载链接等。用户只能查看自己有权限的附件详情。

代码示例

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

console.log("个人附件详情:");
console.log(`文件名: ${attachment.name}`);
console.log(`文件大小: ${attachment.size} bytes (${(attachment.size / 1024).toFixed(2)} KB)`);
console.log(`文件类型: ${attachment.content_type}`);
console.log(`上传时间: ${attachment.upload_time}`);
console.log(`上传者: ${attachment.upload_user.name}`);
console.log(`所属任务ID: ${attachment.resource_id}`);
console.log(`下载链接: ${attachment.temp_url}`);

// 检查下载链接状态并处理过期情况
async function getValidDownloadUrl(attachment: any) {
  const now = new Date();
  const expireTime = new Date(attachment.temp_url_expire);
  
  if (now < expireTime) {
    const remainingMinutes = Math.floor((expireTime.getTime() - now.getTime()) / (1000 * 60));
    console.log(`下载链接有效,剩余时间: ${remainingMinutes} 分钟`);
    return attachment.temp_url;
  } else {
    console.log("下载链接已过期,正在获取新的下载链接...");
    
    // 重新获取附件列表以获得新的下载链接
    const newAttachmentsResponse = await feishuUserClient.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("已获取新的下载链接");
      return updatedAttachment.temp_url;
    } else {
      throw new Error("无法获取有效的下载链接");
    }
  }
}

// 使用示例
try {
  const downloadUrl = await getValidDownloadUrl(attachment);
  console.log(`可用的下载链接: ${downloadUrl}`);
  
  // 可以在这里添加下载逻辑,例如在新窗口中打开下载链接
  // window.open(downloadUrl, '_blank');
  
} catch (error) {
  console.error("获取下载链接失败:", error);
}

// 批量获取多个附件的有效下载链接
async function getValidDownloadUrls(attachmentGuids: string[]) {
  const downloadUrls = [];
  
  for (const guid of attachmentGuids) {
    try {
      const response = await feishuUserClient.getAttachmentById(guid);
      const url = await getValidDownloadUrl(response.data.attachment);
      downloadUrls.push({
        guid: guid,
        name: response.data.attachment.name,
        url: url
      });
    } catch (error) {
      console.error(`获取附件 ${guid} 的下载链接失败:`, error);
      downloadUrls.push({
        guid: guid,
        name: "未知",
        url: null,
        error: error.message
      });
    }
  }
  
  return downloadUrls;
}

// 使用批量获取功能
const attachmentGuids = ["guid1", "guid2", "guid3"];
const downloadUrls = await getValidDownloadUrls(attachmentGuids);
console.log("批量下载链接:", downloadUrls);

删除附件

函数签名

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";

// 安全删除个人附件:先获取信息确认,再删除
async function safeDeletePersonalAttachment(attachmentGuid: string) {
  try {
    // 1. 获取附件详情
    const detailResponse = await feishuUserClient.getAttachmentById(attachmentGuid);
    const attachment = detailResponse.data.attachment;
    
    // 2. 显示附件信息供用户确认
    console.log("准备删除个人附件:");
    console.log(`- 文件名: ${attachment.name}`);
    console.log(`- 文件大小: ${(attachment.size / 1024).toFixed(2)} KB`);
    console.log(`- 上传时间: ${attachment.upload_time}`);
    console.log(`- 文件类型: ${attachment.content_type}`);
    
    // 3. 确认删除(在实际应用中,这里应该显示确认对话框)
    const confirmed = confirm(`确定要删除附件 "${attachment.name}" 吗?此操作不可恢复。`);
    
    if (confirmed) {
      // 4. 执行删除操作
      const deleteResponse = await feishuUserClient.deleteAttachmentById(attachmentGuid);
      
      if (deleteResponse.code === 0) {
        console.log(`个人附件 "${attachment.name}" 删除成功`);
        
        // 记录删除日志(可选)
        const deletionLog = {
          attachmentGuid: attachmentGuid,
          fileName: attachment.name,
          fileSize: attachment.size,
          deletedAt: new Date().toISOString(),
          reason: "用户主动删除"
        };
        console.log("删除日志:", deletionLog);
        
        return true;
      } else {
        console.log(`删除失败: ${deleteResponse.msg}`);
        return false;
      }
    } else {
      console.log("用户取消了删除操作");
      return false;
    }
    
  } catch (error) {
    console.log(`删除个人附件失败: ${error.message}`);
    
    // 检查是否为权限问题
    if (error.message.includes("403") || error.message.includes("permission")) {
      console.log("可能的原因: 没有删除此附件的权限");
    } else if (error.message.includes("404")) {
      console.log("可能的原因: 附件不存在或已被删除");
    }
    
    return false;
  }
}

// 使用安全删除功能
const deleteResult = await safeDeletePersonalAttachment(attachmentGuid);

// 批量删除个人附件(按文件类型)
async function deletePersonalAttachmentsByType(taskId: string, contentType: string) {
  try {
    // 1. 获取任务的所有附件
    const attachmentsResponse = await feishuUserClient.getAttachmentPageList({
      resource_id: taskId,
      page_size: 100
    });
    
    // 2. 筛选指定类型的附件
    const targetAttachments = attachmentsResponse.data.items.filter(
      attachment => attachment.content_type.includes(contentType)
    );
    
    if (targetAttachments.length === 0) {
      console.log(`没有找到类型包含 "${contentType}" 的个人附件`);
      return { deleted: 0, failed: 0 };
    }
    
    console.log(`找到 ${targetAttachments.length} 个类型包含 "${contentType}" 的个人附件:`);
    targetAttachments.forEach(att => console.log(`- ${att.name} (${att.content_type})`));
    
    // 3. 确认批量删除
    const confirmed = confirm(`确定要删除这 ${targetAttachments.length} 个附件吗?此操作不可恢复。`);
    
    if (!confirmed) {
      console.log("用户取消了批量删除操作");
      return { deleted: 0, failed: 0 };
    }
    
    // 4. 执行批量删除
    let deletedCount = 0;
    let failedCount = 0;
    
    for (const attachment of targetAttachments) {
      try {
        await feishuUserClient.deleteAttachmentById(attachment.attachment_guid);
        console.log(`已删除: ${attachment.name}`);
        deletedCount++;
      } catch (error) {
        console.log(`删除失败: ${attachment.name}, 错误: ${error.message}`);
        failedCount++;
      }
    }
    
    console.log(`批量删除完成: 成功 ${deletedCount} 个,失败 ${failedCount} 个`);
    return { deleted: deletedCount, failed: failedCount };
    
  } catch (error) {
    console.error("批量删除个人附件失败:", error);
    return { deleted: 0, failed: 1 };
  }
}

// 使用批量删除功能(例如:删除所有图片文件)
const result = await deletePersonalAttachmentsByType(
  "d300a75f-c56a-4be9-80d1-e47653028ceb", 
  "image"
);

console.log(`删除结果: 成功 ${result.deleted} 个,失败 ${result.failed} 个`);

个人附件管理最佳实践

个人附件管理器类

typescript
class PersonalAttachmentManager {
  private userClient: any;
  
  constructor(userClient: any) {
    this.userClient = userClient;
  }
  
  // 上传个人附件并管理
  async uploadPersonalAttachment(taskId: string, file: File): Promise<any> {
    try {
      // 1. 验证文件
      if (file.size > 50 * 1024 * 1024) {
        throw new Error("文件大小不能超过50MB");
      }
      
      // 2. 转换文件
      const base64Content = await this.fileToBase64(file);
      
      // 3. 上传
      const uploadRequest = {
        resource_id: taskId,
        resource_type: "task",
        files: [{
          name: file.name,
          content: base64Content.split(',')[1],
          content_type: file.type
        }]
      };
      
      const response = await this.userClient.uploadAttachment(uploadRequest);
      const uploadedAttachment = response.data.attachments[0];
      
      console.log(`个人附件上传成功: ${uploadedAttachment.name}`);
      return uploadedAttachment;
      
    } catch (error) {
      console.error("个人附件上传失败:", error);
      throw error;
    }
  }
  
  // 获取个人附件统计
  async getPersonalAttachmentStats(taskId: string): Promise<any> {
    const allAttachments = await this.getAllPersonalAttachments(taskId);
    
    return {
      totalFiles: allAttachments.length,
      totalSize: allAttachments.reduce((sum, att) => sum + att.size, 0),
      fileTypes: this.getFileTypeDistribution(allAttachments),
      uploadDates: this.getUploadDateDistribution(allAttachments),
      largestFile: allAttachments.reduce((max, current) => 
        current.size > max.size ? current : max, allAttachments[0]),
      smallestFile: allAttachments.reduce((min, current) => 
        current.size < min.size ? current : min, allAttachments[0])
    };
  }
  
  // 清理过期的个人附件下载链接
  async refreshExpiredDownloadUrls(taskId: string): Promise<void> {
    const attachments = await this.getAllPersonalAttachments(taskId);
    const now = new Date();
    
    for (const attachment of attachments) {
      const expireTime = new Date(attachment.temp_url_expire);
      if (now >= expireTime) {
        console.log(`刷新附件下载链接: ${attachment.name}`);
        // 重新获取附件列表以更新下载链接
        await this.userClient.getAttachmentPageList({
          resource_id: taskId
        });
      }
    }
  }
  
  // 辅助方法
  private async 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);
    });
  }
  
  private async getAllPersonalAttachments(taskId: string): Promise<any[]> {
    let pageToken: string | undefined = undefined;
    let allAttachments = [];
    
    do {
      const response = await this.userClient.getAttachmentPageList({
        resource_id: taskId,
        page_size: 100,
        page_token: pageToken
      });
      
      allAttachments.push(...response.data.items);
      pageToken = response.data.page_token;
    } while (pageToken);
    
    return allAttachments;
  }
  
  private getFileTypeDistribution(attachments: any[]): Record<string, number> {
    return attachments.reduce((acc, att) => {
      const type = att.content_type.split('/')[0] || 'unknown';
      acc[type] = (acc[type] || 0) + 1;
      return acc;
    }, {});
  }
  
  private getUploadDateDistribution(attachments: any[]): Record<string, number> {
    return attachments.reduce((acc, att) => {
      const date = new Date(att.upload_time).toLocaleDateString();
      acc[date] = (acc[date] || 0) + 1;
      return acc;
    }, {});
  }
}

// 使用个人附件管理器
const personalAttachmentManager = new PersonalAttachmentManager(feishuUserClient);

// 上传个人附件
const fileInput = document.getElementById('fileInput') as HTMLInputElement;
const file = fileInput.files[0];
if (file) {
  const uploadedAttachment = await personalAttachmentManager.uploadPersonalAttachment("task_123", file);
  console.log("上传成功:", uploadedAttachment);
}

// 获取个人附件统计
const stats = await personalAttachmentManager.getPersonalAttachmentStats("task_123");
console.log("个人附件统计:", stats);

// 刷新过期下载链接
await personalAttachmentManager.refreshExpiredDownloadUrls("task_123");