使用Multer上传文件时出现req.file undefined,该如何排查?
问题场景
后端基于Express+Multer实现文件上传接口,前端提交表单后,后端出现req.file为undefined的错误。相关代码如下:
后端路由代码
router.post("/author-image/:id", async (req, res) => { const upload = await Common.upload(req, res); try { await Events.addAuthorImg(req.params.id, upload); return res.json({ status: "ok" }); } catch (error) { console.dir(error); return res.json({ status: "error", error: "Unable to process your request. Please try again later." }); } });
Common.js上传逻辑
const _ = require("underscore"); const UPLOAD_FILE_SIZE_LIMIT = 1024 * 1024 * 40; const ALLOWED_FILE_TYPES = [".png", ".jpg", ".jpeg", ".gif", ".mp3", ".pdf"]; const UPLOAD_FILE_DIR = "./client/public/uploads/"; const multer = require("multer"); const tinify = require("../tinify"); const s3 = require("../s3"); var Q = require('q'); const path = require("path"); const util = require("util"); const fs = require("fs"); const unlinkFile = util.promisify(fs.unlink); const generateRandomId = (length) => { var result = ""; var characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; var charactersLength = characters.length; for (var i = 0; i < length; i++) { result += characters.charAt(Math.floor(Math.random() * charactersLength)); } return result; }; const upload = multer({ storage: multer.diskStorage({ destination: (req, file, callBack) => { callBack(null, UPLOAD_FILE_DIR); // './public/images/' directory name where save the file }, filename: (req, file, callBack) => { callBack( null, generateRandomId(8) + Date.now() + path.extname(file.originalname) ); }, }), fileFilter: function (req, file, callback) { var ext = path.extname(file.originalname); console.dir(file.path); if (ALLOWED_FILE_TYPES.indexOf(ext) === -1) { return callback("Only images are allowed."); } callback(null, true); }, limits: { fileSize: UPLOAD_FILE_SIZE_LIMIT, }, }); module.exports = { upload: async (req, res) => { var defer = Q.defer(); const uploadSingle = upload.single("file"); uploadSingle(req, res, async (err) => { if (err) { if (err instanceof Error) { err = err.message; } defer.reject(err); } console.dir(req.file); if (!req.file) { defer.reject("Please provide a file to upload."); } else { const result = await s3.process( req.file, process.env.AWS_ACCESS_KEY_ID, process.env.AWS_SECRET_ACCESS_KEY, process.env.AWS_BUCKET_REGION, process.env.S3_BUCKET ); // You may apply filter, resize image before sending to client // Deleting from local if uploaded in S3 bucket await unlinkFile(req.file.path); defer.resolve(result.Location); } }); return defer.promise; } };
前端表单代码
<form onSubmit={(ev) => this.handleSubmit(ev)} name="form" method="POST" enctype="multipart/form-data"> <input type="text" name="name" /> </form>
排查与解决方法
补全前端文件输入框:当前表单里只有文本输入,没有文件上传控件。必须添加
type="file"的输入框,且name属性要和后端upload.single("file")的参数一致,也就是name="file"。修改后的表单:<form onSubmit={(ev) => this.handleSubmit(ev)} name="form" method="POST" enctype="multipart/form-data"> <input type="text" name="name" /> <input type="file" name="file" /> </form>修复fileFilter中的错误代码:在
fileFilter函数里,console.dir(file.path)这行有问题——Multer在fileFilter阶段还没生成文件路径,file.path此时不存在,会抛出错误导致文件上传中断。直接删掉这行代码,或者改成打印file.originalname这类存在的属性。完善错误处理逻辑:在
uploadSingle的回调里,当err存在时,要立即终止后续代码执行,避免继续检查req.file。修改后的代码:uploadSingle(req, res, async (err) => { if (err) { if (err instanceof Error) { err = err.message; } return defer.reject(err); // 加return,阻止后续代码运行 } console.dir(req.file); if (!req.file) { defer.reject("Please provide a file to upload."); } else { // 后续S3上传等逻辑 } });验证表单提交逻辑:如果前端用AJAX提交(比如fetch、axios),要确保正确创建
FormData并添加文件。示例:async function handleSubmit(ev) { ev.preventDefault(); const formData = new FormData(ev.target); // 确认formData里包含file字段 console.log([...formData.keys()]); // 应该能看到'file' const response = await fetch(`/author-image/${yourId}`, { method: 'POST', body: formData }); }检查上传目录权限:确保后端配置的
UPLOAD_FILE_DIR(./client/public/uploads/)目录存在,且Node.js进程有读写权限。如果目录不存在,手动创建;权限不足的话,用chmod调整权限。验证文件大小与类型:确认上传的文件大小不超过
UPLOAD_FILE_SIZE_LIMIT(40M),且后缀在ALLOWED_FILE_TYPES列表里。如果文件不符合要求,Multer会拒绝上传,导致req.file为undefined,此时要在错误处理里捕获对应的错误信息。
内容的提问来源于stack exchange,提问作者Rue Vitale

