从 Supabase 迁移
CloudBase PG 的设计在很多关键点上有意对齐 Supabase:相同的 auth / storage schema 划分、对齐的 JWT claims(sub / role / aud)、相同的 anon / authenticated / service_role 三角色模型、相同的「GRANT + RLS」双层权限范式。绝大多数 Supabase 项目可以以最小改动迁移过来。
本文聚焦"代码与 SQL 层面"的对齐与差异,不涉及云资源迁移工具与计费层面的内容。
心智模型对照
| 概念 | Supabase | CloudBase PG | 说明 |
|---|---|---|---|
| 数据库角色 | anon / authenticated / service_role | anon / authenticated / service_role | ✅ 名称语义一致 |
| 账号 schema | auth | auth | ✅ 名称一致 |
| 云存储 schema | storage | storage | ✅ 名称一致 |
JWT sub | uuid 字符串 | varchar(64) 字符串 | ✅ 直接兼容 |
JWT role | anon / authenticated / service_role | anon / authenticated / service_role | ✅ 直接兼容 |
| 客户端 SDK | @supabase/supabase-js | @cloudbase/js-sdk | API 风格相近,主要方法名对齐 |
| 行级权限 | RLS Policy | RLS Policy | ✅ 标准 PostgreSQL 特性,写法完全一致 |
| 自动 REST API | PostgREST | PostgREST | ✅ 同一开源项目,查询语法基本一致 |
| RPC 调用 | .rpc() | .rpc() | ✅ 直接兼容 |
| 文件存储权限 | RLS over storage.objects | RLS over storage.objects | ✅ 同一思路 |
| anon Token | anon API key | anon Publishable Key | ✅ 同一概念 |
| service Token | service_role API key | service_role API Key | ✅ 同一概念 |
关键差异
| 差异点 | Supabase | CloudBase 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 API | ✅ pg_graphql | ❌ 不支持 | 仅使用 RESTful |
| 数据库 branching | ✅ | ❌ 不支持 | 用环境隔离(dev / staging / prod) |
| DDL 通道 | 直连数据库 / Studio | 控制台 SQL 编辑器 / 云 API ExecutePGSql | 用 ExecutePGSql 替换直连 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 POLICY含auth.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 一致。
认证
| 操作 | Supabase | CloudBase 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 迁移流程:
- 导出 Supabase 数据:使用
pg_dump --data-only --no-owner --no-privileges导出业务数据 - 导入 CloudBase:通过云 API
ExecutePGSql逐条执行(注意每次只能执行一条 SQL,需按行拆分) - 同步
auth.users:因authschema 由平台管理,不直接 dump/restore;建议在迁移期间通过 CloudBase 提供的认证 API 让用户重新激活账号,或与平台运营沟通批量导入方案
对于大表,使用 PostgreSQL 的
COPY命令配合ExecutePGSql通常比 INSERT 高效得多。
常见迁移问题
Realtime 订阅无法迁移
PG 模式暂不支持 Realtime。可选方案:
- 短期:客户端定时拉取
- 中期:在云函数中起 WebSocket 服务并直连数据库订阅
LISTEN/NOTIFY - 长期:等待 PG 模式 Realtime 上线
我的 RLS Policy 在 Supabase 跑得通,到了 CloudBase 报错
按以下顺序排查:
- 该角色是否有表级 GRANT(CloudBase 与 Supabase 行为一致,但容易被忽略)
serial主键的 SEQUENCE 权限是否授予
详见 常见错误速查。
db.rpc() 与 Supabase 的 .rpc() 兼容程度
链式 API、过滤器、.single() / .limit() / .order() 等都对齐。详细参考 JS SDK - RPC 调用。