vue-simple-uploader 文件分片上传

801 阅读2分钟

项目中遇到,记录一下,前端使用vue-simple-upload + spark-md5 实现大文件分片上传,断点续传(附代码地址)。

  1. 安装依赖:
npm install vue-simple-uploader --save
npm install spark-md5
  1. main.js中引入vue-simple-uploader
import uploader from 'vue-simple-uploader';
...,
Vue.use(uploader)
  1. 新建FileUpload.vue组件
<template>
  <div class="fileUpload">
    <uploader
      :autoStart="false"
      :options="options"
      :file-status-text="statusText"
      class="uploader-example"
      @file-complete="fileComplete"
      @complete="complete"
      @file-success="fileSuccess"
      @file-added="fileAdded"
    >
      <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-files></uploader-files>
    </uploader>
    <br />
    <el-button @click="allStart()" :disabled="disabled">全部开始</el-button>
    <el-button @click="allStop()" style="margin-left: 4px">全部暂停</el-button>
    <el-button @click="allRemove()" style="margin-left: 4px"
      >全部移除</el-button
    >
  </div>
</template>
<script>
import axios from "axios";
import SparkMD5 from "spark-md5";
export default {
  name: "fileUpload",
  data() {
    return {
      skip: false,
      options: {
        target: "http://localhost:8090/file/upload",
        // 开启服务端分片校验功能
        testChunks: true,
        parseTimeRemaining: function(timeRemaining, parsedTimeRemaining) {
          return parsedTimeRemaining
            .replace(/\syears?/, "年")
            .replace(/\days?/, "天")
            .replace(/\shours?/, "小时")
            .replace(/\sminutes?/, "分钟")
            .replace(/\sseconds?/, "秒");
        },
        // 服务器分片校验函数
        checkChunkUploadedByResponse: (chunk, message) => {
          const result = JSON.parse(message);
          if (result.data.skipUpload) {
            this.skip = true;
            return true;
          }
          return (result.data.uploaded || []).indexOf(chunk.offset + 1) >= 0;
        }
        // headers: {
        //   // 在header中添加的验证,请根据实际业务来
        //   "Access-Token": storage.get(ACCESS_TOKEN),
        // },
      },
      attrs: {
        accept: "image/*"
      },
      statusText: {
        success: "上传成功",
        error: "上传出错了",
        uploading: "上传中...",
        paused: "暂停中...",
        waiting: "等待中...",
        cmd5: "计算文件MD5中..."
      },
      fileList: [],
      disabled: true
    };
  },
  methods: {
    fileSuccess(rootFile, file, response, chunk) {
      const result = JSON.parse(response);
      const fileInfo = {
        identifier: file.uniqueIdentifier,
        filename: file.name,
        totalChunks: chunk.offset,
        totalSize: file.size
      };
      // 是否需要合并
      if (result.data.needMerge && !this.skip) {
        axios
          .post("http://localhost:8090/file/merge", fileInfo)
          .then(res => {
            if (res.code === 200) {
              console.log("上传成功");
            } else {
              console.log(res);
            }
          })
          .catch(function(error) {
            console.log(error);
          });
      } else {
        console.log("上传成功,不需要合并");
      }
      if (this.skip) {
        this.skip = false;
      }
    },
    fileComplete(rootFile) {},
    complete() {},
    fileAdded(file) {
      //示例代码:此处可对上传文件的格式进行校验,如只允许上传后缀为xls的文件
      // const extname = file.getExtension();
      // if (extname !== "xls") {
      //   this.$message("请选择xls文件");
      //   file.ignored = true;
      // } else {
      //   this.computeMD5(file);
      // }
      this.computeMD5(file);
    },
    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 = 5 * 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() {
      console.log(this.fileList);
      this.fileList.map(e => {
        if (e.paused) {
          e.resume();
        }
      });
    },
    allStop() {
      console.log(this.fileList);
      this.fileList.map(e => {
        if (!e.paused) {
          e.pause();
        }
      });
    },
    allRemove() {
      this.fileList.map(e => {
        e.cancel();
      });
      this.fileList = [];
    }
  },
  watch: {}
};
</script>
<style scoped></style>


  1. 页面使用Filepload组件
<file-upload/>
  1. 代码地址
  https://gitee.com/mengchengxian/fileupload.git