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

如何将FormData中的多文件转为JS对象并正确发送API请求?

解决FormData文件上传时数据丢失与后端报错问题

问题核心现象

  1. 构建包含productId和多张图片的FormData后,控制台打印能看到完整数据,但转成普通对象Object.fromEntries(formData.entries())后,图片数组变成空对象
  2. 自定义的fetchWrapper会将请求体转为JSON格式,导致后端返回400错误:提示"productId must be a string"和"productId should not be empty",但payload中明明存在合法的productId
  3. 即使修正后能在控制台看到formDataObj中的文件,请求payload仍丢失文件数据

错误原因分析

  1. File对象无法被JSON序列化:JSON.stringify处理File对象时会直接转为空对象{},导致文件数据完全丢失
  2. 请求格式不匹配:文件上传必须使用multipart/form-data格式,但当前代码强制设置Content-Type: application/json,并且将FormData转为JSON发送,后端无法解析正确的表单数据
  3. 代码笔误:原循环中误用了未定义的files和file变量,正确参数应为photo

修复步骤

1. 修正FormData构建的笔误

将循环中的files改为参数photo,file[i]改为photo[i],确保图片正确添加到FormData中

2. 直接发送FormData实例,不转普通对象

放弃Object.fromEntries转换,直接将FormData作为请求body发送,因为FormData是浏览器专门用于文件上传的格式

3. 修改fetchWrapper适配FormData请求

当body是FormData类型时,不要设置Content-Type: application/json,也不要执行JSON.stringify,浏览器会自动生成正确的multipart/form-data请求头(包含boundary)

修复后完整代码

修正后的addProductImage函数

import client from "@/lib/fetchWrapper";

export default async function addProductImage({
  photo,
  productId,
}: {
  photo: File[];
  productId: string;
}) {
  if (!productId || typeof productId !== "string") {
    throw new Error("productId must be a non-empty string.");
  }

  if (!photo || photo.length === 0) {
    throw new Error("No image files found.");
  }

  try {
    const formData = new FormData();
    formData.append("productId", productId);

    // 修正循环变量,正确添加图片
    for (let i = 0; i < photo.length; i++) {
      formData.append('images[]', photo[i]);
    }

    // 验证FormData内容
    formData.forEach((value, key) => {
      console.log(key, value);
    });

    const response = await client("v1/product-image", {
      method: "POST",
      body: formData, // 直接发送FormData
      // 不要手动设置Content-Type,让浏览器自动处理
    });

    return response;
  } catch (error) {
    console.error("Error uploading product images:", error);
    throw error;
  }
}

修正后的fetchWrapper

export default function client(
  endpoint: string,
  { body, ...customConfig }: any = {}
) {
  const token =
    localStorage.getItem("ecommerceToken") ||
    sessionStorage.getItem("ecommerceToken");
  
  // 初始化headers,默认只设置Authorization,不强制Content-Type
  const headers: Record<string, string> = { Authorization: "" };
  if (token) {
    headers.Authorization = `Bearer ${token}`;
  }

  const config = {
    method: body ? "POST" : "GET",
    ...customConfig,
    headers: {
      ...headers,
      ...customConfig.headers,
    },
  };

  if (body) {
    // 如果body是FormData,直接赋值,不做JSON序列化
    if (body instanceof FormData) {
      config.body = body;
    } else {
      // 普通JSON请求才做序列化并设置Content-Type
      config.headers["content-type"] = "application/json";
      config.body = JSON.stringify(body);
    }
  }

  return fetch(`${process.env.NEXT_PUBLIC_API_URL}/${endpoint}`, config).then(
    async (response) => {
      if (response.ok) {
        return await response.json();
      } else {
        const error = await response.json();
        return Promise.reject(new Error(error?.message));
      }
    }
  );
}

验证要点

  1. 发送请求前,控制台打印的FormData应包含productId和所有图片文件
  2. 在浏览器开发者工具的Network面板中,查看请求的Content-Type应为multipart/form-data; boundary=----WebKitFormBoundaryXXXX(由浏览器自动生成)
  3. 查看请求Payload,应能看到productId字段和每个图片的文件信息(文件名、大小等)
  4. 后端将能正确解析productId和图片文件,不再返回400错误

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 15:18:11