使用 Supabase Storage 与 Uppy 实现 TUS 协议的可恢复上传实战指南

使用 Supabase Storage 与 Uppy 实现 TUS 协议的可恢复上传实战指南 使用 Supabase Storage 与 Uppy 实现 TUS 协议的可恢复上传实战指南【免费下载链接】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导读本文围绕 examples/storage/resumable-upload-uppy 这份官方示例展开讲解如何用 Uppy浏览器端上传组件库通过 TUS 协议向 Supabase Storage 进行可恢复断点续传上传。读完后你将掌握为什么要用可恢复上传、如何在控制台或 SQL 中准备存储桶与公网写入策略、如何用四个核心变量配置前端、以及 TUS 分块上传背后的断点续传与并发冲突机制。仓库中还包含配套的 resumable-upload-signed-uppy签名 URL 变体与官方文档 apps/docs/content/guides/storage/uploads/resumable-uploads.mdx可作为对照与深化阅读。为什么选择可恢复上传在动手前先判断你的场景是否真的需要可恢复上传。根据 Supabase 官方文档 apps/docs/content/guides/storage/uploads/resumable-uploads.mdx当满足以下任一条件时推荐使用 resumable upload 方案上传大文件文件体积可能超过 6MB网络不稳定移动网络、弱网环境下连接可能随时中断需要进度事件希望向用户展示真实的上传进度条。其底层原理是 Supabase Storage 实现了 TUSThe Upload Server开放协议。该协议的核心价值在于上传被打断后可以从上次中断的字节位置继续而不是从头再来。客户端侧既可以使用tus-js-client这样的底层库也可以使用像 Uppy 这类内置 TUS 支持的上层组件库——本示例正是官方推荐的 Uppy 路线。示例目录结构总览仓库中的 examples/storage/resumable-upload-uppy 目录非常精简只有以下文件examples/storage/resumable-upload-uppy/ ├── README.md # 运行说明 ├── index.html # 纯前端上传页面Uppy Dashboard Tus 插件 ├── supabase/ │ ├── config.toml # 本地/远端项目存储配置 │ └── migrations/ │ └── 20241128121139_storage_rls.sql # 允许公网写入的 RLS 策略 └── supabase-logo-wordmark--dark.png # 页面 Logoindex.html是一个零构建、单文件的浏览器页面通过 CDN 引入 Uppy v3.6.1 的样式与 ES Module无需npm install即可演示完整的 TUS 上传闭环——这大大降低了复现成本。前提准备创建存储桶与放行策略示例假设使用 Supabase 控制台或 SQL完成两步前置工作创建存储桶从 Supabase 控制台的 Storage 页面新建一个 bucket示例中默认叫uploads。添加允许上传的策略为storage.objects表放行公网INSERT官方给出 SQL 如下CREATE POLICY allow uploads ON storage.objects FOR INSERT TO public WITH CHECK (bucket_id your-bucket-name);示例仓库中的迁移文件 examples/storage/resumable-upload-uppy/supabase/migrations/20241128121139_storage_rls.sql 就是这条策略的落地版本bucket 名为uploadsCREATE POLICY allow uploads ON storage.objects FOR INSERT TO public WITH CHECK (bucket_id uploads);配套的 supabase/config.toml 则给出了等价的项目级配置若用 CLI 管理项目可直接对照project_id resumable-upload-uppy [api] # Disable data API since we are not using the PostgREST client in this example. enabled false [storage] # The maximum file size allowed for all buckets in the project. file_size_limit 50MiB [storage.image_transformation] enabled false [storage.buckets.uploads] public true # file_size_limit 50MiB # allowed_mime_types [image/png, image/jpeg] # Uncomment to specify a local directory to upload objects to the bucket. # objects_path ./buckets/uploads其中值得注意的参数含义[api].enabled false本示例不用 PostgREST 数据客户端因此关闭数据 API[storage].file_size_limit 50MiB项目级最大上传体积上限此处为 50MiB该上限作用于项目内所有 bucket[storage.buckets.uploads].public true将uploads桶设为公开注释掉的allowed_mime_types、objects_path表明你还可以按桶限定 MIME 类型或将上传对象落到本地目录以便调试。注意FOR INSERT TO public意味着任何人都可向该桶写入对象。示例仅用于演示生产环境请务必改用更严格的认证如FOR INSERT TO authenticated WITH CHECK (bucket_id uploads AND auth.uid() owner_id)或参考本文第五节的签名 URL 方案。配置四个核心变量打开 index.html将文件顶部的四个常量替换为你自己的值const SUPABASE_PUBLISHABLE_KEY replace-with-your-publishable-key const SUPABASE_PROJECT_ID replace-with-your-project-id const STORAGE_BUCKET replace-with-your-bucket-id const BEARER_TOKENreplace-with-your-bearer-token各变量含义如下变量含义获取方式SUPABASE_PUBLISHABLE_KEY项目的可发布密钥anon/publishable keySupabase 项目 Dashboard → Settings → API KeysSUPABASE_PROJECT_ID项目 refURL 中的子域项目 Settings → General形如abcdxyzSTORAGE_BUCKET存储桶名即上文新建的 bucket 名如uploadsBEARER_TOKEN上传鉴权令牌登录用户会话的access_token或服务角色密钥演示用第 53 行据此拼接出 TUS 上传端点const supabaseStorageURL https://${SUPABASE_PROJECT_ID}.supabase.co/storage/v1/upload/resumable进阶推荐使用直连 Storage 域名官方文档特别提示上传大文件时应优先使用直连存储主机名以享受多项性能优化。即把https://project-id.supabase.co换成https://project-id.storage.supabase.co。对应地TUS 端点应写为const supabaseStorageURL https://${SUPABASE_PROJECT_ID}.storage.supabase.co/storage/v1/upload/resumable从源码读懂 Uppy 的配置整个上传逻辑都封装在 index.html 的一个script typemodule中。我们先实例化 Uppy 并挂载 Dashboard 组件第 55–61 行var uppy new Uppy() .use(Dashboard, { inline: true, limit: 10, target: #drag-drop-area, showProgressDetails: true, })参数作用inline: true以内嵌方式渲染在页面#drag-drop-area元素中而不是弹出弹窗limit: 10同时进行的上传任务并发上限showProgressDetails: true在界面上展示进度明细。接着挂载 TUS 插件第 62–74 行这是与 Supabase 对接的关键.use(Tus, { endpoint: supabaseStorageURL, headers: { authorization: Bearer ${BEARER_TOKEN}, apikey: SUPABASE_PUBLISHABLE_KEY, }, uploadDataDuringCreation: true, chunkSize: 6 * 1024 * 1024, allowedMetaFields: [bucketName, objectName, contentType, cacheControl], onError: function (error) { console.log(Failed because: error) }, })逐项拆解其设计意图endpoint即上文的/storage/v1/upload/resumable地址所有 TUS 请求创建/上传/续传都发往此处headers.authorizationBearer token携带访问令牌让存储服务端校验身份headers.apikey携带项目的 publishable keyanonymity key这是所有对 Supabase 网关请求的标准头uploadDataDuringCreation: true在创建上传Creation请求时就携带首块数据减少一次 HTTP 往返显著加快小文件与首块上传chunkSize: 6 * 1024 * 1024分块大小固定为6MB。这是当前 TUS 客户端与 Supabase Storage 约定必须使用的值官方在 resumable-uploads.mdx 中明确注释“NOTE: it must be set to 6MB (for now) do not change it”allowedMetaFields声明允许随 TUS 上传附带的自定义元数据键服务端据此从元数据中解析对象信息onError上传失败时的回调便于定位问题。在 file-added 阶段注入 Supabase 元数据TUS 协议的上传创建请求需要把“存到哪个桶、对象叫什么、MIME 类型是什么”等信息作为元数据传给服务端。示例在第 76–89 行用file-added事件完成注入uppy.on(file-added, (file) { const supabaseMetadata { bucketName: STORAGE_BUCKET, objectName: folder ? ${folder}/${file.name} : file.name, contentType: file.type, } file.meta { ...file.meta, ...supabaseMetadata, } console.log(file added, file) })注意第 52 行的const folder 若想在桶内建目录可填入子目录前缀例如folder documents此时对象路径变为documents/文件名。如果对照官方文档 apps/docs/content/guides/storage/uploads/resumable-uploads.mdx 中的tus-js-client示例会发现还有两个可选元数据值得了解cacheControl如3600对象缓存控制metadata可传JSON.stringify(...)形式的自定义元数据写入对象的user_metadata字段——注意在 Uppy 路线中需要把它加入allowedMetaFields数组才会被透传。监听上传完成事件最后监听complete事件在全部上传成功后做收尾处理第 91–93 行uppy.on(complete, (result) { console.log(Upload complete! Weve uploaded these files:, result.successful) })result.successful数组中保存本次所有成功上传的文件信息可用它刷新文件列表或跳转详情页。本地启动与验证由于页面完全基于浏览器原生 ES Module 与 CDN 依赖只需一个静态文件服务器即可运行。README 推荐用 Python 内置服务器python3 -m http.server在浏览器打开http://localhost:8000把文件拖入 Dashboard 区域即开始上传。页面中还提供了一条指向官方 Resumable Uploads 文档的链接第 36–38 行。与签名 URL 变体的区别若想避免把 bucket 开放给匿名用户可参考同目录的姊妹示例 examples/storage/resumable-upload-signed-uppy/README.md。它的做法是每个文件在file-added时调用createSignedUploadUrl()换取一次性令牌把令牌放入x-signature请求头完成鉴权从而实现无 RLS 放行策略的受限上传。底层机制URL 时效、并发与覆盖理解以下三条由服务端实现保证的语义有助于把示例改造成生产级代码依据均为官方文档对存储服务的描述每个上传拥有独立的临时 URL服务端会为每次上传创建独立的唯一 URL即使多次上传到同一路径也是如此所有分块通过PATCH方法发送到该 URL。这个 URL最长有效 24 小时超时即失效、需要重新发起上传——TUS 客户端库含 Uppy通常在 URL 过期后自动创建新 URL 继续任务。并发冲突返回 409两个及以上客户端同时向同一个上传 URL 推数据时只有一个能成功其余收到409 Conflict这避免了数据损坏两个客户端用不同 URL 上传同一路径时先完成者胜出后者同样收到409 Conflict只有显式设置x-upsert: true请求头时才改为“后完成者覆盖前者”。覆盖写入的默认行为不设置x-upsert而上传到一个已存在的路径默认返回400 Asset Already Exists。官方建议尽量避免覆盖写CDN 需要时间把变更传播到各边缘节点期间会提供过期内容更推荐写入新路径。如需在 TUS 元数据或请求头中启用 upsert可参照官方文档在headers中加入x-upsert: true。生产化要点小结从这一单文件示例出发落地到真实业务时建议鉴权收敛将演示用的公开写入策略替换为authenticated限定或采用签名 URL 方案域名直连大文件上传使用https://project-ref.storage.supabase.co直连域名提升性能善用断点续传能力Uppy 基于file.id指纹在刷新/断网后继续未完成任务服务端保留上传会话直至 24 小时到期控制分块大小TUS 客户端侧chunkSize保持 6MB 约定值后续版本变化以官方文档为准。需要更系统的协议细节上传 URL 语义、并发规则、x-upsert、签名上传可继续阅读仓库内的官方指南 apps/docs/content/guides/storage/uploads/resumable-uploads.mdx。【免费下载链接】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),仅供参考