前端开启多线程实现大文件上传

一、概念

文件切片上传的简单理解:前端先将文件分割成多份,再进行上传,由后端进行切片合并操作

二、具体实现

1.HTML 结构

<template>
  <form action="/upload" method="post"></form>
    <input type="file" name="file" />
  </form>
</template>

2.监听文件选择事件

import { cutFile } from "./cutFile.js";
const inpFile = document.querySelector('input[type="file"]');
inpFile.onchange = async e => {
	const file = e.target.files[0];
	console.time("cutFile"); // 记录时间
	const chunks = await cutFile(file);
	console.timeEnd("cutFile");
};

3.获取文件 hash 值工具函数

使用 spark-md5 来获取文件 hash 值

import "./spark-md5.min.js";
function createChunk(file, index, chunkSize) {
	return new Promise(resolve => {
		const start = index * chunkSize;
		const end = start + chunkSize;
		const spark = new SparkMD5.ArrayBuffer();
		const fileReader = new FileReader();
		const blob = file.slice(start, end);
		fileReader.onload = e => {
			spark.append(e.target.result);
			resolve({ start, end, index, hash: spark.end(), blob });
		};
		fileReader.readAsArrayBuffer(blob);
	});
}

4.进行文件切片并开启多线程

  • 协商分片文件的大小
  • 利用 cpu 多线程进行文件切片
const CHUNK_SIZE = 1024 * 1024 * 5;
const THREAD_COUNT = navigator.hardwareConcurrency || 4;
function cutFile(file) {
	return new Promise(resolve => {
		const chunkCount = Math.ceil(file.size / CHUNK_SIZE);
		const threadChunkCount = Math.ceil(chunkCount / THREAD_COUNT);
		const result = [];
		let finishCount = 0;
		for (let index = 0; index < THREAD_COUNT; index++) {
			// 分配线程任务
			const worker = new Worker("worker.js", { type: "module" });
			const start = index * threadChunkCount;
			let end = (index + 1) * threadChunkCount;
			if (end > chunkCount) end = chunkCount;
			// 发送数据到 Worker
			worker.postMessage({ file, start, end, CHUNK_SIZE });
			// 监听 Worker 发送的消息
			worker.onmessage = function (event) {
				result[index] = event.data;
				worker.terminate();
				finishCount++;
				if (finishCount === THREAD_COUNT) resolve(result.flat());
			};
		}
	});
}

5.线程任务

利用自己的 cpu 开启线程,完成计算任务,并返回结果。

// 监听主线程发送的消息
import { createChunk } from "./createChunks.js";
// 监听主线程发送的消息
self.onmessage = async function (event) {
	const { file, start, end, CHUNK_SIZE } = event.data;
	const result = [];
	for (let i = start; i < end; i++) {
		const prom = await createChunk(file, i, CHUNK_SIZE);
		result.push(prom);
	}
	const chunks = await Promise.all(result);
	// 发送结果回主线程
	self.postMessage(chunks);
};

三、总结

通过上述的代码,我们已经基本实现了前端大文件上传、续传的功能,这其中还有许多可以完善的点,就交给小伙伴们自己挖掘了,感谢大家阅读。

上次更新 2025/3/21 15:13:52