跳到主要内容

从 Supabase 迁移

CloudBase PG 的设计在很多关键点上有意对齐 Supabase:相同的 auth / storage schema 划分、对齐的 JWT claims(sub / role / aud)、相同的 anon / authenticated / service_role 三角色模型、相同的「GRANT + RLS」双层权限范式。绝大多数 Supabase 项目可以以最小改动迁移过来

本文聚焦"代码与 SQL 层面"的对齐与差异,不涉及云资源迁移工具与计费层面的内容。

心智模型对照

概念SupabaseCloudBase PG说明
数据库角色anon / authenticated / service_roleanon / authenticated / service_role✅ 名称语义一致
账号 schemaauthauth✅ 名称一致
云存储 schemastoragestorage✅ 名称一致
JWT subuuid 字符串varchar(64) 字符串✅ 直接兼容
JWT roleanon / authenticated / service_roleanon / authenticated / service_role✅ 直接兼容
客户端 SDK@supabase/supabase-js@cloudbase/js-sdkAPI 风格相近,主要方法名对齐
行级权限RLS PolicyRLS Policy✅ 标准 PostgreSQL 特性,写法完全一致
自动 REST APIPostgRESTPostgREST✅ 同一开源项目,查询语法基本一致
RPC 调用.rpc().rpc()✅ 直接兼容
文件存储权限RLS over storage.objectsRLS over storage.objects✅ 同一思路
anon Tokenanon API keyanon Publishable Key✅ 同一概念
service Tokenservice_role API keyservice_role API Key✅ 同一概念

关键差异

差异点SupabaseCloudBase PG迁移动作
REST 端点路径/rest/v1/<table>/v1/rdb/rest/<table>修改 base URL
认证端点路径/auth/v1/.../auth/v1/...✅ 一致
域名<project>.supabase.co<envId>.api.tcloudbasegateway.com替换域名
Realtime(实时订阅)✅ 内置❌ 不支持改为轮询 / WebSocket 自建
GraphQL APIpg_graphql❌ 不支持仅使用 RESTful
数据库 branching❌ 不支持用环境隔离(dev / staging / prod)
DDL 通道直连数据库 / Studio控制台 SQL 编辑器 / 云 API ExecutePGSqlExecutePGSql 替换直连 SQL 客户端的部分流程

三步迁移流程

第一步:了解预置辅助函数(无需操作)

CloudBase PG 环境已预置 auth.uid()auth.jwt()auth.role()auth.email() 等辅助函数,与 Supabase 内置函数语义一致,原有 RLS Policy SQL 无需修改即可复用。

以下是这些预置函数的定义(仅供参考,环境已内置,无需手动执行):

-- 当前用户 ID(对应 JWT 的 sub)
CREATE OR REPLACE FUNCTION auth.uid() RETURNS text
LANGUAGE sql STABLE AS $$
SELECT current_setting('request.jwt.claims', true)::json->>'sub'
$$;

-- 当前角色
CREATE OR REPLACE FUNCTION auth.role() RETURNS text
LANGUAGE sql STABLE AS $$
SELECT current_setting('request.jwt.claims', true)::json->>'role'
$$;

-- 完整 JWT claims
CREATE OR REPLACE FUNCTION auth.jwt() RETURNS jsonb
LANGUAGE sql STABLE AS $$
SELECT current_setting('request.jwt.claims', true)::jsonb
$$;

-- 当前邮箱(如有)
CREATE OR REPLACE FUNCTION auth.email() RETURNS text
LANGUAGE sql STABLE AS $$
SELECT current_setting('request.jwt.claims', true)::json->>'email'
$$;

第二步:迁移表结构与 RLS Policy

几乎可以直接套用

  • CREATE TABLE / ALTER TABLE:完全兼容
  • CREATE POLICYauth.uid() / auth.role() 等:环境已预置这些函数,无需修改
  • 外键 REFERENCES auth.users(id):CloudBase 中 auth.users.id 类型为 varchar(64)(与 Supabase 的 uuid 字符串兼容),可直接引用;也可去掉外键以保持解耦

注意点

-- Supabase 中的常见写法(直接套用即可)
CREATE TABLE public.todos (
id bigserial PRIMARY KEY,
title text NOT NULL,
owner_id varchar(64) NOT NULL DEFAULT auth.uid(),
created_at timestamptz DEFAULT now()
);

ALTER TABLE public.todos ENABLE ROW LEVEL SECURITY;

GRANT SELECT, INSERT, UPDATE, DELETE ON public.todos TO authenticated;
GRANT USAGE, SELECT ON SEQUENCE public.todos_id_seq TO authenticated;

CREATE POLICY todos_owner_all ON public.todos
FOR ALL TO authenticated
USING (owner_id = auth.uid())
WITH CHECK (owner_id = auth.uid());

上面这段 SQL 同时适用于 Supabase 和 CloudBase PG,零改动。

第三步:替换客户端代码

初始化

- import { createClient } from '@supabase/supabase-js';
- const supabase = createClient(
- 'https://<project>.supabase.co',
- '<anon-key>'
- );

+ import cloudbase from '@cloudbase/js-sdk';
+ const app = cloudbase.init({
+ env: '<envId>',
+ accessKey: '<Publishable Key>', // 等价于 supabase 的 anon key
+ });
+ const auth = app.auth;
+ const db = app.rdb();

查询

绝大部分链式 API 完全一致:

// 共用:Supabase 与 CloudBase 完全相同的写法
const { data, error } = await db
.from('todos')
.select('id, title, is_completed')
.eq('is_completed', false)
.order('created_at', { ascending: false })
.limit(20);

写入 / 更新 / 删除

// INSERT
await db.from('todos').insert({ title: '...' });

// UPDATE
await db.from('todos').update({ is_completed: true }).eq('id', 1);

// DELETE
await db.from('todos').delete().eq('id', 1);

// UPSERT
await db.from('todos').upsert({ id: 1, title: '...' });

API 与 Supabase 一致。

认证

操作SupabaseCloudBase PG
注册supabase.auth.signUp({ email, password })三步式:发短信 → 校验 → auth.signUp(...)(账号密码 / 手机号),见 HTTP API - 账号注册
邮箱/手机密码登录supabase.auth.signInWithPassword({ email, password })auth.signInWithPassword({ username, password })
匿名登录supabase.auth.signInAnonymously()auth.signInAnonymously()
第三方登录supabase.auth.signInWithOAuth({ provider })auth.signInWithProvider({ provider_token }),详见 Auth.signInWithProvider
登出supabase.auth.signOut()auth.signOut()
获取登录态supabase.auth.getSession()auth.getSession()
监听登录态supabase.auth.onAuthStateChange(cb)auth.onAuthStateChange(cb)

RPC

// 共用:完全一致的写法
const { data } = await db.rpc('place_order', { p_product_id: 1, p_quantity: 2 });

云存储

- const { data } = await supabase.storage
- .from('avatars').upload('public/file.png', file);

+ // 上传(用 CloudBase SDK;PG 环境下文件权限由 RLS 控制)
+ await app.uploadFile({
+ cloudPath: 'public/file.png',
+ filePath: file,
+ });

CloudBase 第二阶段会提供 Supabase Storage 兼容的 HTTP API,届时可进一步降低改造成本。详见 PG 模式云存储

数据迁移

数据迁移与 SQL 兼容性建议参考标准 PostgreSQL 迁移流程:

  1. 导出 Supabase 数据:使用 pg_dump --data-only --no-owner --no-privileges 导出业务数据
  2. 导入 CloudBase:通过云 API ExecutePGSql 逐条执行(注意每次只能执行一条 SQL,需按行拆分)
  3. 同步 auth.users:因 auth schema 由平台管理,不直接 dump/restore;建议在迁移期间通过 CloudBase 提供的认证 API 让用户重新激活账号,或与平台运营沟通批量导入方案

对于大表,使用 PostgreSQL 的 COPY 命令配合 ExecutePGSql 通常比 INSERT 高效得多。

常见迁移问题

Realtime 订阅无法迁移

PG 模式暂不支持 Realtime。可选方案:

  • 短期:客户端定时拉取
  • 中期:在云函数中起 WebSocket 服务并直连数据库订阅 LISTEN/NOTIFY
  • 长期:等待 PG 模式 Realtime 上线

我的 RLS Policy 在 Supabase 跑得通,到了 CloudBase 报错

按以下顺序排查:

  1. 该角色是否有表级 GRANT(CloudBase 与 Supabase 行为一致,但容易被忽略)
  2. serial 主键的 SEQUENCE 权限是否授予

详见 常见错误速查

db.rpc() 与 Supabase 的 .rpc() 兼容程度

链式 API、过滤器、.single() / .limit() / .order() 等都对齐。详细参考 JS SDK - RPC 调用

下一步