You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助:在Next.js中使用UUID命名文件并上传至S3桶

在Next.js中实现PDF文件上传至S3并使用UUID命名,同时将文件名存入AWS数据库

1. 依赖安装与AWS配置

先安装所需依赖包:

npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner uuid
# 或使用yarn
yarn add @aws-sdk/client-s3 @aws-sdk/s3-request-presigner uuid

在项目根目录创建.env.local文件,配置AWS相关参数(请勿将此文件提交至版本控制):

AWS_ACCESS_KEY_ID=你的AWS访问密钥ID
AWS_SECRET_ACCESS_KEY=你的AWS秘密访问密钥
AWS_REGION=你的S3桶所在区域(如us-east-1)
S3_BUCKET_NAME=你的S3桶名称
DYNAMODB_TABLE_NAME=存储文件名的DynamoDB表名(按需替换)

2. 后端API实现

2.1 生成S3预签名URL(供前端直接上传)

创建pages/api/get-presigned-url.ts:

import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { v4 as uuidv4 } from "uuid";

const s3Client = new S3Client({
  region: process.env.AWS_REGION,
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
  },
});

export default async function handler(req, res) {
  if (req.method !== "POST") {
    return res.status(405).json({ message: "仅支持POST请求" });
  }

  const { fileType } = req.body;
  if (fileType !== "application/pdf") {
    return res.status(400).json({ message: "仅支持PDF文件上传" });
  }

  // 生成UUID作为唯一文件名
  const fileName = `${uuidv4()}.pdf`;

  try {
    const command = new PutObjectCommand({
      Bucket: process.env.S3_BUCKET_NAME,
      Key: fileName,
      ContentType: fileType,
    });
    // 生成有效期15分钟的预签名URL
    const signedUrl = await getSignedUrl(s3Client, command, { expiresIn: 900 });

    return res.status(200).json({ signedUrl, fileName });
  } catch (error) {
    console.error("生成预签名URL失败:", error);
    return res.status(500).json({ message: "生成上传链接失败" });
  }
}

2.2 将文件名存入AWS数据库(以DynamoDB为例)

创建pages/api/save-filename.ts:

import { DynamoDBClient, PutItemCommand } from "@aws-sdk/client-dynamodb";
import { marshall } from "@aws-sdk/util-dynamodb";

const dynamoDbClient = new DynamoDBClient({
  region: process.env.AWS_REGION,
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
  },
});

export default async function handler(req, res) {
  if (req.method !== "POST") {
    return res.status(405).json({ message: "仅支持POST请求" });
  }

  const { fileName } = req.body;
  if (!fileName) {
    return res.status(400).json({ message: "文件名不能为空" });
  }

  try {
    const params = {
      TableName: process.env.DYNAMODB_TABLE_NAME,
      Item: marshall({
        id: fileName.split(".")[0], // 用UUID作为主键
        fileName: fileName,
        uploadTime: new Date().toISOString(),
      }),
    };

    await dynamoDbClient.send(new PutItemCommand(params));
    return res.status(200).json({ message: "文件名存储成功" });
  } catch (error) {
    console.error("存储文件名失败:", error);
    return res.status(500).json({ message: "存储文件名失败" });
  }
}

3. 前端上传组件实现

创建components/PdfUploader.tsx:

import { useState } from "react";

export default function PdfUploader() {
  const [file, setFile] = useState<File | null>(null);
  const [isUploading, setIsUploading] = useState(false);
  const [message, setMessage] = useState("");

  const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const selectedFile = e.target.files?.[0];
    if (selectedFile && selectedFile.type === "application/pdf") {
      setFile(selectedFile);
      setMessage("");
    } else {
      setMessage("请选择有效的PDF文件");
    }
  };

  const handleUpload = async () => {
    if (!file) return;

    setIsUploading(true);
    setMessage("正在生成上传链接...");

    try {
      // 1. 获取预签名URL和UUID文件名
      const presignedRes = await fetch("/api/get-presigned-url", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ fileType: file.type }),
      });
      if (!presignedRes.ok) throw new Error("获取上传链接失败");
      const { signedUrl, fileName } = await presignedRes.json();

      // 2. 上传PDF到S3
      setMessage("正在上传文件到S3...");
      const uploadRes = await fetch(signedUrl, {
        method: "PUT",
        headers: { "Content-Type": file.type },
        body: file,
      });
      if (!uploadRes.ok) throw new Error("文件上传到S3失败");

      // 3. 传递文件名到后端并存入AWS数据库
      setMessage("正在存储文件名...");
      const saveRes = await fetch("/api/save-filename", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ fileName }),
      });
      if (!saveRes.ok) throw new Error("存储文件名失败");

      setMessage("上传及存储完成!");
      setFile(null);
    } catch (error) {
      setMessage((error as Error).message);
    } finally {
      setIsUploading(false);
    }
  };

  return (
    <div className="p-4">
      <input
        type="file"
        accept="application/pdf"
        onChange={handleFileChange}
        disabled={isUploading}
        className="mb-4"
      />
      {file && <p className="mb-2">已选择文件: {file.name}</p>}
      <button
        onClick={handleUpload}
        disabled={isUploading || !file}
        className="px-4 py-2 bg-blue-500 text-white rounded"
      >
        {isUploading ? "上传中..." : "上传PDF"}
      </button>
      {message && <p className="mt-2 text-red-500">{message}</p>}
    </div>
  );
}

4. 关键注意事项

  • 安全防护:禁止在前端暴露AWS凭证,通过后端生成预签名URL的方式实现前端直传S3,避免凭证泄露。
  • 文件校验:前端和后端都需验证文件类型,确保仅PDF文件可上传。
  • 权限配置:需为S3桶配置允许前端域名的PUT请求CORS规则;IAM用户需拥有s3:PutObject和dynamodb:PutItem权限。
  • 错误处理:生产环境可扩展错误处理逻辑,添加重试机制、日志记录等。

内容的提问来源于stack exchange,提问作者Ravikirubakaran S

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 08:45:38