应用场景
文件上传之后,获得文件的唯一标识(MD5值),将文件切割成多个分块,根据文件标识查询文件未上传的分块,所有分块上传完之后,将分块进行合并。
实现原理
1、上传文件
1 2 3 4 5
| const uploadProps = { name: "file", accept: ".zip", maxCount: 1, }
|
1 2 3 4 5 6 7 8 9
| <Dragger {...uploadProps}> <p className="ant-upload-drag-icon"> <InboxOutlined /> </p> <p className="ant-upload-text">点击或将文件拖拽到这里上传</p> <p className="ant-upload-hint"> 支持文件扩展名:.zip </p> </Dragger>
|
2、文件预处理
在文件上传之前,beforeUpload里对文件进行切割预处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| // 兼容性的处理 const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice; const chunkSize = 1024 * 1024 * 5; // 切片每次5M const chunks = Math.ceil(file.size / chunkSize); let currentChunk = 0; // 当前上传的chunk const spark = new SparkMD5.ArrayBuffer(); // 对arrayBuffer数据进行md5加密,产生一个md5字符串 const chunkFileReader = new FileReader(); // 用于计算出每个chunkMd5 const params = { chunks: [], file: {}}; // 用于上传所有分片的md5信息 const arrayBufferDataT = []; // 用于存储每个chunk的arrayBuffer对象,用于分片上传使用 /* 文件合并所需参数 */ params.file.fileName = file.name; // 文件名 params.file.totalSize = file.size; // 文件总大小 params.file.fileType = file.type; // 文件类型 params.file.relativePath = file.name; // 相对路径 params.file.refProjectId = "dbxt-sf-gd-sz"; // 文件唯一标识
function loadNext() { const start = currentChunk * chunkSize; const end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize; chunkFileReader.readAsArrayBuffer(blobSlice.call(file, start, end)); } loadNext(); let identifier; chunkFileReader.onload = function(e) { // 对每一片分片进行md5加密 if (currentChunk === 0) { spark.append(e.target.result); identifier = spark.end(); } // 每一个分片需要包含的信息 const obj = { totalSize: file.size, relativePath: file.name, chunkNumber: currentChunk + 1, // 块编号 fileName: file.name, identifier, // 块编号 totalChunks: chunks, // 总分片数 chunkSize, // 块大小 currentChunkSize: ((currentChunk * chunkSize + chunkSize) >= file.size) ? file.size - currentChunk * chunkSize : chunkSize // 当前块大小 }; // 每一次分片onload,currentChunk都需要增加,以便来计算分片的次数 currentChunk += 1; params.chunks.push(obj);
// 将每一块分片的arrayBuffer存储起来,用来partUpload const tmp = { chunk: obj.chunkNumber, currentBuffer: e.target.result }; arrayBufferDataT.push(tmp); if (currentChunk < chunks) { // 当前切片总数没有达到总数时 loadNext(); // 计算预处理进度 setpreUploading(true); setpreUploadPercent(Number((currentChunk / chunks * 100).toFixed(2))); } else { setchunksSize(chunks); setpreUploadPercent(100); setFileList([file]); // 记录所有chunks的长度 params.file.identifier = identifier; params.file.fileChunks = params.chunks.length; // 表示预处理结束,将上传的参数,arrayBuffer的数据存储起来 setarrayBufferData(arrayBufferDataT); setpreUploading(false); setuploadParams(params); } };
chunkFileReader.onerror = function() { console.warn("oops, something went wrong."); };
|
3、上传分块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| try { // 判断是否上传 const data = await examChunk(chunks[0]); let { uploadedChunks } = data || []; // 已上传文件 uploadedChunks = Array.from(new Set(uploadedChunks)); if (uploadedChunks.length === uploadParams.file.fileChunks || data.skipUpload) // 如果全部文件快已上传 则直接完成 实现秒传 { setuploadPercent(100); // 文件上传进度 mergeFile();// 合并文件 } else { setuploadPercent(Number((uploadedChunks.length / uploadParams.file.fileChunks * 100).toFixed(2))); const uploadList = chunks.filter(item => { return uploadedChunks.indexOf(item.chunkNumber) < 0; });// 过滤出未上传分片 handlePartUpload(uploadList, uploadList.length); // 分片上传 } } catch (error) { console.log(error); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| // 分片上传 const handlePartUpload = (uploadList, currentChunks) => { let current = currentChunks; uploadList.forEach(async(element) => { const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice; const { identifier, chunkNumber } = element; const formData = new FormData(); // 新建一个Blob对象,将对应分片的arrayBuffer加入Blob中 const blob = new Blob([arrayBufferData[chunkNumber - 1].currentBuffer], { type: "application/octet-stream" }); // 将生成blob塞入到formdata中传入服务端 formData.append("upfile", blob, identifier); Object.keys(element).forEach(el => { formData.append(el, element[el]); }); try { const uploadData = await upload(formData); if (uploadData === 200) { current -= 1; setuploadPercent(Number(((uploadParams.file.fileChunks - current) / uploadParams.file.fileChunks * 100).toFixed(2))); if (current === 0) { mergeFile(); } } else { setUploadFailed(true); setspinning(false); } } catch (error) { setUploadFailed(true); setspinning(false); console.log(error); } }); };
|
4、合并文件块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| const mergeFile = async() => { try { const mergeData = await merge(uploadParams.file); if (mergeData.id) { // setUpfileId(mergeData.id); // message.success('上传成功'); createData(mergeData.id); setuploading(false); setuploaded(true); } else { setspinning(false); setUploadFailed(true); } } catch (error) { setspinning(false); setUploadFailed(true); } };
|