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

使用Next.js API作为中间件转发Multipart/FormData文件上传请求失败问题求助

问题排查与解决方案

从你的代码来看,直接客户端发送Multipart/FormData请求正常,但通过Next.js中转后失败,核心问题大概率是重新构建FormData时文件元信息不完整,导致后端无法正确解析文件。下面是具体的排查点和修复方案:

主要问题点分析

  1. 文件元信息缺失:你在添加文件到新FormData时,只传入了文件读取流,但没有提供原始文件名和MIME类型。后端服务器需要这些信息来正确识别和处理文件。
  2. 无效HTTP状态码:错误返回使用了700,这不是标准的HTTP状态码,应该使用500表示服务器内部错误,避免客户端解析异常。
  3. Session空值风险:没有判断session或backendToken是否存在,若会话失效会导致Authorization头无效,引发后端认证错误。
  4. 字段处理逻辑漏洞:原代码用固定数组遍历字段,可能遗漏客户端传入的其他字段,且未处理formidable解析后可能出现的数组类型字段(多同名字段场景)。

修复后的完整代码

import axios from "axios";
import formidable from "formidable";
import FormData from "form-data";
import type { NextApiRequest, NextApiResponse } from "next";
import { getSession } from "next-auth/react";
import fs from "fs"; // 改为顶部静态导入,符合TS规范

export const config = {
  api: {
    bodyParser: false,
  },
};

export default async (req: NextApiRequest, res: NextApiResponse) => {
  try {
    const session = await getSession({ req });

    // 提前校验会话有效性
    if (!session?.backendToken) {
      return res.status(401).json({ message: "Unauthorized: No valid session" });
    }

    // 解析客户端传来的FormData
    const { fields, files } = await new Promise<{ fields: any; files: any }>(
      (resolve, reject) => {
        const form = new formidable.IncomingForm({
          keepExtensions: true, // 保留文件扩展名,便于后端识别文件类型
        });
        form.parse(req, (err, fields, files) => {
          if (err) reject(err);
          resolve({ fields, files });
        });
      }
    );

    // 重新构建FormData
    const formData = new FormData();
    // 处理所有文本字段(兼容单/多字段场景)
    Object.entries(fields).forEach(([key, value]) => {
      const fieldValue = Array.isArray(value) ? value[0] : value;
      formData.append(key, fieldValue);
    });

    // 处理所有文件字段(兼容单/多文件场景)
    Object.entries(files).forEach(([key, file]) => {
      const fileList = Array.isArray(file) ? file : [file];
      fileList.forEach((item) => {
        formData.append(key, fs.createReadStream(item.filepath), {
          filename: item.originalFilename, // 传递原始文件名给后端
          contentType: item.mimetype, // 传递文件MIME类型
        });
      });
    });

    // 发送请求到后端API
    const axiosConfig = {
      method: "post",
      url: `${process.env.API_BASE_URL}/blogs/`,
      headers: {
        Authorization: `Bearer ${session.backendToken}`,
        ...formData.getHeaders(), // 自动生成带boundary的Content-Type,避免手动拼接出错
      },
      data: formData,
    };

    await axios(axiosConfig);
    res.status(200).json({ message: "Successfully added blog" });
  } catch (error: any) {
    console.error("Forward request failed:", error); // 打印错误日志,便于排查
    res.status(500).json({ 
      message: "Failed to add blog", 
      error: error.message || "Unknown error" 
    });
  }
};

关键修改说明

  1. 补全文件元信息:在formData.append文件时,通过第三个参数传入filename和contentType,确保后端能识别文件的原始属性。
  2. 优化字段/文件处理:使用Object.entries遍历所有字段和文件,同时处理单字段/多字段、单文件/多文件的场景,避免遗漏或重复。
  3. 增加Session校验:提前判断会话有效性,避免无效请求发送到后端,同时返回标准的401未授权状态码。
  4. 修正错误状态码:将错误返回改为500,并添加详细错误信息和日志,便于定位问题。
  5. 标准化导入:将fs改为顶部静态导入,符合TypeScript编码规范。

额外排查建议

  • 检查后端服务器的文件接收配置,确保允许通过中转的FormData请求接收文件。
  • 在Next.js API中添加日志,打印重新构建的FormData字段信息(文件流除外),确认字段和文件信息是否正确。
  • 测试时可以用Postman直接调用Next.js API,对比客户端直接调用后端的请求头和体,找出差异。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 01:04:11