MERN项目使用multer上传PDF时出现Cannot read property 'path' of undefined错误
MERN栈PDF上传报错:
Cannot read property 'path' of undefined 排查方案 问题背景
在MERN栈项目中,前端上传PDF文件时触发Cannot read property 'path' of undefined错误,后端通过Postman测试可正常上传文件并将路径存入数据库,问题出在前端代码逻辑中。
相关代码片段
后端路由
const express = require("express"); const { upload } = require("../middlewares/uploadMiddleware"); const { registerResearchSheet } = require("../controllers/researchSheetControllers"); const router = express.Router(); router.route("/").post(upload.single("file"), registerResearchSheet);
后端上传中间件
const multer = require("multer"); const storage = multer.diskStorage({ destination: function (req, file, cb) { cb(null, "backend/uploads/"); }, filename: function (req, file, cb) { cb( null, new Date().toISOString().replace(/:/g, "-") + "-" + file.originalname ); }, }); const fileFilter = (req, file, cb) => { if (file.mimetype === "application/pdf") { cb(null, true); } else { cb(null, false); } }; const upload = multer({ storage: storage, fileFilter: fileFilter }); module.exports = { upload };
后端控制器
const asyncHandler = require("express-async-handler"); const ResearchSheet = require("../models/researchSheetModel"); const registerResearchSheet = asyncHandler(async (req, res) => { const { title, director, coordinator, students, reviewers, proposalType, investigationType, status, } = req.body; if (!req.file) { res.status(400); throw new Error("The file hasn't be uploaded correctly"); // 触发错误的位置 } const file = req.file.path; const researchSheetExists = await ResearchSheet.findOne({ title, students }); if (researchSheetExists) { res.status(400); throw new Error("Title or students can't be repeated"); } const researchSheet = await ResearchSheet.create({ title, file, director, coordinator, students, reviewers, proposalType, investigationType, status, }); if (researchSheet) { res.status(201).json({ _id: researchSheet._id, title: researchSheet.title, file: researchSheet.file, director: researchSheet.director, coordinator: researchSheet.coordinator, students: researchSheet.students, reviewers: researchSheet.reviewers, proposalType: researchSheet.proposalType, investigationType: researchSheet.investigationType, status: researchSheet.status, }); } else { res.status(400); throw new Error("An error has ocurred"); } });
前端提交逻辑
const [file, setFile] = useState(null); const singleFileUpload = (e) => { setFile(e.target.files[0]); }; const submitHandler = async (e) => { e.preventDefault(); const formData = new FormData(); formData.append("title", title); formData.append("file", file); formData.append("director", director); formData.append("coordinator", coordinator); formData.append("students", students); formData.append("proposalType", proposalType); formData.append("investigationType", investigationType); console.log(file); // 此处能正确打印文件信息 if ( !title || !coordinator || !students || !proposalType || !investigationType ) { setMessage("You must fill all the fields"); } else { dispatch(registerResearchSheetAction(formData)); // history.push("/listresearchsheets"); } }; // 表单部分 <Form.Control className="mb-3" type="file" accept=".pdf" onChange={(e) => singleFileUpload(e)} /> <Button type="submit">Register</Button>
Redux Action代码
export const registerResearchSheetAction = ( title, file, director, coordinator, students, reviewers, proposalType, investigationType, status ) => async (dispatch) => { try { dispatch({ type: RESEARCHSHEET_REGISTER_REQUEST, }); const formData = new FormData(); formData.append("file", file); formData.append("title", title); formData.append("director", director); formData.append("coordinator", coordinator); formData.append("students", students); formData.append("reviewers", reviewers); formData.append("proposalType", proposalType); formData.append("investigationType", investigationType); formData.append("status", status); const config = { headers: { "Content-Type": "multipart/form-data", }, }; const { data } = await axios.post( "/api/researchsheets", formData, config ); dispatch({ type: RESEARCHSHEET_REGISTER_SUCCESS, payload: data }); } catch (error) { dispatch({ type: RESEARCHSHEET_REGISTER_FAIL, payload: error.response && error.response.data.message ? error.response.data.message : error.message, }); } };
错误原因
前端submitHandler已经正确组装了包含文件的FormData,并将其传给registerResearchSheetAction,但Action的参数定义却是多个单独字段(title、file等),导致:
- Action接收的第一个参数是前端传来的
FormData,而非预期的title,后续参数全为undefined - Action里重新组装的
FormData中,file字段值为undefined,后端multer无法解析到文件,因此req.file为undefined,触发报错
另外,手动设置Content-Type: multipart/form-data会丢失axios自动生成的boundary分隔符,也会导致后端无法正确解析FormData。
修复方案
步骤1:修改Redux Action的参数接收逻辑
直接接收前端传来的FormData,不再拆解为单独参数:
export const registerResearchSheetAction = (formData) => async (dispatch) => { try { dispatch({ type: RESEARCHSHEET_REGISTER_REQUEST, }); // 无需重新组装FormData,直接使用传入的实例 const config = { headers: { // 移除手动设置的Content-Type,axios会自动处理 "Content-Type": "multipart/form-data", }, }; const { data } = await axios.post( "/api/researchsheets", formData, config ); dispatch({ type: RESEARCHSHEET_REGISTER_SUCCESS, payload: data }); } catch (error) { dispatch({ type: RESEARCHSHEET_REGISTER_FAIL, payload: error.response && error.response.data.message ? error.response.data.message : error.message, }); } };
步骤2:前端补充字段校验
在submitHandler中增加文件非空校验,避免空文件提交:
if ( !title || !coordinator || !students || !proposalType || !investigationType || !file // 新增文件校验 ) { setMessage("You must fill all the fields and select a PDF file"); } else { dispatch(registerResearchSheetAction(formData)); }
步骤3(可选):优化Action的请求头设置
完全移除手动设置的Content-Type,让axios自动处理,确保boundary正确:
const config = { headers: {}, };
内容的提问来源于stack exchange,提问作者Jorge Parra
相关产品推荐
相关产品推荐

