Skip to content

大文件分片上传-断点续传

简介

大文件分片上传是指将大型文件分割成小块进行上传,以提高上传效率和稳定性。这种方法通常用于网络上传或者文件传输中,特别是当上传的文件大小超过了服务器或网络的限制时。另外如果想做断点续传,文件分片也是必不可少的。

步骤

一般来说,大文件分片上传的实现可以通过以下步骤进行:

  1. 文件分割:将大文件按照固定大小或其他规则分割成多个片段,每个片段的大小要足够小以便于上传。
  2. 逐个上传:逐个上传每个文件片段到服务器,可以使用多线程或并行上传来提高效率。
  3. 上传完成标识:当所有文件片段都成功上传后,需要在服务器端对这些片段进行合并,并且标识整个文件上传完成。
  4. 断点续传:为了应对网络中断或其他意外情况,可以在上传过程中实现断点续传功能,即在上传中断后能够从中断处重新开始上传,而不需要重新上传整个文件。

案例

首先直接看效果

展示 👇以下案例可点击尝试模拟效果

文件分割

change事件中获取文件流,再调用 slice 方法进行切片,拿到切片数组

js
// input change事件
function fileChange(event) {
  const fileflow = event.target.files[0];
  file.value = fileflow;
  // 创建文件分片
  const chunks = createChunks(fileflow);
  refchunks.value = chunks;
  upload();
}

// 创建文件分片
const createChunks = (file) => {
  let start = 0;
  const chunks = [];
  while (start < file.size) {
    chunks.push(file.slice(start, start + refchunkSize.value));
    start += refchunkSize.value;
  }
  return chunks;
};

逐个上传

这边暂时还没有写后端 所以目前就写了一个模拟上传 等到后续写的时候再改回去

js
// 模拟上传
async function upload() {
  const chunks = refchunks.value;
  const maxParallelUploads = 5; // 最大并行上传数量
  let currentIndex = 0; // 当前正在上传的分片索引
  let uploadedCount = 0; // 已成功上传的分片数量

  // 上传单个分片的函数
  function uploadChunk(chunkIndex) {
    // 开始上传 改变状态
    refuploadSatus.value[chunkIndex] = "active";
    // return
    // 模拟上传时间,这里用 setTimeout 模拟异步上传
    setTimeout(() => {
      console.log("上传分片 " + chunkIndex + " 成功");
      // 上传成功 改变状态
      refuploadSatus.value[chunkIndex] = "success";
      uploadedCount++;
      // 如果还有未上传的分片,并且当前并行上传数量小于最大并行上传数量
      if (
        currentIndex < chunks.length &&
        currentIndex - uploadedCount < maxParallelUploads
      ) {
        uploadChunk(currentIndex); // 开始上传下一个分片
        currentIndex++;
      }
      // 如果所有分片都上传完成
      if (uploadedCount === chunks.length) {
        console.log("所有分片上传完成");
      }
    }, Math.random() * 2000); // 为了模拟上传时间随机设置延迟
  }

  // 启动初始的最大并行上传
  for (let i = 0; i < maxParallelUploads && i < chunks.length; i++) {
    uploadChunk(currentIndex);
    currentIndex++;
  }
}

完整代码

vue
<template>
  <div class="upload">
    <input ref="fileUpload" type="file" value="" @change="fileChange" />
    <div v-if="file" class="fileInfo">
      <div class="chunksBox">
        <div
          v-for="(item, index) in refchunks"
          class="chunk ready"
          :class="uploadStatus(index)"
        >
          <p>分片{{ index }}</p>
          <p>大小:{{ kb2mb(item.size) }}Mb</p>
        </div>
      </div>
      <p>文件大小: {{ kb2mb(file.size) }}Mb</p>
    </div>
  </div>
</template>
<script setup>
import { ref, onMounted, computed } from "vue";
const fileUpload = ref(null);
const file = ref(null);
const refchunks = ref(null);
const refchunkSize = ref(1024 * 1024); // 1MB
const refuploadSatus = ref({});

// 创建一个计算属性,转换单位
const kb2mb = computed(() => {
  return function (size) {
    return (size / 1024 / 1024).toFixed(2);
  };
});
// 创建一个计算属性,将number加倍
const uploadStatus = computed(() => {
  return function (index) {
    return refuploadSatus.value[index];
  };
});

// inputchange事件
function fileChange(event) {
  const fileflow = event.target.files[0];
  file.value = fileflow;
  // 创建文件分片
  const chunks = createChunks(fileflow);
  refchunks.value = chunks;
  upload();
}

// 创建文件分片
const createChunks = (file) => {
  let start = 0;
  const chunks = [];
  while (start < file.size) {
    chunks.push(file.slice(start, start + refchunkSize.value));
    start += refchunkSize.value;
  }
  return chunks;
};

// 模拟上传
async function upload() {
  const chunks = refchunks.value;
  const maxParallelUploads = 5; // 最大并行上传数量
  let currentIndex = 0; // 当前正在上传的分片索引
  let uploadedCount = 0; // 已成功上传的分片数量

  // 上传单个分片的函数
  function uploadChunk(chunkIndex) {
    // 开始上传 改变状态
    refuploadSatus.value[chunkIndex] = "active";
    // return
    // 模拟上传时间,这里用 setTimeout 模拟异步上传
    setTimeout(() => {
      console.log("上传分片 " + chunkIndex + " 成功");
      // 上传成功 改变状态
      refuploadSatus.value[chunkIndex] = "success";
      uploadedCount++;
      // 如果还有未上传的分片,并且当前并行上传数量小于最大并行上传数量
      if (
        currentIndex < chunks.length &&
        currentIndex - uploadedCount < maxParallelUploads
      ) {
        uploadChunk(currentIndex); // 开始上传下一个分片
        currentIndex++;
      }
      // 如果所有分片都上传完成
      if (uploadedCount === chunks.length) {
        console.log("所有分片上传完成");
      }
    }, Math.random() * 2000); // 为了模拟上传时间随机设置延迟
  }

  // 启动初始的最大并行上传
  for (let i = 0; i < maxParallelUploads && i < chunks.length; i++) {
    uploadChunk(currentIndex);
    currentIndex++;
  }
}
</script>
<style lang="scss" scoped>
.fileInfo {
  .chunksBox {
    display: flex;
    flex-wrap: wrap;
    border-top: 1px solid #ccc;
    border-left: 1px solid #ccc;

    .chunk {
      width: 20%;
      border-right: 1px solid #ccc;
      border-bottom: 1px solid #ccc;
      text-align: center;
      position: relative;
      overflow: hidden;
      &::after {
        content: "";
        position: absolute;
        top: 0;
        left: 0;
        display: block;
        width: 100%;
        height: 100%;
        z-index: -1;
        background: linear-gradient(to right, #8bc34a, #4caf50);
        transform: translateX(-100%); /* 修改这里 */
        transition: transform 10s ease;
      }
    }

    .ready::after {
    }

    .active::after {
      transform: translateX(-1%); /* 修改这里 */
    }

    .success {
      &::after {
        transition: transform 0.3s ease;
        transform: translateX(0);
      }
    }
  }
}
</style>

未完待续。。。