首页 > 其他分享 >大文件上传

大文件上传

时间:2024-04-19 23:11:06浏览次数:20  
标签:文件 const res formData await fileMd5 file 上传

大文件多线程切片并发上传

  • 技术使用:
    • File.slice
    • FileReader
    • spark-md5
    • web worker

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>大文件分片上传</title>
    <style>
      img {
        width: 120px;
      }
      .ball {
        width: 100px;
        height: 100px;
        border-radius: 50%;
        background-color: pink;
        animation: ball-move 3s linear infinite alternate;
      }
      @keyframes ball-move {
        0% {
          translate: 0 0;
        }
        100% {
          translate: calc(100vw - 100px - 2 * 8px) 0;
        }
      }
    </style>
  </head>
  <body>
    <div class="ball"></div>
    <hr />
    <input type="file" id="fileInput" hidden />
    <button type="button" id="uploadBtn">上传文件</button>
    <button type="button" id="pauseBtn">暂停上传</button>
    <button type="button" id="resumeBtn">继续上传</button>
    <hr />
    <ul class="fileList"></ul>

    <script type="module">
      import SparkMD5 from './spark-md5.js';

      const uploadBtn = document.getElementById('uploadBtn');
      const fileInput = document.getElementById('fileInput');
      const fileList = document.querySelector('.fileList');
      const pauseBtn = document.getElementById('pauseBtn');
      const resumeBtn = document.getElementById('resumeBtn');

      const spark = new SparkMD5.ArrayBuffer();

      pauseBtn.addEventListener('click', async () => {
        const res = await fetch('http://localhost:3001/upload-info').then((res) => res.json());
        console.log('res => ', res);
      });

      resumeBtn.addEventListener('click', async () => {
        await mergeChunks('4f81fe0634a6de12');
      });

      async function uploadFile(file) {
        const formData = new FormData();
        formData.append('file', file);

        const res = await fetch('http://localhost:3001/upload', {
          method: 'POST',
          body: formData,
        }).then((res) => res.json());
      }

      uploadBtn.addEventListener('click', () => {
        fileInput.click();
      });

      fileInput.addEventListener('change', async () => {
        /** @type {File} */
        const file = fileInput.files[0];

        if (!file) return;

        console.log('file.size => ', file.size);

        const chunkSize = 1024 * 64 * 1; // 64KB
        const chunks = Math.ceil(file.size / chunkSize);

        console.time('md5计算耗时');
        // 创建分片, 并每次上传一个分片
        // let currentChunk = 0;
        // // const fileMd5 = await getFileMd5(file);
        // const fileMd5 = await getHash(file);
        // await createChunks(file, chunkSize, fileMd5, currentChunk, chunks);

        const { fileMd5, chunkList } = await createWorker(file, chunkSize, chunks);
        console.log('data => ', fileMd5, chunkList);

        // 检测哪些文件是否已上传过,并上传剩余的分片
        // await checkFile(fileMd5, chunkList);

        const fetchList = chunkList.map((chunk) => {
          const formData = new FormData();
          formData.append('file', chunk.blob);
          formData.append('chunk', chunk.index);
          formData.append('chunks', chunks);
          formData.append('chunkId', chunk.hash);
          formData.append('fileId', fileMd5);
          formData.append('mime', file.type);

          return fetch('http://localhost:3001/upload2', {
            method: 'POST',
            body: formData,
          });
        });

        const res = await Promise.all(fetchList);
        // console.log('res => ', res);
        await mergeChunks(fileMd5, file.type);

        console.timeEnd('md5计算耗时');
      });

      function createWorker(file, chunkSize, chunks) {
        return new Promise((resolve) => {
          const worker = new Worker('./file-worker.js', {
            type: 'module',
          });
          worker.onmessage = (e) => {
            resolve(e.data);
          };
          worker.postMessage({ file, chunkSize, chunks });
        });
      }

      /**
       *创建分片
       * @param {File} file 文件
       * @param {number} chunkSize 分片大小
       * @param {string} fileMd5 文件md5
       * @param {number} currentChunk 当前分片索引
       * @param {number} chunks 分片总数
       */
      async function createChunks(file, chunkSize, fileMd5, currentChunk, chunks) {
        let start = currentChunk * chunkSize;
        let end = Math.min(start + chunkSize, file);
        const blob = file.slice(start, end);

        const formData = new FormData();
        formData.append('file', blob);
        formData.append('chunk', currentChunk);
        formData.append('chunks', chunks);
        // formData.append('chunkId', createMD5(await blob.text()));
        formData.append('chunkId', await getHash(blob));
        formData.append('fileId', fileMd5);

        try {
          const res = await fetch('http://localhost:3001/upload2', {
            method: 'POST',
            body: formData,
          });
          if (res.ok) {
            console.log(`分片${currentChunk}上传成功`);
            currentChunk++;
            if (currentChunk < chunks) {
              await createChunks(file, chunkSize, fileMd5, currentChunk, chunks);
            } else {
              console.log('fileMd5 => ', fileMd5);
              await mergeChunks(fileMd5);
            }
          }
        } catch (err) {
          console.log(err);
        }
      }

      function getHash(blob) {
        return new Promise((resolve) => {
          const reader = new FileReader();
          reader.onload = (e) => {
            const data = e.target.result;
            spark.append(data);
            resolve(spark.end());
          };
          reader.readAsArrayBuffer(blob);
        });
      }

      async function checkFile(fileMd5) {
        const res = await fetch(`http://localhost:3001/upload-info/${fileMd5}`).then((res) => res.json());
        console.log('res => ', res);
      }

      async function mergeChunks(fileId, fileType) {
        if (!fileId) throw new Error('fileId is required');

        const res = await fetch(`http://localhost:3001/merge/${fileId}`, {
          method: 'POST',
          body: JSON.stringify({ mime: fileType }),
        }).then((res) => res.json());
        console.log('res => ', res);
        // if (res.code === 0) {
        //   const img = new Image();
        //   img.src = `http://localhost:3001${res.data.url}`;
        //   const li = document.createElement('li');
        //   li.appendChild(img);
        //   fileList.appendChild(li);
        // }
      }
    </script>
  </body>
</html>
import SparkMD5 from './spark-md5.js';

// 监听来自主线程的消息,计算文件的 MD5 值并返回处理后的分片列表
self.addEventListener('message', async (e) => {
  const { file, chunkSize, chunks } = e.data;

  // 计算可用的线程数,减去2以确保不占用所有CPU资源
  const threadCount = navigator.hardwareConcurrency - 2 || 4;
  // 调用 createWorkers 函数,使用多线程处理文件分片
  const chunkList = await createChunkWorkers(file, chunkSize, chunks, threadCount);

  // 将分片的哈希值拼接成字符串,并计算文件的 MD5 值
  const fileMd5 = await createFileHash(chunkList);

  // 向主线程发送文件的 MD5 值和处理后的分片列表
  postMessage({ fileMd5, chunkList });
  self.close();
});

// 创建多个线程处理文件分片
function createChunkWorkers(file, chunkSize, chunks, threadCount) {
  return new Promise((resolve) => {
    // 每个线程需要处理的分片数
    const threadChunkCount = Math.ceil(chunks / threadCount);

    // 完成的线程数
    let finishedCount = 0;
    const result = new Array(chunks).fill('');
    // 循环创建线程,并监听线程消息
    for (let i = 0; i < threadCount; i++) {
      const worker = new Worker('./chunk-worker.js', {
        type: 'module',
      });
      worker.onmessage = (e) => {
        // 处理线程返回的分片列表数据
        const { chunkList } = e.data;

        // 将每个分片的数据存入结果数组中
        chunkList.forEach((chunk) => {
          result[chunk.index] = chunk;
        });

        finishedCount++;
        // 如果所有线程处理完成,则返回结果数组
        if (finishedCount === threadCount) {
          resolve(result);
        }
      };
      // 向线程发送处理文件分片的消息
      worker.postMessage({
        index: i,
        file,
        start: i * threadChunkCount,
        end: Math.min((i + 1) * threadChunkCount, chunks),
        chunkSize,
      });
    }
  });
}

// 计算文件的 MD5 值
async function createFileHash(list) {
  const spark = new SparkMD5.ArrayBuffer();
  for await (const { buf } of list) {
    spark.append(buf);
  }
  return spark.end();
}
import SparkMD5 from './spark-md5.js';

// 添加事件监听器,当接收到消息时运行该异步函数
self.addEventListener('message', async (e) => {
  // 从消息中获取索引、文件、起始位置、结束位置和块大小
  const { index, file, start, end, chunkSize } = e.data;
  // 创建空的块列表
  const chunkList = [];

  // 循环计算每个块的哈希并加入块列表
  for (let i = start; i < end; i++) {
    // 从文件中切割出一个块
    const blob = file.slice(i * chunkSize, (i + 1) * chunkSize);

    // 计算块的MD5哈希,并将索引、哈希和块本身加入块列表
    const { buf, hash } = await createHash(blob);
    chunkList.push({
      index: i,
      hash,
      buf,
      blob,
    });
  }

  // 发送包含索引和块列表的消息
  postMessage({ index, chunkList });
  // 关闭当前 worker
  self.close();
});

// 创建哈希值的函数
function createHash(blob) {
  return new Promise((resolve) => {
    const fileReader = new FileReader();
    const spark = new SparkMD5.ArrayBuffer();

    // 当文件读取完成后生成MD5哈希值
    fileReader.addEventListener('load', (e) => {
      const buf = e.target.result;
      spark.append(buf);
      resolve({ buf, hash: spark.end() });
    });
    fileReader.readAsArrayBuffer(blob);
  });
}

标签:文件,const,res,formData,await,fileMd5,file,上传
From: https://www.cnblogs.com/chlai/p/18146970

相关文章

  • python 修改jenkins的配置文件
    python有jenkins获取配置文件的api,也有修改配置文件的api,下面介绍下如果修改jenkinsjob的配置文件内容:importreimporttimeimportjenkinsjenkins_url="http://xxx.com/jenkins"username="zhangsan"token="1.......de"jenkins=jenkins.Jenkins(jenkins_url,us......
  • SpringBoot 上传图片
    1概述新做的博客系统需要在markdown文本中插入图片,之前完成过上传图片的相关配置,但未做总结,借着这个机会,对于springboot上传图片接口的相关配置和操作,做一个系统性阐述。以作为未来相关业务的参考。本文主要阐述后端相关配置,少量前端(vue3)内容仅是为了作为测试。2配置文......
  • 文件体积达到 1 GB 甚至 1 TB 的图片会呈现何种内容?
    作者:文森特汪的热血链接:https://www.zhihu.com/question/360608822/answer/1253394326来源:知乎著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。我就存了一张1.34G的图片,存在电脑里这是它的截图<imgsrc="https://picx.zhimg.com/50/v2-8136c3e43f6cbffb2aa4......
  • 在Linux中如何删除指定时间之前修改的文件?
    1、与文件有关的时间在说明如何删除符合这种要求的文件之前,先来看看与文件有关的有哪些时间简名   全名    中文名  含义atime  access  time   访问时间 文件中的数据最后被访问的时间mtime  modify  time   修改时间 文件中的数据......
  • Unity获取指定文件夹下的所有文件
    usingSystem.IO;publicvoidGetFiles(){stringpath=string.Format("{0}",Application.streamingAssetsPath);//stringpath=string.Format("{0}",@"C:\Users\USER\Desktop\JXBWG\Assets\StreamingAssets&qu......
  • blog.admin net8发布二级目录,及图片上传路径处理
    1、发布二级目录,修改以下配置,及对应的二级目录名跟配置的一致 2、图片上传a、修改后台api代码imgController.cspublicasyncTask<MessageModel<string>>InsertPicture([FromForm]UploadFileDtodto){if(dto.file==null||!dto.file.Any())returnFail......
  • linux文件被谁删除了
     如果你也有如标题所属的困扰。可以尝试一下linuxaudit功能。1 是否能用1.1这个功能需要内核启用,要编译时打开了如下图所示的选项 1.2启动内核的时候,也不能是关着的。(红框这行不能有) 1.3 服务是不是开着的(下图就是内核选项没开时候的提示)systemctlstatusaud......
  • python 获取文件夹下所有fbx文件的名字并保存到txt文件中
    代码:importosdefget_fbx_files_and_write_to_txt(folder_path,output_file_path):fbx_files=[]#遍历指定文件夹中的所有文件foriteminos.listdir(folder_path):item_path=os.path.join(folder_path,item)#检查是否为文件,并且......
  • 内核config文件打开CONFIG_DEVMEM后出现For kernel requirements at matrix level 5,
    内核config文件打开CONFIG_DEVMEM后出现编译错误:checkvintfI04-1823:30:02409602409602check_vintf.cpp:84]List'out/target/product/sc126/system/product/etc/vintf/':NosuchfileordirectorycheckvintfI04-1823:30:02409602409602check_vintf.cpp:84]L......
  • linux使用官方安装包安装的lazarus如需修改lazarus文件需要用root权限
    最近有网友反馈linux使用官方安装包安装的lazarus,按尝试解决linux下Lazarus2.2.0版代码编辑器和SynEdit不支持中文输入的Bug(2024.02.27解决《修正LinuxLazarusIDE代码编辑器中文输入》后用fpcupdeluxe重新编译lazarus时出错的Bug)-秋·风-博客园(cnblogs.com),修改后重构laz......