Supabase Storage 文件存储
数据库适合存结构化数据,图片、视频、PDF 这类文件要交给 Storage。这一篇学习 Bucket 的概念,实现头像上传和公开访问,并了解私有文件的安全控制。
Storage 是什么
Supabase Storage 是托管文件的云存储服务,类似网盘。你把文件传上去,它返回一个可访问的 URL,前端把 URL 存进数据库就完成了文件的管理闭环。
| 场景 | 做法 |
|---|---|
| 头像图片 | 上传到公开 Bucket,数据库存 URL |
| 文章封面 | 同上 |
| 用户隐私文件 | 上传到私有 Bucket,用签名 URL 访问 |
Bucket 概念
Bucket(存储桶)是文件的分组容器,相当于网盘里的一个文件夹。可以建任意多个,每个 Bucket 单独设置公开或私有
| 类型 | 谁能访问 | 典型用途 |
|---|---|---|
| 公开 Bucket | 任何人,有 URL 就能看 | 头像、图片、公开文件 |
| 私有 Bucket | 只有授权用户 | 隐私文件、付费资料 |
创建 Bucket
在 Dashboard 的 Storage 菜单点击 New bucket,名称填 avatars,勾选 Public bucket。公开 Bucket 里的文件会有一个永久有效的公开 URL。
也可以在代码里创建
const { data, error } = await supabase
.storage
.createBucket('avatars', { public: true })
上传文件
前端上传头像是标准流程,input 选择文件后直接传
<script setup>
import { supabase } from '@/lib/supabase'
const fileInput = ref(null)
async function uploadAvatar() {
const file = fileInput.value.files[0]
if (!file) return
const { data: { user } } = await supabase.auth.getUser()
const fileName = `${user.id}-${Date.now()}.png`
const { error } = await supabase
.storage
.from('avatars')
.upload(fileName, file)
if (!error) console.log('上传成功')
}
</script>
<template>
<input ref="fileInput" type="file" accept="image/*" />
<button @click="uploadAvatar">上传头像</button>
</template>
上传的文件名不要直接用用户随便起的名字,用用户 ID 加时间戳拼接,避免重名覆盖和路径穿越问题。路径里可以加目录前缀,比如 user1/avatar.png。
获取公开 URL
公开 Bucket 里的文件,直接用 getPublicUrl 拼出完整地址
const { data } = supabase
.storage
.from('avatars')
.getPublicUrl(fileName)
console.log(data.publicUrl) // 可直接放入 img 标签的 src
把这个 URL 更新到 profiles 表,头像功能就完整了
await supabase
.from('profiles')
.update({ avatar_url: data.publicUrl })
.eq('id', user.id)
私有文件的签名 URL
私有 Bucket 里的文件没有公开 URL,需要服务端生成有时效的签名地址
const { data, error } = await supabase
.storage
.from('private-docs')
.createSignedUrl('secret.pdf', 60) // 60 秒后过期
console.log(data.signedUrl)
签名 URL 到期即失效,适合付费内容、账单这类场景。注意 createSignedUrl 也可以用 anon key 调用,是否允许由 RLS 策略决定。
用 RLS 控制文件权限
Storage 的文件权限同样由 RLS 管理,策略写在 storage.objects 这张系统表上。默认规则是谁都能读,谁都不能写。写策略前先给 Bucket 开放上传
-- avatars 桶的任何人可读
create policy "avatars 公开读"
on storage.objects for select
using (bucket_id = 'avatars');
-- 登录用户可往 avatars 桶上传自己的文件
create policy "avatars 登录用户上传"
on storage.objects for insert
with check (
bucket_id = 'avatars'
and auth.role() = 'authenticated'
);
bucket_id 就是 Bucket 名称。auth.role() 返回 authenticated 表示已登录。文件路径建议带用户 ID 前缀,配合策略限定用户只能操作自己的文件
create policy "用户只能删除自己的文件"
on storage.objects for delete
using (
bucket_id = 'avatars'
and (storage.foldername(name))[1] = auth.uid()::text
);
这里 storage.foldername 把文件路径拆成文件夹数组,第一段和用户 ID 比对。删除权限比上传敏感,实际项目按需配置。
常用操作补充
| 操作 | 方法 |
|---|---|
| 下载文件 | .download() 返回文件内容 |
| 删除文件 | .remove([文件名]) 传入数组 |
| 移动文件 | .move() 修改路径 |
| 更新同名文件 | .update() 覆盖 |
下一篇学习 Realtime 实时订阅,让页面在数据变化时自动更新。