免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

使用 Refine Supabase Data Provider 构建实时 CRUD 后台:从示例到源码级实践指南

使用 Refine Supabase Data Provider 构建实时 CRUD 后台:从示例到源码级实践指南 使用 Refine Supabase Data Provider 构建实时 CRUD 后台从示例到源码级实践指南【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine本文基于 Refine 仓库中的 Supabase 官方示例示例文档、示例源码完整讲解如何在 Refine v5 中接入 Supabase包括 Data Provider 数据读写、Live Provider 实时订阅、Auth Provider 认证集成以及 Storage 文件上传等典型后台功能。读完本文你将掌握 Supabase 与 Refine 的完整集成方案并能理解过滤器、排序、实时事件在底层的映射原理直接复用到自己的项目中。示例概览一个开箱即用的 Supabase 后台该示例覆盖了 Refine 与 Supabase 集成的三大核心能力对应示例 frontmatter 中声明的example-tags: [data-provider, live-provider, auth-provider]Data Provider通过refinedev/supabase提供的dataProvider完成对 Supabase 数据库PostgreSQL的增删改查Live Provider通过liveProvider让界面实时响应数据库变更Auth Provider对接 Supabase Auth邮箱密码登录 Google OAuth并配合Authenticated组件保护路由。示例业务模型为blog_posts与categories两张表类型定义见 src/interfaces/index.d.ts并实现了列表、创建、编辑、详情四个页面同时演示了 MD 编辑器uiw/react-md-editor、图片上传Supabase Storage与关联表展示等常见后台场景。如何在本地运行该示例在仓库根目录下可通过 Refine CLI 一键创建独立示例项目npm create refine-applatest -- --example>import { createClient } from refinedev/supabase; const SUPABASE_URL https://iwdfzvfqbtokqetmbmbp.supabase.co; const SUPABASE_KEY eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYzMDU2NzAxMCwiZXhwIjoxOTQ2MTQzMDEwfQ._gr6kXGkQBi9BM9dx5vKaNKYj_DJN1xlkarprGpM_fU; export const supabaseClient createClient(SUPABASE_URL, SUPABASE_KEY, { db: { schema: public, }, auth: { persistSession: true, }, });这里有两个值得注意的配置项db.schema指定默认数据库 schema示例使用public可保证表与 RLS 策略在预期 schema 下生效auth.persistSession启用会话持久化刷新页面后登录状态不丢失。在 packages/supabase/src/index.ts 中可以看到createClient直接来自supabase/supabase-js并原样导出同时整个包的导出面包含types、utils、dataProvider与liveProvider四个模块——这意味着你既可以使用 Refine 提供的封装客户端也可以沿用自己已有的 supabase-js 客户端实例它同样是dataProvider(supabaseClient)/liveProvider(supabaseClient)的参数。第二步在 Refine 中装配 Data、Live 与 Auth Provider在 src/App.tsx 中三者被一次性注入RefineRefine dataProvider{dataProvider(supabaseClient)} liveProvider{liveProvider(supabaseClient)} routerProvider{routerProvider} authProvider{authProvider} resources{[ { name: blog_posts, list: /blog-posts, create: /blog-posts/create, edit: /blog-posts/edit/:id, show: /blog-posts/show/:id, meta: { canDelete: true, }, }, ]} options{{ liveMode: off, syncWithLocation: true, warnWhenUnsavedChanges: true, }} 几点关键设计dataProvider(supabaseClient)将 supabase-js 客户端包装为 Refine 标准 Data Provider之后useTable、useForm、useShow等 Hook 的所有 CRUD 调用都会翻译成对 Supabase REST/PostgREST 的请求liveProvider(supabaseClient)基于 Supabase Realtime 的频道订阅实现结合liveMode的三种取值auto/manual/off控制页面是否自动更新。options.liveMode: off这是示例特意给出的重要约束——注释中明确说明见 src/App.tsx当前supabase/supabase-jsv2 客户端与refinedev/supabase尚不支持同时建立多个实时订阅因此开启全局liveMode会导致非预期行为正确的做法是按页面手动设置liveMode: auto或liveMode: manual下文编辑页正是这样做的warnWhenUnsavedChanges配合路由层的UnsavedChangesNotifier在离开页面时提示未保存的表单。Auth Provider登录、注册与密码重置示例中的authProvider全部基于supabaseClient.auth实现src/App.tsx典型的几个方法// 登录支持 OAuth 与邮箱密码两种方式 login: async ({ email, password, providerName }) { if (providerName) { const { data, error } await supabaseClient.auth.signInWithOAuth({ provider: providerName, }); // ... } const { data, error } await supabaseClient.auth.signInWithPassword({ email, password, }); // ... },register→auth.signUpforgotPassword→auth.resetPasswordForEmail并携带redirectTo指向/update-password页面updatePassword→auth.updateUserlogout→auth.signOutcheck→ 读取auth.getSession()无会话则返回{ authenticated: false, redirectTo: /login }getIdentity→ 返回用户信息getPermissions→ 返回用户role。值得一提的错误处理onError中对PGRST301与401错误返回{ logout: true }即当会话过期/令牌失效时自动登出配合 Supabase 的 RLS 策略形成完整的权限闭环。路由层面使用Authenticated包裹受保护区域未登录跳转/login登录页AuthPage传入 Google OAuth 按钮并预设了演示账号inforefine.dev/refine-supabase见 src/App.tsx。第三步列表页——查询、排序、关联与过滤列表页src/pages/posts/list.tsx演示了 Refine Supabase 最常用的查询能力const { tableProps, sorters, filters } useTableIPost({ meta: { select: *, categories(title), // PostgREST 嵌入关联表 }, sorters: { initial: [{ field: id, order: asc }], }, });关联查询meta.select直接透传给 PostgREST 的select参数*, categories(title)会一并返回每条帖子关联的分类标题随后Table.Column dataIndex{[categories, title]}即可直接渲染服务端排序getDefaultSortOrder(id, sorters)与列上的sorter组合实现点击表头即走后端排序服务端过滤FilterDropdowngetDefaultFilter(categoryId, filters, in)实现多选分类过滤useSelect的pagination: { mode: server }让下拉选项分页加载server 端分页Refine 的 Data Provider 会把页码与页大小转换为 PostgREST 的range参数。第四步创建/编辑页——表单、Markdown 与 Storage 上传创建页src/pages/posts/create.tsx展示了完整的表单与文件上传流程。核心是Upload.Dragger的customRequestcustomRequest{async ({ file, onError, onSuccess }) { const rcFile file as RcFile; await supabaseClient.storage .from(refine) .upload(public/${rcFile.name}, file, { cacheControl: 3600, upsert: true, }); const { data } await supabaseClient.storage .from(refine) .getPublicUrl(public/${rcFile.name}); onSuccess?.({ url: data?.publicUrl }, new XMLHttpRequest()); }}关键点上传到名为refine的 bucket 的public/前缀下需在 Supabase 控制台开启该 bucket 的 Public 访问并配置存储 RLS 策略cacheControl: 3600设置 CDN 缓存时长upsert: true允许同名文件覆盖上传成功后通过getPublicUrl取回公开 URL作为onSuccess的结果写回表单表单字段images使用normalizeFilesrc/utility/normalize.ts将 antd Upload 的fileList归一化为{ uid, name, url, type, size, percent, status }结构后入库对应的IPost.images类型为IFile[]。编辑页的实时体验liveMode 手动模式编辑页src/pages/posts/edit.tsx演示了liveMode: manual的正确用法——不强制刷新表单而是提醒用户手动刷新const { formProps, saveButtonProps, query: queryResult } useFormIPost({ liveMode: manual, onLiveEvent: () { setIsDeprecated(true); // 弹出数据已变更警告条 }, }); const handleRefresh () { queryResult?.refetch(); setIsDeprecated(false); };当其他客户端修改了该记录Supabase Realtime 推送事件后页面顶部出现“This post is changed. Reload to see its latest version.”警告条并提供RefreshButton手动拉取最新数据从而避免在用户编辑中途强行覆盖其输入。详情页src/pages/posts/show.tsx采用同样的策略并配合useOne按需加载分类标题、MarkdownField渲染 Markdown、ImageField展示上传的图片。第五步源码级原理——过滤器如何映射到 PostgRESTrefinedev/supabase的 Data Provider 之所以能透明地把 Refine 的CrudFilter变成数据库查询核心在于两个工具函数。generateFilter.ts 将 Refine 的条件操作符翻译为 supabase-js 查询链例如Refine 操作符supabase-js 查询语义eq/nequery.eq / query.neq等于 / 不等于in/inaquery.in / query.contains属于数组 / 数组包含csninaquery.not(field, cs, {...})数组不包含gt/gte/lt/ltequery.gt / gte / lt / lte比较运算betweenquery.gte(...).lte(...)区间长度必须为 2否则抛出明确错误contains/containssquery.ilike(%v%) / like(%v%)模糊匹配大小写不敏感/敏感nullquery.is(field, null)为空startswith/endswithilike(v%) / ilike(%v)前后缀匹配orquery.or(orSyntax)拼接 PostgREST 的 or 语法and抛出Operator and is not supported显式拒绝其中or分支会将子条件序列化为 PostgREST 的 Horrock 语法如categoryId.eq.1,title.ilike.%foo%并在ina/nina、contains、startswith、endswith等场景自动补充{}、%等必要包裹符号。而 mapOperator.ts 负责把 Refine 的CrudOperators映射为 PostgREST 运算符例如ne → neq、contains → ilike、ncontains → not.ilike、nnull → not.is等并明确抛出between/nbetween不支持的错误——这正是generateFilter中between单独用gte lte实现的原因。handleError.ts与generateFilter.ts一起构成了 Data Provider 错误归一化与查询构建的底层支撑。另外refinedev/supabase的 Data Provider 在写操作时还会自动处理created_at、updated_at等时间戳字段的序列化并支持通过meta.select控制返回字段Live Provider 则通过supabaseClient.channel()监听 Postgres 变更事件再分发给订阅了liveMode的 Hook。这些行为与示例页面中的表现完全对应读者可结合 packages/supabase/src/dataProvider/index.ts 与 packages/supabase/src/liveProvider/index.ts 继续深挖。运行注意事项与常见陷阱Realtime 在 StackBlitz 中不可用官方文档明确提示supabase.md“StackBlitz environment does not allow Realtime features to work”因此在沙箱中调试实时功能可能无效果实时演示请在本机运行。不要开启全局liveMode: auto由于 supabase-js v2 客户端与refinedev/supabase的订阅实现限制同一时间支持单个订阅建议像示例那样保持全局liveMode: off在需要实时能力的页面单独设置liveMode: manual或auto。RLS 与匿名 key示例使用的是 Supabase 公开演示项目与 anon key在自有项目中务必为blog_posts、categories、storage.objects配置符合业务的安全策略Row Level Security否则接口会返回PGRST权限错误示例的onError已对PGRST301做了登出处理。Storage bucket 与 CORS上传前需在 Supabase 控制台创建 bucket示例为refine、开放 public 读权限并配置存储策略开发环境下若跨域访问资源需确认 Supabase 项目的 CORS 白名单包含你的本地域名。运行环境示例要求node 20见 package.json 的engines字段推荐使用仓库采用的 pnpm 工作区方式安装依赖。小结Supabase 为后台应用提供了数据库、认证、实时订阅与对象存储的一站式能力而 Refine 的refinedev/supabase包将这些能力统一抽象为 Data Provider / Live Provider / Auth Provider让 CRUD 页面可以用声明式 Hook 直接驱动。通过本文示例你可以快速获得一个带登录鉴权、关联查询、服务端排序过滤、文件上传与实时提醒的完整后台骨架如需进一步验证行为仓库中还提供了对应的端到端测试cypress/e2e/data-provider-supabase可供参考。之后只需替换为自有项目的 URL、key 与表结构即可无缝迁移到生产环境。【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表