注意:本文仅介绍前端使用方法,后端实现方式请查看我的另一篇文章:* Spring Boot 实现大文件分片上传、断点续传及秒传

vue-simple-uploader 简介

vue-simple-uploader 是基于 simple-uploader.js 封装的 Vue 上传组件。

其 GitHub 地址:simple-uploader/vue-uploader

文档:https://github.com/simple-uploader/vue-uploader/blob/master/README_zh-CN.md

simple-uploader.js 文档:https://github.com/simple-uploader/Uploader/blob/develop/README_zh-CN.md

特性:

  • 支持文件、多文件、文件夹上传
  • 支持拖拽文件、文件夹上传
  • 统一对待文件和文件夹,方便操作管理
  • 可暂停、继续上传
  • 错误处理
  • 支持“快传”,通过文件判断服务端是否已存在从而实现“快传”
  • 上传队列管理,支持最大并发上传
  • 分块上传
  • 支持进度、预估剩余时间、出错自动重试、重传等操作

效果图:

效果图

文件上传流程

整体流程

使用

  1. 安装 vue-simple-uploader
npm install vue-simple-uploader --save
  1. main.js 中引用:
import uploader from 'vue-simple-uploader'
Vue.use(uploader)
  1. 在页面中使用
<template>
  <div class="container">
    <uploader 
      ref="uploader"
      :options="options"
      :file-status-text="fileStatusText"
      @file-added="onFileAdded"
      @file-success="onFileSuccess"
      @file-error="onFileError"
      @file-progress="onFileProgress"
      class="uploader-example">
      <uploader-unsupport></uploader-unsupport>
      <uploader-drop>
        <p>拖动文件到这里上传</p>
        <uploader-btn>选择文件</uploader-btn>
        <uploader-btn :attrs="attrs">选择图片</uploader-btn>
        <uploader-btn :directory="true">选择文件夹</uploader-btn>
      </uploader-drop>
      <uploader-list></uploader-list>
    </uploader>
  </div>
</template>
  1. vue-simple-uploaderoptions 配置
// 分片大小,10MB
const CHUNK_SIZE = 10 * 1024 * 1024
export default {
    data () {
      return {
        options: {
          // 上传地址
          target: 'http://127.0.0.1:8025/upload',
          // 是否开启服务器分片校验。默认为 true
          testChunks: true,
          // 分片大小
          chunkSize: CHUNK_SIZE,
          // 并发上传数,默认为 3 
          simultaneousUploads: 3,
          // 分片校验函数,判断分片是否上传,秒传和断点续传基于此方法
          checkChunkUploadedByResponse: (chunk, message) => {
            let messageObj = JSON.parse(message)
            let dataObj = messageObj.data
            if (dataObj.uploaded !== undefined) {
              return dataObj.uploaded
            }
            // 判断文件或分片是否已上传,已上传返回 true,这里的 uploadedChunks 是后台返回
            return (dataObj.uploadedChunks || []).indexOf(chunk.offset + 1) >= 0
          }
        },
        attrs: {
          accept: 'image/*'
        },
        // 修改上传状态
        fileStatusTextObj: {
          success: '上传成功',
          error: '上传错误',
          uploading: '正在上传',
          paused: '停止上传',
          waiting: '等待中'
        },
        // 上传并发数
        simultaneousUploads: 3,
        uploadIdInfo: null,
        uploadFileList: [],
        fileChunkList: []
      }
    }
}
  1. 添加 vue-simple-uploader 相关的方法。
  methods: {
      // 用于文件校验,忽略该文件则返回 false,文件不会添加到上传列表中
      onFileAdded(file, event) {
        // 1. todo 判断文件类型是否允许上传
        // 2. 计算文件 MD5 并请求后台判断是否已上传,是则取消上传
        console.log('校验MD5')
        this.getFileMD5(file, md5 => {
          if (md5 != '') {
            // 修改文件唯一标识
            file.uniqueIdentifier = md5
            // 恢复上传
            file.resume()
          }
        })
      },
      // 上传成功事件
      onFileSuccess(rootFile, file, response, chunk) {
        alert('上传成功')
      },
      // 上传过程出错处理
      onFileError(rootFile, file, message, chunk) {
        console.log('上传出错:' + message)
      },
      // 文件上传进度
      onFileProgress(rootFile, file, chunk) {
        console.log(`当前进度:${Math.ceil(file._prevProgress * 100)}%`)
      },
      fileStatusText(status, response) {
        if (status === 'md5') {
          return '校验MD5'
        } else {
          return this.fileStatusTextObj[status]
        }
      },
      saveFileUploadId(data) {
        localStorage.setItem(FILE_UPLOAD_ID_KEY, data)
      }
    }

分片上传

vue-simple-uploader 自动将文件进行分片,通过配置 options 中的 chunkSize 来指定分片大小。

如果选择中配置了 testChunks: true ,则在每次上传前,vue-simple-uploader 会先发送一个 GET 请求,来判断文件或分片是否已经上传,如下图所示:

校验分片

基于此,我们可以用于实现断点续传和秒传。

文件MD5计算

由于断点续传和秒传的基础是文件的 MD5 值,作为文件的唯一标识,通过文件 MD5 值查询后台判断是秒传还是断点续传。

我使用的加密工具是 spark-md5,需要先安装:

npm install spark-md5 --save

在页面上使用:

import SparkMD5 from 'spark-md5'
// 分片大小,10MB
const CHUNK_SIZE = 10 * 1024 * 1024
  export default {
    methods: {
      getFileMD5(file, callback) {
        let spark = new SparkMD5.ArrayBuffer()
        let fileReader = new FileReader()
        let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
        let currentChunk = 0
        let chunks = Math.ceil(file.size / CHUNK_SIZE)
        let startTime = new Date().getTime()
        file.pause()
        loadNext()
        fileReader.onload = function(e) {
          spark.append(e.target.result)
          if (currentChunk < chunks) {
            currentChunk++
            loadNext()
          } else {
            let md5 = spark.end()
            console.log(`MD5计算完毕:${md5},耗时:${new Date().getTime() - startTime} ms.`)
            callback(md5)
          }
        }
        fileReader.onerror = function() {
          this.$message.error('文件读取错误')
          file.cancel()
        }
        function loadNext() {
          const start = currentChunk * CHUNK_SIZE
          const end = ((start + CHUNK_SIZE) >= file.size) ? file.size : start + CHUNK_SIZE
          fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end))
        }
      }
    }
  }

由于需要提交文件 MD5 ,我们可以直接将文件 MD5 赋值给 File 的 uniqueIdentifier 属性,修改文件的唯一标识。

这个步骤的操作可以写在 onFileAdded 方法里:

onFileAdded(file, event) {
    // 计算文件 MD5
    this.getFileMD5(file, md5 => {
       if (md5 != '') {
         // 修改文件唯一标识
         file.uniqueIdentifier = md5
         // 恢复上传
         file.resume()
       }
    })
}

断点续传和秒传

后台服务根据前端传的文件 MD5 查询判断是断点续传或秒传:

  • 后台服务查询该文件已上传,则直接返回秒传信息及文件 URL;

  • 后台服务查询该文件已上传分片,则返回已上传的分片信息,前端根据返回的分片信息来确定哪些分片继续上传。

这个步骤在 vue-simple-uploader 配置 optionscheckChunkUploadedByResponse 方法实现:

options: {
    target: 'http://127.0.0.1:8086/api/file/upload',
    testChunks: true,
    chunkSize: CHUNK_SIZE,
    //  判断分片是否上传,秒传和断点续传基于此方法
    checkChunkUploadedByResponse: (chunk, message) => {
        let messageObj = JSON.parse(message)
        let dataObj = messageObj.data
        // 判断文件或分片是否已上传,已上传返回 true
        if (dataObj.uploaded !== undefined) {
            return dataObj.uploaded
        }
        // 这里的 uploadedChunks 是后台返回的已上传分片列表,自己根据情况修改
        return (dataObj.uploadedChunks || []).indexOf(chunk.offset + 1) >= 0
    }
}

后台返回的数据格式为:

{
  "success": true,
  "code": 200,
  "message": "操作成功",
  "data": {
    "uploadedChunks": [
      1,
      2,
      4,
      5,
      6
    ]
  }
}

总结

vue-simple-uploader 的使用还是很简单的,它帮我们实现了很多操作,比如文件分片上传、支持秒传、上传进度、出错自动重试等。我们只需配置好参数,协调好后端接口,就可以很快的解决上传问题。

​ 博客中仅介绍了 vue-simple-uploader 最基本的用法,实际开发中还要考虑文件类型白名单、上传接口的 Token 校验、重试机制等问题,请勿直接用于生产项目中。更多的使用方法请查看官方文档,结合自身需求做相应的配置和修改。

​ 后端实现方式请查看我的另一篇文章:Spring Boot 实现大文件分片上传、断点续传及秒传

本项目源码:lanweihong/vue-simple-uploader-sample

待改进

  • 实现多文件上传,考虑使用 web-workerrequestIdleCallback 来实现计算大文件 MD5
  • 封装成独立组件,方便使用

由于最近没有时间,就先到这吧,以后有时间再继续完善此上传组件。

参考文献

[1]. 基于vue-simple-uploader封装文件分片上传、秒传及断点续传的全局上传插件

[2]. 基于vue-simple-uploader实现分片上传、秒传、断点续传的全局上传插件

文章目录