文件分块上传

  1. 文件上传 大于20M时分块上传
  2. 创建一个FileHandler类 来进行文件的一些判断处理
/* eslint-disable */
import { getSTS } from "@/utils/api/global";
import MD5 from "spark-md5";
import {uploadChunks, uploadSFile} from '@/utils/api/upload.js'
import Chunk from "@/utils/share/chunk";
const FileHandler = class FileHandler {
  constructor(options) {
    this.options = options;
  }
  // 单文件上传
  uploadSingleFile(filePath, file) {
    const formData = new FormData()
    formData.append('file', file)
    return new Promise(function(resolve, reject) {
      uploadSFile(formData).then(res => {
        resolve(res)
      })
    })
  }
  // 上传文件入口
  uploadFile = (path, file, progress, tempCheckpoint) => {
    const filePath = path + "/" + hashFilePath(file) + "/" + file.name;
    let multipartUpload = function() {
      console.log(progress)
      return Chunk.uploadFileChunk(file,progress)
      // Chunk.uploadFileChunk(file)
    }
    if (file.size > 1024 * 1024 * 20) {
      try {
        return multipartUpload();
      } catch (e) {
        return new Promise(function(resolve, reject) {
          reject("文件上传失败,请重试!");
        });
      }
    } else {
      // 小于20Mb的文件直接上传
      return this.uploadSingleFile(filePath, file)
    }
  }

  // 判断是不是png/jpg格式的图片
  isImage = async function(file) {
    return (await isPNG(file)) || (await isJPG(file));
  };

  // 判断是不是mp4/webm格式的视频
  isVideo = async function(file) {
    return (await isMp4(file)) || (await isWebm(file));
  };

  // 判断是不是符合要求的附件格式(word/excel/ppt/pdf/jpg/png/txt)
  isAttachment = async function(file, exts) {
    if (exts.includes("word")) {
      if (await isDoc(file)) return true;
    }
    if (exts.includes("excel")) {
      if ((await isXls(file)) || (await isDocx(file))) return true;
    }
    if (exts.includes("ppt")) {
      if (await isDocx(file)) return true;
    }
    if (exts.includes("pdf")) {
      if (await isPDF(file)) return true;
    }
    if (exts.includes("jpg")) {
      if (await isJPG(file)) return true;
    }
    if (exts.includes("png")) {
      if (await isPNG(file)) return true;
    }
    if (exts.includes("txt")) {
      // txt没有文件头,使用后缀名判断
      const index = file.name.lastIndexOf(".");
      const ext = file.name.substr(index + 1);
      if (ext === "txt") return true;
    }
    return false;
  };
};

function getFileType(file, length) {
  return new Promise(resolve => {
    const reader = new FileReader();
    reader.onload = function(e) {
      const res = e.target.result
        .split("")
        .map(v => v.charCodeAt())
        .map(v => v.toString(16).toUpperCase())
        .map(v => v.padStart(2, "0"))
        .join("");
      resolve(res);

      // const view = new DataView(e.target.result);
      // let num, hexValue;
      // if (length === 8) {
      //   num = view.getUint32(view, 0, false);
      //   hexValue = Number(num).toString(16);
      //   // 长度不够用0填充
      //   if (hexValue.length < 8) {
      //     hexValue = hexValue.padStart(8, "0");
      //   }
      // } else {
      //   num = getUint64(view, 0, false);
      //   hexValue = Number(num).toString(16);
      //   if (hexValue.length < 16) {
      //     hexValue = hexValue.padStart(16, "0");
      //   }
      // }
      // hexValue = hexValue.toLocaleUpperCase();
      // resolve(hexValue);
    };
    // reader.readAsArrayBuffer(file);
    reader.readAsBinaryString(file.slice(0, length));
  });
}

function hashFilePath(file) {
  const t = new Date().getTime().toString();
  let path = MD5.hash(t);
  path = path + "_" + file.name;
  return path;
}
// 验证文件是不是png格式
async function isPNG(file) {
  const res = await getFileType(file, 4);
  return res === "89504E47";
}

// 验证文件是不是jpg/jpeg/jpe格式
async function isJPG(file) {
  const res = await getFileType(file, 3);
  return res === "FFD8FF";
}

// // 验证文件是不是gif格式
// async function isGIF(file) {
//   const res = await getFileType(file, 4);
//   return res === "47494638";
// }

// 验证文件是不是pdf格式
async function isPDF(file) {
  const res = await getFileType(file, 7);
  return res === "255044462D312E";
}

// 验证文件是不是docx/xlsx/pptx格式
async function isDocx(file) {
  const res = await getFileType(file, 4);
  return res === "504B0304";
}

// 验证文件是不是doc/xls格式
async function isXls(file) {
  const res = await getFileType(file, 4);
  return res === "D0CF11E0";
}

// 验证文件是不是doc格式
async function isDoc(file) {
  const res = await getFileType(file, 4);
  return res === "0D444F43";
}

// // 验证文件是不是vnd.ms-powerpoint/vnd.ms-excel/vnd.visio等格式
// async function isPPT(file) {
//   const res = await getFileType(file, 8);
//   return res === "D0CF11E0A1B11AE1";
// }

// async function isRTF(file) {
//   const res = await getFileType(file, 6);
//   return res === "7B5C72746631";
// }

// 验证文件是不是Mp4格式
async function isMp4(file) {
  const res = await getFileType(file, 8);
  return (
    res === "0000001466747970" ||
    res === "0000001866747970" ||
    res === "0000001C66747970" ||
    res === "0000002066747970"
  );
}

// 验证文件是不是Webm格式
async function isWebm(file) {
  const res = await getFileType(file, 4);
  return res === "1A45DFA3";
}

// 验证文件是不是Mp3格式
export async function isMp3(file) {
  const res = await getFileType(file, 3);
  return res === "494443";
}

export default new FileHandler();

  1. 创建一个块处理文件 Chunk.js
import sparkMD5 from 'spark-md5'
import axios from 'axios'
import pathname from '@/utils/api/pathname'
import { uploadChunks as uploadChunkFunc } from '@/utils/api/upload.js'
/* eslint-disable */
const Chunk = class Chunk {
  constructor(options) {
    this.options = options;
    this.CHUNK_SIZE = 5*1024*1024
  }
  // 文件切片
  createFileChunk = function (file,size=this.CHUNK_SIZE) {
    const chunks = [] 
    let cur = 0
    while(cur<file.size){
      chunks.push({index:cur, file:file.slice(cur,cur+size)})
      cur+=size
    }
    console.log(chunks)
    return chunks
  }


  // 仿react Fiber利用浏览器空闲时间处理切片hash
  calculateHashIdle = async function calculateHashIdle(){
    const chunks = this.chunks
    return new Promise(resolve=>{
      const spark = new sparkMD5.ArrayBuffer()
      let count = 0 

      const appendToSpark = async file=>{
        return new Promise(resolve=>{
          const reader = new FileReader()
          reader.readAsArrayBuffer(file)
          reader.onload = e=>{
            spark.append(e.target.result)
            resolve()
          }
        })
      }
      const workLoop = async deadline=>{
        // timeRemaining获取当前帧的剩余时间
        while(count<chunks.length && deadline.timeRemaining()>1){
          // 空闲时间,且有任务
          await appendToSpark(chunks[count].file)
          count++
          if(count<chunks.length){
            this.hashProgress = Number(
              ((100*count)/chunks.length).toFixed(2)
            )
          }else{
            this.hashProgress = 100
            resolve(spark.end())
          }
        }
        window.requestIdleCallback(workLoop)
      }
      // 浏览器一旦空闲,就会调用workLoop
      window.requestIdleCallback(workLoop)
    })
  }

  calculateHashSample = async function (files){
    return new Promise(resolve=>{
      const spark = new sparkMD5.ArrayBuffer()
      const reader = new FileReader()
      const file = files
      const size = file.size
      const offset = 2*1024*1024
      // 第一个2M,最后一个区块数据全要
      let chunks = [file.slice(0,offset)]
      let cur = offset
      while(cur<size){
        if(cur+offset>=size){
          // 最后一个区块
          chunks.push(file.slice(cur, cur+offset))

        }else{
          // 中间的区块
          const mid = cur+offset/2
          const end = cur+offset
          chunks.push(file.slice(cur, cur+2))
          chunks.push(file.slice(mid, mid+2))
          chunks.push(file.slice(end-2, end))
        }
        cur+=offset
      }
      // 中间的,取前中后各2字节
      reader.readAsArrayBuffer(new Blob(chunks))
      reader.onload = e=>{
        spark.append(e.target.result)
        this.hashProgress = 100
        resolve(spark.end())
      }
    })
  }

  uploadFileChunk = async function (file,progress) {
    console.log(progress)
    let chunksAll = this.createFileChunk(file)
    // 全量hash
    // const hash = await this.calculateHashIdle()
    // 抽样hash
    const hash = await this.calculateHashSample(file)
    // hash = hash
    const chunks = chunksAll.map((chunk,index)=>{
      // 切片的名字 hash+index
      let intIndex = (parseInt(index)+1)
      // const name = hash +'-'+ intIndex;
      // return {
      //   hash,
      //   name,
      //   index,
      //   chunk:chunk.file,
      // }
      return {
        chunkNumber: intIndex,
        chunkSize: this.CHUNK_SIZE,
        currentChunkSize: chunk.file.size,
        fileId: hash,
        fileName: file.name,
        multipartFile: chunk.file,
        totalChunks: chunksAll.length,
        totalSize: file.size
      }
    })
    // 上传
    const p = this.uploadChunks(chunks,progress)
    console.log(p)
    return p;
  }

  uploadChunks = function (chunks,progress) {
    const requests = chunks
            .map((chunk,index)=>{
              const form = new FormData();
              form.append('multipartFile',chunk.multipartFile)
              form.append('chunkNumber',chunk.chunkNumber)
              form.append('chunkSize',chunk.chunkSize)
              form.append('currentChunkSize',chunk.currentChunkSize)
              form.append('fileId',chunk.fileId)
              form.append('fileName',chunk.fileName)
              form.append('totalChunks',chunk.totalChunks)
              form.append('totalSize',chunk.totalSize)
            return {chunk:form, index:chunk.chunkNumber,error:0}
          })
          // .map(({chunk,index})=> {
          //      return uploadChunkFunc(chunk)
          //   }
          // )
        // 并发量控制  参考tiny-async-pool、es6-promise-pool、p-limit
        const p = this.sendRequest(requests, 4,progress)
        console.log(p)
        return p
  }
  sendRequest = function(chunks, limit = 1,progress) {
    return new Promise((resolve,reject)=>{
      const len = chunks.length
      let counter = 0 
      let isStop = false;
      const start = ()=>{
        if(isStop) {
          return 
        }
        const task = chunks.shift();
        console.log(task)
        if(task) {
          const {chunk,index} = task
          uploadChunkFunc(chunk).then((res)=>{
            if(res.code == 0) {
              if(counter == len-1) {
                // 上传完成
                progress(res.data.progress)
                resolve(res)
              }else{
                counter++;
                progress(res.data.progress)
                start();
              }
            }
          }).catch((e)=>{
            reject()

            console.log('error',task.error)
            // if(task.error<2){
            //   task.error++
            //   chunks.unshift(task)
            //   start()
            // }else{
            //   // 错误3次
            //   isStop = true
            //   console.log('isStop')
            //   reject()
            // }
          })
        }else{
          limit = -1;
          return false;
        }
      }
      while(limit>0) {
        start();
        limit -= 1;
      }
    })
  }
}
export default new Chunk();

  1. 调用 文件上传
import FileHandler from '@/utils/share/upload.js'
upload (file) {
    FileHandler.uploadFile(this.path, file, p => {
    	// 上传进度条
      let progress = Math.round(p * 100)
      this.$emit("progress", progress)
    }).then(res => { // 成功回调函数
      const { objectUrl } = res.data;
      const obj = {
        url: objectUrl,
        name: file.name
      }
      this.$emit('upload', obj)
    }).catch((e)=>{ // 失败回调函数
        this.$emit("progress", 100);
        // 上传失败
        this.$message(`上传失败了请重试~`);
        this.attachmentList = [];
      })
},
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值