跳到主要内容

Postgres CDC

监听 PostgreSQL 表变更。presence / postgres_changes 监听必须在 subscribe() 之前绑定。

前置条件

本页代码能跑通的前提是业务库已配置好 数据库侧准备(publication、RLS 策略、必需的 grant)。这些没做的话,订阅会成功但收不到任何事件,容易误判成 SDK 问题。

const channel = realtime.channel("db-changes");

channel.on("postgres_changes", { event: "*", schema: "public" }, (payload) => {
console.log("public schema 变更", payload);
});

channel.on(
"postgres_changes",
{ event: "INSERT", schema: "public", table: "messages" },
(payload) => {
console.log("新增", payload.new);
}
);

channel.on(
"postgres_changes",
{
event: "UPDATE",
schema: "public",
table: "users",
filter: "username=eq.Realtime",
},
(payload) => {
console.log("更新", payload.new, payload.old);
}
);

channel.subscribe((status, err) => {
if (status === "SUBSCRIBED") {
console.log("开始接收数据库变更");
}
if (status === "CHANNEL_ERROR") {
console.error(err);
}
});

filter 可以是原始字符串,也可以用 postgresChangesFilter() 构建,二者线格式相同:

import { postgresChangesFilter } from "@cloudbase/js-sdk/realtime-js";

// 字符串
{ event: "UPDATE", schema: "public", table: "users", filter: "id=eq.1" }

// Builder
{
event: "UPDATE",
schema: "public",
table: "users",
filter: postgresChangesFilter().eq("id", 1),
}
运算符字符串Builder含义
eqid=eq.1.eq("id", 1)等于
neqid=neq.1.neq("id", 1)不等于
lt / lte / gt / gteage=gte.18.gte("age", 18)比较
instatus=in.(active,pending).in("status", ["active", "pending"])属于列表
like / iliketitle=like.%foo%.like("title", "%foo%")模式匹配
isdeleted_at=is.null.is("deleted_at", null)IS null/true/false
match / imatchtitle=match.^foo.match("title", "^foo")POSIX 正则
isdistinctvalue=isdistinct.1.isDistinct("value", 1)NULL 安全不等于

取反:字符串加 not. 前缀,或使用 .not(column, operator, value)。多个条件用逗号(AND)连接,Builder 则链式调用。

channel.on(
"postgres_changes",
{
event: "UPDATE",
schema: "public",
table: "orders",
filter: postgresChangesFilter()
.gt("amount", 100)
.not("status", "in", ["draft", "archived"]),
},
(payload) => console.log(payload)
);

select 只接收部分列,减小载荷:

channel.on(
"postgres_changes",
{
event: "*",
schema: "public",
table: "users",
select: ["id", "first_name"],
},
(payload) => {
// payload.new 仅含 { id, first_name }
console.log(payload);
}
);

需要等服务端确认 CDC 订阅就绪后再触发 SUBSCRIBED 时,设置 postgres_changes_options.wait

const channel = realtime.channel("db-changes", {
config: {
postgres_changes_options: { wait: true, timeout: 15000 },
},
});
提示

Realtime 在服务端按单表 WAL 过滤,没有资源嵌套(!inner)和 or() 分组。like / ilike 通配符使用 % 而不是 *

数据库侧准备

postgres_changes 走的是业务库的逻辑复制

1. 把表加入 publication

服务端从 publication cloudbase_realtime 读取变更(名称由租户配置决定,若你的环境指定了其他名称以配置为准)。没有加进去的表,变更完全不触发

ALTER PUBLICATION cloudbase_realtime ADD TABLE public.messages;

确认已加入的表:

SELECT schemaname, tablename
FROM pg_publication_tables
WHERE pubname = 'cloudbase_realtime';

2. 业务表授权 + 开启 RLS

变更事件是按业务表自己的 RLS 策略逐订阅者校验的:

-- 让角色有读权限
GRANT SELECT ON public.messages TO anon, authenticated;

-- 开启行级安全
ALTER TABLE public.messages ENABLE ROW LEVEL SECURITY;

-- 只让用户收到自己的数据
CREATE POLICY messages_select ON public.messages
FOR SELECT TO authenticated
USING (owner_id = auth.uid());