用户任务附件管理 - (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");