Unified Platform Host

对象存储与直传上传

presigned 直传到 Cloudflare R2,开发环境使用本地 Provider。

App API 的上传流程集成了真实对象存储:客户端向服务端申请 presigned URL, 把文件字节直接上传到存储,然后确认。文件内容永远不经过 Next.js 进程, 生产文件保存在 Cloudflare R2——而不是应用服务器的磁盘上。

提供的能力

  • StorageProvider 抽象与两个实现:R2(生产)和本地磁盘(仅开发)。
  • POST /api/app/v1/uploads——校验类型/大小后,为服务端生成的对象 Key 签发直传 URL。
  • POST /api/app/v1/uploads/complete——确认对象存在、执行大小上限,返回可用 URL。幂等。
  • 按用户划分的 Key 前缀,用于归属校验和删除账户时的清理。

重要文件

文件作用
src/server/app/uploads/uploadsService.ts校验、Key 生成、归属、complete
src/server/app/uploads/storage/StorageProvider.tsProvider 接口
src/server/app/uploads/storage/r2StorageProvider.tsCloudflare R2(S3 兼容)Provider
src/server/app/uploads/storage/localStorageProvider.ts仅开发环境的本地 Provider
src/app/api/app/v1/uploads/local/route.ts仅开发环境的 presigned PUT 目标(生产返回 404)

流程

申请 presigned URL

客户端发送 contentTypesize 和可选的 filename。服务端按白名单校验 MIME 类型(jpeg/png/webp/gif,用于头像图片)和 5 MB 大小上限,然后在 uploads/<userId>/… 下生成不可预测的对象 Key——客户端永远无法指定 Key。

直接上传到存储

响应包含 uploadUrlmethod 和客户端必须原样携带的 headers。R2 的 presigned PUT 会把 Content-TypeContent-Length 绑定进签名,上传 与声明不符的类型或更大的对象会在存储层直接失败。URL 5 分钟过期。

调用 complete 确认

携带 objectKey 调用 POST /uploads/complete。服务端校验 Key 属于当前用户 (前缀检查——他人的 Key 返回 404,绝不暗示是否存在)、读取对象元数据、若 超限则删除对象,最后返回公开 URL。只有确认过的对象才对业务可见。

选择 Provider

STORAGE_PROVIDER 决定实现——并且没有静默 fallback

取值行为
local(默认)通过仅开发环境的 /uploads/local 路由写入 public/ 下。生产环境拒绝启动并给出明确错误。
r2Cloudflare R2。缺少 R2_* 配置会在首次使用时抛出明确的配置错误。

R2 配置

STORAGE_PROVIDER=r2
R2_ACCOUNT_ID=
R2_ACCESS_KEY_ID=
R2_SECRET_ACCESS_KEY=
R2_BUCKET=
R2_PUBLIC_BASE_URL=https://cdn.example.com

R2_PUBLIC_BASE_URL 是用于构建对象 URL 的公开基址(自定义域名或 r2.dev)。在 Cloudflare 控制台创建 Bucket、对其具有对象读写权限的 API Token,以及公开的自定义域名。

孤儿对象清理交给 Bucket 生命周期规则。 已签发但从未确认的上传会留下 无引用的对象。请配置 R2 生命周期规则(例如:uploads/ 下创建数天后仍未被 应用确认的对象自动删除),而不是在应用里加清理任务。删除账户会按前缀清理 该用户的全部对象——见账户删除

旧的 Web 路由 POST /api/storage/upload-image(见 文件上传与存储)仍写入本地磁盘,只适合开发阶段。 需要持久化上传时请使用本页的 App API 流程——它才是接入 R2 的那条链路。

相关页面

本页目录