文件上传与存储

把图片上传到持久化 Cloudflare R2,并通过 Worker 提供访问。

存储已与 Cloudflare R2 完整接入:登录用户上传到 UPLOADS binding,Worker 读取路由再返回对象。R2 对象不会像本地上传目录那样随构建或 Worker 部署消失; 不过内置访问策略仍是需要产品化加固的示例。

请求流程

POST /api/storage/upload-image
  → 校验 Better Auth session
  → 解析名为 "files" 的 FormData entry
  → 校验数量、MIME 前缀与大小
  → env.UPLOADS.put(生成的 key, body, httpMetadata)
  → { success: true, data: { urls: ["/api/storage/<key>"] } }

GET /api/storage/<key>
  → env.UPLOADS.get(key)
  → 连同保存的 HTTP metadata 与 ETag 流式返回
文件作用
src/routes/api/storage/upload-image.tsSession 校验、multipart 解析、校验响应与上传响应
src/lib/storage/image-upload.ts限制、MIME/扩展名映射、生成 key、R2 写入与 URL 路径
src/routes/api/storage/$key.tsR2 查询与 Worker 代理对象响应
wrangler.jsoncUPLOADS R2 binding 与 bucket 名称
src/lib/storage/image-upload.test.ts校验与写入行为测试

当前每次最多五个文件,每个 5 MB。Key 由 crypto.randomUUID() 加推导扩展名 组成,因此用户文件名不会成为对象路径。上传时把声明的 content type 保存为 R2 HTTP metadata;返回的是应用相对路径,不是 bucket 直链。

配置 R2

创建生产 bucket

pnpm wrangler r2 bucket create soar-tanstack-uploads

也可以使用自己的名称。

绑定到 Worker

应用代码期待 binding 名为 UPLOADS

"r2_buckets": [
	{ "binding": "UPLOADS", "bucket_name": "soar-tanstack-uploads" }
]

UPLOADS 是 binding,不是 secret 或 URL。image-upload.ts$key.tscloudflare:workers 导入 env,并在请求期间读取 env.UPLOADS。不要在模块加载 时初始化 R2 handle。

本地运行

pnpm dev 使用 Cloudflare Vite 插件与 Miniflare 本地 R2 状态。请通过应用本身 测试上传,不要另建本地 public/uploads fallback;后者会测试完全不同的存储模型。

访问策略

创建对象需要有效 session,但 GET /api/storage/$key 当前对知道 key 的任何人 公开。这只适合公开产品图片。私有对象应在 D1 保存所有权/可见性并逐次授权读取, 或通过专门策略层返回短时签名访问。

Worker 代理让 URL 保持在应用 origin,也便于加授权,但每次读取都会消耗 Worker 资源。公开高流量资源可考虑 R2 自定义域名/CDN 与明确缓存 header;需要应用策略的 内容则保留 Worker 路由。

需要补齐的校验

当前只检查 file.type.startsWith("image/") 和大小;不会检查 magic byte、解码 图片、校验尺寸、移除 metadata,也没有把所有可接受 MIME 严格限制在扩展名映射 中。客户端可以伪造 multipart metadata,上线前应增加服务端内容检查。

生产策略还应覆盖:

  • 用户存储配额、请求总大小与上传限流;
  • 对象所有权、读/删/替换授权与审计数据;
  • 防碰撞前缀,或按需使用 tenant/user namespace;
  • 废弃上传清理、保留、删除与备份预期;
  • 缓存 header、content disposition、CSP/content sniffing 与恶意文件检查;
  • 成本监控与滥用响应。

验证

  • 未登录上传返回 401。
  • 5 MB 以下有效图片返回 /api/storage/<generated-key> 且可读取。
  • 缺文件、超过五个文件、非图片 MIME 与超限文件均返回 4xx。
  • 未知 key 返回 404;响应保留 content type 与 ETag。
  • 重建/重新部署 Worker 后对象仍可用。
  • 添加访问策略后,私有对象测试会拒绝未授权读取。
  • 签名/尺寸测试能拒绝只在声明上是图片的内容。

相关页面

On this page