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