求助:在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
相关产品推荐
相关产品推荐

