返回博客列表
技术文章Supabase微信小程序实时通信文件存储

实时通信与文件存储

2026年04月🇨🇳 中文

在上一篇文章中,我们为“ProjectX 灵感协作小程序”完成了底层的表结构设计,并写好了发布灵感卡片的云函数。

今天,我们要为这个平台注入视觉和灵魂:允许创作者上传高清的设计草图,并允许团队成员之间开启实时的脑暴讨论室。

这完美对应了 Supabase 的另外两块基石组件:Storage(文件存储)Realtime(实时通信)


第一站:展示你的灵感蓝图(Storage 机制解析)

要分享一个极具创意的想法,几张设计草图是必不可少的。Supabase Storage 是基于 AWS S3 协议构建的对象存储服务,它被抽象为三层结构:

  • Bucket(存储桶):最高层级的容器。分为 Public(公开,如图床)和 Private(私有,如机密合同)。
  • Object(对象):实际存储的文件。
  • URL:访问文件的方式。

对于协作平台的灵感草图,毫无疑问我们要创建一个 Public Bucket(假设命名为 blueprints),因为我们要向所有人展示创意。

1. 突破小程序限制:wx.uploadFile

Supabase 官方提供的 SDK 使用的是 fetch + FormData 的标准 Web 方式来上传文件,但这在微信小程序的特殊 JS 运行环境中是行不通的。

小程序有自己专属的文件上传 API:wx.uploadFile。我们的适配层 (supabase-wechat-stable-v2) 已经在底层做好了极其巧妙的桥接,你在业务层依然可以无缝调用 SDK:

// 选择本地相册图片
const res = await wx.chooseMedia({ count: 1 });
const tempFilePath = res.tempFiles[0].tempFilePath;

// 像调用标准 API 一样直接上传
const { data, error } = await supabase.storage
  .from('blueprints')
  .upload(`sketches/quantum_design.jpg`, tempFilePath, {
    upsert: true
  });

技术内幕:适配层拦截了这个请求,将它转化为了原生的 wx.uploadFile。并极其关键地将 FormData 的 name 参数硬编码为了 "file",因为这是 Supabase 接口强制要求的字段名。

2. 构建严密的文件目录与权限边界

如果所有人都能上传图片到 blueprints Bucket,会不会有人恶意覆盖别人的设计图? 这就是 Storage RLS 发挥威力的地方了。每一个上传的文件,在底层的 storage.objects 表中都有对应的元数据。

推荐的路径命名法是:{user_id}/{timestamp}-{random}.jpg

配合这个路径规则,我们可以写下这样一条无懈可击的安全策略(Policy):

-- 用户只能往自己 UUID 命名的文件夹里塞草图
CREATE POLICY "用户只能管理自己的灵感库"
ON storage.objects FOR INSERT
TO authenticated
WITH CHECK (
  bucket_id = 'blueprints'
  AND (storage.foldername(name))[1] = auth.uid()::text
);

storage.foldername(name) 会把文件路径按 / 切成数组,我们校验数组的第一个元素是不是当前用户的 ID。黑客根本无法把图片写进别人的目录!


第二站:开启实时脑暴(Realtime 架构解析)

协作者看到了精妙的草图,非常有共鸣,马上点击了“参与讨论”。这时候,我们就需要一个极其顺滑的沟通界面。

灵感脑暴实时讨论室 UI 演示 (图:我们期望构建的团队协作实时讨论室)

要实现这样的丝滑体验,靠传统的轮询(每秒去查一次数据库)是不现实的。必须借助 Supabase Realtime

1. Realtime 底层的 WebSocket 协议

Supabase 的 Realtime 并不是无中生有,它底层采用的是大名鼎鼎的 Phoenix Channel 协议。 传统的 WebSocket 是“一根管子全家共用”,容易发生数据混乱;而 Phoenix Channel 在连接之上引入了**“频道(Channel)”**的概念,实现了多路复用。

小程序适配要点:微信的 SocketTask 和标准的浏览器 WebSocket API 完全不同(比如绑定事件是用 task.onMessage 而不是 ws.addEventListener)。我们底层通过构建一个 EventEmitter 事件发射器,硬生生把小程序 Socket 伪装成了标准 WebSocket,这才让 Supabase 的 Realtime SDK 得以顺利在微信里跑起来。

2. 玩转三种实时订阅模式

在一个现代化的协作室里,Supabase Realtime 提供了三种截然不同的神兵利器,请在正确的场合使用它们:

  1. Postgres Changes(监听数据库变更)

    • 原理:服务端通过解析 Postgres 的 WAL(Write-Ahead Log),只要 messages 表里有一条新记录 INSERT,立刻推给客户端。
    • 应用场景接收协作者发来的新消息。讨论记录是必须持久化进数据库的重要资产。
  2. Broadcast(广播)

    • 原理:客户端把消息发给服务端,服务端不进数据库,直接广播给房间里的其他人。
    • 应用场景“队友正在输入...” 的顶部提示。这种稍纵即逝的状态,存进数据库毫无意义,直接广播最快。
  3. Presence(在线状态)

    • 原理:底层采用 CRDT 算法,在分布式网络中同步当前频道有哪些用户,以及他们的附加状态(如昵称、头像)。
    • 应用场景灵感详情页显示“当前有 3 人正在浏览此草案”,或者讨论区顶部显示对方“当前在线/离线”。

避坑指南:在小程序的生命周期中,记得在 onLoadchannel.subscribe(),并在页面注销 onUnload 时一定要调用 channel.unsubscribe()。否则随着用户不断进出不同的讨论页,堆积的孤儿频道会导致严重的内存泄漏!