JS实现大文件上传

80 阅读5分钟

JS 大文件上传 核心知识讲解

大文件上传的核心解决方案是分片上传,搭配断点续传、秒传优化,解决大文件上传超时、失败、占用带宽过高的问题,是前端开发必备实用技能。 核心逻辑:将大文件按固定大小切割为小分片,逐个上传,后端接收所有分片后合并为完整文件。

一、核心核心原理(3大核心)

1. 分片上传:前端把大文件(如100MB)切成多个小分片(如5MB/片),分批请求上传,避免单次请求数据过大导致超时/失败。 2. 秒传:上传前先计算文件唯一哈希值,传给后端校验,若后端已存在相同哈希的文件,直接返回上传成功,无需重复上传。 3. 断点续传:上传中断后,前端请求后端查询已上传的分片,仅重新上传未完成的分片,无需从头开始。

二、前端核心实现步骤(完整可落地)

  1. 前置准备:核心API依赖

前端实现全靠2个核心API,无需额外插件,原生JS即可实现

  •  File.slice(start, end) :切割文件,核心分片API,返回新的Blob对象(分片文件)。
  •  SparkMD5 :第三方轻量库(推荐),用于计算文件哈希值(比原生API稳定、高效),支持分片计算大文件哈希,避免卡顿。
  1. 5步实现基础分片上传

1. 配置基础参数:定义分片大小(如 510241024  = 5MB)、文件唯一标识、上传接口地址。 2. 文件分片处理:通过 File.slice() 循环切割文件,生成分片数组,记录每个分片的索引、大小。 3. 计算文件哈希:用 SparkMD5 读取文件二进制数据,生成唯一哈希(用于秒传、断点续传校验)。 4. 分片逐个上传:遍历分片数组,用 axios/fetch 上传,携带文件哈希、分片索引、总分片数3个核心参数。 5. 通知后端合并:所有分片上传成功后,调用后端合并接口,告知后端该哈希的文件分片已全部上传,触发合并。

  1. 关键优化:断点续传+秒传实现
  • 秒传:计算出文件哈希后,先调用后端「校验接口」,后端判断哈希是否存在,存在则直接返回成功,跳过上传流程。
  • 断点续传:调用后端「查询已上传分片接口」,传入文件哈希,后端返回已上传的分片索引列表,前端过滤掉已上传分片,只传未完成的。

三、核心代码示例(前端关键代码)

  1. 依赖引入(SparkMD5)

html

 

  1. 核心上传逻辑(完整JS代码)

javascript

// 1. 核心配置 const config = { chunkSize: 5 * 1024 * 1024, // 分片大小5MB uploadUrl: '/api/upload/chunk', // 分片上传接口 checkUrl: '/api/upload/check', // 秒传/断点续传校验接口 mergeUrl: '/api/upload/merge', // 分片合并接口 };

// 2. 计算文件哈希(核心:大文件异步计算,避免卡顿) function calculateFileHash(file) { return new Promise((resolve) => { const spark = new SparkMD5.ArrayBuffer(); const fileReader = new FileReader(); const chunkSize = config.chunkSize; const totalChunks = Math.ceil(file.size / chunkSize); let currentChunk = 0;

fileReader.onload = (e) => {
  spark.append(e.target.result);
  currentChunk++;
  if (currentChunk >= totalChunks) {
    resolve(spark.end()); // 最终哈希值
  } else {
    loadNextChunk();
  }
};

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

}); }

// 3. 分片上传核心函数 async function uploadLargeFile(file) { const fileSize = file.size; const fileName = file.name; const chunkSize = config.chunkSize; const totalChunks = Math.ceil(fileSize / chunkSize); const fileHash = await calculateFileHash(file); // 拿到文件唯一哈希

// 第一步:校验文件(秒传+断点续传) const checkRes = await axios.post(config.checkUrl, { fileHash, fileName }); if (checkRes.data.isExist) return alert('秒传成功!'); // 秒传直接返回 const uploadedChunks = checkRes.data.uploadedChunks || []; // 已上传分片索引

// 第二步:上传未完成的分片 for (let index = 0; index < totalChunks; index++) { if (uploadedChunks.includes(index)) continue; // 跳过已上传分片 const start = index * chunkSize; const end = Math.min(start + chunkSize, fileSize); const chunk = file.slice(start, end); // 切割分片 // 构造表单数据,携带核心参数 const formData = new FormData(); formData.append('fileChunk', chunk); formData.append('fileHash', fileHash); formData.append('chunkIndex', index); formData.append('totalChunks', totalChunks); formData.append('fileName', fileName); // 上传单个分片 await axios.post(config.uploadUrl, formData, { headers: { 'Content-Type': 'multipart/form-data' }, onUploadProgress: (e) => { // 进度条计算:(已上传分片数+当前分片进度)/总分片数 const progress = ((index + e.loaded/e.total) / totalChunks) * 100; console.log(上传进度:${progress.toFixed(2)}%); } }); }

// 第三步:所有分片上传完成,请求合并 await axios.post(config.mergeUrl, { fileHash, fileName, totalChunks }); alert('大文件上传成功!'); }  

四、前后端交互核心约定

前端上传的关键参数,后端必须接收并处理,否则无法完成分片合并,核心约定如下:

1. 校验接口:前端传 fileHash + fileName  → 后端返回 是否已存在(秒传) + 已上传分片索引(断点续传) 。 2. 分片上传接口:前端传 fileChunk(分片文件) + fileHash(文件唯一标识) + chunkIndex(分片索引) + totalChunks(总分片数)  → 后端接收后按「文件哈希-分片索引」命名存储,避免混淆。 3. 合并接口:前端传 fileHash + fileName + totalChunks  → 后端按分片索引排序,拼接所有分片为完整文件,删除临时分片文件。

五、避坑要点(前端必看)

1. 哈希计算卡顿:大文件(如1GB+)计算哈希时,需异步分片计算(如代码中 calculateFileHash 实现),避免主线程阻塞导致页面卡死。 2. 分片大小选择:建议5-10MB,过小会增加请求次数,过大则失去分片意义,需根据项目带宽调整。 3. 上传顺序:无需严格按索引顺序上传(可并行上传),但合并时必须按索引排序,并行上传可提升上传速度(需后端支持)。 4. 跨域问题:上传接口需配置CORS,允许 multipart/form-data 请求,且后端需允许携带自定义参数。 5. 中断处理:监听上传请求的中断事件,记录当前上传进度,方便后续断点续传。

六、进阶优化方案

1. 分片并行上传:同时上传多个分片(如并发3个),提升上传效率,需控制并发数避免请求过多。 2. 分片重试机制:单个分片上传失败时,自动重试3次,仍失败则暂停上传,提示用户。 3. 进度条优化:结合已上传分片数和当前分片上传进度,计算精准的整体上传进度。 4. 大文件预览:上传前通过 URL.createObjectURL(file) 实现文件预览,提升用户体验。