express 处理 multipart/form-data 类型请求

前言

  1. Express v4.16.0 引入了 express.json()express.urlencoded() 中间件,express.json() 可解析json类型的req.body,express.urlencoded() 可解析urlencoded类型的req.body;
  2. Express v4.17.0 又引入 express.raw()express.text() 中间件,express.raw() 可解析raw类型的req.body(解析为Buffer), express.text() 可解析text类型的req.body(解析为String);
  3. 上述 express.json()、express.raw()、express.text()、express.urlencoded() 中间件,实际上都是基于 body-parser 中间件封装的;
  4. 上述4种中间件对于处理大部分类型的req.body足够了,但却无法处理multipart类型的req.body,body-parser 官网 也很明确的告诉我们 not handle multipart bodies,而且推荐了用于处理 multipart bodies 的其他中间件,如下图
    body-parser官网介绍,不支持处理multipart类型的body

multer 安装

这里我选择 multer中间件来处理 multipart/form-data类型数据。其主要用于上传文件,它是写在 busboy 之上非常高效。
multer 详细介绍及使用方式请参考:multer 官网

在应用项目中执行npm命令,安装multer中间件。

$ npm install multer

multer 使用

1. 基本使用

Multer 中间件会在express的request对象中添加 body、file、files 等对象,其中 req.body 中包含文本域信息,req.file、req.files 对象包含表单上传的文件信息。

const express = require("express");
const multer = require("multer");
const router = express.Router();
const upload = multer();

router.post('/This_is_router_path', upload.'中间件方法', function (req, res) {
    
    
  // req.body 获取文本域信息
  // req.file、req.files 获取上传的文件信息
})
module.exports = router;

接下来做详细的使用介绍 ↓↓↓(为了方便介绍,示例代码只给出重点部分)

.none() 仅文本域信息

// .none() 不需要参数,使用该中间件的路由,只处理表单的文本域信息,保存在req.body
// 如果任何文件上传到这个模式,将发生"LIMIT_UNEXPECTED_FILE"错误
router.post("/upload/none", upload.none(),function (req,res){
    
    
    let ret = {
    
    
        "text_field": req.body,
        "file_field": req.file
    }
    res.send(ret);
})

表单仅有文本域信息
.none() 仅处理文本域信息

.single() 单文件上传

// .single(fieldname) 接受一个以 fieldname 命名的文件,文件信息保存在 req.file
// 如果上传的文件多于1个,或者上传的文件名与fieldname不匹配,将发生"LIMIT_UNEXPECTED_FILE"错误
// 不指定filedname,则 .single() 等效于 .none()
router.post("/upload/single",upload.single('afile'),function (req,res){
    
    
    let ret = {
    
    
        "text_field": req.body,
        "file_field": req.file	// req.file 是一个对象
    }
    res.send(ret);
})

上传1个文件,filename为afile
.single() 处理单个文件上传及文本域信息

.array() 多文件上传

// .array(fieldname[, maxCount]) 接受一个以 fieldname 命名的文件数组,maxCount 来限制上传的最大数量,文件信息保存在 req.files
// 如果上传的文件多于maxCount限定个数,或者上传了与fieldname不匹配的文件,将发生"LIMIT_UNEXPECTED_FILE"错误
// 不指定fieldname或maxCount=0,则与 .none()等效
router.post("/upload/array",upload.array("afile",2),function (req,res){
    
    
    let ret = {
    
    
        "text_field": req.body,
        "file_field": req.files		//req.files 是一个数组,每个元素是一个文件信息对象
    }
    res.send(ret);
})

上传多个文件,filename为afile
.array() 多文件上传

.fields() 多文件上传

// .fields(fields) 接受指定 fields 的混合文件。
// fields 是对象数组,具有 name 和可选的 maxCount 属性,文件信息保存在 req.files
// 如果某name的文件上传的个数多于对应maxCount限定个数,或者上传了未指定name的文件,将发生"LIMIT_UNEXPECTED_FILE"错误
// .fields([]) 与 .none() 等效,注意如果[]也不传,则报路由找不到
const upFields = upload.fields([{
    
     name: 'afile', maxCount: 2 }, {
    
     name: 'bfile', maxCount: 1 }]);
router.post("/upload/fields",upFields,function (req,res){
    
    
    let ret = {
    
    
        "text_field": req.body,
        "file_field": req.files	//req.files 是一个对象,键是文件名,值是文件数组
    }
    res.send(ret);
})

上传多个文件,fileds为2个afile、1个bfile
在这里插入图片描述

.any() 多文件上传

// .any() 不需要参数,使用该中间件的路由,可接受一切上传的文件,文件数组将保存在 req.files
// 未上传文件,则 req.files为[],不会报错
router.post("/upload/any",upload.any(),function (req,res){
    
    
    let ret = {
    
    
        "text_field": req.body,
        "file_field": req.files	//req.files 是一个数组,每个元素是一个文件信息对象
    }
    res.send(ret);
})

上传任意文件
.any() 多文件处理

2. 上传控制

上边在做文件上传示例讲解时,实例化multer()并没有传参。
实际上 multer(options) 可接受一个 options 对象,来控制上传的文件
options 对象及作用:

Key Description
dest or storage 在哪里存储文件
fileFilter 文件过滤器,控制哪些文件可以被接受
limits 限制上传的数据
preservePath 保存包含文件名的完整文件路径

2.1 文件存储

multer 支持两种文件存储方式:磁盘存储、内存存储

磁盘存储

方式1:通过 dest 指定存储路径

const upload = multer({
    
    dest:__dirname+"/../../upload"});
// 为了避免命名冲突,Multer 将为每个文件设置为一个随机文件名,并且是没有扩展名的

dest 指定文件上传路径

方式2:通过 storage 指定存储路径

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

const upload = multer({
    
     storage: storage })
// destination、filename 都是用来确定文件存储位置的函数
// destination 用来指定文件存储位置,指定的文件夹必须由用户自己创建,如果不找不到这个文件夹,则上传文件时会报路由找不到
// destination 如果不设置,则会使用系统默认的临时文件夹
// filename 用来给上传的文件重命名,如果不设置filename,则 Multer 将为每个文件设置为一个随机文件名,并且是没有扩展名的
// filename 指定的文件名要保证唯一性,不然上传同名的文件则会直接覆盖,不会有任何提示(示例中通过给文件名+时间戳来一定程度上避免重名情况)

storage 指定文件上传路径

内存存储

使用内存存储方式,文件信息将包含一个 buffer 字段,里面包含了整个文件数据

const storage = multer.memoryStorage()
const upload = multer({
    
     storage: storage })
// 等效于 const upload = multer(),上边基本使用示例里就是用的不传任何参数的方式,可以看到不传任何参数时,默认使用了内存存储方式
// 注意:使用内存存储时,如果上传非常大的文件,或者非常多的小文件,可能会导致应用程序内存溢出

使用内存存储方式

2.2 文件过滤

Multer 通过 fileFilter 来设置一个函数来控制什么文件可以上传以及什么文件应该跳过

const filter = (req,file,cb) => {
    
    
    // 回调函数cb,通过boolean值来指示是否应接受该文件
    // 拒绝这个文件,使用`false`,cb(null, false)
    // 接受这个文件,使用`true`,cb(null, true)
    // 当然你也可以选择抛一个错误,cb(new Error('err_msg'))
    let ext = path.extname(file.originalname);
    req.refused_files = [];
    const refused_ext = [".png"];
    if(refused_ext.includes(ext)){
    
    
    	// return cb(new multer.MulterError("LIMIT_UNEXPECTED_FILE",file.fieldname));
        return cb(null,false,req.refused_files.push(file));
    }
    return cb(null,true);
}
const upload = multer({
    
     fileFilter: filter });

// 这里 /upload/any 返回结果加一个refused_files,来测试上边的过滤配置是否生效
router.post("/upload/any",upload.any(),function (req,res){
    
    
    let ret = {
    
    
        "text_field": req.body,
        "file_field": req.files,
        "refused_files": req.refused_files
    }
    res.send(ret);
})

过滤条件测试结果如下
fileFilter 对上传文件进行过滤处理

2.3 大小限制

使用 limits 对象,指定一些数据大小的限制

Key Description Default
fieldNameSize field 名字最大长度 100 bytes
fieldSize field 值的最大长度 1MB
fields 非文件 field 的最大数量 无限
fileSize 在 multipart 表单中,文件最大长度 (字节单位) 无限
files 在 multipart 表单中,文件最大数量 无限
parts 在 multipart 表单中,part 传输的最大数量(fields + files) 无限
headerPairs 在 multipart 表单中,键值对最大组数量 2000
// 数据大小限制
const limits = {
    
    
    "fileSize":1024 * 10,	//限制上传文件大小为10kb
    "files":2				//限制文件上传个数最多2个
}
const upload = multer({
    
    "limits":limits});
// 后边会讲超过限制时,如何捕获报错信息

文件大小超过限制报错信息 LIMIT_FILE_SIZE
文件大小超过限制报错
上传文件数量超过限制报错 LIMIT_FILE_COUNT
上传文件数量超过限制报错

2.4 完整路径

Multer通过 preservePath 来控制是否保存包含文件名的完整文件路径

const upload = multer({
    
     dest:__dirname+"/../../upload", preservePath:true});

说明:preservePath 设置 true 或 false 好像没啥区别。。。有知道的欢迎评论区留言

3. 异常处理

当遇到一个错误,multer 将会把错误发送给 express
在这里插入图片描述
如果你想捕捉 multer 发出的错误,你可以自己调用中间件程序,可以使用 multer 对象下的 MulterError 类。
这里整理了两种捕获multer错误的方式。

方式1

在路由的常规代码中,在使用multer中间件时进行错误处理,示例代码如下

const upFields = upload.fields([{
    
    "name":"afile","maxCount":2},{
    
    "name":"bfile","maxCount":1}]);
router.post("/upload/fields",function (req,res){
    
    
    upFields(req,res,function (err){
    
    
        if(err){
    
    res.send(err);}
        else{
    
    
            let ret = {
    
    "text_field": req.body,"file_field": req.files}
            res.send(ret);
        }
    })
})

router.post("/upload/any",function (req,res){
    
    
    upload.any()(req,res,function (err){
    
    
        if(err){
    
    res.send(err);}
        else{
    
    
            let ret = {
    
    "text_field": req.body,"file_field": req.files,"refused_files": req.refused_files}
            res.send(ret);
        }
    })
})

测试效果:
使用multer中间件时 异常捕获
使用multer中间件时 异常捕获

方式2

方式1需要在每个路由代码中分别进行错误捕获,这样如果路由较多就会有重复代码。其实在路由之前可以通过使用 app.use() 或 router.use() 对错误进行统一处理,示例如下

router.post("/upload/none", upload.none(),function (req,res){
    
    
    let ret = {
    
    "text_field": req.body, "file_field": req.file}
    res.send(ret);
})

// 对multer错误及其他未知错误进行统一处理
router.use(function (err, req, res, next) {
    
    
    if(err instanceof multer.MulterError){
    
    
        // A Multer error occurred when uploading.
        res.status(500).send(err);
    }else if(err){
    
    
        // An unknown error occurred.
        res.status(500).send(err);
    }
    next();
});

注意:router.use()错误处理函数 要放在所有router.METHOD 之后;同样的,app.use()错误处理函数,也需要放到app.use(require(“./routes/multer_demo”)) 路由导入函数后

测试效果:
在这里插入图片描述

主动抛multer错误

在定义 fileFilter 文件过滤函数时,默认在用户上传了不允许的文件后直接过滤掉了,不会报错。假如想要在用户上传了不允许的文件类型时,抛出multer错误,那么就可以在定义 fileFilter 文件过滤函数时,通过 multer.MulterError 主动抛一个 LIMIT_UNEXPECTED_FILE 错误,代码如下:

const filter = (req,file,cb) => {
    
    
    let ext = path.extname(file.originalname);
    req.refused_files = [];
    const refused_ext = [".png"];
    if(refused_ext.includes(ext)){
    
    
        const mError = new multer.MulterError("LIMIT_UNEXPECTED_FILE", file.fieldname);
        // 默认的message内容为Unexpected field,这里我自定义一个message
        mError.message = "Not allowed file type " + ext;
        return cb(mError);
        // return cb(null,false,req.refused_files.push(file));
    }
    return cb(null,true);
}

测试效果:
主动抛出MulterError
参考资料:
multer 错误类型
https://www.npmjs.com/package/multer#error-handling

multer 使用手册
https://github.com/expressjs/multer/blob/master/doc/README-zh-cn.md

multer 异常处理
https://www.5axxw.com/questions/content/00bwmd
https://www.coder.work/article/4009424

猜你喜欢

转载自blog.csdn.net/B11050729/article/details/129351341