分片上传-

发布于:2025-09-06 ⋅ 阅读:(19) ⋅ 点赞:(0)
  •  分片上传原理:客户端将选择的文件进行切分,每一个分片都单独发送请求到服务端;
  • 断点续传 & 秒传原理:客户端
    发送请求询问服务端某文件的上传状态
    ,服务端响应该文件已上传分片,客户端再将未上传分片上传即可;
    • 如果没有需要上传的分片就是秒传;
    • 如果有需要上传的分片就是断点续传;
  • 每个文件要有自己唯一的标识,这个标识就是将整个文件进行MD5加密,这是一个Hash算法,将加密后的Hash值作为文件的唯一标识;
    • 使用spark-md5第三方工具库,spark-md5是指一个用于计算MD5哈希值的前端JavaScript库
  • 文件的合并时机:当服务端确认所有分片都发送完成后,此时会发送请求通知服务端对文件进行合并操作;

如下图所示是前端分片上传的整体流程:

  • 第一步:将文件进行分片,并计算其Hash值(文件的唯一标识)
  • 第二步:发送请求,询问服务端文件的上传状态
  • 第三步:根据文件上传状态进行后续上传
    • 文件已经上传过了
      • 结束 --- 秒传功能
    • 文件存在,但分片不完整
      • 将未上传的分片进行上传 --- 断点续传功能
    • 文件不存在
      • 将所有分片上传
  • 第四步:文件分片全部上传后,发送请求通知服务端合并文件分片

案例实现

  • 前端使用 Element Plus UI

  • 实现文件选择 → 计算 Hash → 分片上传 → 进度显示

  • 假设后端提供接口

    1. POST /upload/check → 接收 fileHash,返回已上传分片列表

    2. POST /upload/chunk → 上传单个分片

    3. POST /upload/merge → 所有分片上传完成后通知合并

<template>
  <el-upload
    :file-list="fileList"
    :before-upload="beforeUpload"
    :show-file-list="false"
  >
    <el-button type="primary">选择文件上传</el-button>
  </el-upload>

  <el-progress
    v-if="uploading"
    :percentage="uploadProgress"
    :text-inside="true"
  ></el-progress>
</template>

<script setup>
import { ref } from 'vue';
import SparkMD5 from 'spark-md5';
import axios from 'axios';

const fileList = ref([]);
const uploadProgress = ref(0);
const uploading = ref(false);
const chunkSize = 2 * 1024 * 1024; // 2MB

// 计算文件Hash
function calculateFileHash(file) {
  return new Promise((resolve, reject) => {
    const spark = new SparkMD5.ArrayBuffer();
    const fileReader = new FileReader();
    const chunks = Math.ceil(file.size / chunkSize);
    let currentChunk = 0;

    fileReader.onload = e => {
      spark.append(e.target.result);
      currentChunk++;
      if (currentChunk < chunks) {
        loadNext();
      } else {
        resolve(spark.end());
      }
    };

    fileReader.onerror = () => reject('文件读取错误');

    function loadNext() {
      const start = currentChunk * chunkSize;
      const end = Math.min(file.size, start + chunkSize);
      fileReader.readAsArrayBuffer(file.slice(start, end));
    }

    loadNext();
  });
}

// 分片上传
async function uploadFileChunks(file, fileHash) {
  const chunks = Math.ceil(file.size / chunkSize);

  // 先询问服务端已上传分片
  const { data } = await axios.post('/upload/check', { fileHash });
  const uploadedChunks = data.uploaded || [];

  let uploadedCount = 0;

  for (let i = 0; i < chunks; i++) {
    if (uploadedChunks.includes(i)) {
      uploadedCount++;
      uploadProgress.value = Math.floor((uploadedCount / chunks) * 100);
      continue; // 已上传,跳过
    }

    const start = i * chunkSize;
    const end = Math.min(file.size, start + chunkSize);
    const chunkData = file.slice(start, end);
    const formData = new FormData();
    formData.append('file', chunkData);
    formData.append('fileHash', fileHash);
    formData.append('index', i);

    await axios.post('/upload/chunk', formData, {
      onUploadProgress: e => {
        // 分片进度可加权到整体进度
        const chunkProgress = e.loaded / e.total;
        uploadProgress.value = Math.floor(
          ((uploadedCount + chunkProgress) / chunks) * 100
        );
      },
    });

    uploadedCount++;
    uploadProgress.value = Math.floor((uploadedCount / chunks) * 100);
  }

  // 分片上传完成,通知合并
  await axios.post('/upload/merge', { fileHash, totalChunks: chunks });
}

// 选择文件上传
async function beforeUpload(file) {
  uploading.value = true;
  uploadProgress.value = 0;
  fileList.value = [file];

  // 计算Hash
  const fileHash = await calculateFileHash(file);

  // 分片上传
  await uploadFileChunks(file, fileHash);

  uploading.value = false;
  ElMessage.success('文件上传完成!');

  return false; // 阻止默认上传
}
</script>

1.文件 Hash 的作用是什么?为什么要计算 Hash?

Hash 用作文件的唯一标识,可以判断文件是否已经上传过(秒传),也可以实现断点续传。

同样,合并分片后可以通过 Hash 校验文件完整性。

2.Hash 是怎么计算的?为什么要用增量计算?

使用 FileReader 将文件分片读取,逐块用 SparkMD5 增量计算 Hash。

对大文件一次性计算 Hash 内存占用大且阻塞界面,增量计算避免一次性加载整个文件。

fileReader.readAsArrayBuffer异步读取分片,触发fileReader.onload回调添加到spark中

3.大文件上传可能出现性能瓶颈,你如何优化?

并发上传多分片,充分利用带宽,提高上传速度。

分片大小调节,避免请求次数过多或分片过大导致单次失败。

Hash 计算优化,例如只读取前 N MB + 文件大小组合做快速 Hash。

4.前端上传大量分片时,浏览器内存会不会撑爆?如何避免?

通过分片逐块读取,每次只在内存中处理当前分片,读取完成后释放内存。

5.单个分片上传失败怎么处理?

前端可设置自动重试次数(如 3 次)。

若多次失败,提示用户网络异常或重试。

6.分片上传完成后如何合并?

按分片索引顺序读取所有分片,顺序写入最终文件,生成完整文件。

合并完成后再次计算文件 Hash 或 MD5,与客户端 Hash 比对,如果一致,说明文件完整。


网站公告

今日签到

点亮在社区的每一天
去签到