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

如何用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);
  });
}

关键细节说明

  1. 禁用NextJS默认解析:必须设置api.bodyParser: false,否则NextJS会提前读取请求体,导致Formidable无法处理流式上传。
  2. 事件驱动的字段/文件处理:通过field事件先获取formType,再在file事件中处理文件流,确保路径生成所需的参数已就绪。
  3. 无文件落地的流式传输:直接将Formidable提供的文件可读流Pipe到Azure的openWriteStream,全程不生成本地临时文件,节省服务器磁盘资源。
  4. 错误覆盖:同时监听Formidable的解析错误、文件流错误和Azure上传流错误,确保异常场景能正确返回状态码。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 01:30:16