3.6 函数计算与业务逻辑


3.6 函数计算与业务逻辑

一句行话:Edge Functions 是 Supabase 里的"无服务器函数",跑在 Deno 运行时上、部署到边缘节点。它补足了前面组件的空白——当逻辑既不是简单 CRUD、又不适合塞进数据库函数时,就放在这里。这一节我们讲它适合干什么、怎么写、怎么安全地用管理员密钥,以及和数据库函数的取舍。

什么时候该用 Edge Functions

适合:

  • 处理第三方 webhook(支付回调、消息推送)。
  • 代理外部 API,避免在前端暴露密钥。
  • 轻量计算/转换(图片元数据、文本处理)。
  • 需要在请求里做 RLS 之外的自定义校验。

不适合:

  • 长事务、批处理(有执行时长上限、无状态)。
  • 重 CPU 计算(边缘节点算力有限,冷启动明显)。
  • 当数据库函数(PL/pgSQL)就能解决时——能下推到 PG 的尽量下推,少一跳网络。

写一个函数

函数代码放在 supabase/functions/函数名/index.ts,用 Deno 的 serve 入口:

// supabase/functions/hello/index.ts import { createClient } from 'https://esm.sh/@supabase/supabase-js@2' Deno.serve(async (req) => { const { name } = await req.json().catch(() => ({ name: 'world' })) const supabase = createClient( Deno.env.get('SUPABASE_URL')!, Deno.env.get('SUPABASE_ANON_KEY')! ) return new Response(JSON.stringify({ message: `hello ${name}` }), { headers: { 'Content-Type': 'application/json' }, }) })

部署:

supabase functions deploy hello # 访问:https://<项目ref>.functions.supabase.co/hello

安全地用管理员密钥

有些逻辑需要绕过 RLS(比如管理员给用户发通知)。这时在函数里用 SUPABASE_SERVICE_ROLE_KEY,但务必自己校验调用者是否真的有权,绝不能信任客户端声明的"我是管理员":

// supabase/functions/admin-notify/index.ts import { createClient } from 'https://esm.sh/@supabase/supabase-js@2' Deno.serve(async (req) => { // 1. 用 anon 客户端解析调用者 JWT const authHeader = req.headers.get('Authorization')! const client = createClient( Deno.env.get('SUPABASE_URL')!, Deno.env.get('SUPABASE_ANON_KEY')! ) const token = authHeader.replace('Bearer ', '') const { data: user } = await client.auth.getUser(token) if (!user.user) return new Response('未登录', { status: 401 }) // 2. 查数据库确认其是否为管理员(而非信请求体) const admin = createClient( Deno.env.get('SUPABASE_URL')!, Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')! // 仅此处用,绕过 RLS ) const { data: role } = await admin .from('user_roles') .select('is_admin') .eq('user_id', user.user.id) .single() if (!role?.is_admin) return new Response('无权限', { status: 403 }) // 3. 真正的管理员操作... return new Response('已通知') })

关键点:管理员身份是从数据库查出来的,不是从请求参数读的。service_role 只在"已确认有权"之后、且只用于必要的管理员操作。

下面 SVG 画了"函数用双客户端做权限校验"的模式,避免 service_role 滥用:

03-06-fig01

展开案例:支付 webhook 落库

背景:用支付平台,用户付款后平台回调一个 webhook,要把订单标为已支付并开通权限。

操作过程:

  1. 部署一个 pay-webhook 函数,验证回调签名(防伪造),再用 admin 客户端写库:
// supabase/functions/pay-webhook/index.ts import { createClient } from 'https://esm.sh/@supabase/supabase-js@2' Deno.serve(async (req) => { const body = await req.text() // 校验签名(伪代码,按支付平台文档实现) if (!verifySignature(req.headers.get('x-signature')!, body)) return new Response('bad sig', { status: 400 }) const event = JSON.parse(body) const admin = createClient( Deno.env.get('SUPABASE_URL')!, Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')! ) await admin .from('orders') .update({ paid: true }) .eq('id', event.order_id) return new Response('ok') })
  1. 在支付平台后台把 webhook 地址填成 https://<ref>.functions.supabase.co/pay-webhook
  2. 用户付款 → 平台回调 → 函数验签后更新订单 → 前端靠 Realtime 或轮询看到 paid=true

结果:支付状态可靠落库,且因为只有带正确签名的请求才能改库,伪造回调无效。

解读:webhook 是 Edge Function 的典型主场——它要根据外部事件触发、要验签、要绕过 RLS 写库,又不适合放进数据库函数(签名算法在 SQL 里写很别扭)。

变式:如果担心函数执行超时,把"验签+记原始事件"先做,真正的"开通权限"用数据库触发器在 orders.paid 变 true 时异步完成,函数只负责收件,职责更单一。

本节要点回顾

  • Edge Functions 适合 webhook、外部代理、轻计算,不适合长事务重计算。
  • 用 service_role 必须先在函数内校验调用者真实权限,不信请求体。
  • 能下推到数据库函数的逻辑优先放 PG,少一跳网络。

⚠️ 函数里用了 service_role 就等于拿到数据库管理员权限。任何从 req 来的"角色/权限"字段都不可信,必须回数据库查。把校验写在前、管理员操作写在后,顺序不能反。

💡 我们建议给所有对外函数加签名校验或至少要求带有效 JWT,不要让函数成为"无需认证就能触发管理员操作"的后门。函数入口是第一道防线。

第三章我们把能跑的主题全走了一遍:建项目、CRUD、认证、实时、存储、函数。第四章进入生产级话题:性能、安全、部署、排障、演进。


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