前端实现大文件分片

前言

文件上传的场景在工作中时常遇见,必不可免的有时会需要上传一个很大的文件,上传时间比较久,如果遇见网络问题或其他因素影响,容易导致传输失败,这里可以使用分片上传来解决这个问题。

思路:将大文件切成小片异步上传可以解决大文件上传过慢的问题,但如果文件很大时,采用一次性异步上传,会在同一时间产生过多的请求,后端服务器处理不过来,导致请求异常。因此将文件切成小片后,再分组上传,比如一组10个切片,等待10个切片的请求上传完成后,再接着下一组,以此减轻对服务器的压力。

 

项目目录:

 

示例功能在 Vue3 中实现: 

在 App.vue 中代码

<script setup>
import { nextTick, onMounted } from 'vue'

onMounted(() => {
  nextTick(() => {
    initFile()
  })
})

const initFile = () => {
  const inpFile = document.querySelector('#inpFile')
  inpFile.onchange = async (e) => {
    const file = e.target.files[0]
    console.time('cutTime')
    // 文件分片结果
    const chunks = await cutFile(file)
    console.timeEnd('cutTime')
    console.log(chunks)
  }
}

// 分片大小
const CHUNK_SIZE = 1024 * 1024 * 5;
// 线程数量:cpu数量或者默认4个
const THREAD_COUNT = navigator.hardwareConcurrency || 4;

// 将文件进行分片
const cutFile = (file) => {
  return new Promise((resolve) => {
    const result = [];
    // 分片数量
    const chunkCount = Math.ceil(file.size / CHUNK_SIZE);
    // 每个线程需要处理的分片数量
    const workerChunkCount = Math.ceil(chunkCount / THREAD_COUNT);
    // 完成的分片数量
    let finishCount = 0;
    for (let i = 0; i < THREAD_COUNT; i++) {
      // 创建线程,不开启线程在处理时会出现页面卡顿的情况
      const worker = new Worker("/src/worker.js", {
        type: "module",
      });
      
      // 每个线程的起始和结束索引
      const startIndex = i * workerChunkCount;
      let endIndex = startIndex + workerChunkCount;
      if (endIndex > chunkCount) {
        endIndex = chunkCount;
      }
      // 向线程发送消息
      worker.postMessage({
        file,
        CHUNK_SIZE,
        startIndex,
        endIndex,
      });

      // 监听线程返回的消息
      worker.onmessage = (e) => {
        for (let i = startIndex; i < endIndex; i++) {
          result[i] = e.data[i - startIndex];
        }
        // 关闭线程
        worker.terminate();
        finishCount++;
        // 线程全部完成
        if (finishCount >= THREAD_COUNT) {
          resolve(result);
        }
      };
    }
  });
}
</script>

<template>
  <div>
    <input type="file" id="inpFile"/>
  </div>
</template>

<style scoped>
.logo {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
  transition: filter 300ms;
}
.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
  filter: drop-shadow(0 0 2em #42b883aa);
}
</style>

前提:安装 spark-md5 插件 

npm i --save spark-md5

在 worker.js 文件中代码

import SparkMD5 from 'spark-md5'
onmessage = async (e) => {
  const { file, CHUNK_SIZE, startIndex, endIndex } = e.data
  // promise 数据
  const proms = []
  for (let i = startIndex; i < endIndex; i++) {
    proms.push(createChunk(file, i, CHUNK_SIZE))
  }
  // 一起获取全部的 promise 结果
  const chunks = await Promise.all(proms)
  // 发送消息
  postMessage(chunks)
}

// 创建分片
function createChunk(file, index, size) {
  return new Promise((resolve) => {
    const start = index * size;
    const end = start + size;
    // 创建md5对象
    const spark = new SparkMD5.ArrayBuffer();
    // 创建文件读取实例
    const fileReader = new FileReader();
    fileReader.onload = (e) => {
      // 将分片数据追加到md5对象
      spark.append(e.target.result);
      resolve({
        start,
        end,
        index,
        hash: spark.end(),
      });
    };
    // 读取文件
    fileReader.readAsArrayBuffer(file.slice(start, end));
  });
}

相关推荐

  1. 文件分片上传前端手写

    2024-02-02 14:26:01       32 阅读
  2. 前端中如何实现文件上传?

    2024-02-02 14:26:01       28 阅读
  3. 文件分片【笔记】

    2024-02-02 14:26:01       30 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-02-02 14:26:01       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-02-02 14:26:01       106 阅读
  3. 在Django里面运行非项目文件

    2024-02-02 14:26:01       87 阅读
  4. Python语言-面向对象

    2024-02-02 14:26:01       96 阅读

热门阅读

  1. 如何创建和使用索引?

    2024-02-02 14:26:01       58 阅读
  2. leetcode-top100链表专题一

    2024-02-02 14:26:01       59 阅读
  3. Python程序设计 基础数据类型

    2024-02-02 14:26:01       43 阅读
  4. vue的组件化和模块化

    2024-02-02 14:26:01       56 阅读
  5. WINHTTP忽略HTTPS证书

    2024-02-02 14:26:01       54 阅读
  6. 蓝桥杯 压缩矩阵

    2024-02-02 14:26:01       59 阅读