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

MERN-Stack项目带文件的文章上传功能异常问题求助

问题根因

一共4个核心问题触发你遇到的两类报错:

  • 手动设置Content-Type: multipart/form-data时没有携带自动生成的boundary分隔符,直接触发Multipart: Boundary not found错误。axios识别到FormData类型请求时会自动填充带正确boundary的Content-Type,手动写死配置会覆盖这个自动生成的逻辑。
  • 前端提交时把FormData实例额外包了一层普通对象,导致axios收到的不是原生FormData,无法自动识别请求类型、生成正确请求头,这也是移除手动Content-Type配置后返回403的核心原因。
  • multer存储配置字段名写错:multer diskStorage配置接收的参数是全小写的filename,你写的是驼峰格式fileName,会导致multer无法正确处理上传文件、触发解析异常。
  • multer配置的文件存储路径写了相对路径,服务运行时很容易因为工作目录变化找不到目标文件夹,触发文件写入失败。
分步修复方案

1. 修复前端提交逻辑

不要把FormData包裹在额外的普通对象里,直接把FormData实例传给action,同时修正fileData的初始值避免无效参数提交:

// fileData初始值设为null,不要设空对象
const [fileData, setFileData] = useState(null)
const fileInput = useRef(null);
const fileChange = (e)=>{
  console.log(fileInput.current.files);
  setFileData(fileInput.current.files[0])
}

const onSubmit = (e)=>{
  e.preventDefault();
  const data = new FormData();
  // 判断选中文件后再追加,避免提交无效值
  if(fileData instanceof File){
    data.append("img", fileData);
  }
  data.append("ressort", formdata.ressort);
  data.append("theme", formdata.theme);
  data.append("title", formdata.title);
  data.append("content", formdata.content)
  // 直接传入FormData实例,不要包裹{data: data}结构
  dispatch(createMainNews(data));
}

注意:不要尝试调用getHeaders()方法,该方法是Node端旧版第三方form-data库的API,浏览器原生FormData、新版axios都没有内置这个方法,手动调用必然报「不是函数」的错误。

2. 修复Redux service层请求配置

删掉手动写死的Content-Type配置,让axios自动识别FormData类型生成带正确boundary的请求头:

const API_URL = "http://localhost:5000/api/mainNews/";
const createMainNews = async (mainnewsData, token)=>{
  const config = {
    headers: {
      // 只保留鉴权token头即可,不需要手动配置Content-Type
      token: `Bearer ${token}`,
    },
  }
  // 此处mainnewsData就是直接传入的FormData实例
  const response = await axios.post(API_URL, mainnewsData, config);
  return response.data;
}

3. 修复Multer存储配置

把写错的fileName字段改成正确的filename,同时用绝对路径配置存储目录,避免工作目录变化导致路径匹配失败:

const multer = require("multer");
const path = require("path");

const storage = multer.diskStorage({
  destination:(req,file, callback)=>{
    // 用path.resolve拼接绝对路径,根据自己的项目目录调整层级即可,提前确保目标文件夹真实存在
    callback(null, path.resolve(__dirname, '../../frontside/public/uploads/'))
  },
  // 字段为全小写filename,不是驼峰格式
  filename: (req, file, callback)=>{
    callback(null, Date.now()+ "--"+ file.originalname)
  }
})

const upload = multer({storage:storage});

module.exports = upload;

4. 优化后端接口错误处理

原代码直接抛出403没有返回具体错误信息,不利于排查问题,同时调整文件名字段取值避免重名文件覆盖:

router.post("/", upload.single("img"), verifyTokenAndAuthorization, async (req,res)=>{
  try{
    // 提前校验必填项
    if(!req.file){
      return res.status(400).json({message: "请上传封面图片"});
    }
    const newMainNews = new MainNews({
      // 取multer生成的带时间戳的文件名,不要用用户上传的原始文件名,避免重名覆盖
      img: req.file.filename,
      ressort: req.body.ressort,
      theme: req.body.theme,
      title: req.body.title,
      content:req.body.content,
    });
    const savedMainNews = await newMainNews.save();
    res.status(200).json(savedMainNews);
  } catch(error){
    res.status(500).json({message: "文章发布失败", error: error.message})
  }
});
验证要点

修改完成后可以在浏览器开发者工具的网络面板检查上传请求:

  • 请求头里的Content-Type会自动携带boundary=----xxxx格式的随机分隔符
  • 请求Payload为FormData格式,可以看到img文件和其他文本字段
  • 后端配置的uploads文件夹可以正常写入上传的文件

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 23:16:30