Multer自定义上传中间件校验不通过仍保存文件如何解决?
问题原因
你当前的校验逻辑执行时机晚于multer的文件写入操作:使用diskStorage时,multer会在文件上传接收完成后就自动写入到指定的uploads目录,你后续的格式、大小校验是在文件已经落盘之后才运行的,所以即便校验不通过,不符合要求的文件也已经被保存了。
推荐解决方案:使用multer内置的前置校验
multer自带fileFilter(文件类型校验)和limits(大小限制)配置项,会在文件写入磁盘之前执行校验,不符合要求的文件直接不会被保存,也不需要你后续手动清理。
另外你当前的multer实例是全局创建的,无法适配不同上传场景的filetypes、fileSize自定义参数,需要把multer实例的创建移到uploadFile工厂函数内部,修改后的完整代码如下:
const { check, validationResult } = require("express-validator") const multer = require('multer') const mime = require('mime-types') const fs = require('fs') // 磁盘存储配置可以复用 const storage = multer.diskStorage({ destination: function (req, file, cb) { cb(null, 'uploads/') }, filename: function (req, file, cb) { const filename = Date.now()+"-"+file.originalname cb(null, filename) } }) const uploadFile = (fieldname,filetypes,fileSize)=>{ return (req,res,next)=>{ // 针对当前上传场景动态创建multer实例 const upload = multer({ storage: storage, // 大小限制,单位字节,超限制会自动抛出MulterError limits: { fileSize: fileSize }, // 文件类型前置校验 fileFilter: (req, file, cb) => { const ext = mime.extension(file.mimetype) if(filetypes.includes(ext)){ // 校验通过,接受文件 cb(null, true) }else{ // 校验不通过,拒绝文件,同时挂载错误信息到req req.fileError = { param: "image", msg: `Only ${filetypes.toString()} allowed` } cb(null, false) } } }).single(fieldname) upload(req,res,function(err){ if (err instanceof multer.MulterError) { // 大小超出等multer内置错误 req.fileError = { param: "image", msg: err.code === 'LIMIT_FILE_SIZE' ? `File size should not exceed ${formatBytes(fileSize)}` : "Unable to process request" } } else if (err) { req.fileError = { param: "image", msg: "Unknown upload error" } } // 无论是否有错误都继续后续逻辑 next() }) } } const course_validator = [ check("name") .trim() .isLength({min:3,max:100}) .withMessage("Course name should be between 3 to 100 characters") ] // 注意:文件上传需要用POST请求,GET请求没有请求体无法传输文件 app.post("/create/post",uploadFile("image",["jpeg","jpg"],122880),(req,res)=>{ const errors = validationResult(req) if(!errors.isEmpty() || req.fileError){ return res.json({ status: false, error: req.fileError ? [...errors.array(),req.fileError] : errors.array() }) } // 校验全部通过,业务逻辑 res.json({status: true, msg: 'Upload success'}) })
轻量修改方案(不改动现有结构)
如果你不想调整现有逻辑,可以在校验不通过的分支手动删除已经落盘的文件:
// 引入fs模块 const fs = require('fs') // 在校验不通过的分支增加删除逻辑 else if (filetypes.includes(mime.extension(req.file.mimetype)) === false) { req.fileError = { param: "image", msg: `Only ${filetypes.toString()} allowed` } // 手动删除已保存的文件 fs.unlink(req.file.path, () => {}) return next() } else if (req.file.size > fileSize) { req.fileError = { param: "image", msg: `File size should not exceed ${formatBytes(fileSize)}` } // 手动删除已保存的文件 fs.unlink(req.file.path, () => {}) return next() }
额外注意事项
你原代码中路由用了app.get,文件上传必须用POST/PUT等支持请求体的请求方法,否则无法正常接收文件。
内容的提问来源于stack exchange,提问作者Gnanavel
相关产品推荐
相关产品推荐

