管理文件
本文介绍 PG 模式云存储中的文件列表、文件信息、存在性检查、复制、移动和删除操作。
初始化
import cloudbase from '@cloudbase/js-sdk';
const app = cloudbase.init({ env: '<env-id>' });
const bucket = app.storage.from('user-files');
所有路径参数都是 Bucket 内对象名,不需要传 cloud:// fileID。
列出文件
使用 list() 按前缀列出对象,支持游标分页:
const { data, error } = await bucket.list('user-123', {
limit: 20,
withDelimiter: true,
sortBy: {
column: 'created_at',
order: 'desc',
},
});
if (error) {
throw error;
}
console.log(data.folders);
console.log(data.objects);
if (data.hasNext) {
const nextPage = await bucket.list('user-123', {
cursor: data.nextCursor,
});
console.log(nextPage.data?.objects);
}
list() 需要 storage.objects 上的 SELECT Policy 放行。对于用户私有目录,通常应限制前缀的第一级目录等于当前用户 ID。
CREATE POLICY user_files_select ON storage.objects
FOR SELECT TO authenticated
USING (
bucket_id = 'user-files'
AND (storage.foldername(name))[1] = auth.uid()
);
获取文件信息
使用 info() 获取对象元信息:
const { data, error } = await bucket.info('user-123/avatar.png');
if (error) {
throw error;
}
console.log(data.bucketId);
console.log(data.contentType);
console.log(data.metadata);
如果需要在 SQL 中分析文件大小、类型、上传者等信息,也可以直接查询 storage.objects:
SELECT id, bucket_id, name, owner_id, metadata, user_metadata, created_at
FROM storage.objects
WHERE bucket_id = 'user-files'
AND name = 'user-123/avatar.png';
检查文件是否存在
const { data: exists, error } = await bucket.exists('user-123/avatar.png');
if (error) {
throw error;
}
console.log(exists);
exists() 同样受 SELECT Policy 影响:无权限读取的对象,即使真实存在,也不应向当前用户暴露存在性。
复制文件
使用 copy() 复制对象:
const { data, error } = await bucket.copy(
'user-123/avatar.png',
'user-123/avatar-copy.png',
{
upsert: true,
copyMetadata: true,
}
);
if (error) {
throw error;
}
console.log(data.path);
跨 Bucket 复制时,需要当前用户具备:
- 源 Bucket 对象的读取权限;
- 目标 Bucket 路径的写入权限。
await bucket.copy('user-123/avatar.png', 'archive/user-123/avatar.png', {
destinationBucket: 'archive-files',
copyMetadata: true,
});
移动文件
使用 move() 移动或重命名对象:
const { error } = await bucket.move(
'user-123/avatar.png',
'user-123/archive/avatar.png'
);
if (error) {
throw error;
}
移动语义可以理解为「复制到目标位置 → 删除源对象」。因此跨 Bucket 移动时,当前用户需要同时具备:
- 源对象
SELECT权限; - 源对象
DELETE权限; - 目标路径
INSERT/UPDATE权限。
删除文件
使用 remove() 删除一个或多个对象:
const { data, error } = await bucket.remove([
'user-123/avatar.png',
'user-123/avatar-old.png',
]);
if (error) {
throw error;
}
console.log(data);
删除必须通过 SDK 或 Storage API 执行,不要直接 DELETE FROM storage.objects。
-- 不推荐:会被 protect_delete 触发器拦截
DELETE FROM storage.objects
WHERE bucket_id = 'user-files'
AND name = 'user-123/avatar.png';
直接删除元数据会让对象存储后端残留孤儿文件,因此默认会被触发器拒绝。详见 常见问题:为什么直接 DELETE 报错。
批量操作建议
批量列表、批量签名、批量删除都建议控制单次规模,并做好失败重试:
- 列表使用游标分页,不要假设一次能列完所有对象;
- 批量删除前先确认前缀和权限条件,避免误删;
- 大批量清理建议由可信服务端使用
service_role执行,并记录审计日志; - 删除与业务表有关联的附件时,优先采用「先删对象,再删业务引用」或事务性补偿流程。
RLS 操作映射
| 文件管理操作 | 需要的 DML 权限 | 说明 |
|---|---|---|
list() | SELECT | 列出对象和目录 |
info() / exists() | SELECT | 查看对象元信息或存在性 |
copy() | SELECT + INSERT / UPDATE | 读源对象,写目标对象 |
move() | SELECT + INSERT / UPDATE + DELETE | 复制后删除源对象 |
remove() | DELETE | 删除对象 |
如果需要区分「下载」和「列目录」,可使用 storage.operation() 系列辅助函数。详见 权限管理。
AI 友好提示
让 AI 生成文件管理逻辑时,建议提供:
Bucket: team-files
路径模板:<team_id>/<filename>
列表权限:团队成员可以列出本团队目录
删除权限:仅团队 admin 可以删除
移动权限:需要保持在同一个 team_id 目录内
批量规模:每次最多处理 100 个对象
尤其要明确「能否列目录」和「能否删除」,避免 AI 只根据可下载权限推断出过宽的管理权限。