跳到主要内容

限制与最佳实践

本文汇总 PG 模式云存储的命名限制、上传限制、路径设计、权限策略和 AI 友好的工程约定。

命名限制

Bucket 名称

Bucket 名称建议使用稳定、可读、可被代码引用的英文标识,例如:

avatars
public-assets
private-files
team-files
article-assets

建议遵循:

  • 使用小写字母、数字和 -
  • 避免使用空格、中文和特殊符号;
  • 名称表达业务用途,而不是环境名或临时需求;
  • 一旦用于生产,不要频繁改名。

对象路径

对象名需要遵循以下规则:

  • 不能为空;
  • 不能以 / 开头;
  • 不能出现连续 /
  • 推荐使用大小写英文字母、数字,以及 -!_.*
  • 如果文件或文件夹名包含中文,访问和请求时中文部分会按 URL Encode 转为百分号编码;
  • 不建议使用特殊字符:` ^ " \\ { } [ ] ~ % # > < 以及 ASCII 128-255 十进制字符;
  • 可能需要特殊处理的字符::;=&$@+?、空格,以及 ASCII 控制字符。

上传限制

单文件大小

使用 storage.buckets.file_size_limit 设置单文件大小上限,单位为字节:

UPDATE storage.buckets
SET file_size_limit = 20 * 1024 * 1024
WHERE id = 'user-files';

建议按 Bucket 场景设置不同限制:

Bucket建议限制说明
avatars2 MB - 5 MB用户头像
public-assets10 MB - 50 MB公开图片、静态资源
private-files按业务决定用户文档、压缩包
team-files按团队套餐决定团队协作文件

MIME 类型

使用 storage.buckets.allowed_mime_types 设置允许上传的 MIME 类型:

UPDATE storage.buckets
SET allowed_mime_types = ARRAY['image/png', 'image/jpeg', 'image/webp']
WHERE id = 'avatars';

建议对高风险 Bucket 开启 MIME 白名单,尤其是用户可上传的公开资源 Bucket。

路径设计最佳实践

路径应同时满足三件事:

  1. 用户容易理解;
  2. RLS 容易表达;
  3. AI 和代码生成工具不容易误用。
业务场景推荐路径原因
用户私有文件<uid>/<filename>可用一级目录隔离用户
用户头像<uid>/avatar.<ext>固定位置便于覆盖和查询
团队文件<team_id>/<filename>可 JOIN 团队成员表
文章附件articles/<article_id>/<filename>可 JOIN 文章表
公开静态资源assets/<version>/<filename>便于缓存和版本管理

不建议在同一个 Bucket 下混用完全不同的路径语义,例如同时存在:

<uid>/<filename>
public/<filename>
team/<team_id>/<filename>

这会让 RLS 条件变复杂,也容易让 AI 生成错误策略。

权限策略最佳实践

先限制 Bucket,再限制路径或 owner

每条 Policy 都建议先写 bucket_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()
);

避免写没有 Bucket 限制的宽泛策略:

-- 不推荐:可能影响所有 Bucket
CREATE POLICY unsafe_select ON storage.objects
FOR SELECT TO authenticated
USING (owner_id = auth.uid());

区分读、写、改、删

不要用一条过宽策略解决所有问题。建议分别声明:

  • FOR SELECT:读取、下载、列表、签名链接;
  • FOR INSERT:新建对象;
  • FOR UPDATE:覆盖对象、修改元数据;
  • FOR DELETE:删除对象。

谨慎开放匿名写

公开读不等于公开写。生产环境中通常不建议:

FOR INSERT TO anon
WITH CHECK (true)

如果确实需要匿名上传,应至少限制 Bucket、路径、文件大小、MIME 类型,并配合业务侧风控。

谨慎使用 service_role

service_role 拥有 BYPASSRLS,会绕过所有 RLS Policy。只应在云函数、服务端或可信运维脚本中使用,不能暴露到浏览器、小程序或移动端。

覆盖文件最佳实践

覆盖同一路径文件可能带来缓存一致性问题:

  • 浏览器可能继续使用旧缓存;
  • CDN 节点可能短时间内仍返回旧内容;
  • 并发上传时后完成的请求可能覆盖先完成的请求。

建议:

场景建议
用户头像可固定路径覆盖,但展示 URL 加版本参数
文章封面 / 商品图使用新文件路径,业务表更新引用
静态资源使用版本化路径,例如 logo.v2.png
多人协作文件避免直接覆盖,使用版本号或业务表记录版本

CDN 场景下的缓存命中、cacheControl 和 Public / Private Bucket 选择,见 CDN 加速与缓存

查询与索引建议

如果大量 Policy 或查询依赖路径首段,建议使用 path_tokens 并建立索引:

CREATE INDEX idx_objects_bucket_first_folder
ON storage.objects (bucket_id, (path_tokens[1]));

Policy 中可写:

bucket_id = 'user-files'
AND path_tokens[1] = auth.uid()

这比反复调用 (storage.foldername(name))[1] 更利于索引优化。

与业务表联动

对于团队文件、订单附件、文章封面等场景,不建议把所有权限都编码进路径。更推荐让文件权限跟随业务表:

CREATE POLICY article_asset_read ON storage.objects
FOR SELECT TO anon, authenticated
USING (
bucket_id = 'article-assets'
AND EXISTS (
SELECT 1 FROM public.articles a
WHERE a.cover_key = storage.objects.name
AND (a.is_public OR a.owner_id = auth.uid())
)
);

这样文件权限会与业务对象保持一致,避免出现「文章已私密但图片仍公开」的问题。

AI 友好最佳实践

为了让 AI 更可靠地生成代码、SQL 和文档,建议为每个 Bucket 维护一份结构化契约。

Bucket 契约模板

Bucket: <bucket-id>
用途:<这个 Bucket 存什么文件>
路径模板:<路径规则>
读取权限:<谁可以读>
上传权限:<谁可以上传>
覆盖权限:<谁可以覆盖>
删除权限:<谁可以删除>
大小限制:<单文件大小>
MIME 限制:<允许的 MIME 类型>
公开访问:<是否可用 getPublicUrl>
关联业务表:<如 team_members / articles>

示例

Bucket: team-files
用途:团队协作附件
路径模板:<team_id>/<filename>
读取权限:team_members 中的成员可读
上传权限:team_members 中的成员可上传
删除权限:team_members.role = 'admin' 的成员可删除
大小限制:50 MB
MIME 限制:image/png、image/jpeg、application/pdf、application/zip
公开访问:否,只能 download 或 createSignedUrl
关联业务表:public.team_members(team_id, user_id, role)

把这类契约放进需求、README 或代码注释中,AI 在生成 SDK 调用、RLS Policy、测试用例和排障建议时会更稳定。