Logo花火漫画开发文档

创建与部署规范

Edge Functions 的创建审批流程、开发规范和部署要求

创建新函数的前置要求

创建新的 Edge Function 之前,必须先联系 Galentwww 进行评估。未经评估不提供创建和部署新函数。

新建 Edge Function 需要同时满足以下条件:

  1. 现有函数无法满足需求 — 已确认当前已部署的 Edge Functions 均不能覆盖目标功能
  2. RPC 无法替代 — 已评估过使用 Postgres RPC 函数实现的可行性,确认 RPC 无法满足(例如需要调用外部 API、需要访问非数据库资源等)
  3. Galentwww 审批通过 — 向 Galentwww 说明需求背景、为什么现有方案不可行,获得明确同意

评估流程

提出需求

整理清楚要实现的功能、输入输出格式、调用频率预估,联系 Galentwww。

评估现有方案

Galentwww 会协助判断:

  • 是否有现有 Edge Function 可以扩展来覆盖该需求
  • 是否可以通过数据库 RPC 函数实现(优先选择 RPC)
  • 是否可以在客户端侧完成逻辑

确认创建

仅当以上方案均不可行时,审批通过,方可开始开发新函数。

开发规范

禁止硬编码

所有配置值、URL、密钥等禁止硬编码在函数代码中。应通过环境变量或 Supabase Secrets 注入:

// ✅ 正确:通过环境变量获取
const cdnDomain = Deno.env.get("CDN_DOMAIN") ?? "";
const webhookUrl = Deno.env.get("WEBHOOK_URL") ?? "";

// ❌ 错误:硬编码
const cdnDomain = "https://bucket.xfmanga.top";
const webhookUrl = "https://hooks.slack.com/xxx";

敏感信息使用 Secrets

对于 API 密钥、第三方服务凭证等敏感内容,必须使用 Supabase 的 Secret 功能管理,不得明文写入代码或普通环境变量:

# 设置 Secret
supabase secrets set MY_API_KEY=sk_live_xxxxxxxxxxxx

# 在函数中读取
Deno.env.get("MY_API_KEY")

# 查看已有 Secrets(不会显示值)
supabase secrets list

Secrets 不会出现在代码仓库、部署日志和 Dashboard 中。已设置的 Secret 只能被覆盖或删除,无法查看原始值。

避免使用 SERVICE_ROLE_KEY

SUPABASE_SERVICE_ROLE_KEY 拥有绕过所有 RLS 策略的最高权限。如非必要,严禁使用

使用 SUPABASE_ANON_KEY 创建客户端,配合 RLS 策略控制权限:

const supabase = createClient(
  Deno.env.get("SUPABASE_URL")!,
  Deno.env.get("SUPABASE_ANON_KEY")!,
);

如果需要以请求用户的身份操作数据库,从请求头中提取用户 JWT 并透传:

const authHeader = req.headers.get("Authorization") ?? "";
const supabase = createClient(
  Deno.env.get("SUPABASE_URL")!,
  Deno.env.get("SUPABASE_ANON_KEY")!,
  { global: { headers: { Authorization: authHeader } } }
);

以下情况可以使用 SERVICE_ROLE_KEY,但需要在代码注释中说明原因:

  • 需要跨用户操作数据(如定时任务批量更新)
  • 需要访问不对外暴露的管理表
  • 对接外部 Webhook 回调(无用户上下文)
// ⚠️ 使用 SERVICE_ROLE_KEY:此函数为系统级定时任务,
//    需要更新所有用户的 popularity 字段,无用户上下文
const supabase = createClient(
  Deno.env.get("SUPABASE_URL")!,
  Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!,
);

身份与权限校验

需要确认当前用户是否具备管理员或编辑者身份时,优先调用数据库 RPC 函数,而不是在 Edge Function 中自行查表判断:

// ✅ 推荐:调用 RPC 函数判断身份
const { data: isAdmin } = await supabase.rpc("is_admin");
const { data: isAdminOrEditor } = await supabase.rpc("is_admin_or_editor");

if (!isAdmin) {
  return new Response(
    JSON.stringify({ error: "权限不足" }),
    { status: 403, headers: { "Content-Type": "application/json" } }
  );
}
// ❌ 避免:自己查 profiles 表判断角色
const { data: user } = await supabase
  .from("profiles")
  .select("role")
  .eq("id", userId)
  .single();
if (user?.role !== "admin") { ... }

is_admin()is_admin_or_editor() 是已在数据库中定义好的 RPC 函数,它们基于 auth.uid() 自动获取当前用户身份,无需传参。这两个函数同时也被 RLS 策略复用,保证权限判断逻辑的一致性。

部署流程

确认函数开发完成并在本地测试通过后,执行部署:

# 部署单个函数
supabase functions deploy my-function

# 部署全部函数
supabase functions deploy

部署后验证

# 确认函数状态为 ACTIVE
supabase functions list

# 查看实时日志
supabase functions logs my-function

# 手动调用测试
curl -X POST \
  'https://<project-ref>.supabase.co/functions/v1/my-function' \
  -H 'Authorization: Bearer <ANON_KEY>' \
  -H 'Content-Type: application/json' \
  -d '{"test": true}'

规范核对清单

部署前请对照以下清单自检:

  • 已获得 Galentwww 的创建审批
  • 代码中无硬编码的 URL、密钥、配置值
  • 敏感信息通过 supabase secrets set 管理
  • 未使用 SERVICE_ROLE_KEY(如使用了需注释说明原因)
  • 身份校验通过 is_admin() / is_admin_or_editor() RPC 完成
  • 本地测试通过
  • 错误情况有合理的 HTTP 状态码和错误信息返回

On this page