企业网盘之分片上传组件

效果展示

 包含技术点

1、分片上传。

2、文件秒传。

3、文件夹上传

4、文件续传。

5、文件拖拽上传。

组件目录

 

实现分析

分片上传

通过H5 FileUpload对象可以实现文件上传, mutiple属性可以支持文件多选。拿到文件对象后,调用完整的分片上传流程:计算MD5-添加文件-获取鉴权信息-递归上传分片-上传完成。

如何切割文件分片?

方法介绍:blob.slice(); 属于Blob对象的一个方法,而File对象是继承Blob对象的,因此File对象也含有slice方法。

如何计算进度?
通过文件大小及分片计算算法,算出分片大小和分片数目以后,通过当前上传的分片数目除以总共分片数目得到进度信息。

如何计算上传速度?

每上传一个分片,记录分片上传请求的时间,通过分片的大小除以请求时间得到速度信息。

上传分片的代码:

//  上传分片
  const uploadPart = (resolve, reject) => {
    let blob = file.slice((params.PartNumber - 1) * chunkSize, params.PartNumber * chunkSize);
    let begin = new Date().getTime();
    params['Body'] = blob;
    s3.uploadPart(params, function (err, data) {
      if (err || item.status !== 'uploading') {
        reject(err || item.status);
      } else {
        item.percentage = Math.round(params.PartNumber * 100 / frameNum); // 进度
        let spend = (new Date().getTime() - begin) / 1000;  // 消耗时间
        item.speed = Math.round(blob.size / spend) * 1.3;  // 速度
        log_content.frameSeq = params.PartNumber;
        l_params.logContent = JSON.stringify(log_content);
        // debugger;
        log(l_params).then(() => {
          if (params.PartNumber < frameNum) {
            params.PartNumber += 1;
            uploadPart(resolve, reject);
          } else {
            item.status = 'success';
            resolve();
          }
        }).catch((err) => {
          reject(err);
        });
      }
    });
  };

文件秒传

秒传是将上传的文件与服务器中的文件进行比对,若云端存在相同文件,则将直接把文件秒速保存到你的网盘。调用“添加文件”接口,若服务端返回文件ID,表明云端已存储相同文件,新的文件会存储为源文件的一份索引。

文件夹上传

chrome的私有属性webkitdiretory可以支持文件夹上传。分片上传流程需要增加递归创建文件夹:递归创建文件夹-计算MD5-添加文件-获取鉴权信息-递归上传分片-上传完成。

判断文件为文件夹上传?

file.webkitRelativePath属性。

判断文件是否一次上传?

这里要处理的场景是,一个文件夹上传两次,第二次需要对文件夹重命名。如何判断一批文件是一次上传的,还是同名文件夹上传多次。可以通过上传框change的时候,构造文件对象增加时间戳属性,同一批文件的时间戳相同。

HTML:

<input ref="folder" @change='handleChange' name="folderInput" multiple="" webkitdirectory="" accept="*/*" type="file">

递归创建文件夹可参考Node算法:

const mkdirs = (dirname, callback, errback) => {
  fs.stat(dirname, (err, stats) => {
    if (err) {
      mkdirs(path.dirname(dirname), () => {
        fs.mkdir(dirname, callback)
      }, errback)
    } else {
        if (stats.isDirectory()) {
          callback()
        } else {
          errback()
        }
    }
  })
}

文件续传

大文件在上传过程中进行中断网络或刷新浏览器等操作,重新登录可断点上传。后台记录文件上传的分片信息,当上传过程被终止以后,重新登录查询当前用户未上传成功的续传列表。存储那边会保留已上传的分片,断点上传从未上传的分片开始,可大大减少大文件上传终止需重新上传的时间。

文件拖拽上传

H5新特性可实现文件拖拽上传。其中,与拖拽文件相关的事件有dragover(文件拖拽在悬浮)dragleave(文件拖拽离开)drop(文件拖拽放下)。在事件对象中,一个e.dataTransfer这样的属性,它是一个DataTransfer类型的数据,有如下的属性

属性 类型 说明
files FileList 拖拽的文件列表
items DataTransferItemList 拖拽的数据(有可能是字符串)
types Array 拖拽的数据类型 该属性在Safari下比较混乱

完整的组件代码:

<template>
  <div
    class="uploadMask"
    style="position: fixed"
    :class="{
      'is-dragover': dragover
    }"
    v-show="dragover"
    @drop.prevent="onDrop"
    @dragover.prevent="onDragover"
    @dragleave.prevent="dragover = false"
  >
    <slot></slot>
  </div>
</template>
<script>
  export default {
    name: 'ElUploadDrag',
    props: {
      disabled: Boolean
    },
    data() {
      return {
        dragover: false
      };
    },
    methods: {
      onDragover() {
        if (!this.disabled) {
          this.dragover = true;
        }
      },
      /*eslint-disable*/
      onDrop(e) {
        let _this = this;
        if (!this.disabled) {
          let event = e || window.event;
          this.dragover = false;
          let df = event.dataTransfer;  // 拖曳操作的过程中,我们可以用过dataTransfer对象来传输数据,以便在拖曳操作结束的时候对数据进行其他的操作;
          let len = df.files.length;
          let dealFileCnt = 0; // 读取文件是个异步的过程,需要记录处理了多少个文件了
          let files = [];

          function callback (files) { // 抛出文件数组
            _this.$emit('file', files);
          }

          // 检测是否已经把所有的文件都遍历过了
          function checkDropFinish () {
            if ( dealFileCnt === len - 1 ) {
              console.log('ie');
              callback(files); //  所有的文件都遍历过了emit 出去
            }
            dealFileCnt++;
          }

          if (df.items) {  // 有dataTransfer项目列表时
            for (let i = 0; i < len; i++) {
              let entry = df.items[i].webkitGetAsEntry(); // 读取拖拽元素信息
              if (entry.isFile && !entry.isDirectory) {  // isDirectory是否是文件夹
                files.push(df.files[i]);
              }
            }
            callback(files);
          } else {  // ie浏览器
            for (let i = 0; i < len; i++) {
              let dropFile = df.files[i];
              if (dropFile.type) {
                files.push(dropFile);
                checkDropFinish()
              } else {
                try {
                  var fileReader = new FileReader();
                  fileReader.readAsDataURL(dropFile.slice(0, 3));

                  fileReader.addEventListener('load', function (e) {
                    console.log(e, 'load');
                    files.push(dropFile);
                    checkDropFinish();
                  }, false);

                  fileReader.addEventListener('error', function (e) {
                    console.log(e, 'error,不可以上传文件夹');
                    checkDropFinish();
                  }, false);
                } catch (e) {
                  console.log(e, 'catch error,不可以上传文件夹');
                  checkDropFinish();
                }
              }
            }
          }
        }
      }
    },
    mounted() {
      let app = document.getElementsByClassName('app')[0];
      app.ondragstart = function (e) { // 拖拽开始
        e.preventDefault();//取消默认的链接元素和图片元素拖拽会触发拖拽上传
      };
      app.addEventListener('dragover', (e) => {  // 拖拽到另一个容器是促发
        e.preventDefault();
        this.onDragover();
      });
    }
  };
</script>

 参照文章

https://segmentfault.com/a/1190000013298317

posted @ 2019-01-18 14:45  苏晗夜  阅读(745)  评论(0编辑  收藏  举报