图片上传下载-功能实现

文件上传下载

三种方式实现文件上传功能

1. 传统form表单(基本被废弃)

向后台发送数据的三种编码格式

—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
    

2. ajax发送FormData数据格式(两种方式性能差异不大,均常用)

服务器: 接收到客户端传递的文件信息,创建文件并返回客户端存储的文件地址

a. 限制上传文件MIME TYPE

 
 <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
}

b. 单文件上传

// 获取文件对象
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)  

3. ajax发送base64数据格式

服务器: 接收到客户端传递的base64信息,把base64转换为具体的文件存储并返回客户端存储的文件地址

两种方式实现服务器存储文件功能

1. multiparty

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}`
}

2. multer

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')

磁盘存储引擎可以让你控制文件的存储。有两个选项可用,destinationfilename。他们都是用来确定文件存储位置的函数。

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')
})

你可能感兴趣的:(node,ES6,打包压缩)