欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 科技 > 能源 > 前端切片上传、上传进度、断点续传、秒传

前端切片上传、上传进度、断点续传、秒传

2025/4/5 10:03:43 来源:https://blog.csdn.net/qq_36816794/article/details/146986740  浏览:    关键词:前端切片上传、上传进度、断点续传、秒传

一:整体流程

1.1. 核心流程图解

已存在
部分存在
不存在
开始上传
文件分片
计算文件Hash
查询服务器状态
秒传成功
上传剩余分片
上传全部分片
合并文件
上传完成

1.2. 关键实现步骤

  1. 文件分片与哈希计算
    • 将大文件切割为多个固定大小的块(如 1MB/片)
    • 使用 SparkMD5 计算文件唯一指纹
  2. 断点续传实现
    • 通过文件哈希查询服务器已上传分片
    • 仅上传缺失的分片
  3. 并发控制策略
    • 使用 Promise 实现请求队列
    • 限制最大并发数(默认 3 个并行请求)
  4. 进度同步机制
    • 基于已上传分片数计算整体进度
    • 通过回调函数实时更新进度

二:核心代码实现解析

2.1. 文件分片与哈希计算

/*** 将目标文件分片 并 计算文件Hash* @param {File} targetFile 目标上传文件* @param {number} baseChunkSize 上传分块大小,单位Mb* @returns {chunkList:ArrayBuffer,fileHash:string}*/
async function sliceFile(targetFile, baseChunkSize = 1) {return new Promise((resolve, reject) => {// 初始化分片方法let blobSlice =File.prototype.slice ||File.prototype.mozSlice ||File.prototype.webkitSlice;let chunkSize = baseChunkSize * 1024 * 1024;// 分片数let targetChunkCount = targetFile && Math.ceil(targetFile.size / chunkSize);// 当前已执行分片数let currentChunkCount = 0;// 当前已收集分片数let chunkList = [];let spark = new SparkMD5.ArrayBuffer();let fileReader = new FileReader();let fileHash = null;// 检查文件是否存在if (!targetFile) {return reject(new Error('文件不存在'));}fileReader.onload = e => {const curChunk = e.target.result;// 将当前分快追加到 spark 对象中spark.append(curChunk);currentChunkCount++;chunkList.push(curChunk);// 判断分快是否全部读取成功if (currentChunkCount >= targetChunkCount) {// 全部读取完成,计算文件HashfileHash = spark.end();resolve({chunkList,fileHash});} else {loadNext();}};fileReader.onerror = () => reject(new Error('文件读取失败'));// 未全部读取完成,读取下一个分快const loadNext = () => {// 计算分快的起始位置和终止位置const start = chunkSize * currentChunkCount;const end = start + chunkSize;if (end > targetFile.size) end = targetFile.size;// 读取文件,触发 fileReader.onload 事件fileReader.readAsArrayBuffer(blobSlice.call(targetFile, start, end));};loadNext();});
}

关键点说明:

  • 使用 File.slice() 实现文件切割
  • 分片大小建议根据实际场景调整(视频文件可适当增大)
  • MD5 计算全程增量更新,避免内存溢出

2.2. 分片上传队列控制

/*** 将文件分片数据发送到服务器* @param {Array<Object>} postFormData 包含 FormData 的对象数组,由 uploadChunk 函数生成* @param {number} limit 并发上传的最大数量,默认为 3* @param {string} uploadUrl 上传文件的服务器地址* @returns {Promise<boolean>} 上传成功返回 true* @throws {Error} 上传失败抛出错误* * 实现了以下功能:* 1. 控制并发上传数量* 2. 失败自动重试,最多重试 3 次* 3. 跟踪上传进度* 4. 所有分片上传完成后返回结果*/
function postToServer(postFormData, limit = 3, uploadUrl) {return new Promise((resolve, reject) => {if (!postFormData || !postFormData.length) {resolve(true);return;}if (!uploadUrl) {reject(new Error('上传URL不能为空'));return;}let len = postFormData.length;let counter = 0;let isStop = false;const startPost = async () => {// 检查是否还有数据需要上传if (postFormData.length === 0 || isStop) {return;}const formDatas = postFormData.shift();if (!formDatas) return;try {await requestInstance.post(uploadUrl, formDatas.formData);counter++;formDatas.progress = Math.ceil(counter / len * 100);// 所有请求都已结束,返回结果if (counter === len) {resolve(true);return;}// 请求还未结束,继续启动任务startPost();} catch (error) {if (formDatas.error >= 3) {isStop = true;reject(new Error('上传失败,已重试3次'));return;}formDatas.error++;// 将错误的内容放到数据列表中,然后立马重试postFormData.unshift(formDatas);startPost();}};// 限制并发数,启动上传任务const actualLimit = Math.min(limit, postFormData.length);for (let index = 0; index < actualLimit; index++) {startPost();}});
}

并发控制要点:

  • 使用计数器控制最大并行数
  • 任务队列动态管理
  • 错误自动重试机制(默认最多重试3次)

2.3. 断点续传实现逻辑

/*** @param {File} file 目标上传文件* @param {number} baseChunkSize 上传分块大小,单位Mb* @param {string} uploadUrl 上传文件的后端接口地址* @param {string} vertifyUrl 验证文件上传的接口地址* @param {string} mergeUrl 请求进行文件合并的接口地址* @param {Function} progress_cb 更新上传进度的回调函数* @returns {Promise}*/
async function uploadFile(file,baseChunkSize,uploadUrl,vertifyUrl,mergeUrl,progress_cb
) {try {if (!file) {throw new Error('文件不能为空');}if (!progress_cb || typeof progress_cb !== 'function') {progress_cb = progress => {console.log(`上传进度: ${progress}%`);};}const { chunkList, fileHash } = await sliceFile(file, baseChunkSize);let allChunkList = chunkList;// 需要上传的分片let neededFileList = [...allChunkList]; // 默认所有分片都需要上传let progress = 0;if (vertifyUrl) {try {const { data } = await requestInstance.post(vertifyUrl, {fileHash,totalCount: allChunkList.length,extname: '.' + file.name.split('.').pop()});const { needFileList, message } = data;if (message) console.info(message);// 无待上传文件,秒传if (needFileList && needFileList.length === 0) {progress_cb(100);return { success: true, message: '文件秒传成功' };}// 部分上传成功,更新需要上传的分片if (needFileList) {neededFileList = needFileList;}} catch (error) {console.error('验证文件上传状态失败:', error);// 验证失败时继续上传所有分片}}// 断点续传// 同步上传进度progress =(allChunkList.length - neededFileList.length) / allChunkList.length * 100;progress_cb(progress);// 上传if (neededFileList.length) {const postFormData = uploadChunk(neededFileList, fileHash);await postToServer(postFormData, 3, uploadUrl);// 发送请求,通知后端合并const extname = '.' + file.name.split('.').pop();try {await requestInstance.post(mergeUrl, { fileHash, extname });} catch (error) {console.error('文件合并请求失败:', error);throw new Error('文件合并失败');}// 上传完成后更新进度为100%progress_cb(100);return { success: true, message: '文件上传成功' };}return { success: true, message: '文件上传完成' };} catch (error) {console.error('文件上传过程中发生错误:', error);progress_cb(0); // 重置进度throw error;}
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词