当前位置:首页 > 技术分析 > 正文内容

springboot整合vue2实现大文件分片上传、秒传、断点续传

ruisui883个月前 (02-03)技术分析11

实现说明

本篇文章主要介绍vue端的整合和技术知识点的分析,springboot后台代码及整合请参考上一篇文章:springboot整合vue2-uploader文件分片上传、秒传、断点续传

项目开源地址:
https://gitee.com/msxy/qingfeng-vue

其他产品开源地址:https://gitee.com/msxy

安装uploader和spark-md5的依赖

npm install --save vue-simple-uploader
npm install --save spark-md5

mainjs导入uploader

import uploader from 'vue-simple-uploader'
Vue.use(uploader)

创建uploader组件





Uploader组件使用






核心方法分析


      
      
        

将文件拖放到此处以上传

选择文件 选择图片 选择文件夹

全部开始 全部暂停 全部移除

options参数分析

参考 simple-uploader.js 配置。此外,你可以有如下配置项可选:

parseTimeRemaining(timeRemaining, parsedTimeRemaining) {Function}用于格式化你想要剩余时间,一般可以用来做多语言。参数:

    • timeRemaining{Number}, 剩余时间,秒为单位
    • parsedTimeRemaining{String}, 默认展示的剩余时间内容,你也可以这样做替换使用:
parseTimeRemaining: function (timeRemaining, parsedTimeRemaining) {
  return parsedTimeRemaining
    .replace(/\syears?/, '年')
    .replace(/\days?/, '天')
    .replace(/\shours?/, '小时')
    .replace(/\sminutes?/, '分钟')
    .replace(/\sseconds?/, '秒')
}

categoryMap {Object}

文件类型 map,默认:

{
  image: ['gif', 'jpg', 'jpeg', 'png', 'bmp', 'webp'],
  video: ['mp4', 'm3u8', 'rmvb', 'avi', 'swf', '3gp', 'mkv', 'flv'],
  audio: ['mp3', 'wav', 'wma', 'ogg', 'aac', 'flac'],
  document: ['doc', 'txt', 'docx', 'pages', 'epub', 'pdf', 'numbers', 'csv', 'xls', 'xlsx', 'keynote', 'ppt', 'pptx']
}

autoStart {Boolean}默认 true, 是否选择文件后自动开始上传。

fileStatusText {Object}默认:

{
  success: 'success',
  error: 'error',
  uploading: 'uploading',
  paused: 'paused',
  waiting: 'waiting'
}

用于转换文件上传状态文本映射对象。

0.6.0 版本之后,fileStatusText 可以设置为一个函数,参数为 (status, response = null), 第一个 status 为状态,第二个为响应内容,默认 null,示例:

fileStatusText(status, response) {
  const statusTextMap = {
    uploading: 'uploading',
    paused: 'paused',
    waiting: 'waiting'
  }
  if (status === 'success' || status === 'error') {
    // 只有status为success或者error的时候可以使用 response

    // eg:
    // return response data ?
    return response.data
  } else {
    return statusTextMap[status]
  }
}

fileComplete方法

fileComplete(rootFile) {
  // 一个根文件(文件夹)成功上传完成。
  // console.log("fileComplete", rootFile);
  // console.log("一个根文件(文件夹)成功上传完成。");
},

complete方法

 complete() {
      // 上传完毕。
      // console.log("complete");
    },

fileSuccess方法

文件上传成功,进行合并。

fileSuccess(rootFile, file, response, chunk) {
      // console.log(rootFile);
      // console.log(file);
      // console.log(message);
      // console.log(chunk);
      const result = JSON.parse(response);
      console.log(result.success, this.skip);

      if (result.success && !this.skip) {
        axios
          .post(
            "http://127.0.0.1:8888/upload/merge",
            {
              identifier: file.uniqueIdentifier,
              filename: file.name,
              totalChunks: chunk.offset,
            },
            {
              headers: { "Access-Token": storage.get(ACCESS_TOKEN) }
            }
          )
          .then((res) => {
            if (res.data.success) {
              console.log("上传成功");
            } else {
              console.log(res);
            }
          })
          .catch(function (error) {
            console.log(error);
          });
      } else {
        console.log("上传成功,不需要合并");
      }
      if (this.skip) {
        this.skip = false;
      }
    },

filesAdded方法

文件选择完成,进行文件分片处理。

filesAdded(file, fileList, event) {
      // console.log(file);
      file.forEach((e) => {
        this.fileList.push(e);
        this.computeMD5(e);
      });
    },
    computeMD5(file) {
      let fileReader = new FileReader();
      let time = new Date().getTime();
      let blobSlice =
        File.prototype.slice ||
        File.prototype.mozSlice ||
        File.prototype.webkitSlice;
      let currentChunk = 0;
      const chunkSize = 1024 * 1024;
      let chunks = Math.ceil(file.size / chunkSize);
      let spark = new SparkMD5.ArrayBuffer();
      // 文件状态设为"计算MD5"
      file.cmd5 = true; //文件状态为“计算md5...”
      file.pause();
      loadNext();
      fileReader.onload = (e) => {
        spark.append(e.target.result);
        if (currentChunk < chunks) {
          currentChunk++;
          loadNext();
          // 实时展示MD5的计算进度
          console.log(
            `第${currentChunk}分片解析完成, 开始第${
              currentChunk + 1
            } / ${chunks}分片解析`
          );
        } else {
          let md5 = spark.end();
          console.log(
            `MD5计算完毕:${file.name} \nMD5:${md5} \n分片:${chunks} 大小:${
              file.size
            } 用时:${new Date().getTime() - time} ms`
          );
          spark.destroy(); //释放缓存
          file.uniqueIdentifier = md5; //将文件md5赋值给文件唯一标识
          file.cmd5 = false; //取消计算md5状态
          file.resume(); //开始上传
        }
      };
      fileReader.onerror = function () {
        this.error(`文件${file.name}读取出错,请检查该文件`);
        file.cancel();
      };
      function loadNext() {
        let start = currentChunk * chunkSize;
        let end =
          start + chunkSize >= file.size ? file.size : start + chunkSize;
        fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end));
      }
    },

allStart全部开始

 allStart() {
  console.log(this.fileList);
  this.fileList.map((e) => {
    if (e.paused) {
      e.resume();
    }
  });
},

allStop全部停止

    allStop() {
      console.log(this.fileList);
      this.fileList.map((e) => {
        if (!e.paused) {
          e.pause();
        }
      });
    },

allRemove全部移除

    allRemove() {
      this.fileList.map((e) => {
        e.cancel();
      });
      this.fileList = [];
    },

文件分片

vue-simple-uploader自动将文件进行分片,在optionschunkSize中可以设置每个分片的大小。

如图:对于大文件来说,会发送多个请求,在设置testChunkstrue后(在插件中默认就是true),会发送与服务器进行分片校验的请求,下面的第一个get请求就是该请求;后面的每一个post请求都是上传分片的请求

看一下发送给服务端的参数,其中chunkNumber表示当前是第几个分片,totalChunks代表所有的分片数,这两个参数都是都是插件根据你设置的chunkSize来计算的。

需要注意的就是在最后文件上传成功的事件中,通过后台返回的字段,来判断是否要再给后台发送一个文件合并的请求。

MD5的计算过程

断点续传及秒传的基础是要计算文件的MD5,这是文件的唯一标识,然后服务器根据MD5进行判断,是进行秒传还是断点续传。

file-added事件之后,就计算MD5,我们最终的目的是将计算出来的MD5加到参数里传给后台,然后继续文件上传的操作,详细的思路步骤是:

  1. 把uploader组件的autoStart设为false,即选择文件后不会自动开始上传
  2. 先通过 file.pause()暂停文件,然后通过H5的FileReader接口读取文件
  3. 将异步读取文件的结果进行MD5,这里我用的加密工具是spark-md5,你可以通过npm install spark-md5 --save来安装,也可以使用其他MD5加密工具。
  4. file有个属性是uniqueIdentifier,代表文件唯一标示,我们把计算出来的MD5赋值给这个属性 file.uniqueIdentifier = md5,这就实现了我们最终的目的。
  5. 通过file.resume()开始/继续文件上传。
computeMD5(file) {
      let fileReader = new FileReader();
      let time = new Date().getTime();
      let blobSlice =
        File.prototype.slice ||
        File.prototype.mozSlice ||
        File.prototype.webkitSlice;
      let currentChunk = 0;
      const chunkSize = 1024 * 1024;
      let chunks = Math.ceil(file.size / chunkSize);
      let spark = new SparkMD5.ArrayBuffer();
      // 文件状态设为"计算MD5"
      file.cmd5 = true; //文件状态为“计算md5...”
      file.pause();
      loadNext();
      fileReader.onload = (e) => {
        spark.append(e.target.result);
        if (currentChunk < chunks) {
          currentChunk++;
          loadNext();
          // 实时展示MD5的计算进度
          console.log(
            `第${currentChunk}分片解析完成, 开始第${
              currentChunk + 1
            } / ${chunks}分片解析`
          );
        } else {
          let md5 = spark.end();
          console.log(
            `MD5计算完毕:${file.name} \nMD5:${md5} \n分片:${chunks} 大小:${
              file.size
            } 用时:${new Date().getTime() - time} ms`
          );
          spark.destroy(); //释放缓存
          file.uniqueIdentifier = md5; //将文件md5赋值给文件唯一标识
          file.cmd5 = false; //取消计算md5状态
          file.resume(); //开始上传
        }
      };
      fileReader.onerror = function () {
        this.error(`文件${file.name}读取出错,请检查该文件`);
        file.cancel();
      };
      function loadNext() {
        let start = currentChunk * chunkSize;
        let end =
          start + chunkSize >= file.size ? file.size : start + chunkSize;
        fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end));
      }
    },

给file的uniqueIdentifier 属性赋值后,请求中的identifier即是我们计算出来的MD5

秒传及断点续传

在计算完MD5后,我们就能谈断点续传及秒传的概念了。

服务器根据前端传过来的MD5去判断是否可以进行秒传或断点续传:

  • a. 服务器发现文件已经完全上传成功,则直接返回秒传的标识。
  • b. 服务器发现文件上传过分片信息,则返回这些分片信息,告诉前端继续上传,即断点续传

在每次上传过程的最开始,vue-simple-uploader会发送一个get请求,来问服务器我哪些分片已经上传过了,这个请求返回的结果也有几种可能:

  • a. 如果是秒传,在请求结果中会有相应的标识,比如我这里是skipUploadtrue,且返回了url,代表服务器告诉我们这个文件已经有了,我直接把url给你,你不用再传了,这就是秒传
  • b. 如果后台返回了分片信息,这是断点续传。如图,返回的数据中有个uploaded的字段,代表这些分片是已经上传过的了,插件会自动跳过这些分片的上传。

图b1:断点续传情况下后台返回值

前端做分片检验:checkChunkUploadedByResponse

插件自己是不会判断哪个需要跳过的,在代码中由options中的
checkChunkUploadedByResponse
控制,它会根据 XHR 响应内容检测每个块是否上传成功了,成功的分片直接跳过上传
你要在这个函数中进行处理,可以跳过的情况下返回true即可。

checkChunkUploadedByResponse: function (chunk, message) {
	 let objMessage = JSON.parse(message);
     if (objMessage.skipUpload) {
         return true;
     }

     return (objMessage.uploaded || []).indexOf(chunk.offset + 1) >= 0
},

注:skipUploaduploaded 是我和后台商议的字段,你要按照后台实际返回的字段名来。


优化MD5计算

原uploader中计算MD5的方式为对整个文件直接计算MD5,很吃内存,容易导致浏览器崩溃
我改成了通过分片读取文件的方式计算MD5,防止直接读取大文件时因内存占用过大导致的网页卡顿、崩溃。

自定义的状态

(之前我就封装了几种自定义状态,最近总有小伙伴问怎么没有“校验MD5”,“合并中”这些状态,我就把我的方法写出来了,方式很笨,但是能实现效果)

插件原本只支持了successerroruploadingpausedwaiting这几种状态,

由于业务需求,我额外增加了“校验MD5”“合并中”“转码中”“上传失败”这几种自定义的状态

由于前几种状态是插件已经封装好的,我不能改源码,只能用比较hack的方式:
当自定义状态开始时,要手动调一下
statusSet方法,生成一个p标签盖在原本的状态上面;当自定义状态结束时,还要手动调用statusRemove移除该标签。

this.statusSet(file.id, 'merging');
this.statusRemove(file.id);

扫描二维码推送至手机访问。

版权声明:本文由ruisui88发布,如需转载请注明出处。

本文链接:http://www.ruisui88.com/post/792.html

标签: vue url传参
分享给朋友:

“springboot整合vue2实现大文件分片上传、秒传、断点续传” 的相关文章

手把手教你Vue之父子组件间通信实践讲解【props、$ref 、$emit】

组件是 vue.js 最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用。那么组件间如何通信,也就成为了vue中重点知识了。这篇文章将会通过props、$ref和 $emit 这几个知识点,来讲解如何实现父子组件间通信。转载链接:https://www.jia...

深入理解Vue.js组件通信:父子组件与子父组件数据交互详解

什么是Vue组件通讯 Vue.js 组件通信是指在 Vue 应用的不同组件之间进行数据交换和状态同步的过程。由于 Vue 的组件是基于单文件组件(SFCs)的模块化设计,每个组件都有自己的作用域,因此它们不能直接访问彼此的数据。为了使组件之间能够协同工作,Vue 提供了几种不同的通信方式。以下是 V...

Windows 下 Git 拉 Gitlab 代码

读者提问:『阿常你好,Windows 下 Git 拉 Gitlab 代码的操作步骤可以分享一下吗?』阿常回答:好的,总共分为五个步骤。一、Windows 下安装 Git官网下载链接:https://git-scm.com/download/winStandalone Installer(安装版)注意...

7 招教你轻松搭建以图搜图系统

作者 | 小龙责编 | 胡巍巍当您听到“以图搜图”时,是否首先想到了百度、Google 等搜索引擎的以图搜图功能呢?事实上,您完全可以搭建一个属于自己的以图搜图系统:自己建立图片库;自己选择一张图片到库中进行搜索,并得到与其相似的若干图片。Milvus 作为一款针对海量特征向量的相似性检索引擎,旨在...

2024年,不断突破的一年

迈凯伦F1车队不久前拿下了2024年度总冠军,距离上一次还是二十几年前。在此期间,另一领域内,一个充满革新活力的腕表品牌——RICHARD MILLE理查米尔,正不断发展,与F1运动、帆船、古董车展等领域,共享着对速度与极限的无尽向往。RICHARD MILLE的发展与F1车手们在赛道上的卓越表现交...

js中数组filter方法的使用和实现

定义filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。语法var newArray = arr.filter(callback(element[, index[, selfArr]])[, thisArg])参数callback循环数组每个元素时调用的回调函数。回调函...