跳到主要内容

上传文件

本文介绍如何在 PG 模式云存储中上传文件,并说明 contentType、自定义元数据、覆盖上传、路径设计和常见失败原因。

前置条件

开始前请确认:

  1. 已创建 Bucket,参见 Bucket 管理
  2. 已为 storage.objects 配置允许上传的 INSERT Policy。
  3. 客户端已登录,并能通过 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.pngBucket 内对象名。因为已经通过 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。对于 BlobArrayBuffer 或无扩展名文件,建议显式指定 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;
}

系统元数据如 sizemimetypecacheControl 由 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 USINGUPDATE 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 策略模式库

排障速查

现象可能原因处理建议
上传返回 403INSERT 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。