2.2 核心组件详解


2.2 核心组件详解

上一节我们把链路打通了,这一节把五个核心组件单独拎出来看:Auth、Database、Storage、Realtime、Edge Functions。每个都讲它解决什么、怎么用、以及边界在哪——也就是"该用别用"的判断。理解组件边界,才能避免把所有逻辑都塞进数据库或都塞进函数。

Auth:认证与 JWT

Auth 服务(源自 GoTrue)负责注册、登录、签发 JWT。登录成功后返回的 JWT 里带 sub(用户 uuid)、role 等声明,后续所有请求靠它做 RLS 判断。

import { createClient } from '@supabase/supabase-js' const supabase = createClient(URL, ANON_KEY) // 邮箱密码注册 const { data, error } = await supabase.auth.signUp({ email: 'dev@example.com', password: 'a-strong-password', }) // data.user.id 就是后续 RLS 里的 auth.uid() // 登录后拿 session const { data: session } = await supabase.auth.signInWithPassword({ email: 'dev@example.com', password: 'a-strong-password', }) // session.access_token 即为 Bearer token,supabase-js 自动附带

边界:Auth 管"你是谁",不管"你能看哪些行"——那是 RLS 的事。常见误区是把权限逻辑写在 Auth 的 metadata 里,结果是应用层到处读 metadata 判断,既慢又易漏。正确做法:身份交给 JWT,权限交给 RLS。

Database:Postgres 即一切

Database 就是标准 Postgres,Supabase 在它之上加了:自动 REST/GraphQL API(PostgREST)、RLS、触发器、扩展(pgvector、PostGIS 等)。你写的每一条 SQL 策略,同时约束了 API 和未来的函数调用。

-- 开启一个扩展,比如向量检索 create extension if not exists vector; -- 建表时直接享受 JSONB、数组、范围类型 create table public.docs ( id bigint generated always as identity primary key, title text, embedding vector(1536), tags text[] );

边界:Database 强在事务与关系,但不适合做长时间运行的计算任务(比如转码视频)。那种活交给 Edge Functions 或外部服务。

Storage:对象存储

Storage 存文件(图片、视频、文档),元数据在 Postgres,对象本身在 S3 兼容层。权限同样由 RLS 通过策略表控制。下面的代码上传一张头像并限定只有本人能覆盖:

-- 权限策略:用户只能写自己 userId 命名的目录 create policy "头像归属" on storage.objects for all using (auth.uid()::text = (storage.foldername(name))[1]) with check (auth.uid()::text = (storage.foldername(name))[1]);
// 前端上传,路径以用户 id 开头 await supabase.storage .from('avatars') .upload(`${userId}/me.png`, file, { upsert: true }) // 上传成功后可通过 getPublicUrl 拿访问地址

边界:Storage 不适合当数据库用。有人把结构化数据存成 JSON 文件再读,既慢又难查询。该建表建表。

Realtime:变更广播

Realtime 订阅 Postgres 的 WAL,把 INSERT/UPDATE/DELETE 推到 WebSocket 客户端。支持表监听、行级过滤、broadcast(自定义事件)、presence(在线状态)。

const channel = supabase .channel('room:42') .on('broadcast', { event: 'cursor' }, (e) => { console.log('别人光标位置', e.payload) }) .subscribe() // 自己移动光标时广播 await supabase.channel('room:42').send({ type: 'broadcast', event: 'cursor', payload: { x: 120, y: 80 }, })

边界:Realtime 解决"数据变了通知你",不解决"历史消息存储"。聊天记录还得落库(见 3.4 节),Realtime 只管推送增量。

Edge Functions:边缘业务逻辑

Edge Functions 是基于 Deno 的 TypeScript 运行时,跑在离用户近的边缘节点,适合无状态、短耗时的逻辑(webhook 处理、第三方 API 代理、轻计算)。它能用服务角色密钥直连数据库,绕过 RLS 做管理员操作——所以要小心。

// supabase/functions/hello/index.ts import { createClient } from 'https://esm.sh/@supabase/supabase-js@2' Deno.serve(async (req) => { const supabase = createClient( Deno.env.get('SUPABASE_URL')!, Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')! // 注意:绕过 RLS ) const { data } = await supabase.from('articles').select('count') return new Response(JSON.stringify(data)) })

部署:

supabase functions deploy hello

边界:函数无状态、有冷启动、执行有时间上限,不适合跑批处理或长事务。重计算放到专用 worker。

下面 SVG 把这五个组件的能力边界和"不该做什么"画成一张对照矩阵:

02-02-fig01

展开案例:评论系统该把组件怎么排

背景:做一个带实时刷新的评论区,要求新评论立刻出现在所有看客屏幕上。

操作与排布:

  • Database:建 comments 表,RLS 让登录用户可插、所有人可读(前面 1.1 写过)。
  • Realtime:前端 on('postgres_changes', {event:'INSERT', table:'comments'}) 监听,新评论推送即渲染。
  • Auth:登录态决定能否插评论。
  • 不用 Edge Functions(逻辑简单,客户端直写即可),不用 Storage(评论无文件)。

结果:新评论由某人写入表 → Realtime 推给所有订阅者 → 各自前端追加 DOM。全程无后端服务。

解读:组件各司其职,没有"为了用而用"。很多新手会硬塞一个 Edge Function 去做"写入后通知",其实 Realtime 已经把这件事包了。

变式:若评论要过敏感词过滤再上屏,那就在插入前加一个 Edge Function 做校验,再决定写不写表——这时函数才该出场。

本节要点回顾

  • Auth 管身份,RLS 管权限,两者分工别混淆。
  • Database 是事务与关系的中枢,Storage 存文件,Realtime 推增量,Edge Func 跑轻逻辑。
  • 每个组件都有"不适合"的边界,越界会埋坑。

⚠️ Edge Functions 用 service_role 密钥会绕过 RLS,等于数据库管理员权限。千万别在前端暴露这个密钥,也别在函数里信任客户端传来的"我是管理员"字段。

💡 我们给的组件选择口诀:要存关系数据→Database;要传文件→Storage;要即时通知→Realtime;要无状态小逻辑→Edge Func;要登录→Auth。按这个顺序对号入座,基本不会错配。

下一节看 Dashboard 怎么把这些组件可视化,以及它在日常开发里的真实用处。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U