文件上传与存储

处理文件上传与对象存储。

模板包含一个已鉴权的图片上传路由。它将文件写入本地文件系统并返回公开 URL——这是一个可用的起点,但并非持久化存储。请把它当作接入 S3、R2 或 Vercel Blob 的集成点。

提供的能力

  • 一个已鉴权的 POST /api/storage/upload-image 路由。
  • 仅限图片的校验与无冲突的文件名。
  • 为已保存文件返回的 /uploads/... URL。

重要文件

文件作用
src/app/api/storage/upload-image/route.ts上传处理器
public/uploads/文件写入位置(运行时创建)

路由如何工作

src/app/api/storage/upload-image/route.ts
export async function POST(request: Request) {
  const session = await auth.api.getSession({ headers: await headers() });
  if (!session?.user) return /* 401 未授权 */;

  const formData = await request.formData();
  const files = formData.getAll("files");
  // …写入每个文件,返回其 URL
}

逐步说明:

  1. 需要鉴权——路由读取 Better Auth 会话,无用户时返回 401
  2. 多部分表单——读取 files 字段(用 getAll,因此支持多文件),空上传 返回 400
  3. 仅限图片——每一项必须是类型以 image/ 开头的 File,其余一律拒绝。
  4. 安全文件名——扩展名由 MIME 类型映射而来(jpg/png/webp/gif),文件名为 crypto.randomUUID(),因此上传不会冲突也不会暴露原始名称。
  5. 写入并响应——文件写入 public/uploads/,路由返回 { success: true, data: { urls: ["/uploads/<uuid>.<ext>"] } }

持久化问题

本地磁盘在生产环境中不持久。 文件写入 public/uploads,本地开发可用, 但在 Vercel、Serverless 或容器化部署中不会保留——文件系统是临时的、按 实例隔离的。重新部署/扩容时上传会丢失,且无法跨实例共享。请勿原样在生产中 依赖此路由。

接入对象存储

将文件系统写入替换为对象存储 SDK(AWS S3、Cloudflare R2、Vercel Blob 等)。 保留路由的鉴权检查与校验,只替换“写文件 + 构造 URL”这部分:

添加 SDK + 凭据

安装 SDK,并将其凭据作为仅服务端的环境变量添加(见 环境变量)。

替换写入

不再 writeFile(...)public/uploads,而是把缓冲区上传到你的存储桶, 并使用返回的对象 URL。

返回真实 URL

把存储桶/CDN 的 URL 放进路由已有的 urls 数组,这样调用方无需改动。

生产要求

在向真实用户开放上传前,请补充:

  • 大小限制——拒绝过大文件(该路由未限制大小)。
  • 更强校验——校验真实内容类型而非仅凭声明的 MIME,并考虑重新编码图片。
  • 访问策略——按对象决定公开还是签名/私有 URL。
  • 清理——记录删除时清除孤立文件。
  • CDN——通过 CDN 提供以提升性能。
  • 滥用防护——对路由限流并限定到合适的用户范围。

相关页面

On this page