六维教程

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 实时订阅,让页面在数据变化时自动更新。

下一篇 Supabase Realtime 实时订阅

上一篇
Supabase 数据关联查询
下一篇
Supabase Realtime 实时订阅