—form表单的enctype属性
application/x-www-form-urlencoded
默认表单数据编码,在发送到服务器之前,所有字符都会进行编码(空格转换为 “+” 加号,特殊符号转换为 ASCII HEX 值)。格式:
{user:z s,pwd:123} => user=z+s&pwd=123
Content-Type: application/x-www-form-urlencoded
multipart/form-data
将表单中的数据变成二进制数据进行上传,不对字符编码。在使用包含 文件上传控件的表单 时,必须使用该值。格式:
{user:z s,pwd:123,file:test.txt} =>
------WebKitFormBoundary2zCBIZMMAJmEO32J
Content-Disposition: form-data; name="user"
z s
------WebKitFormBoundary2zCBIZMMAJmEO32J
Content-Disposition: form-data; name="pwd"
123
------WebKitFormBoundary2zCBIZMMAJmEO32J
Content-Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plain
我是test.txt的内容
------WebKitFormBoundary2zCBIZMMAJmEO32J--
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary2zCBIZMMAJmEO32J
text/plain
以纯文本形式进行编码,其中不含任何控件或格式字符。格式:
{user:z s,pwd:123} =>
user=z s
pwd=123
Content-Type: text/plain
服务器: 接收到客户端传递的文件信息,创建文件并返回客户端存储的文件地址
<input type="file" id="fileInp" accept="image/*">
let limitType = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif']
if (!limitType.includes(file.type)){
alert('不支持的上传格式!')
fileInp.value = '';
return
}
// 获取文件对象
let file = fileInp.files[0];
// 通过FormData类创建一个空对象
let formData = new FormData()
// 默认Content-Type: multipart/form-data
// 通过append()方法来追加数据
formData.append('chunk', file);
// 发送文件信息
let res = await axios.post('http://127.0.0.1:5678/single1',formData)
服务器: 接收到客户端传递的base64信息,把base64转换为具体的文件存储并返回客户端存储的文件地址
multiparty文档
const multiparty = require('multiparty')
// 实例化Form
let form = new multiparty.Form()
// 设置单文件大小限制
form.maxFilesSize = 2 * 1024 * 1024;
// 解析二进制流
form.parse(req, function(err, fields, file){
// 处理错误
if (err) {
res.send({
code: 1,
err
})
return
}
})
file
{
chunk: [ // FormData追加的键名
{
fieldName: 'chunk', // 同上
originalFilename: '1.jpg', // 文件的原始名
// 解析后存入服务器的临时缓存文件地址
path: '/var/folders/xg/0mlhvtz544s685y_q6pf2fv80000gn/T/naSmL8-gS6m7B7YqujRBu6S9.jpg',
headers: [Object], // 文件二进制流的头部信息
size: 3314 // 文件大小
}
]
}
md5加密处理文件名
module.exports = (filename) => {
// 最后一个点的在文件名的下标
let dotIndex = filename.lastIndexOf('.'),
// 文件名称
name = filename.substring(0,dotIndex),
// 文件后缀
suffix = filename.substring(dotIndex+1);
// md5加密文件名称并且加入时间戳实现唯一值
name = require('crypto').createHash('md5').update(name).digest('hex')+new Date().getTime()
// 返回加密后的文件名
return `${name}.${suffix}`
}
multer文档
简单使用
app.use(multer({dest: './upload'}).single('chunk'))
multer模块会将前端传来的二进制流信息挂载到请求的file中(array为files),具体信息如下:
{
fieldname: 'chunk', // FormData追加的键名
originalname: '1.jpg', // 文件的原始名
encoding: '7bit', // 编码
mimetype: 'image/jpeg', // 文件的MIME类型
destination: './upload', // 目标文件夹
filename: '57f97d113d1bcb0d8a90f2e492d42280', // 存储的文件名
path: 'upload/57f97d113d1bcb0d8a90f2e492d42280', // 文件存储目录
size: 3314
}
也会在upload文件夹中会创建一个32位hash值命名的文件,该文件内容为上传文件,但是没有扩展名。我们可以通过file信息和文件的简单的读写生成我们需要的文件。
磁盘存储引擎(DiskStorage)
let storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, '/tmp/my-uploads')
},
filename: function (req, file, cb) {
cb(null, file.fieldname + '-' + Date.now())
}
})
app.use(multer({storage})).single('chunk')
磁盘存储引擎可以让你控制文件的存储。有两个选项可用,destination
和 filename
。他们都是用来确定文件存储位置的函数。
destination
是用来确定上传的文件应该存储在哪个文件夹中。也可以提供一个 string
(例如 '/tmp/uploads'
)。如果没有设置 destination
,则使用操作系统默认的临时文件夹。
注意: 如果你提供的 destination
是一个函数,你需要负责创建文件夹。当提供一个字符串,multer 将确保这个文件夹是你创建的。
filename
用于确定文件夹中的文件名的确定。 如果没有设置 filename
,每个文件将设置为一个随机文件名,并且是没有扩展名的。
文件过滤器 —— 过滤文件类型和大小
app.use(multer({
//用于过滤文件的函数
fileFilter: function (req, file, cb) {
let ext = path.extname(file.originalname);
let extArr = ['.jpg', '.jpeg', '.gif', '.png'];
if (!extArr.includes(ext)) {
//拒绝这个文件
//cb(null, false);
//当然我们还可以发送一个错误
cb(new Error('扩展名不正确'));
}
if (file.size > 100 * 1024) {
cb(new Error('只能上传100KB以下的文件'));
}
//接受这个文件
cb(null, true);
}
})).single('chunk')
错误处理机制
当遇到一个错误,multer 将会把错误发送给 express。你可以使用一个比较好的错误展示页 (express标准方式)。
如果你想捕捉 multer 发出的错误,你可以自己调用中间件程序。如果你想捕捉 Multer 错误,你可以使用 multer
对象下的 MulterError
类 (即 err instanceof multer.MulterError
)。
var multer = require('multer')
var upload = multer().single('avatar')
app.post('/profile', function (req, res) {
upload(req, res, function (err) {
if (err instanceof multer.MulterError) {
// 发生错误
} else if (err) {
// 发生错误
}
// 一切都好
})
})
HTTP可以多个并发传递6—7个请求
把大文件切片化(5个)
file是Blob的实例,Blob.prototype.silce可以把一个文件切片处理
同时并发五个切片的上传请求
/chunk 处理每一个切片的请求
chunk:文件切片
filename:切片的名字 ->文件名-索引.后缀
upload目录下创建一个以文件名命名的临时目录,把切片存储到这个目录下
xxxxx
xxxxx-0-png
xxxxx-1-png
…
等5个切片都上传完,再向服务器发送一个合并切片的请求
/merge 合并
filename:文件名 文件名.后缀
根据文件名找到之前存储的临时目录,按顺序读取目录中的切片信息,把每一个切片信息合并起来(合并完记得删除临时目录和里面的切片即可)
在切片上传的简单应用
实例一个代理器,对对外部的访问进行修改
let _data = new Proxy({
total: 0
}, {
set(target, key, value) {
target[key] = value; // 调用_data.total = 20 即_data[total] = 20
if (_data.total > 100) {
progress.innerHTML = '上传完成'
}
progress.innerHTML = `${_data.total}%`
}
})
上传成功每次切片,total+20即可
实时显示上传进度
为axios配置onUploadProgress
属性
let res = await axios({
url: 'http://127.0.0.1:5678/single1',
method: 'post',
data: formData,
onUploadProgress: ev => {
// ev.loaded 已请求
// ev.total 全部请求
_data.total = Math.ceil(ev.loaded/ev.total*100)
}
})
app.get('/download/:name', (req, res) => {
let filename = req.params.name
res.writeHead(200, {// force-download
'Content-Type': 'application/octet-stream', //告诉浏览器这是一个二进制文件
'Content-Disposition': 'attachment; filename=' + filename, //告诉浏览器这是一个需要下载的文件
}); fs.createReadStream(`./upload/c4ca4238a0b923820dcc509a6f75849b1585121027347.jpg`).pipe(res);
// res.download('./upload/c4ca4238a0b923820dcc509a6f75849b1585121027347.jpg')
})