ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Supabase Realtime:基于 WebSocket 的实时数据同步机制与三大能力实战(Database Changes、Presence、Broadcast)

Supabase Realtime:基于 WebSocket 的实时数据同步机制与三大能力实战(Database Changes、Presence、Broadcast) Supabase Realtime基于 WebSocket 的实时数据同步机制与三大能力实战Database Changes、Presence、Broadcast【免费下载链接】supabaseThe Postgres development platform. Supabase gives you a dedicated Postgres database to build your web, mobile, and AI applications.项目地址: https://gitcode.com/GitHub_Trending/supa/supabaseSupabase Realtime 通过 WebSocket 将 Postgres 数据库状态与所有已连接客户端做实时同步是 Supabase 构建 Web、移动端与 AI 应用时的实时通信底座。本篇基于仓库中的 Realtime 概览文档 展开结合仓库内 Realtime 编码规范、授权聊天示例 SupaSecureSlack 与 Presence 示例 的源码讲解 Database Changes、Presence、Broadcast 三项能力的调用方式、RLS 授权模型与生产级最佳实践。读完后你可以独立完成带私有通道的实时聊天系统、在线用户状态跟踪以及基于数据库触发器的事件广播。一、能力总览Realtime 的三大功能Realtime 概览文档 将 Supabase Realtime 的核心能力归纳为三类三者共享同一条 WebSocket 连接与 Channel频道抽象1. Database Changes数据库变更监听实时监听 Postgres 的 INSERT、UPDATE、DELETE 事件。你可以订阅指定表、按列过滤条件筛选只接收关心的变更。底层由 Postgres 逻辑复制Logical Replication即 CDC变更数据捕获驱动。2. Presence在线状态同步在所有连接客户端之间存储并同步在线用户状态。可以跟踪谁在线、正在浏览哪个页面、光标位置在哪里。客户端断开连接时对应的状态会被自动清理——这一点在 Presence 示例 中得到验证客户端只需在SUBSCRIBED状态后调用channel.track(...)上报自己的状态服务端负责在断连时回收。3. Broadcast广播消息向订阅了同一 Channel 的所有客户端发送任意消息。适用于输入指示器typing indicator、实时光标、游戏状态、通知推送等任何不需要持久化的实时通信场景。二、技术细节传输层与授权模型概览文档给出的技术要点如下这是理解后续所有代码示例的前提维度取值传输协议WebSockets应用层协议Phoenix Channels数据库集成Postgres 逻辑复制CDC客户端库JavaScriptsupabase-js、DartFlutter、Swift、Kotlin授权机制数据库变更订阅应用 Row Level SecurityRLS策略扩展方式可水平扩展至多个节点从授权模型看Realtime 的关键设计是私有通道的权限校验直接落在 Postgres RLS 上。具体做法是对realtime.messages表服务端自动生成的realtimeschema 内的表编写 SELECT/INSERT 策略策略中通过realtime.topic()函数取当前消息的 topic、用auth.uid()取当前用户 ID 做业务判断。SupaSecureSlack 示例 README 中的策略即典型写法CREATE POLICY authenticated can read broadcast and presence state ON realtime.messages AS PERMISSIVE FOR SELECT TO authenticated USING ( EXISTS ( SELECT 1 FROM public.rooms_users WHERE user_id (select auth.uid()) AND room_topic realtime.topic() AND realtime.messages.extension in (broadcast, presence) ) ); CREATE POLICY authenticated can send broadcast and track presence ON realtime.messages AS PERMISSIVE FOR INSERT TO authenticated WITH CHECK ( EXISTS ( SELECT 1 FROM public.rooms_users WHERE user_id (select auth.uid()) AND room_topic realtime.topic() AND realtime.messages.extension in (broadcast, presence) ) );其中extension in (broadcast, presence)这个条件很关键同一张realtime.messages表同时承载 Broadcast 与 Presence 两类消息策略需要按消息类型分别放行。Flutter Figma 克隆示例的迁移脚本 则展示了更通用的项目成员模型直接把realtime.topic()当作project_id解析并复用is_project_member()函数create policy Project members can receive presence and broadcast messages. on realtime.messages for select using (is_project_member(realtime.topic()::uuid)); create policy Project members can send presence and broadcast messages. on realtime.messages for insert with check (is_project_member(realtime.topic()::uuid));从这两处实现可以推断只要 topic 命名与业务实体 ID 对齐如room:123RLS 策略就能用一套通用的成员校验函数覆盖所有通道无需为每个房间单独写策略。三、Broadcast 实战私有通道与消息收发Broadcast 的核心客户端 API 是supabase.channel()的建连配置与channel.send()的发送调用。SupaSecureSlack 的受保护页面 展示了完整的建频道—监听—订阅—追踪链路let newChannel supabase.channel(selectedRoom, { config: { broadcast: { self: true }, private: true, // 告知服务端使用私有通道触发 RLS 授权校验 }, }) newChannel .on(broadcast, { event: message }, ({ payload: payload }) addMessage(payload.user_id user?.id, false, payload.message) ) .subscribe((status, err) { if (status SUBSCRIBED) { setChannel(newChannel) newChannel.track({ email: user?.email }) // 订阅成功后再上报 presence 状态 } if (status CHANNEL_ERROR) { setError(err?.message || null) // RLS 拒绝授权时会落到这个状态 } })发送消息则是一条结构化的channel.send调用channel?.send({ type: broadcast, event: message, payload: { message, user_id: user?.id }, })Realtime 编码规范 对通道配置项给出了完整注释建议建连时按如下结构组织const channel supabase.channel(room:123:messages, { config: { broadcast: { self: true, ack: true }, presence: { key: user-session-id, enabled: true }, private: true, // 使用 RLS 授权时必须开启 }, })各配置项含义broadcast.self: true让发送者自己也能收到自己发的广播默认收不到需自行回显broadcast.ack: true服务端收到消息后回一个确认acknowledgmentpresence.key用于标识 presence 状态的自定义键例如用户会话 IDpresence.enabled开启该通道的 presence 跟踪只要客户端注册了on(presence)监听客户端库会自动置位无需手动设置private: true要求认证并应用 RLS 策略。使用数据库触发器realtime.broadcast_changes或依赖 RLS 的通道必须设为私有。规范明确建议生产环境优先私有通道默认private: false的公共通道不建议在生产中使用。数据库变更postgres_changes 与触发器 broadcast两种路线Database Changes 能力原生由 Postgres 逻辑复制驱动客户端历史上用postgres_changes事件订阅表变更。但仓库内的 Realtime 编码规范 给出了当前仓库推荐的做法值得重点关注新应用不要使用postgres_changes单线程、扩展性受限数据库变更通知应改用broadcast 数据库触发器调用realtime.broadcast_changes或realtime.send。规范的函数选型决策表把选型讲得很直白使用场景推荐函数不选 postgres_changes 的原因自定义载荷、带业务逻辑broadcast更灵活、性能更好数据库变更通知触发器 broadcast更易扩展、载荷可定制高频更新broadcast 精简载荷吞吐量与控制力更好用户在线/状态跟踪presence谨慎使用专门用于状态同步客户端间通信不带触发器的broadcast仅走 WebSocket更灵活、性能更好对应的数据库侧写法规范给了一个通用捕获触发器函数——广播到以表名:行 ID命名的 topicCREATE OR REPLACE FUNCTION notify_table_changes() RETURNS TRIGGER AS $$ SECURITY DEFINER LANGUAGE plpgsql AS $$ BEGIN PERFORM realtime.broadcast_changes( TG_TABLE_NAME || : || COALESCE(NEW.id, OLD.id)::text, TG_OP, TG_OP, TG_TABLE_NAME, TG_TABLE_SCHEMA, NEW, OLD ); RETURN COALESCE(NEW, OLD); END; $$;需要更精细控制时可以按表写专用触发器topic 用room:前缀 room_id并支持条件广播——只在字段真正变化时推送-- 仅广播有意义的变更 IF TG_OP UPDATE AND OLD.status IS DISTINCT FROM NEW.status THEN PERFORM realtime.broadcast_changes( room: || NEW.room_id::text, TG_OP, TG_OP, TG_TABLE_NAME, TG_TABLE_SCHEMA, NEW, OLD ); END IF;而realtime.send用于发送不绑定表结构的自定义消息例如把{id, status}以status_changed事件推给某个房间。需要注意两个安全细节realtime.broadcast_changes默认要求私有通道规范说明这是为防止安全事故而设计并且这些数据库函数不应在客户端代码里调用。从postgres_changes迁移到触发器 broadcast时规范给出了三步走客户端把.on(postgres_changes, { event: *, schema, table })替换为对INSERT/UPDATE/DELETE三个 broadcast 事件的监听数据库加触发器再为realtime.messages配好 SELECT 授权策略。四、Presence 实战在线用户的追踪与自动清理Presence 的完整生命周期在 nextjs-auth-presence 示例 中体现得很清楚四步走// 1. 建通道时用 presence.key 声明以谁为粒度追踪 const channel supabaseClient.channel(online-users, { config: { presence: { key: this_user?.email ?? Unknown } }, }) // 2. 监听 presence.sync拉取全量在线状态 channel.on(presence, { event: sync }, () { const presentState channel.presenceState() setUserState({ ...presentState }) }) // 3. 监听增量事件 presence.join / presence.leave channel.on(presence, { event: join }, ({ newPresences }) { console.log(New users have joined: , newPresences) }) // 4. 订阅成功后调用 track 上报自己的状态 channel.subscribe(async (status) { if (status SUBSCRIBED) { await channel.track({ user_name: this_user?.email ?? Unknown }) } })要点是sync事件给出通道内 presence 的完整快照join/leave给出增量状态以presence.key为键聚合同一 key 的多次 track 会覆盖更新。客户端断开后状态由服务端自动清理因此在线列表天然准确无需额外心跳逻辑。规范同时提醒presence 属于专用能力应节制使用在线状态、计数器高频业务数据走 broadcast。五、私有通道授权从 Demo 看完整落地SupaSecureSlack 示例 是仓库内最完整的Realtime 授权参考实现目标是用带授权的私有通道构建聊天系统用户可建房间room、把他人拉进房间、发送不持久化的消息。整个授权链路由三张表 策略构成建表public.profiles用户资料由 auth 触发器自动写入、public.rooms房间topic唯一、public.rooms_users房间-用户关联全部ENABLE ROW LEVEL SECURITY授权策略除三张业务表的策略外核心是上文第二节的realtime.messagesSELECT/INSERT 策略——用户只有在rooms_users中存在对应(user_id, room_topic)记录时才能读写该 topic 的广播与 presence触发器insert_user()函数 on_new_auth_create_profile触发器保证新用户注册时profiles同步生成一行。客户端侧只有一个开关建通道时声明config: { private: true }。示例 README 特别注明了版本前提——需使用supabase/realtime-jsv2.44.0 或更高版本才支持私有通道配置。运行效果即前文截图所示双方都在房间中时可互发消息一旦某用户不在rooms_users中其订阅会以CHANNEL_ERROR状态失败页面提示 You do not have access to this room对应 page.tsx 中的错误处理分支。此外规范建议在 Dashboard 的 Realtime Settings 中开启private-only channels强制所有通道走私有模式进一步杜绝公共通道被误用。六、Topic 命名、扩展性与重连机制Topic 命名规范Realtime 编码规范 给出了一套可直接套用的约定Topic 模式scope:entity或scope:entity:id例如room:123:messages、game:456:moves、user:789:notificationsEvent 模式entity_actionsnake_case例如message_created、user_joined、game_ended避免update、change这类泛化命名使用细粒度专属 topic 而非全局大 topic消息只投递给真正订阅者能显著降低网络流量、提升并发容量、让 RLS 策略更精准对比global:notifications与room:${roomId}:messages。性能与扩展建议规范给出的运维向建议包括每个逻辑作用域用一个通道高流量 topic 可分片chat:shard:1、chat:shard:2关注 Realtime Settings 中的Database connection pool size配置RLS 策略涉及的所有列都应建索引规范示例中专门建了idx_room_members_user_room复合索引。重连与通道状态机客户端内置指数退避自动重连与断线后自动重订阅可通过reconnectAfterMs调整重试节奏log_level: info可打开调试日志。channel.subscribe回调会报告四种状态这也是错误处理的骨架SUBSCRIBED连接成功含重连成功开始收消息TIMED_OUT连接尝试超时CLOSED通道关闭CHANNEL_ERROR发生错误RLS 拒绝即落此状态客户端会自动重试。框架集成时规范给出的 React 模式值得照搬用useRef缓存 channel、订阅前检查channel.state防止重复订阅、必须在 cleanup 中调用supabase.removeChannel()——包含清理逻辑被列进了代码生成检查清单。七、常见使用场景概览文档列出的典型场景与三种能力的对应关系如下协作编辑与实时光标→ broadcast光标/选区等高频临时状态 presence协作者身份聊天与消息应用→ 私有通道 broadcast RLSSupaSecureSlack 即完整范例实时仪表盘与分析面板→ 触发器 broadcast或数据库变更订阅多人游戏→ broadcast游戏状态、动作 细粒度 topicgame:123:moves通知与动态流→realtime.send或 broadcastuser:456:notifications拍卖与竞价系统→ 触发器 broadcast 保证竞价事件的可靠推送。八、参考资料概览文档apps/www/content/md/realtime.md编码规范与函数选型examples/prompts/use-realtime.md授权聊天 Demo私有通道 RLSexamples/realtime/nextjs-authorization-demo/README.md、核心页面实现Presence 示例examples/realtime/nextjs-auth-presence/pages/index.tsx项目成员模型的 Realtime RLS 迁移examples/realtime/flutter-figma-clone/supabase/migrations/20240808072352_auth.sql概览文档还指向 Supabase 官方在线文档Realtime Guides 与 JavaScript subscribe API Reference可进一步查阅细节。【免费下载链接】supabaseThe Postgres development platform. Supabase gives you a dedicated Postgres database to build your web, mobile, and AI applications.项目地址: https://gitcode.com/GitHub_Trending/supa/supabase创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进