前言
- Express v4.16.0 引入了
express.json()
、express.urlencoded()
中间件,express.json() 可解析json类型的req.body,express.urlencoded() 可解析urlencoded类型的req.body; - Express v4.17.0 又引入
express.raw()
、express.text()
中间件,express.raw() 可解析raw类型的req.body(解析为Buffer), express.text() 可解析text类型的req.body(解析为String); - 上述 express.json()、express.raw()、express.text()、express.urlencoded() 中间件,实际上都是基于
body-parser
中间件封装的; - 上述4种中间件对于处理大部分类型的req.body足够了,但却无法处理multipart类型的req.body,body-parser 官网 也很明确的告诉我们 not handle multipart bodies,而且推荐了用于处理 multipart bodies 的其他中间件,如下图
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);
})
表单仅有文本域信息
.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
.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
.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);
})
上传任意文件
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 将为每个文件设置为一个随机文件名,并且是没有扩展名的
方式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 指定的文件名要保证唯一性,不然上传同名的文件则会直接覆盖,不会有任何提示(示例中通过给文件名+时间戳来一定程度上避免重名情况)
内存存储
使用内存存储方式,文件信息将包含一个 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);
})
过滤条件测试结果如下
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);
}
})
})
测试效果:
方式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);
}
测试效果:
参考资料:
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