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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 15:18:04