在上上一期博文中,我们一起学习了如下几个重点知识,在此向大家罗列:
如果以上重点知识还未掌握的同学,请先掌握以上知识,再观看本期的知识点,以达到更好的学习效果。观看上期文章,请点击下方链接
XMLHttpRequest 链接地址:https://blog.csdn.net/abraham_ly/article/details/113526496
那么下面进入今天的学习主题,今天我们要学习一个新的Node.js中间件,即 Multer
中间件,主要用于是实现文件上传的功能。
Multer :是Node.js中的一个第三方包,或者说是第三方中间件。它用于解析或者说处理 multipart/form-data
类型的表单数据,该中间件主要用于文件上传功能的实现。但是 Multer 不会处理任何非 multipart/form-data
类型的表单数据。
所有,要想使用该中间件,则必须先将表单数据转换为 FormData对象,这样 Multer 中间件才有办法解析或处理 FormData 对象。
打开Node.js终端,切换到当前或者其他目录,输入并执行以下命令,安装最新版本的 multer中间件:
npm install multer
在安装、导入并使用multer中间件后,Multer 会添加一个 body
对象以及 file
或 files
对象到express
模块的request(请求)
对象中。然而 body
对象包含表单的文本域信息,即除input:file外的文本域
,file
或 files
对象包含对象表单上传的文件信息,即 input:file
内的文件
1、基本使用方法
// 导入express模块
var express = require('express')
// 导入multer中间件
var multer = require('multer')
// 设置文件上传的地址,即文件路径
var upload = multer({ dest: 'uploads/' })
// 创建服务器
var app = express()
// app.请求类型(路由地址,上传时的文件名称,路由处理函数)
app.post('/profile', upload.single('avatar'), function (req, res, next) {
// req.file 是 `avatar` 文件的信息
// req.body 将具有文本域数据,如果存在的话
})
// app.请求类型(路由地址,上传时的文件名称和上传的最大数量,路由处理函数),对文件的最大上传数量做限制
app.post('/photos/upload', upload.array('photos', 12), function (req, res, next) {
// req.files 是 `photos` 文件数组的信息
// req.body 将具有文本域数据,如果存在的话
})
// 创建混合文件,这里是两个文件,采用对象数组的形式存储每一个文件,并且设置了上传文件的名称和上传的最大数量,如果一次性上传多个文件,则需要使用这种方式
var cpUpload = upload.fields([{ name: 'avatar', maxCount: 1 }, { name: 'gallery', maxCount: 8 }])
app.post('/cool-profile', cpUpload, function (req, res, next) {
// req.files 是一个对象 (String -> Array) 键是文件名,值是文件数组
//
// 例如:
// req.files['avatar'][0] -> File
// req.files['gallery'] -> Array
//
// req.body 将具有文本域数据,如果存在的话
})
2、如果需要处理一个只有文本域的表单(不包含文件),应当使用.none()
// 导入express模块
var express = require('express')
// 创建服务器
var app = express()
// 导入multer中间件
var multer = require('multer')
// 设置文件上传地址无
var upload = multer()
// 全局的路由中间件
app.post('/profile', upload.none(), function (req, res, next) {
// req.body 包含文本域
})
每个文件都应该具有以下信息:
Key(键) | Description(描述) | Note(注意) |
---|---|---|
fieldname |
Field name 由表单指定 | |
originalname |
用户计算机上的文件的名称 | |
encoding |
文件编码 | |
mimetype |
文件的 MIME 类型 | |
size |
文件大小(字节单位) | |
destination |
保存路径 | DiskStorage(磁盘存储引擎) |
filename |
保存在 destination 中的文件名 |
DiskStorage |
path |
已上传文件的完整路径 | DiskStorage |
buffer |
一个存放了整个文件的 Buffer |
MemoryStorage(内存存储引擎) |
multer(options)
构造函数
Multer
接受一个 options 对象,其中最基本的是dest
属性,这将告诉Multer
将上传文件保存到什么地方。如果省略了 options 对象,这些文件将保存在内存之中,永远不会写入磁盘。
为了避免命名冲突,Multer
会修改上传上传的文件名。这个冲重命名功能可以根据您的需要定制。
以下是可以传递给 multer
的选项:
Key(键) | Description(描述) |
---|---|
dest or storage |
在哪里存储文件 |
fileFilter |
文件过滤器,控制哪些文件可以被接受 |
limits |
限制上传的数据 |
preservePath |
保存包含文件名的完整文件路径 |
通常,一般的网页应用,只需要设置dest
属性即可,像这样:
var upload = multer({ dest: 'uploads/' });
如果你想在上传时进行更多的控制,那么你可以使用storage
代替dest
。 Multer 具有 DiskStorage(磁盘存储引擎)
和MemoryStorage(内存存储引擎)
两个存储引擎;另外还可以从第三方获得更多的可用引擎。
.single(fieldname)
接受一个以fieldname
命名的文件。这个文件的信息保存在req.file
中。
// app.请求类型(路由地址,上传时的文件名称,路由处理函数)
app.post('/profile', upload.single('avatar'), function (req, res, next) {
// req.file 是 `avatar` 文件的信息
// req.body 将具有文本域数据,如果存在的话
})
.array(fieldname[,maxCount])
接受一个以fieldname
命名的文件数组,可以配置maxCount
来限制上传的最大数量,这些文件的信息保存在req.files
中。
// app.请求类型(路由地址,上传时的文件名称和上传的最大数量,路由处理函数),对文件的最大上传数量做限制
app.post('/photos/upload', upload.array('photos', 12), function (req, res, next) {
// req.files 是 `photos` 文件数组的信息
// req.body 将具有文本域数据,如果存在的话
})
.fields(fields)
接受指定的fields
的混合文件,这些文件的信息保存在req.files
中,fields
应该是一个对象数组,应该具有name
属性和可选的maxCount
属性。
Example:
var cpUpload = upload.fields([{ name: 'avatar', maxCount: 1 }, { name: 'gallery', maxCount: 8 }])
app.post('/cool-profile', cpUpload, function (req, res, next) {
// req.files 是一个对象 (String -> Array) 键是文件名,值是文件数组
//
// 例如:
// req.files['avatar'][0] -> File
// req.files['gallery'] -> Array
//
// req.body 将具有文本域数据,如果存在的话
})
.none()
只接受文本域。如果任何文件上传到这个模式,将发生 “LIMIT_UNEXPECTED_FILE” 错误。这和upload.fields([])
的效果是一样的。
// 全局的路由中间件
app.post('/profile', upload.none(), function (req, res, next) {
// req.body 包含文本域
})
.any()
接受一切上传的文件。文件数组将保存在req.files
中。
// 全局的路由中间件
app.post('/profile', upload.none(), function (req, res, next) {
// req.files 包含文件数组
// req.body 包含文本信息
})
⚠ 警告:确保你总是处理了用户的文件上传。请永远不要将 multer
中间件作为全局中间件使用,因为恶意用户可以上传文件到一个你无法预料的路由之中,那么就应该在你需要处理文件上传的路由上使用
// 在单独的路由上去使用multer中间件
router.post("/add", upload.single("conpicimage"), articleHandler.addArticle);
DiskStorage
)磁盘存储引擎可以让你控制文件的存储。
// 配置磁盘存储引擎的选项
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, '/tmp/my-uploads')
},
filename: function (req, file, cb) {
cb(null, file.fieldname + '-' + Date.now())
}
})
// 使用磁盘存储引擎
var upload = multer({ storage: storage })
有两个选项可用,destination
和filename
。它们都是用来确定文件存储位置的函数。
destination:是用来确定上传的文件应该存储在哪个文件夹中,也可以提供一个String字符串(例如'tmp/uploads'
)。如果没有设置destination
,则使用操作系统默认的临时文件夹。
注意 ❗:如果你提供的destination
是一个函数,你需要负责创建文件夹。当提供一个字符串,multer中间件 将确保这个文件夹是你创建的。
filename:用于确定文件夹中的文件名的确定。如果没有设置,每个文件将设置一个随机文件名,并且是没有扩展名的。
注意 ❗:
req.body
可能还没有完全填充,这取决于客户端发送字段和文件到服务器的顺序MemoryStorage
)内存存储引擎将文件存储在内存中的 Buffer 对象中,它没有任何选项
// 配置内存存储引擎
var storage = multer.memoryStorage()
// 使用内存存储引擎
var upload = multer({ storage: storage })
当使用内存存储引擎,文件信息将包含一个 buffer 字段,里面包含了整个文件数据。
警告⚠ :当你使用内存存储,上传非常大或者非常小的文件,都会导致你的应用程序内存溢出。
设置一个函数,用来控制什么文件可以上传,什么文件应该跳过,这个函数应该看起来像这样:
function fileFilter (req, file, cb) {
// 这个函数应该调用 `cb` 用boolean值来
// 指示是否应接受该文件
// 拒绝这个文件,使用`false`,像这样:
cb(null, false)
// 接受这个文件,使用`true`,像这样:
cb(null, true)
// 如果有问题,你可以总是这样发送一个错误:
cb(new Error('I don\'t have a clue!'))
}
如果上传文件时,遇到一个异常错误,我们需要捕获multer抛出的异常,可以使用multer对象下的 MulterError类(即err instanceof multer.MulterError
),异常捕获代码如下:
var multer = require('multer')
// 注意❗:当进行multer异常捕获时,single函数一定要写在外边,如果不进行异常捕获,那么可以像上文一样直接挂载在路由上
var upload = multer().single('avatar')
app.post('/profile', function (req, res) {
upload(req, res, function (err) {
if (err instanceof multer.MulterError) {
// 发生错误
} else if (err) {
// 发生错误
}
// 一切都好
})
})
// 导入express模块
const express = require("express");
// 导入cors模块
const cors = require("cors");
// 导入uploads路由模块,单个文件上传的模块
const R_upload = require("./route/upload");
// 导入uploads路由模块,多个文件上传的模块
const R_uploads = require("./route/uploads");
// 创建服务器
const app = express();
// 配置跨域请求的cors中间件
app.use(cors());
// 使用可以解析表单数据的中间件
app.use(express.urlencoded({ extended: false }));
// 使用R_upload模块,单个文件上传的路由
app.use("/api", R_upload);
// 使用R_uploads模块,多个文件上传的路由
app.use("/apis", R_uploads);
// 监听并开启服务器
app.listen(8024, "127.0.0.1", () => {
console.log("127.0.0.1:8024 服务器已成功开启!");
});
// 导入express模块
const express = require("express");
// 导入路径模块
const path = require("path");
// 创建路由对象
const router = express.Router();
// 导入multer模块
const multer = require("multer");
// 配置磁盘存储引擎
const storage = multer.diskStorage({
// 设置文件上传的路径
destination: function(req, file, cb) {
// 使用path模块拼接路径
cb(null, path.join(__dirname + "./../files/upload"));
},
// 设置文件名称
filename: function(req, file, cb) {
// 获取当前的时间戳
let name = new Date().getTime();
// 以.分割原始文件名称,并返回两个数组
let extend = file.originalname.split(".");
// 通过for循环获取数组的最后一个元素,即原始文件的扩展名
for (let i = extend.length - 1; i < extend.length; i++) {
// 记录并替换扩展名
extend = extend[i];
// 跳出循环
break;
}
// 拼接文件名称和扩展名,生成文件
let filename = name + "." + extend;
// 设置文件的名称
cb(null, filename);
}
});
// 使用磁盘存储引擎
const upload = multer({
// 使用磁盘存储引擎
storage: storage
// 可接受的文件名称
}).single("photo");
// POST请求,并上传一个以 photo 命名的文件
router.post("/upload", upload, (req, res) => {
// 如果请求的file对象为空,或者file对象中的fieldname字段名不等于photo,那么就证明没有选择照片或者选择的照片名称不是指定的
if (!req.file || req.file.fieldname !== "photo") {
// 响应状态码和提示信息
return res.send({
status: 1,
msg: "照片未选择"
});
}
// 捕获multer引起的异常
upload(req, res, function(err) {
// 判断是否是multer模块内部引擎的异常
if (err instanceof multer.MulterError) {
return res.send({
status: 1,
msg: "文件上传出现异常,请稍后再试!"
});
// 判断是否是其他异常
} else if (err) {
return res.send({
status: 1,
msg: "服务器异常,请联系管理员!"
});
}
// 一切都好
return res.send({
status: 0,
msg: "文件上传成功!"
});
});
});
// 共享路由模块
module.exports = router;
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>单个文件上传title>
<link rel="stylesheet" href="./assets/layui-v2.5.7/css/layui.css">
head>
<body style="padding: 20px; background-color: #f2f2f2;">
<div class="layui-main">
<div class="layui-card">
<div class="layui-card-header" style="background-color: #009688; color: #fff;">单个文件上传div>
<div class="layui-card-body">
<form class="layui-form" action="">
<div class="layui-form-item" style="width: 300px;">
<label class="layui-form-label">请选择照片label>
<div class="layui-input-block">
<input style="padding-top: 5px;" name="photo" type="file" name="title" required lay-verify="required" placeholder="请输入标题" autocomplete="off" class="layui-input">
div>
div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit="" lay-filter="formDemo">立即提交button>
<button type="reset" class="layui-btn layui-btn-primary">重置button>
div>
div>
form>
div>
div>
div>
<script src="./assets/jQuery-v3.5.1/jquery-3.5.1.min.js">script>
<script src="./assets/layui-v2.5.7/layui.all.js">script>
<script src="./assets/layui-v2.5.7/layui.js">script>
<script>
$(function() {
// 表单的提示事件
$("form.layui-form").on("submit", function(e) {
// 清除默认事件
e.preventDefault();
// 如果没有选择文件
if (!$("input:file")[0]) {
return layer.open({
title: "提示",
icon: 5,
content: "请选择文件!",
time: 2000
})
}
// 创建formdata对象
let formdata = new FormData();
// 向formdata中添加属性
formdata.append("photo", $("input:file")[0].files[0])
$.ajax({
type: "POST",
url: "http://127.0.0.1:8024/api/upload",
data: formdata,
cache: false,
processData: false,
contentType: false,
success: function(res) {
// 状态码为0则上传文件失败
if (res.status !== 0) {
// 采用layui的弹出层
return layer.open({
title: "提示",
icon: 5,
content: "文件上传失败,请稍后再试!",
time: 2000
})
}
// 否则上传成功,使用layui弹出层提示用户
layer.open({
title: "提示",
icon: 6,
content: "文件上传成功!",
time: 2000
})
}
})
});
});
script>
body>
html>
// 导入express模块
const express = require("express");
// 导入路径模块
const path = require("path");
// 创建路由对象
const router = express.Router();
// 导入multer模块
const multer = require("multer");
// 配置磁盘存储引擎
const storage = multer.diskStorage({
// 设置文件上传的路径
destination: function(req, file, cb) {
// 使用path模块拼接路径
cb(null, path.join(__dirname + "./../files/uploads"));
},
// 设置文件名称
filename: function(req, file, cb) {
// 获取当前的时间戳
let name = new Date().getTime();
// 以.分割原始文件名称,并返回两个数组
let extend = file.originalname.split(".");
// 通过for循环获取数组的最后一个元素,即原始文件的扩展名
for (let i = extend.length - 1; i < extend.length; i++) {
// 记录并替换扩展名
extend = extend[i];
// 跳出循环
break;
}
// 拼接文件名称和扩展名,生成文件
let filename = name + "." + extend;
// 设置文件的名称
cb(null, filename);
}
});
// 使用磁盘存储引擎
const upload = multer({
// 使用磁盘存储引擎
storage: storage
// 可接受多个指定名称的文件和数量
}).fields([{
name: "photo-zs",
maxCount: 1
}, {
name: "photo-ls",
maxCount: 1
}, {
name: "photo-ww",
maxCount: 1
}]);
// POST请求,并上传一个以 photo 命名的文件
router.post("/uploads", upload, (req, res) => {
// 如果file对象为空,那么就证明没有选择照片,或者照片的数量不等于3
if (!req.files || Object.keys(req.files).length !== 3) {
// 响应状态码和提示信息
return res.send({
status: 1,
msg: "照片未选择"
});
}
// 捕获multer引起的异常
upload(req, res, function(err) {
// 判断是否是multer模块内部引擎的异常
if (err instanceof multer.MulterError) {
return res.send({
status: 1,
msg: "文件上传出现异常,请稍后再试!"
});
// 判断是否是其他异常
} else if (err) {
return res.send({
status: 1,
msg: "服务器异常,请联系管理员!"
});
}
// 一切都好
return res.send({
status: 0,
msg: "文件上传成功!"
});
});
});
// 共享路由模块
module.exports = router;
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>单个文件上传title>
<link rel="stylesheet" href="./assets/layui-v2.5.7/css/layui.css">
head>
<body style="padding: 20px; background-color: #f2f2f2;">
<div class="layui-main">
<div class="layui-card">
<div class="layui-card-header" style="background-color: #009688; color: #fff;">单个文件上传div>
<div class="layui-card-body">
<form class="layui-form" action="">
<div class="layui-form-item" style="width: 300px;">
<label class="layui-form-label">请选择照片label>
<div class="layui-input-block">
<input style="padding-top: 5px;" multiple name="photo-zs" type="file" name="title" required lay-verify="required" placeholder="请选择文件" autocomplete="off" class="layui-input">
div>
div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit="" lay-filter="formDemo">立即提交button>
<button type="reset" class="layui-btn layui-btn-primary">重置button>
div>
div>
form>
div>
div>
div>
<script src="./assets/jQuery-v3.5.1/jquery-3.5.1.min.js">script>
<script src="./assets/layui-v2.5.7/layui.all.js">script>
<script src="./assets/layui-v2.5.7/layui.js">script>
<script>
$(function() {
//表单的提示事件
$("form.layui-form").on("submit", function(e) {
// 清除默认事件
e.preventDefault();
// 获取到input中的所有文件
let files = $("input:file")[0].files;
// 判断是否选择了图片
if (files.length <= 0) {
return layer.open({
title: "提示",
icon: 5,
content: "请选择文件!",
time: 2000
})
// 如果选择文件的个数不为3
} else if (files.length !== 3) {
return layer.open({
title: "提示",
icon: 5,
content: "必须选择三个文件!",
time: 2000
})
}
// 定义formdata对象所需的三个key
let filesName = ["photo-zs", "photo-ls", "photo-ww"];
// 创建formdata对象
let formdata = new FormData();
// 通过循环遍历向formdata中添加文件和key
for (let i = 0; i < files.length; i++) {
formdata.append(filesName[i], files[i]);
}
// 使用ajax请求服务器
$.ajax({
type: "POST",
url: "http://127.0.0.1:8024/apis/uploads",
data: formdata,
cache: false,
processData: false,
contentType: false,
success: function(res) {
// 状态码为0则上传文件失败
if (res.status !== 0) {
// 采用layui的弹出层
return layer.open({
title: "提示",
icon: 5,
content: "文件上传失败,请稍后再试!",
time: 2000
})
}
// 否则上传成功,使用layui弹出层提示用户
layer.open({
title: "提示",
icon: 6,
content: "文件上传成功!",
time: 2000
})
}
})
});
});
script>
body>
html>
其实实现文件上传功能的方法特别多,比如 layui框架 就内置了文件上传的方法和学习文档,以及咱们已经学习过的 formidable 中间件 和 今天学习的 multer 中间件,它们都是实现文件上传的利器,都各有利弊,但又都是一样的原理,也必须通过FormData对象
才能实现。所以不管什么方法,能实现我们的需求,并且思路清晰,那么就是好的中间件,像框架、插件都是这个道理。
所以在新的一年里,我希望正在学习编程或者打算学习编程的同学都能够专心、用心,不能糊里糊涂的学,因为我们学习的目标不是为了玩,而是为了就业。祝愿各位朋友在新的一年了能够涅槃重生,早日就业,拿到自己期望的工资,在这里也祝愿像博主一样今年打算找工作的朋友,在面试、机试的时候能够超长发挥,顺利进入自己喜欢的公司,感谢大家的支持!