前端使用 spark-md5 实现大文件切片上传

发布于:2025-06-02 ⋅ 阅读:(23) ⋅ 点赞:(0)

需要计算文件MD5和、分片MD5:

封装公共方法代码如下:

import SparkMD5 from "spark-md5"

/**
 * 计算文件MD5
 * @param file
 * @returns
 */
export function calculateFileMD5(file) {
  return new Promise((resolve) => {
    const reader = new FileReader();
    reader.onload = function (e) {
      const spark = new SparkMD5.ArrayBuffer();
      spark.append(e.target.result);
      resolve(spark.end());
    };
    reader.readAsArrayBuffer(file);
  })
}

/**
 * 计算分片MD5
 * @param chunk 当前切切片
 * @returns
 */
export function calculateChunkMD5(chunk) {
  return new Promise((resolve) => {
    const reader = new FileReader();
    reader.onload = function (e) {
      const spark = new SparkMD5.ArrayBuffer();
      spark.append(e.target.result);
      resolve(spark.end());
    };
    reader.readAsArrayBuffer(chunk);
  })
}

在上传页面调用:

<template>
  <form id="uploadForm">
    <div class="fileBox">
      <span class="fileLabel">文件:&nbsp; </span>
      <input type="file" id="file" name="file">
    </div>
    <el-progress id="progressBar" :percentage="progress" style="width: 335px;"></el-progress>
    <button type="submit">提交</button>
  </form>
</template>

<script setup>
import { calculateFileMD5, calculateChunkMD5 } from './calculateMd5'
import { ElMessage, ElMessageBox } from 'element-plus'
import axios from 'axios'
import { getToken } from "@/utils/auth";
const emit = defineEmits(['send-upload'])
const uploadChunkUrl = ref(import.meta.env.VITE_APP_BASE_API + "/dbms/file/chunk"); //上传接口
const mergeUrl = ref(import.meta.env.VITE_APP_BASE_API + "/dbms/file/merge"); //合并接口

const file = ref(null)
const progress = ref(0)

async function uploadFile(file) {
  const chunkSize = 5 * 1024 * 1024; // 5MB每片
  const totalChunks = Math.ceil(file.size / chunkSize);
  const fileMd5 = await calculateFileMD5(file); // 计算整个文件的MD5

  for (let i = 0; i < totalChunks; i++) {
    const start = i * chunkSize;
    const end = Math.min(file.size, start + chunkSize);
    const chunk = file.slice(start, end);
    const chunkMd5 = await calculateChunkMD5(chunk); // 计算当前分片的MD5
    progress.value = Math.round((i / totalChunks) * 100) // 计算上传进度

    const formData = new FormData();
    
    formData.append('file', chunk);
    formData.append('chunkNumber', i + 1);
    formData.append('totalChunks', totalChunks);
    formData.append('fileMd5', fileMd5);
    formData.append('chunkMd5', chunkMd5);
    formData.append('fileName', file.name);

    await axios.post(uploadChunkUrl.value, formData, {
      headers: { 'Content-Type': 'multipart/form-data', 'Authorization': 'Bearer ' + getToken() }
    });
  }

  // 所有分片上传完成后,通知后端合并
  const res = await axios.post(mergeUrl.value, {
    fileMd5: fileMd5,
    fileName: file.name,
    totalChunks: totalChunks
  });
  // console.log(res)
  if (res.status == 200) {
    progress.value = 100
    ElMessage.success('文件上传成功!')
    // 上传成功 通知父组件关闭弹窗,清空下拉表单,更新列表数据
    emit('send-upload')
  }
}

onMounted(async () => {
  /**触发上传**/
  document.getElementById("uploadForm").onsubmit = async function (event) {
    event.preventDefault();
    
    const fileInput = document.getElementById("file");
    file.value = fileInput.files[0];
    if (!file.value) {
      ElMessage.warning("请选择文件!")
      return;
    }
    uploadFile(file.value)
  }
})
</script>