上传文件
本文介绍如何在 PG 模式云存储中上传文件,并说明 contentType、自定义元数据、覆盖上传、路径设计和常见失败原因。
前置条件
开始前请确认:
- 已创建 Bucket,参见 Bucket 管 理。
- 已为
storage.objects配置允许上传的INSERTPolicy。 - 客户端已登录,并能通过
auth.uid()在 RLS 中识别当前用户。
基础上传
import cloudbase from '@cloudbase/js-sdk';
const app = cloudbase.init({ env: '<env-id>' });
const bucket = app.storage.from('avatars');
const { data, error } = await bucket.upload('user-123/avatar.png', file, {
contentType: file.type || 'image/png',
});
if (error) {
throw error;
}
console.log(data.id);
console.log(data.path);
console.log(data.fullPath);
路径 user-123/avatar.png 是 Bucket 内对象名。因为已经通过 app.storage.from('avatars') 指定 Bucket,不要再传 cloud:// fileID。
路径设计
对象路径会直接参与 RLS 判断,建议先设计路径,再写 Policy。
| 场景 | 推荐路径 | 常见 Policy 条件 |
|---|---|---|
| 用户头像 | <uid>/avatar.png | (storage.foldername(name))[1] = auth.uid() |
| 用户私有文件 | <uid>/<filename> | owner_id = auth.uid() 或一级目录等于 auth.uid() |
| 团队文件 | <team_id>/<filename> | JOIN team_members 判断成员关系 |
| 文章附件 | articles/<article_id>/<filename> | JOIN articles 判断文章可见性 |
| 公开资源 | assets/<version>/<filename> | 公开读,服务端写 |
路径是权限模型的一部分。避免在同一个 Bucket 中混用多套路径语义,否则 RLS 会变复杂,也不利于 AI 和团队成员理解。
指定 Content-Type
如果上传的是 File 对象,可以优先使用 file.type。对于 Blob、ArrayBuffer 或无扩展名文件,建议显式指定 contentType。
await bucket.upload('user-123/avatar.webp', file, {
contentType: 'image/webp',
});
contentType 会影响浏览器预览、下载行为,也会参与 Bucket 的 allowed_mime_types 校验。
自定义元数据
上传时可以写入业务自定义元数据:
const { error } = await bucket.upload('user-123/avatar.png', file, {
contentType: 'image/png',
metadata: {
usage: 'avatar',
source: 'profile-page',
},
});
if (error) {
throw error;
}
系统元数据如 size、mimetype、cacheControl 由 Storage API 写入。业务自定义元数据会进入 user_metadata,可在 RLS 中读取,也可通过 SQL 查询。
覆盖上传
默认情况下,上传到已存在路径会失败。如果确实需要覆盖同名对象,请传入 upsert: true:
const { error } = await bucket.upload('user-123/avatar.png', file, {
contentType: 'image/png',
upsert: true,
});
覆盖上传通常需要当前用户同时满足:
- 新建对象时的
INSERT WITH CHECK; - 覆盖已有对象时的
UPDATE USING和UPDATE WITH CHECK。
如果只写了 INSERT Policy,首次上传可能成功,但覆盖上传会失败。
覆盖文件的缓存风险
如果文件已经被浏览器、CDN 或中间代理缓存,覆盖同一路径后,用户可能短时间内仍访问到旧内容。
对于公开资源、文章图片、商品图等强缓存资源,推荐使用新路径而不是覆盖原路径:
assets/logo.v2.png
articles/123/cover-20260614.png
uploads/<uid>/<uuid>.jpg
对于头像这类固定路径资源,可以使用 upsert: true,但建议前端在展示 URL 上附带版本参数或更新时间,避免缓存旧图。
上传限制
上传请求会同时受以下规则约束:
| 限制来源 | 示例 |
|---|---|
| Bucket 单文件大小 | storage.buckets.file_size_limit |
| Bucket MIME 白名单 | storage.buckets.allowed_mime_types |
RLS INSERT / UPDATE | 路径、owner、团队成员关系等 |
| 对象命名规则 | 不能空、不能以 / 开头、不能包含连续 / |
集中说明见 限制与最佳实践。
常见上传策略
每个用户只能上传到自己的目录
CREATE POLICY user_files_insert ON storage.objects
FOR INSERT TO authenticated
WITH CHECK (
bucket_id = 'user-files'
AND (storage.foldername(name))[1] = auth.uid()
);
上传者必须是对象 owner
CREATE POLICY private_insert ON storage.objects
FOR INSERT TO authenticated
WITH CHECK (
bucket_id = 'private-files'
AND owner_id = auth.uid()
);
团队成员可上传到团队目录
CREATE POLICY team_files_insert ON storage.objects
FOR INSERT TO authenticated
WITH CHECK (
bucket_id = 'team-files'
AND EXISTS (
SELECT 1 FROM public.team_members tm
WHERE tm.team_id = (storage.foldername(name))[1]
AND tm.user_id = auth.uid()
)
);
更多模板见 RLS 策略模式库。
排障速查
| 现象 | 可能原因 | 处理建议 |
|---|---|---|
| 上传返回 403 | INSERT WITH CHECK 不通过 | 检查 bucket_id、路径首段、owner_id 和当前 auth.uid() |
| 覆盖上传失败 | 缺少 UPDATE Policy | 补充 FOR UPDATE USING + WITH CHECK |
| 文件过大 | 超过 file_size_limit | 调整 Bucket 限制或换 Bucket |
| MIME 类型不允许 | 不在 allowed_mime_types 中 | 检查 contentType 和 Bucket 白名单 |
| 同名对象已存在 | 未设置 upsert: true | 使用新路径或显式覆盖 |
AI 友好提示
给 AI 生成上传代码或 RLS 策略时,尽量提供以下结构化信息:
Bucket: user-files
路径模板:<uid>/<filename>
上传者:authenticated 用户
允许操作:上传、覆盖、删除自己的文件
限制:单文件 20 MB;允许 image/png、image/jpeg、application/pdf
禁止:上传到其他用户目录;匿名上传
比起只说「帮我做文件上传」,这种描述能让 AI 更稳定地生成正确的 SDK 调用和 RLS Policy。