ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Supabase Flutter 用户管理系统实战:从魔法链接登录到资料存储的完整指南

Supabase Flutter 用户管理系统实战:从魔法链接登录到资料存储的完整指南 Supabase Flutter 用户管理系统实战从魔法链接登录到资料存储的完整指南【免费下载链接】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本文以 Supabase 官方仓库中的 Flutter User Management 示例应用examples/user-management/flutter-user-management为核心系统讲解如何用 Flutter 与 Supabase 从零构建一个具备邮箱魔法链接登录、个人资料读写与头像图片上传能力的完整应用。读完本文你将掌握supabase_flutter客户端的初始化、Auth 会话监听与魔法链接鉴权、Postgres 数据库行级安全RLS策略设计以及 Storage 上传与签名 URL 的实战用法并能在 iOS、Android 与 Web 三端直接运行该示例。一、示例应用定位一套最小可运行的 Flutter Supabase 全栈模板该示例是 Supabase 官方仓库中user-management系列示例的 Flutter 实现用于演示三条核心能力链路使用Supabase Auth 魔法链接Magic Link让用户通过邮箱免密登录使用Supabase DatabasePostgres存储并读取用户资料使用Supabase Storage存储用户头像图片文件。与 Next.js、React、Swift、Vue 等同目录下的兄弟示例不同flutter-user-management 的独特价值在于它演示了 Flutter 跨端iOS / Android / Web下处理 Supabase 的完整写法尤其是 Web 端魔法链接回调与移动端 Deep Link 回调在代码中的差异处理这些细节在纯 Web 示例中不会出现。应用本身是一个标准的 Flutter Material 应用代码结构清晰文件职责lib/main.dart入口加载.env、初始化Supabase、根据会话状态决定首页路由并提供全局 SnackBar 扩展lib/pages/splash_page.dart启动页依据本地是否已有会话跳转登录页或账户页README 主流程之外的辅助页lib/pages/login_page.dart登录页输入邮箱发送魔法链接并监听onAuthStateChange自动跳转lib/pages/account_page.dart账户页读取/更新profiles表上传头像后回写avatar_urllib/components/avatar.dart头像组件调用系统相册选取图片、上传 Storage、生成签名 URL二、快速启动环境准备与三端运行命令1. 获取 Supabase 项目凭据并配置.env参照示例目录下的 .env.example在flutter-user-management目录中复制出.env文件并填入自己的项目凭据SUPABASE_URLhttps://mysupabasereference.supabase.co SUPABASE_PUBLISHABLE_KEYmy.supabase.publishable.key两个字段的含义分别为SUPABASE_URL你的 Supabase 项目地址即 Project Settings → API 中的 Project URL格式形如https://xxx.supabase.coSUPABASE_PUBLISHABLE_KEY对外公开的 API Key即 anon/publishable key用于浏览器与移动端等不可信环境。仓库在此处使用 publishable key 命名对应新版本 Supabase 的密钥体系旧项目也可沿用 anon key。.env之所以必须以该文件名出现在仓库中是因为 pubspec.yaml 的flutter.assets段已将.env声明为打包资源flutter_dotenv才能在运行时通过dotenv.load()将其注入环境。2. 初始化与三端运行环境准备完成后依次执行flutter pub get然后按目标平台选择运行命令。iOS 与 Android 直接运行flutter run若以 Web 方式运行并在localhost:3000启动flutter run -d web-server --web-hostname localhost --web-port 30003. 源码中的初始化要点在 lib/main.dart 的main()中先加载环境变量再初始化客户端Futurevoid main() async { await dotenv.load(); await Supabase.initialize( url: dotenv.env[SUPABASE_URL]!, anonKey: dotenv.env[SUPABASE_ANON_KEY]!, ); runApp(const MyApp()); } final supabase Supabase.instance.client;需要注意两点示例通过全局supabase变量暴露客户端在main.dart顶部定义供各页面直接导入使用它等价于Supabase.instance.client的单例从源码结构看这是为示例保持代码简洁而采用的写法大型项目中更推荐用依赖注入或单一封装模块管理该客户端。Supabase.initialize内部会完成 WebSocket 与 Auth 的初始配置。main()之后MaterialApp的home属性根据本地是否已存在会话做首屏分流supabase.auth.currentSession null时进入LoginPage否则直接进入AccountPage。由于supabase_flutter会持久化会话用户重启 App 后仍可保持登录态。三、数据库 Schema一张表 三层安全策略README 中给出了完整的初始化 SQL。仓库将该 SQL 固化在 supabase/migrations/20240404030631_init.sql可作为本地开发迁移脚本直接使用。全部内容可拆解为四个部分逐一说明。1.profiles用户资料表-- Create a table for public profiles create table profiles ( id uuid references auth.users not null, updated_at timestamp with time zone, username text unique, avatar_url text, website text, primary key (id), unique(username), constraint username_length check (char_length(username) 3) );设计要点id是主键同时外键引用auth.users即每条 profile 都唯一对应用户系统中的一个 Auth 用户username声明为unique且通过username_length检查约束要求用户名至少 3 个字符从数据层杜绝过短用户名avatar_url用于存放头像图片地址website是示例性扩展字段演示如何为个人资料增加信息项。2. 开启行级安全RLSalter table profiles enable row level security;开启 RLS 后若未创建策略默认所有连接方对该表都没有任何访问权限。示例接下来的三条策略正是在此基础上逐步放开公开读 本人写的最小权限模型。3. 三条访问策略create policy Public profiles are viewable by everyone. on profiles for select using ( true ); create policy Users can insert their own profile. on profiles for insert with check ( (select auth.uid()) id ); create policy Users can update own profile. on profiles for update using ( (select auth.uid()) id );第一条允许任何人含匿名用户读取所有公开资料这是头像、主页等公开信息的常见需求第二条限定只能为自己插入 profilewith check (auth.uid() id)会在写入时校验行数据防止用户伪造他人的id写入第三条限定只能更新自己的资料using (auth.uid() id)过滤可被更新的行防止越权篡改他人记录。注意migration 文件与 README 中该约束的写法略有差异——README 版本使用(select auth.uid()) id的子查询形式migration 中使用auth.uid() id两者语义等价以 20240404030631_init.sql 的简洁写法为准即可。4. 开启 Realtime 并准备 Storage Bucket-- Set up Realtime! begin; drop publication if exists supabase_realtime; create publication supabase_realtime; commit; alter publication supabase_realtime add table profiles; -- Set up Storage! insert into storage.buckets (id, name) values (avatars, avatars); create policy Avatar images are publicly accessible. on storage.objects for select using ( bucket_id avatars ); create policy Anyone can upload an avatar. on storage.objects for insert with check ( bucket_id avatars );Realtime 部分先重置supabase_realtime发布publication再把profiles表加入其中。此后的数据变更即可通过 Postgres 逻辑复制推送给订阅了该表的 Realtime 客户端Storage 部分向storage.buckets插入名为avatars的存储桶并为该桶中的对象开放公开可读、人人可上传两条策略对应头像这种本身就是要对外展示、且登录用户都能替换自己头像的场景。四、登录流程魔法链接 会话监听实现无密码鉴权登录页的完整实现在 lib/pages/login_page.dart核心是两个异步机制发送魔法链接与监听登录态变化自动跳转。1. 发送邮箱魔法链接await supabase.auth.signInWithOtp( email: _emailController.text.trim(), emailRedirectTo: kIsWeb ? null : io.supabase.flutterquickstart://login-callback/, );关键点在于emailRedirectTo的跨端差异在Web端kIsWeb true传入nullSupabase 会使用项目默认的 Site URL 作为回调地址在iOS / Android端传入自定义 URL Schemeio.supabase.flutterquickstart://login-callback/用户在邮箱点击链接后系统通过 Deep Link 把认证结果交回 App由supabase_flutter完成深层链接解析并换取会话。若你修改了 iOS 工程中的 bundle identifier 或 Android 中的包名需同步更新此处的 scheme并在两端原生工程iOSInfo.plist/ Android Manifest 的 intent-filter中注册对应 scheme。发送成功后页面通过context.showSnackBar(Check your email for a login link!)提示用户查收邮件。2. 监听认证状态实现自动跳转_authStateSubscription supabase.auth.onAuthStateChange.listen( (data) { if (_redirecting) return; final session data.session; if (session ! null) { _redirecting true; Navigator.of(context).pushReplacement( MaterialPageRoute(builder: (context) const AccountPage()), ); } }, onError: (error) { // AuthException 会携带 error.message 用于提示 }, );这里的onAuthStateChange返回StreamAuthStateApp 生命周期内任何认证事件登录、登出、令牌刷新都会触发回调。示例在initState中订阅、在dispose中调用_authStateSubscription.cancel()取消订阅避免内存泄漏_redirecting布尔值则防止回调被重复触发导致多次跳转。由于_signIn会先做表单输入校验与 loading 状态切换并用on AuthException/ 通用catch分级捕获错误登录页对邮箱格式错误网络故障等异常都能给出明确的 SnackBar 反馈。五、资料读写从profiles表查询与 upsert进入账户页后lib/pages/account_page.dart 在initState阶段调用_getProfile()拉取当前用户资料final userId supabase.auth.currentSession!.user.id; final data await supabase.from(profiles).select().eq(id, userId).single(); _usernameController.text (data[username] ?? ) as String; _websiteController.text (data[website] ?? ) as String; _avatarUrl (data[avatar_url] ?? ) as String;这是典型的 Supabase Dart 查询链from(profiles)指定表 →select()全列选取 →eq(id, userId)按当前用户主键过滤 →single()断言只返回一行。由于字段可能为空代码用?? 兜底回填到输入框避免空值异常。更新资料时使用upsertfinal updates { id: user!.id, username: userName, website: website, updated_at: DateTime.now().toIso8601String(), }; await supabase.from(profiles).upsert(updates);upsert的行为是存在则更新、不存在则插入配合 RLS 中只能插入/更新自己的策略若用户首次进入尚无 profile 行这次写入会以auth.uid()为其新建记录若已存在则整行更新。updated_at使用 Dart 侧生成的 ISO 8601 时间字符串写入若希望由数据库自动维护该时间戳可改为updated_at now()的 RPC 或数据库触发器。账户页底部提供Sign Out按钮调用supabase.auth.signOut()清除本地会话随后跳回LoginPage。六、头像上传Storage 上传二进制 签名 URL 回写头像能力封装在 lib/components/avatar.dart它既是 UI 组件也承载了选图 → 上传 → 取 URL → 回写 profile的完整业务逻辑。1. 本地选图与压缩final picker ImagePicker(); final imageFile await picker.pickImage( source: ImageSource.gallery, maxWidth: 300, maxHeight: 300, );通过image_picker插件调起系统相册maxWidth/maxHeight均限定为 300px在客户端先完成尺寸约束避免大图直接上传浪费带宽与存储。2. 上传二进制文件到avatars桶final bytes await imageFile.readAsBytes(); final fileExt imageFile.path.split(.).last; final fileName ${DateTime.now().toIso8601String()}.$fileExt; final filePath fileName; await supabase.storage.from(avatars).uploadBinary( filePath, bytes, fileOptions: FileOptions(contentType: imageFile.mimeType), );实现细节值得留意文件名以DateTime.now().toIso8601String()时间戳加原始扩展名拼成天然唯一避免不同用户上传同名文件相互覆盖示例把filePath设为仅文件名即上传到桶的根目录使用uploadBinary直接上传内存字节数组而非upload的文件路径版本配合FileOptions(contentType: ...)显式声明 MIME 类型让浏览器/App 端能正确渲染图片。3. 生成长期有效的签名 URL 并回写final imageUrlResponse await supabase.storage .from(avatars) .createSignedUrl(filePath, 60 * 60 * 24 * 365 * 10); widget.onUpload(imageUrlResponse);示例并未走公开桶直出 URL的路线而是为上传对象创建有效期为60 * 60 * 24 * 365 * 10秒即 10 年的签名 URL再通过回调onUpload交给AccountPage由_onUpload将avatar_urlupsert 到profiles表。这正是 RLS 中存储桶 select 策略公开可读存在的原因之一——即便使用了带签名 URL桶策略依然允许直接公开访问。Avatar组件在imageUrl非空时用Image.network渲染头像为空时展示灰色占位块与No Image提示兼顾了首次登录尚无头像的场景。七、从示例到项目可以继续深入仓库的参考路径如果需要把该示例扩展为真实的业务应用或想对照其他技术栈的实现方式可以在仓库中找到以下相互印证的资源对照实现同属user-management目录的 nextjs-user-management、swift-user-management、react-user-management 等示例使用相同的profiles表结构与策略思想可横向对比不同语言客户端的 API 差异客户端能力扩展supabase_flutter还支持基于 PostgREST 的实时订阅、auth.signUp密码注册、auth.onAuthStateChange令牌自动刷新等能力均可从当前示例的 Auth/Database/Storage 三条链路继续延伸本地开发示例自带的 supabase/config.toml 与迁移脚本可直接用于supabase start本地开发环境从而在不连接云端项目的情况下先跑通全流程。八、小结示例应用的整体数据流把上面的内容串起来一个完整会话周期中的数据流为用户在LoginPage输入邮箱 → 发送魔法链接 → 邮件回调触发onAuthStateChange→ 自动跳转AccountPageAccountPage依据auth.uid()从profiles表读取本人资料并填充表单用户修改资料点击 Update →upsert写回受 RLS 的 insert/update 策略约束用户选择头像 → 客户端压缩后uploadBinary至avatars桶 → 生成长期签名 URL → 回写avatar_url页面即时刷新头像点击 Sign Out →signOut()清除会话 → 返回登录页。从 README 的快速启动指引到 20240404030631_init.sql 的完整 Schema再到 main.dart、login_page.dart、account_page.dart、avatar.dart 的逐层实现整个示例只用了极少的代码就覆盖了 Auth、Database 与 Storage 三大 Supabase 核心能力。它既是学习supabase_flutter的最佳最小样例也是一套可以快速改造成真实 Flutter 应用骨架的参考模板。【免费下载链接】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),仅供参考
返回列表