PolarDB Supabase是PolarDB PostgreSQL版推出全托管的Supabase服务,以PolarDB PostgreSQL版为核心,整合了Realtime实时数据库、RESTful API、GoTrue身份认证、文件存储、日志采集等关键功能,并在此基础上进行了优化与增强,为您省去Supabase复杂的参数管理和应用运维等操作,并提供了兼具灵活性与高性能的后端解决方案。您可以基于PolarDB Supabase快速搭建Web、SaaS平台、AI集成应用等现代化应用。
接下来,将为您展示如何使用PolarDB Supabase快速搭建一个会议笔记系统。
效果展示
交互体验如下,打开两个浏览器,进入同一个会议,可以观察到两侧的信息是实时同步的。会议笔记系统的核心功能包括实时协作编辑、用户在线状态管理、文件上传与管理、任务追踪、活动日志以及标签系统。
核心功能
完整数据库功能
Supabase基于PolarDB PostgreSQL版数据库构建,提供完整的数据库功能:
- 关系型数据库: 支持复杂的表关系、外键约束、事务处理。
- JSONB支持: 存储半结构化数据,如笔记内容、活动数据。
- 全文搜索: 内置全文搜索功能。
- 扩展支持: 支持PostgreSQL扩展,如UUID生成、时间戳处理。
-- 示例:使用JSONB存储复杂数据 CREATE TABLE notes ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, content JSONB DEFAULT '{}', -- 存储富文本内容 activity_data JSONB DEFAULT '{}' -- 存储活动元数据 );
实时订阅(Realtime)
基于PostgreSQL的逻辑复制,实现实时数据同步:
- 数据库变更监听: 监听INSERT、UPDATE、DELETE事件。
- 频道管理: 支持多个独立频道,按需订阅。
- 事件过滤: 通过条件过滤减少不必要的事件。
- 自动重连: 网络断开时自动重连。
// 实时订阅示例 const channel = supabase .channel('meeting-updates') .on('postgres_changes', { event: 'UPDATE', schema: 'public', table: 'notes', filter: 'meeting_id=eq.123' }, (payload) => { console.log('笔记已更新:', payload.new) }) .subscribe()
身份认证(Auth)
内置完善的身份认证和授权系统:
- 多种登录方式: 邮箱密码、社交登录、魔法链接。
- 会话管理: 自动处理token刷新、会话持久化。
- 用户管理: 用户注册、密码重置、邮箱验证。
- 匿名用户: 支持临时用户访问。
// 认证示例 const { data: { user }, error } = await supabase.auth.signInWithPassword({ email: 'user@example.com', password: 'password' })
行级安全(RLS)
基于PostgreSQL的行级安全策略:
- 细粒度权限控制: 基于用户、角色、数据条件控制访问。
- 策略定义: 使用SQL定义访问规则。
- 自动应用: 所有查询自动应用安全策略。
- 性能优化: 在数据库层面过滤数据。
-- RLS策略示例 CREATE POLICY "Users can only see their own meetings" ON meetings FOR SELECT USING (auth.uid() = created_by); CREATE POLICY "Users can update their own meetings" ON meetings FOR UPDATE USING (auth.uid() = created_by);
存储服务(PolarFS)
基于PolarDB文件系统(Polar File System,简称PolarFS)进行文件存储与管理:
- 文件上传: 支持大文件上传、断点续传。
- 访问控制: 基于RLS的文件访问权限。
- 文件管理: 文件夹组织、元数据管理。
// 文件上传示例 const { data, error } = await supabase.storage .from('meeting-files') .upload('document.pdf', file, { cacheControl: '3600', upsert: false })
边缘函数(Edge Functions)
基于Deno的服务器端函数:
- TypeScript支持: 原生TypeScript支持。
- 数据库访问: 直接访问Supabase数据库。
- 第三方集成: 调用外部API、处理webhook。
// 边缘函数示例 import { serve } from "https://deno.land/std@0.168.0/http/server.ts" import { createClient } from 'https://esm.sh/@supabase/supabase-js@2' serve(async (req) => { const supabase = createClient( Deno.env.get('SUPABASE_URL') ?? '', Deno.env.get('SUPABASE_ANON_KEY') ?? '' ) const { data } = await supabase.from('meetings').select('*') return new Response(JSON.stringify(data), { headers: { 'Content-Type': 'application/json' } }) })
API(REST & GraphQL)
自动生成的API接口:
- REST API: 自动生成CRUD接口。
- GraphQL: 支持GraphQL查询(企业版)。
- 实时API: 支持实时查询和订阅。
- 类型安全: TypeScript类型。
// REST API示例 const { data, error } = await supabase .from('meetings') .select('*, notes(*)') .eq('id', meetingId) .single()
搭建会议笔记系统
在会议笔记系统中,将主要应用以下功能:
功能 |
说明 |
数据存储,主要记录会议、笔记、任务、用户状态等表信息。 |
|
协作功能,用于实时同步笔记编辑、用户在线状态。 |
|
数据安全,用于控制用户访问权限。 |
|
文件管理,用于会议资料上传和下载。 |
|
用户管理,用于管理用户登录和会话管理。 |
应用技术栈
- 前端:Next.js 15 + React 18 + TypeScript。
- 后端:PolarDB Supabase (PostgreSQL + 鉴权 + 实时订阅 + 存储)。
- UI:Tailwind CSS + Radix UI。
- 状态管理:React Hooks + 本地状态。
数据库设计
为会议笔记系统的核心功能设计会议表、笔记表、用户在线状态表、标签表及任务表等系统表,并通过外键约束以确保数据的一致性。
SQL示例:
-- 创建会议表 CREATE TABLE IF NOT EXISTS meetings ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, title VARCHAR(255) NOT NULL, description TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- 创建笔记表 CREATE TABLE IF NOT EXISTS notes ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, meeting_id UUID REFERENCES meetings(id) ON DELETE CASCADE, content JSONB DEFAULT '{}', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- 创建用户在线状态表 CREATE TABLE IF NOT EXISTS user_presence ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, meeting_id UUID REFERENCES meetings(id) ON DELETE CASCADE, user_name VARCHAR(100) NOT NULL, user_color VARCHAR(7) DEFAULT '#1890ff', is_typing BOOLEAN DEFAULT FALSE, last_seen TIMESTAMP WITH TIME ZONE DEFAULT NOW(), created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(meeting_id, user_name) ); -- 创建标签表 CREATE TABLE IF NOT EXISTS tags ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, meeting_id UUID REFERENCES meetings(id) ON DELETE CASCADE, name VARCHAR(50) NOT NULL, color VARCHAR(7) DEFAULT '#1890ff', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- 创建任务表 CREATE TABLE IF NOT EXISTS tasks ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, meeting_id UUID REFERENCES meetings(id) ON DELETE CASCADE, title VARCHAR(255) NOT NULL, description TEXT, assignee VARCHAR(100), status VARCHAR(20) DEFAULT 'pending', due_date TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- 创建会议活动日志表 CREATE TABLE IF NOT EXISTS meeting_activities ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, meeting_id UUID REFERENCES meetings(id) ON DELETE CASCADE, user_name VARCHAR(100) NOT NULL, activity_type VARCHAR(50) NOT NULL, -- 'join', 'leave', 'edit', 'add_tag', 'add_task', etc. activity_data JSONB DEFAULT '{}', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- 会议资料表:meeting_files CREATE TABLE IF NOT EXISTS meeting_files ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), meeting_id UUID REFERENCES meetings(id) ON DELETE CASCADE, file_name VARCHAR(255) NOT NULL, file_url TEXT NOT NULL, uploader VARCHAR(100), uploaded_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), file_size BIGINT, mime_type VARCHAR(100) ); -- 为会议ID加索引 CREATE INDEX IF NOT EXISTS idx_meeting_files_meeting_id ON meeting_files(meeting_id);
实时订阅配置
启用实时功能
-- 启用实时订阅功能 ALTER PUBLICATION supabase_realtime ADD TABLE meetings; ALTER PUBLICATION supabase_realtime ADD TABLE notes; ALTER PUBLICATION supabase_realtime ADD TABLE user_presence; ALTER PUBLICATION supabase_realtime ADD TABLE tags; ALTER PUBLICATION supabase_realtime ADD TABLE tasks; ALTER PUBLICATION supabase_realtime ADD TABLE meeting_activities; ALTER PUBLICATION supabase_realtime ADD TABLE meeting_files;
客户端实时订阅
// 创建自定义Hook管理实时订阅 export function useRealtime(meetingId: string, callbacks: RealtimeCallbacks) { const channelsRef = useRef<any[ ]>([ ]) const cleanup = useCallback(() => { channelsRef.current.forEach((channel) => { supabase.removeChannel(channel) }) channelsRef.current = [] }, []) useEffect(() => { if (!meetingId) return // 清理之前的连接 cleanup() // 创建多个专用频道 const presenceChannel = supabase .channel(`presence:${meetingId}`) .on( "postgres_changes", { event: "*", schema: "public", table: "user_presence", filter: `meeting_id=eq.${meetingId}`, }, (payload) => { console.log("User presence change:", payload) if (callbacks.onUserPresenceChange) { loadOnlineUsers() } }, ) .subscribe() // 保存频道引用用于清理 channelsRef.current = [presenceChannel, /* 其他频道 */] }, [meetingId, callbacks]) return { cleanup } }
实时订阅设计指南
- 功能隔离:按业务场景划分独立频道(如在线状态、文档协作、任务通知),避免逻辑耦合。
- 事件过滤:通过
filter参数限定事件范围(如指定会议ID),减少冗余数据传输。 - 资源回收:在组件卸载或页面跳转时,主动调用
removeChannel清理所有频道连接,释放资源。 - 错误处理:监听订阅通道的状态,对断连、超时等异常进行重试或降级处理。
安全性
行级安全(RLS)
-- 启用RLS ALTER TABLE meetings ENABLE ROW LEVEL SECURITY; -- 创建安全策略 CREATE POLICY "Users can view all meetings" ON meetings FOR SELECT USING (true); CREATE POLICY "Users can create meetings" ON meetings FOR INSERT WITH CHECK (true); CREATE POLICY "Users can update their own meetings" ON meetings FOR UPDATE USING (auth.uid() = created_by);
用户认证
const login = useCallback(async (email: string, password: string) => { try { const { data, error } = await supabase.auth.signInWithPassword({ email, password }) if (error) throw error if (data.user) { const userData = transformSupabaseUser(data.user) setUser(userData) localStorage.setItem("meeting_user", JSON.stringify(userData)) return { success: true, user: userData } } return { success: false, error: '登录失败' } } catch (error: any) { return { success: false, error: error.message || '登录失败' } } }, []) const transformSupabaseUser = (supabaseUser: SupabaseUser): User => ({ id: supabaseUser.id, email: supabaseUser.email || null, name: supabaseUser.user_metadata?.name || supabaseUser.email?.split('@')[0] || '用户', avatar_url: supabaseUser.user_metadata?.avatar_url || null, created_at: supabaseUser.created_at, is_anonymous: supabaseUser.user_metadata?.is_anonymous || false, })
客户端集成
客户端配置
// lib/supabase.ts import { createClient } from '@supabase/supabase-js' const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL! const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! export const supabase = createClient(supabaseUrl, supabaseAnonKey) // 类型定义 export interface Meeting { id: string created_at: string title: string description: string | null }
环境变量管理
# 生产环境变量 NEXT_PUBLIC_SUPABASE_URL=<YOUR_SUPABASE_PUBLIC_URL> NEXT_PUBLIC_SUPABASE_ANON_KEY=<YOUR_SUPABASE_ANON_KEY>
快速搭建
- 下载项目示例代码PolarDB-Supabase-App-Demo。
- 环境配置:在项目根目录创建
.env.local文件,并将以下值替换为您PolarDB Supabase的配置。
说明
您可在集群的AI能力 > AI应用列表页面,单击您的应用ID进入应用详情页,在拓扑图与配置页签中查看相关配置信息。
<YOUR_SUPABASE_PUBLIC_URL>为集群公网地址。<YOUR_SUPABASE_ANON_KEY>为集群参数secret.jwt.anonKey的值。
# 生产环境变量 NEXT_PUBLIC_SUPABASE_URL=<YOUR_SUPABASE_PUBLIC_URL> NEXT_PUBLIC_SUPABASE_ANON_KEY=<YOUR_SUPABASE_ANON_KEY>
- 数据库初始化:在项目根目录的
scripts文件夹下找到01-create-tables.sql文件,并在Supabase Dashboard上面的右侧导航栏SQL Editor中执行SQL。
- 运行项目:在项目根目录执行如下命令。运行功能后,默认本地访问地址为
http://localhost:3000,您可直接在浏览器中访问系统。
说明
在运行项目之前,请确保在您的本地环境中已安装
Node.js和pnpm。
Node.js:请前往Node.js官方网站进行下载和安装。pnpm:在安装Node.js后,可通过npm进行全局安装,命令为npm install -g pnpm。
pnpm install pnpm dev
经验总结
基于上述会议笔记系统项目的实战经验,PolarDB Supabase最佳实践包括:
遵循这些最佳实践可以构建稳定、安全、高性能的PolarDB Supabase应用。