5分钟带你回顾大文件分片以及异步计算hash的步骤

发布时间 2023-09-01 17:12:51作者: 汪小热

背景

   文件上传功能在中后台项目中是最常见的功能,分片上传是一种将大文件分割成多个小片段进行上传的技术,可以有效提高文件上传的速度和稳定性。

思路

   1.首先就是使用File.slice对文件进行分割产出一个数组用于存储每个小的chunk片段

   2.异步计算hash值,可用作标识文件进而实现文件的断点续传、秒传等功能,这里我们采用spark-MD5计算文件hash值

   3.在计算hash的时候,特别是文件比较大的时候就会明显感觉应用卡顿,所以采用webworker的方式另外开一个线程去计算文件hash值

 

实现

   本文基于vue作简单实现

1.简单布局-主要是一个input标签,加上用于展示当前计算hash值的进度。

<template>
  <div>
    <div>
      <input
        @change="handleChange"
        ref="inputRef"
        type="file"
      />选择文件
    </div>
    当前计算hash百分比进度<h1 style="color:#fff">{{ precentageRef }}%</h1>
  </div>
</template>

2.创建切片功能函数

const createFileChunk = (file, size = 1024 * 20) => { //可根据传入尺寸确定单个chunk大小
  const chunk = [];
  for (let i = 0; i < file.size; i += size) {
    chunk.push(file.slice(i, i + size));
  }
  return chunk;
};

3.创建hash读取函数

const createHash = async (file) => { //采用异步读取,主要将file传给worker读取
  return new Promise((resolve) => {
    const myWorker = new Worker(); // 创建worker
    myWorker.postMessage({ file });//将要读取hash的文件传给worker
    myWorker.onmessage = (e) => {//读取的过程,用onmessage监听,可以设置读取进度百分比
      const {percentage,hash}=e.data
      precentageRef.value=percentage//将百分比实时展示
      if(percentage>=100){//如果到了100%代表已经读取完毕,就resolve出去
        resolve(hash)
      }
    }; 
  });
};

4.创建worker读取文件hash

import SparkMD5 from 'spark-md5'
 let percentage = 0;//初始化百分比
self.onmessage = (e) => {
  const { file } = e.data;//拿到传过来的文件
  const spark = new SparkMD5();
  function _read(index) {//index为读取的文件chunk下标
    let fileReader = new FileReader();//创建fileReader对文件chunk进行数据读写,并将内容拿给sparkMD5计算hash
    fileReader.readAsArrayBuffer(file[index]);
    fileReader.onload = (e) => {
      spark.append(e.target.result)
      if (index >= file.length-1) {//如果读取完毕就向外发送读取完毕消息
        self.postMessage({
          hash: spark.end(),
          percentage:100
        });
      } else {//否则递归读取下一个chunk
        percentage+=100/file.length
        self.postMessage({ 
          hash: 'pending',
          percentage:percentage
        });
        _read(index + 1);
      }
    };
  }
  _read(0);
};

  

这就是文件分片以及异步计算hash的简化流程。

完整代码:

fileLoader.vue

<template>
  <div>
    <div>
      <input
        @change="handleChange"
        ref="inputRef"
        type="file"
      />选择文件
    </div>
    当前计算hash百分比进度<h1 style="color:#fff">{{ precentageRef }}%</h1>
  </div>
</template>

<script setup>
import Worker from '../worker.js?worker'
import {ref} from 'vue'
const precentageRef=ref(0)
const handleChange = async (e) => {
  const { files } = e.target;
  if(!files)return
  let c = createFileChunk(files[0],1024*1024);

  const hash = await createHash(c);
  if (hash) {
    console.log(c, hash);
  }
};
const handleClick=()=>{
  alert(1)
}
const createFileChunk = (file, size = 1024 * 20) => {
  const chunk = [];
  for (let i = 0; i < file.size; i += size) {
    chunk.push(file.slice(i, i + size));
  }
  return chunk;
};

const createHash = async (file) => {
  return new Promise((resolve) => {
    const myWorker = new Worker(); // 创建worker
    myWorker.postMessage({ file });
    myWorker.onmessage = (e) => {
      console.log(e.data);
      const {percentage,hash}=e.data
      precentageRef.value=percentage
      if(percentage>=100){
        resolve(hash)
      }
    }; // Greeting from Worker.js,worker线程发送的消息
  });
};
</script>

<style lang="scss" scoped></style>

  

worker.js

import SparkMD5 from 'spark-md5'
 let percentage = 0;
self.onmessage = (e) => {
  const { file } = e.data;
  const spark = new SparkMD5();
  function _read(index) {
    let fileReader = new FileReader();
    console.log(index,file.length,file,file[index])
    fileReader.readAsArrayBuffer(file[index]);
    fileReader.onload = (e) => {
      spark.append(e.target.result);

      if (index >= file.length-1) {
        self.postMessage({
          hash: spark.end(),
          percentage:100
        });
      } else {
        percentage+=100/file.length
        self.postMessage({
          hash: 'pending',
          percentage:percentage.toFixed(2)
        });
        _read(index + 1);
      }
    };
  }
  _read(0);
};

  

效果:UI界面比较low!!!