如何用Formidable流式上传至Azure Blob Storage(NextJS非App目录)
如何用Formidable将文件流式上传至Azure Blob Storage(无需临时文件存储)
核心思路
要跳过本地临时文件存储,关键是利用Formidable的事件监听机制替代默认的文件落地逻辑,将客户端上传的文件流直接Pipe到Azure Blob的上传流中,全程不生成本地文件。
完整TypeScript实现(NextJS非App目录)
import type { NextApiRequest, NextApiResponse } from 'next'; import { BlobServiceClient, BlockBlobClient } from '@azure/storage-blob'; import formidable from 'formidable'; import { env } from '../../../env.mjs'; // 替换为你的环境变量导入路径 // 必须禁用NextJS默认的请求体解析,让Formidable处理流式上传 export const config = { api: { bodyParser: false, }, }; // 保留你原有的动态路径生成逻辑 function createDynamicPath(formType: string, filename: string): string { return `${formType}/${Date.now()}-${filename}`; } export default async function handler(req: NextApiRequest, res: NextApiResponse) { if (req.method !== 'POST') { return res.status(405).json({ error: '仅支持POST请求' }); } // 初始化Azure Blob客户端 const blobServiceClient = BlobServiceClient.fromConnectionString( env.AZURE_STORAGE_CONNECTION_STRING ); const containerClient = blobServiceClient.getContainerClient(env.AZURE_CONTAINER_NAME); // 可选:如果容器不存在则创建 await containerClient.createIfNotExists(); let formType: string | null = null; const uploadedBlobs: { url: string; filename: string }[] = []; // 用Promise包裹Formidable的事件监听,适配NextJS的异步处理 return new Promise<void>((resolve, reject) => { const form = formidable({ multiples: true }); form // 先捕获formType字段值,用于生成Blob路径 .on('field', (name, value) => { if (name === 'formType') { formType = value as string; } }) // 处理每个上传的文件 .on('file', async (_, file) => { if (!formType || !file.originalFilename) { reject(new Error('请求参数无效:缺少formType或文件名')); return; } // 创建Blob客户端并获取可写流 const dynamicPath = createDynamicPath(formType, file.originalFilename); const blockBlobClient = containerClient.getBlockBlobClient(dynamicPath); const uploadStream = blockBlobClient.openWriteStream({ blobHTTPHeaders: { blobContentType: file.mimetype || undefined, // 保留文件原MIME类型 }, }); // 将Formidable的文件可读流直接Pipe到Azure的上传流 file.pipe(uploadStream); // 监听流的完成/错误事件 await new Promise((streamResolve, streamReject) => { uploadStream.on('finish', streamResolve); uploadStream.on('error', streamReject); file.on('error', streamReject); }); uploadedBlobs.push({ url: blockBlobClient.url, filename: file.originalFilename, }); }) // 所有字段和文件处理完成 .on('end', () => { if (!formType) { res.status(400).json({ error: '缺少formType字段' }); resolve(); return; } res.status(200).json({ message: '文件上传成功', blobs: uploadedBlobs, }); resolve(); }) // 捕获解析或上传过程中的错误 .on('error', (err) => { console.error('上传错误:', err); res.status(500).json({ error: '文件上传失败' }); reject(err); }) // 开始解析请求 .parse(req); }); }
关键细节说明
- 禁用NextJS默认解析:必须设置
api.bodyParser: false,否则NextJS会提前读取请求体,导致Formidable无法处理流式上传。 - 事件驱动的字段/文件处理:通过
field事件先获取formType,再在file事件中处理文件流,确保路径生成所需的参数已就绪。 - 无文件落地的流式传输:直接将Formidable提供的文件可读流Pipe到Azure的
openWriteStream,全程不生成本地临时文件,节省服务器磁盘资源。 - 错误覆盖:同时监听Formidable的解析错误、文件流错误和Azure上传流错误,确保异常场景能正确返回状态码。
内容的提问来源于stack exchange,提问作者boparai
相关产品推荐
相关产品推荐

