Flutter+Supabase构建AI学习平台:3天完成54家服务商整合

Flutter+Supabase构建AI学习平台:3天完成54家服务商整合 1. 项目概述一个为AI学习者打造的“一站式大学”如果你最近也在关注AI领域大概率会和我有同样的感受这个领域的变化速度已经快到让人喘不过气。今天OpenAI发布了新模型明天Anthropic更新了上下文窗口后天又冒出一个你没听过的初创公司推出了某个垂直领域的专用API。对于开发者、产品经理甚至是想要入行的学习者来说光是搞清楚“现在有哪些AI服务”、“它们各自能做什么”、“我应该怎么用”就已经是一项浩大的工程。这不再是一个可以靠几篇博客文章就能跟上的时代了。信息是碎片化的散落在官方文档、技术博客、社区帖子和新闻快讯里。于是我萌生了一个想法能不能建一个“AI领域的大学”把所有这些分散的知识系统化、结构化地整理起来让任何一个想学习的人都能在一个地方按图索骥地掌握主流AI服务商的核心信息这就是“AI University”项目的起点。它的核心目标很简单成为一个覆盖主流AI服务商的、动态更新的、内置学习与考核机制的应用平台。更具体一点我为自己设定了几个关键指标第一内容要全至少要覆盖50家以上的AI提供商第二架构要灵活新厂商的加入不能导致应用重构第三要有用户粘性通过游戏化的方式激励持续学习第四开发要快用最小可行产品MVP验证想法。最终我使用Flutter和Supabase在3天时间内构建了一个覆盖54家AI提供商、具备完整学习-测验-排名-分享功能的应用。这篇文章我会详细拆解整个项目的架构设计、实现细节以及那些让我在极短时间内完成开发的关键决策与工具链。无论你是想了解如何高效整合Flutter与Supabase还是对构建数据驱动的动态应用感兴趣亦或是想学习如何管理一个内容快速更迭的项目相信都能从中找到参考。2. 技术选型与架构设计思路在项目启动前技术栈的选择直接决定了开发效率和后期的可维护性。我的核心诉求是前端要能快速构建美观、跨平台的UI后端要能极简地处理数据存储、用户认证和实时更新最好无需自己维护服务器。2.1 为什么是Flutter Supabase这个组合几乎是当前小型团队或个人开发者快速验证想法的“黄金搭档”。Flutter的选择基于三点考虑跨平台一致性我需要应用能同时覆盖Web、iOS和Android。Flutter一份代码多处部署的特性完美契合了快速启动、广泛触达的需求。特别是对于学习类应用用户可能在电脑浏览器上浏览也可能在手机碎片时间学习跨平台支持至关重要。开发效率与热重载Flutter的热重载功能对于UI调试效率的提升是颠覆性的。在构建包含动态标签页、卡片式内容、交互式测验等复杂UI时能够实时看到改动效果极大加快了开发节奏。丰富的生态系统provider或riverpod用于状态管理http或dio用于网络请求还有大量成熟的UI组件库。这意味着我不需要从零开始造轮子可以把精力集中在业务逻辑上。Supabase的选择则更偏向于后端服务的“开箱即用”真正的后端即服务BaaS它提供了PostgreSQL数据库、即时API、身份认证、存储和实时订阅功能。对于这个项目我主要用到前三项。最关键的是它的数据库是真正的PostgreSQL这意味着我可以使用所有熟悉的SQL功能包括视图、函数、行级安全策略RLS这对于实现排行榜、分数更新等复杂逻辑至关重要。无缝的客户端集成Supabase为Flutter提供了官方且维护良好的SDKsupabase_flutter。通过几行配置我就能在应用中进行用户登录、注册并直接通过生成的API与数据库交互完全绕过了传统后端API的编写工作。行级安全策略RLS这是Supabase的一个杀手级功能。我可以在数据库层面定义“哪些用户能访问或修改哪些数据”而不是在应用代码中写一堆权限判断。例如在ai_university_scores表上我可以设置一条策略用户只能插入或更新自己的分数记录。这样客户端直接upsert分数时无需担心安全问题也省去了编写专用API端点的麻烦。注意虽然Supabase的RLS非常强大但在设计策略时务必谨慎。错误的策略可能导致数据泄露或写入失败。我的经验是为每张需要权限控制的表先明确“匿名用户”、“已认证用户”、“管理员”等角色再针对SELECT、INSERT、UPDATE、DELETE操作分别编写策略并在开发初期进行充分测试。2.2 核心架构以数据库为中心的动态驱动传统的内容型应用内容如提供商的介绍、模型列表往往是硬编码在应用内的或者通过一个需要发版才能更新的配置文件来管理。这对于AI这个日新月异的领域来说是完全不可接受的。我的目标是增加一个新的AI提供商只需要在数据库里插入数据应用界面自动随之变化无需更新客户端。为此我设计了完全以数据库为中心的架构单一事实来源所有静态学习内容提供商概述、模型列表、API指南和动态内容最新新闻都存储在Supabase的一张核心表ai_university_content中。这张表的结构决定了应用的形态。动态UI生成Flutter应用启动时首先查询数据库获取所有唯一的provider提供商列表。然后根据这个列表动态生成顶部的标签页Tab和对应的内容页面。这意味着只要我在数据库里为“Sakana AI”这个新提供商插入了数据下次用户打开应用或刷新时界面上就会自动出现“Sakana AI”的标签页。内容与逻辑分离测验题目、分数、用户学习进度等也都存储在各自的表中。应用UI只负责展示和交互所有业务逻辑如计算总分、更新连续学习天数尽可能通过数据库的视图View和函数Function来完成。这种架构的最大优势是解耦和可维护性。内容运营者甚至是我自己通过脚本可以专注于维护数据库里的内容而应用开发者可以专注于优化用户体验和性能两者通过定义清晰的数据库Schema进行协作互不干扰。3. 数据库设计与内容管理策略数据库是整个平台的基石设计的好坏直接影响到功能实现的复杂度、性能以及未来的扩展性。3.1 核心内容表的设计我创建了主表ai_university_content其设计思路如下CREATE TABLE ai_university_content ( id uuid DEFAULT gen_random_uuid() PRIMARY KEY, provider text NOT NULL, -- 例如openai, anthropic, gemini category text NOT NULL, -- 例如overview, models, api, news title text NOT NULL, content text NOT NULL, -- 存储Markdown格式的文本 published_at date, UNIQUE (provider, category) -- 唯一约束确保每个提供商每个类别只有一份内容 );字段解析与设计理由provider和category这两个字段构成了内容的“坐标”。provider标识这是哪家AI公司category定义内容类型。UNIQUE约束确保了一个提供商在一个类别下只有一份最新内容避免了数据混乱。content使用text类型存储Markdown这是关键决策。Markdown格式轻量、易读易写并且Flutter端有非常成熟的Markdown渲染库如flutter_markdown。这让我可以在后端用最简单的方式维护富文本内容前端也能获得不错的排版效果无需引入复杂的富文本编辑器。published_at这是一个可选的日期字段对于news类别的文章特别有用可以用于排序和筛选。实操心得关于内容结构化最初我考虑过为models模型列表设计更复杂的JSONB字段来存储结构化数据但最终选择了统一的Markdown。原因在于早期阶段速度优先Markdown足以清晰展示模型名称、参数、特点等信息。如果未来需要更复杂的查询如“找出所有支持函数调用的模型”我可以再通过数据库迁移将这部分内容拆分到专门的子表或转换为JSONB。在MVP阶段避免过度设计用最简单的方式跑通流程是第一要务。3.2 自动化内容更新双保险机制静态内容如公司概述、API基础用法可以手动维护但“新闻”news类别必须自动化。AI领域的新闻更新太快手动更新不现实。我设计了一个两层级的“双保险”自动更新系统第一层快速抓取GitHub Actions 每2小时一次我编写了一个Python脚本部署在GitHub Actions上。这个脚本会轮询我预先收集好的几十个AI相关博客、官网公告的RSS源。每次运行它会抓取每个源最新的5篇文章标题和摘要然后通过Supabase的API更新到ai_university_content表中categorynews的记录里。这保证了信息的时效性用户在白天几乎能看到小时级的最新动态。第二层深度加工Claude Code NotebookLM 每4小时一次RSS摘要通常比较简短。为了提供更有价值的“新闻解读”我设置了另一个更慢但更智能的流程。同样使用定时任务如Cron Job或另一个GitHub Actions调用Claude Code实例。这个实例会读取第一层抓取到的新闻摘要然后利用NotebookLM等工具进行深度研究和分析生成一段包含背景、技术要点、潜在影响的、更丰富的解释性内容。最后它用这段更优质的内容去覆盖overwrite第一层生成的简单摘要。“后来者胜”的冲突解决策略两个系统都会更新同一条记录同一个provider的news。我采用了简单的“时间戳最新覆盖”策略。Supabase的UPDATE操作会自然更新记录。这样清晨用户看到的是快速的RSS摘要下午看到的就是经过Claude深度加工后的解读版。这种设计既保证了速度又提升了内容质量。注意事项自动化更新一定要处理好错误和异常。我的脚本里包含了重试机制、网络超时设置并且会记录详细的日志。如果Supabase API调用失败脚本会发送通知到我的Slack或邮箱。对于内容抓取务必遵守网站的robots.txt协议控制请求频率避免给对方服务器造成压力。4. Flutter前端实现详解前端应用是用户直接交互的界面需要做到清晰、流畅并能完美适配从数据库动态获取的数据结构。4.1 动态UI构建从数据到界面应用的核心界面是一个DefaultTabController包含顶部的标签栏和对应的内容页面。关键点在于标签和内容都不是硬编码的。第一步获取提供商列表应用启动后首先向Supabase发送一个查询获取所有不重复的provider值final ListMapString, dynamic providerData await _supabase .from(ai_university_content) .select(provider) .execute(); // 去重并排序 ListString providers providerData.map((e) e[provider] as String).toSet().toList()..sort();第二步动态生成Tab根据获取到的providers列表动态生成Tab组件。为了更友好我维护了一个本地的Map将provider的代码如openai映射为显示名称和表情符号。final MapString, MapString, String _providerMeta { openai: {name: OpenAI, emoji: }, anthropic: {name: Anthropic, emoji: }, gemini: {name: Google Gemini, emoji: }, // ... 其他53家提供商 }; ListTab buildTabs(ListString providers) { return providers.map((providerCode) { final meta _providerMeta[providerCode] ?? {name: providerCode, emoji: }; return Tab( child: Row( mainAxisSize: MainAxisSize.min, children: [ Text(meta[emoji]!), SizedBox(width: 4), Text(meta[name]!), ], ), ); }).toList(); }这样即使数据库里新增了一个provider为sakana_ai的记录前端也会自动生成一个显示为“Sakana AI ”的标签页只要我在_providerMeta里添加了映射。第三步按需加载内容每个标签页对应的内容页面是一个FutureBuilder或StreamBuilder它根据当前选中的provider和category如‘overview’去Supabase查询对应的contentMarkdown文本然后使用flutter_markdown包进行渲染。通过TabBarView的physics属性设置为NeverScrollableScrollPhysics()可以防止用户快速滑动时触发过多的并行网络请求改为根据索引懒加载。4.2 游戏化功能实现积分、连续记录与排行榜学习需要正向反馈。我设计了三个核心的游戏化功能测验积分、连续学习记录和排行榜。1. 测验与积分系统每个提供商的api类别内容后会附带一个简单的测验例如选择题。用户提交答案后前端会计算得分并直接向ai_university_scores表执行upsert操作。-- 该表通过RLS确保用户只能操作自己的记录 CREATE TABLE ai_university_scores ( user_id uuid REFERENCES auth.users NOT NULL, provider text NOT NULL, score int DEFAULT 0, last_updated_at timestamptz DEFAULT now(), UNIQUE (user_id, provider) );在Flutter中操作非常简单await _supabase.from(ai_university_scores).upsert({ user_id: _currentUser.id, provider: currentProvider, score: newScore, // 计算出的新分数 }).execute();得益于RLS策略这个直接来自客户端的操作是安全的。upsert确保了如果用户第一次测验该提供商就插入记录后续再次测验则更新分数。2. 连续学习记录系统这个功能旨在鼓励用户每日登录。逻辑是记录用户最后一次学习浏览任何提供商内容或进行测验的日期。如果本次学习日期与上次记录日期不是同一天则连续天数加1。 我选择在Supabase中创建一个存储过程Stored Procedure来处理这个逻辑因为它涉及读取、判断和更新在数据库端完成更原子、更高效。CREATE OR REPLACE FUNCTION public.increment_streak(user_uuid uuid) RETURNS void LANGUAGE plpgsql SECURITY DEFINER -- 以函数定义者的权限运行绕过RLS进行特定更新 AS $$ DECLARE last_study_date date; current_streak int; BEGIN -- 从用户配置表中获取上次学习日期和当前连续天数 SELECT last_studied, streak INTO last_study_date, current_streak FROM user_profiles WHERE id user_uuid; -- 如果上次学习不是今天则增加连续天数 IF last_study_date IS NULL OR last_study_date CURRENT_DATE THEN UPDATE user_profiles SET streak COALESCE(current_streak, 0) 1, last_studied CURRENT_DATE WHERE id user_uuid; END IF; END; $$;在Flutter端用户完成一次学习行为后调用这个远程过程调用RPC即可await _supabase.rpc(increment_streak, params: {user_uuid: _currentUser.id}).execute();3. 排行榜的实现排行榜需要聚合所有用户的分数。直接在客户端查询ai_university_scores表然后排序计算在用户量多时会非常低效。最佳实践是在数据库端创建一个视图View。CREATE VIEW ai_university_leaderboard AS SELECT user_id, SUM(score) AS total_score, COUNT(DISTINCT provider) AS providers_studied, RANK() OVER (ORDER BY SUM(score) DESC) AS rank FROM ai_university_scores GROUP BY user_id;这个视图实时计算每个用户的总分、学习过的提供商数量并使用窗口函数RANK()进行排名。Flutter端只需要像查询普通表一样查询这个视图结果就是已经排好序的排行榜数据性能极佳。final leaderboardData await _supabase .from(ai_university_leaderboard) .select() .order(rank) .execute();4.3 分享功能将成就可视化为了让用户有更强的分享动力我实现了“学习成就卡片”生成功能。思路是在Flutter中将一个包含用户头像、昵称、总分、排名等信息的Widget渲染成一张图片。关键技术点RepaintBoundary与Web下载包装Widget将需要分享的卡片Widget用RepaintBoundary包裹并赋予一个GlobalKey。RepaintBoundary( key: _shareCardKey, child: ShareCardWidget(userData: userData), // 自定义的卡片组件 )转换为图片使用GlobalKey获取RenderRepaintBoundary对象并将其渲染为ui.Image。final RenderRepaintBoundary boundary _shareCardKey.currentContext!.findRenderObject() as RenderRepaintBoundary; final ui.Image image await boundary.toImage(pixelRatio: 2.0); // pixelRatio提高分辨率 final ByteData? byteData await image.toByteData(format: ui.ImageByteFormat.png); final Uint8List pngBytes byteData!.buffer.asUint8List();触发下载Web端在Flutter Web环境中可以将图片字节转换为Data URL并通过模拟点击一个隐藏的a标签来触发下载。import dart:html as web; // 仅在Web端导入 final base64 base64Encode(pngBytes); final anchor web.AnchorElement() ..href data:image/png;base64,$base64 ..download ai_university_progress.png; anchor.click();对于移动端iOS/Android则需要使用path_provider和image_gallery_saver等包将图片保存到相册。这个功能虽然代码量不大但极大地提升了产品的传播属性和用户的成就感。5. 部署、自动化与效率提升心法一个完整的项目离不开部署和运维。同时如何在3天内完成54个提供商的内容填充是另一个值得分享的效率故事。5.1 部署与自动化提醒应用部署Flutter Web应用构建后可以非常方便地部署到任何静态托管服务如Firebase Hosting、Vercel、Netlify或Cloudflare Pages。我选择了Firebase Hosting因为它与Flutter生态集成良好部署命令简单flutter build web然后firebase deploy。学习提醒自动化为了提升用户留存我实现了一个“学习提醒”系统。通过GitHub Actions的定时任务Cron功能每天在目标用户群体的活跃时间例如东京时间上午9点触发一个工作流。# .github/workflows/daily-reminder.yml name: Daily Study Reminder on: schedule: - cron: 0 0 * * * # UTC时间每天00:00即东京时间09:00 jobs: send-reminders: runs-on: ubuntu-latest steps: - name: Call Reminder Endpoint run: | curl -X POST https://your-project.supabase.co/functions/v1/send_study_reminders \ -H Authorization: Bearer ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }} \ -H Content-Type: application/json这个工作流会调用一个部署在Supabase Edge Functions边缘函数中的send_study_reminders函数。该函数内部逻辑是查询数据库找出最近3到30天内没有学习记录的用户。根据每个用户的历史进度和连续天数生成个性化的提醒消息例如“你的连续学习天数已达5天再学习2天即可获得周常勋章今天来看看Groq的API吧”。通过集成推送通知服务如OneSignal或发送邮件将提醒触达用户。实操心得Edge Functions的选择Supabase Edge Functions基于Deno非常适合这种轻量、事件驱动的后台任务。相比于自己维护一个服务器或使用复杂的云函数服务它的配置更简单与Supabase数据库的交互也更直接。对于发送通知这种可能耗时的操作放在Edge Function中异步执行不会阻塞主应用。5.2 “3天54提供商”的效率秘诀这可能是整个项目中最“硬核”的部分。内容创作是耗时的尤其是要保证54家提供商每家都有overview、models、api三个板块的优质内容。我的秘诀是高度结构化的模板 AI辅助生成 自动化流水线。创建内容模板我为每个提供商的三个板块设计了固定的Markdown模板。overview.md公司背景、核心方向、主要产品。models.md以表格形式列出主要模型名称、上下文长度、关键特点、发布日期。api.mdAPI快速入门指南包括认证方式、一个简单的代码示例如cURL或Python。利用Claude Code进行批量生产我没有手动编写54份内容。而是准备了种子信息公司官网、文档链接然后启动多个Claude Code实例。每个实例负责一个提供商。我给它指令“请根据[官网链接]按照以下模板生成三个Markdown文件。” Claude Code能够自动浏览网页、提取信息并格式化成我需要的模板。严格的文件与流程管理我使用一个本地目录supabase/migrations/来管理所有的SQL种子文件。每个提供商对应一个SQL文件如2024032001_insert_openai_content.sql。Claude Code在生成内容后会直接写入对应的SQL文件。然后我通过Supabase CLI一键运行所有迁移supabase db reset数据就完整地进入了数据库。并行化工作流我同时在3个Claude Code实例上工作每个实例负责不同的提供商集合。一个实例在生成A公司的内容时另一个实例已经在审核B公司生成的内容第三个实例则在编写C公司的API示例。这种“流水线”作业将我的角色从“创作者”转变为“审核与调度员”极大提升了效率。核心避坑技巧AI生成的内容一定要人工复核特别是技术细节和代码示例。我的流程是Claude生成 - 我快速浏览关键数据如模型参数是否准确- 运行SQL迁移 - 在开发中的Flutter应用里实时查看效果。发现问题立即回退修改。永远不要完全信任AI的输出尤其是涉及具体数字和代码时。6. 遇到的问题、解决方案与未来规划在高速开发中不可避免地会遇到各种问题。以下是几个典型问题及我的解决思路。6.1 性能与用户体验优化问题1首次加载白屏时间过长由于应用启动时需要从Supabase查询提供商列表和用户数据在网络不佳时会有明显的等待。解决方案数据预取与缓存使用supabase_flutter包自带的本地缓存功能或集成hive、shared_preferences将提供商列表等不常变的数据在首次加载后缓存到本地。下次启动时先显示缓存数据再在后台静默更新。骨架屏Skeleton Screen在内容加载区域使用骨架屏动画给用户即时反馈降低等待的焦虑感。分页与懒加载对于未来可能非常长的新闻列表实现分页查询而不是一次性拉取所有数据。问题2Web端图片分享功能在移动端不兼容如前所述Web端使用dart:html实现下载但这在移动端编译时会报错。解决方案使用条件导入Conditional Import和平台接口Platform Interface。// 定义一个抽象类 abstract class ShareService { Futurevoid saveImage(Uint8List pngBytes); } // 在Web端的实现 // share_service_web.dart class ShareServiceWeb implements ShareService { Futurevoid saveImage(Uint8List pngBytes) async { // ... 使用dart:html的实现 } } // 在移动端的实现通过MethodChannel调用原生代码 // share_service_mobile.dart class ShareServiceMobile implements ShareService { static const platform MethodChannel(com.example.app/share); Futurevoid saveImage(Uint8List pngBytes) async { try { await platform.invokeMethod(saveImage, {imageBytes: pngBytes}); } catch (e) { print(保存图片失败: $e); } } } // 主文件中通过条件导出决定使用哪个实现这样代码就能根据编译平台自动选择正确的实现。6.2 数据一致性与安全考量问题并发更新下的数据竞争在“连续学习记录”场景中如果用户非常快速地在两个设备上同时进行学习可能会触发两次increment_streak函数调用导致连续天数被错误地增加多次。解决方案利用数据库的事务和更精确的条件判断。在存储过程中可以基于last_studied日期进行更原子化的检查与更新。PostgreSQL的UPDATE ... WHERE语句本身在事务中就是原子的。更稳健的做法是将检查逻辑完全放在数据库函数中就像我之前示例的那样它在一个事务内完成读取、判断和更新可以有效避免竞争条件。安全提醒虽然Supabase的RLS极大地简化了权限管理但必须充分测试每条策略。特别是对于INSERT和UPDATE操作要确保用户不能修改user_id来操作他人数据。对于像“排行榜”这种需要聚合所有用户数据的视图要为其设置单独的RLS策略通常允许已认证用户SELECT但禁止INSERT/UPDATE/DELETE。6.3 项目演进与未来可能的方向这个MVP在3天内验证了核心想法。如果项目继续发展我会优先考虑以下几个方向内容深度与互动性增强交互式代码沙盒在api板块集成一个可运行的代码编辑器如使用Zipper或Replit的嵌入服务让用户可以直接在浏览器里修改和运行调用AI API的代码看到实时结果。视频与图文教程为复杂的API或模型特性制作简短的视频讲解或图文指南。社区贡献开放内容贡献渠道让社区专家可以提交内容更新或新的提供商介绍通过Pull Request流程进行审核合并。学习路径个性化智能推荐根据用户已学习的提供商和测验分数推荐下一个最适合学习的内容。例如学完了OpenAI GPT-4系统可以推荐学习Anthropic Claude并进行对比分析。技能图谱将AI能力标签化如“文本生成”、“图像识别”、“语音合成”为用户生成可视化的技能掌握图谱。技术架构升级离线支持利用Flutter的isar或sembast等本地数据库实现核心学习内容的离线阅读和测验同步则在有网络时进行。实时协作如果引入社交功能如好友一起学习可以利用Supabase的Realtime功能实现学习状态的实时同步。微服务化当用户量和功能复杂度增长到一定程度可以将内容抓取、通知发送、数据分析等后台任务拆分为独立的微服务提高系统的可伸缩性和可维护性。这个项目的核心收获不仅仅是快速构建了一个可用的产品更验证了“Flutter Supabase”这套技术栈对于个人开发者或小团队在追求速度与灵活性时的强大威力。它将我从繁琐的后端开发中解放出来让我能专注于产品逻辑和用户体验。而通过AI工具链的辅助又将内容生产的效率提升了一个数量级。对于有志于快速实现想法的开发者来说这套方法论或许值得一试。