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

Next.js中Cloudinary API单图上传提交时出现400错误

问题分析与修复方案

核心错误点

1. 上传的是Data URL而非原始文件对象

图片选择组件中,你用FileReader把文件转成Data URL存在image状态,但上传时将这个字符串传给了FormData。Cloudinary需要接收原始文件对象,而非base64编码字符串,这是导致400请求错误的核心原因。

2. Cloudinary路由文件获取方式错误

使用express-fileupload中间件时,文件会被存在req.files对象里,而非req.file,后端因此无法获取到上传的文件。

3. 创建文章逻辑缺失且存在语法错误

createProductHandler中上传图片后未调用创建文章的API,还存在未定义的data变量以及重复的router.push。


具体修复步骤

步骤1:修改图片选择组件,保存原始文件对象

无需将文件转成Data URL,直接保存原始文件,同时生成预览用的Data URL:

// 图片选择组件页面 - 组件顶部新增状态
const [previewImage, setPreviewImage] = useState("");

// 修改handleImages函数
const handleImages = (e) => {
  const file = e.target.files[0];
  // 保存原始文件对象
  setImage(file);
  // 生成预览图的Data URL
  const reader = new FileReader();
  reader.readAsDataURL(file);
  reader.onload = (e) => {
    setPreviewImage(e.target.result);
  };
};

// 修改handleRemove函数
const handleRemove = () => {
  setImage(null);
  setPreviewImage("");
};

// 修改预览图渲染部分
{!previewImage ? (
  <img src="../../../images/no_image.png" alt="" />
) : (
  <div className={styles.images__main_grid_wrap}>
    <div className={styles.blur}></div>
    <img src={previewImage} alt="" />
    <div className={styles.images__main_grid_actions}>
      <button onClick={handleRemove}>
        <RiDeleteBin7Fill />
      </button>
      <button>
        <RiShape2Line />
      </button>
    </div>
  </div>
)}

步骤2:修复创建文章页面的上传逻辑

上传时传递原始文件对象给FormData,并补充创建文章的API调用:

// 创建文章页面 - 修改createProductHandler函数
const createProductHandler = async () => {
  try {
    setLoading(true);
    let uploaded_image = null;

    if (image) {
      const path = "product images";
      let formData = new FormData();
      formData.append("path", path);
      // 传递原始文件对象
      formData.append("file", image);
      uploaded_image = await uploadImages(formData);
    }

    // 调用创建文章的API
    const createRes = await axios.post("/api/product", {
      ...product,
      image: uploaded_image?.url,
      public_id: uploaded_image?.public_url,
    });

    setLoading(false);
    toast.success(createRes.data.message);
    router.push("/admin/dashboard/product/all");
  } catch (error) {
    setLoading(false);
    toast.error(error.response?.data?.message || "创建失败");
  }
};

步骤3:修复Cloudinary路由的文件获取逻辑

从req.files中获取文件(对应FormData里的key"file"):

// Cloudinary API路由处理页面 - 修改router.post逻辑
router.post(async (req, res) => {
  try {
    const { path } = req.body || {};
    // 从req.files获取上传的文件
    const file = req.files?.file;
    if (!file) {
      return res.status(400).json({ message: "未选择文件" });
    }

    const img = await uploadToCloudinaryHandler(file, path);
    removeTmp(file.tempFilePath);
    res.json(img);
  } catch (error) {
    console.log(error);
    return res.status(500).json({ message: error.message });
  }
});

步骤4:完善文章创建API,添加图片字段

确保News模型包含image和public_id字段,然后修改路由:

// 文章创建API路由处理页面 - 修改router.post逻辑
router.post(async (req, res) => {
  try {
    db.connectDb();
    const slug = slugify(req.body.name);
    const newProduct = new Product({
      ...req.body,
      slug,
      image: req.body.image,
      public_id: req.body.public_id,
    });
    await newProduct.save();
    res.status(200).json({ message: "文章创建成功" });
    db.disconnectDb();
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 14:05:55