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

React+Node.js使用Multer上传文件时req.file返回Undefined求助

Multer处理文件上传时req.file返回undefined的问题修复

问题根源

  1. 前端请求错误:Axios POST请求中,地址、MLS ID等字段被放在了config参数里,而非FormData中,导致后端无法读取这些字段;同时selectedFile初始值设为字符串,不符合文件对象类型,可能引发遍历错误。
  2. 后端处理错误:使用multer.array('file')处理多文件上传时,对应获取文件的属性是req.files而非req.file;路由中先尝试读取req.body,但此时Multer还未解析请求,req.body为空;另外路由文件中使用app.use添加中间件无效,应改用router.use。

修复后的代码

React 代码修改

import React, { useState } from 'react';
import './Properties_Upload.css';
import Navbar from '../../../Components/Navbar/Navbar';
import Footer from '../../../Components/Footer/Footer';
import Axios from 'axios';
import { useNavigate } from 'react-router-dom';

export default function Properties_Upload() {
    const [addressLine, setAddressLine] = useState('');
    const [addressLine2, setAddressLine2] = useState('');
    const [addressCity, setAddressCity] = useState('');
    const [addressState, setAddressState] = useState('');
    const [addressZipCode, setAddressZipCode] = useState('');
    const [mlsID, setMLSID] = useState('');
    // 修正初始值为null,匹配文件对象类型
    const [selectedFile, setSelectedFile] = useState(null);
    const [errorMessage, setErrorMessage] = useState('');
    const navigate = useNavigate();
    
    const propertyInfoHandler = async () => {
        const data = new FormData() 

        // 将所有表单字段添加到FormData
        data.append('addressLine', addressLine);
        data.append('addressLine2', addressLine2);
        data.append('addressCity', addressCity);
        data.append('addressState', addressState);
        data.append('addressZipCode', addressZipCode);
        data.append('mlsID', mlsID);

        // 遍历添加选中的文件
        if (selectedFile) {
            for (let x = 0; x < selectedFile.length; x++) {
                data.append('file', selectedFile[x]);
            }
        }

        Axios.post('http://localhost:3001/property/properties_upload', data)
        .then((response) => {
            navigate('/properties');
            if (response.data.errorMessage){
                setErrorMessage(response.data.message);
            }
        })
        .catch(err => {
            setErrorMessage('上传失败,请重试');
            console.error(err);
        });
    };

    return (
        <>
            <Navbar />
            <div className='propertiesUploadBody'>
                <h1>Properties Upload</h1>
                <div className='propertyInfoFormBody'>
                    <h1>Property Information</h1>
                    <p className='addressLine'> *Property address: <input name='addressLine' placeholder='Address' required autoComplete="off" onChange={(e) => setAddressLine(e.target.value)} /> </p>
                    <p className='addressLine2'> Apartment/Suite/Unit: <input name='addressLine2' placeholder='Apartment/Suite/Unit' autoComplete="off" onChange={(e) => setAddressLine2(e.target.value)} /> </p>
                    <p className='addressCity'> *Property city: <input name='addressCity' placeholder='City' required autoComplete="off" onChange={(e) => setAddressCity(e.target.value)} /> </p>
                    <p className='addressState'> *Property state: <input name='addressState' placeholder='State' maxLength={2} required autoComplete="off" onChange={(e) => setAddressState(e.target.value)} /> </p>
                    <p className='addressZipCode'> *Property zip code: <input type='number' name='addressZipCode' placeholder='Zip Code' required autoComplete="off" onChange={(e) => setAddressZipCode(e.target.value)} /> </p>
                    <p className='propertyMLSID'> MLS ID: <input type='number' name='propertyMLSID' placeholder='MLS ID' required autoComplete="off" onChange={(e) => setMLSID(e.target.value)} /></p>
                </div>
                <div className='propertyFileFormBody'>
                    <h1>Property Image Upload</h1>
                    <p> Upload Images of the property.</p>
                    <input className='uploadFiles' type='file' name='file' multiple required onChange={(e) => setSelectedFile(e.target.files)} />
                </div>
                <h2 style={{color: "red"}}>{errorMessage}</h2>
                <button className='uploadButton' onClick={propertyInfoHandler}>Upload Property</button>
            </div>
            <Footer />
        </>
    )
}

Node.js 代码修改

const express = require('express');
const router = express.Router();
const db = require('../config/db');
const bcrypt = require('bcrypt');
const saltRounds = 10;
const session = require('express-session');
const bodyParser = require('body-parser');
const SqlDbStore = require('express-mysql-session')(session);
const passport = require('passport');
const cookieParser = require('cookie-parser');
const multer = require('multer');

// 改用router.use添加中间件,确保路由层级生效
router.use(cookieParser());
router.use(bodyParser.json());
router.use(bodyParser.urlencoded({ extended: true }));
router.use(passport.session());
router.use(session({
    key: 'session_cookie_name',
    secret: 'session_cookie_secret',
    store: new SqlDbStore({
        host: 'localhost',
        port: 3306,
        user: 'root',
        password: '**************',
        database: '**************',
    }),
    resave: false,
    saveUninitialized: false,
    cookie:{
        maxAge:1000*60*60*24,
        secure: false
    }
}));

const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, './Images');
  },
  filename: function (req, file, cb) {
    cb(null, Date.now() + '-' + file.originalname );
  }
});

const upload = multer({ storage: storage }).array('file');

// 将upload作为中间件前置,确保先解析文件和表单数据
router.post('/properties_upload', upload, (req, res) => {
  // 此时可正确读取req.body和req.files
  const addressLine = req.body.addressLine;
  const addressLine2 = req.body.addressLine2;
  const addressCity = req.body.addressCity;
  const addressState = req.body.addressState;
  const addressZipCode = req.body.addressZipCode;
  const mlsID = req.body.mlsID;
  const rentalID = Math.floor(Math.random()*90000) + 10000;

  // 多文件上传用req.files(数组)而非req.file
  console.log("fileInfo: ", req.files);
  
  // 此处可添加数据库插入逻辑
  // db.query(...)

  return res.status(200).send({ files: req.files, propertyData: req.body });
});

module.exports = router;

关键说明

  • 前端必须把所有需要传递的字段(文件+普通文本)都放入FormData,Axios会自动设置正确的Content-Type: multipart/form-data。
  • 后端使用multer.array('file')时,文件列表存储在req.files(数组);单个文件上传用multer.single('file')才会使用req.file。
  • 路由中需将upload作为中间件放在处理函数前,确保Multer先完成请求解析,之后才能读取req.body和req.files。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 10:45:42