徐州市做网站,重庆网站设计方案,做电子商务网站,上海市教育网官网大文件分片上传
内容 一般情况下#xff0c;前端上传文件就是new FormData,然后把文件 append 进去#xff0c;然后post发送给后端就完事了#xff0c;但是文件越大#xff0c;上传的文件也就越长#xff0c;如果在上传过程中#xff0c;突然网络故障#xff0c;又或者…大文件分片上传
内容 一般情况下前端上传文件就是new FormData,然后把文件 append 进去然后post发送给后端就完事了但是文件越大上传的文件也就越长如果在上传过程中突然网络故障又或者请求超时等待过久等等情况就会导致错误而后又得重新传大文件。所以这时候就要使用分片上传了就算断网了也能继续接着上传断点上传如果是之前上传过这个文件了服务器还存着就不需要做二次上传了秒传。 7.17实现方式 首先获取文件信息后设定分片大小对文件进行分片(slice函数)而后为文件生成一个hash对文件进行标注。在请求时先验证所上传的文件是否已经存在于服务器若是则直接提示上传成功即秒传功能若不存在或部分存在则需要后端返回上传成功的分片标识数组前端将使用成功分片数组与原文件分片数组进行处理得到未上传成功的分片而后将未成功的分片以并发方式上传至后端。上传后即完成了整个文件的上传向后端发送合并分片请求即完成大文件分片上传断点续传功能。 7.19实现方式即彻底完成功能
步骤 由于前端计算md5耗时过长可能会导致页面卡死因此考虑使用Web Worker来计算md5即使用worker.js使用spark-md5文件来计算分片数组分片哈希数组文件整体哈希。在拿到web worker所得到的分片数组分片哈希数组文件整体哈希后即可开始进行大文件上传工作前端使用文件整体哈希、文件名以及分片数组作为请求参数调用后端/init接口初始化上传操作。【initSend函数】。初始化操作完成后前端使用文件整体哈希作为参数调用后端/status接口获取此文件分片的状态信息前端根据后端所返回的状态信息使用filter以及every方法得到文件是否已经上传的状态existFile、后端存在的文件分片existChunks。若existFile为true则直接提示上传成功即秒传功能。【verifyInfo函数】若existFile为false则使用后端返回的existChunks数组与分片数组进行对比得到未上传成功的分片数组并将其分片信息分片哈希值分片内容分片序号以及文件整体哈希值作为formData参数发送至后端/chunk接口在发送分片时使用并发操作并限制最大并发数为6【uploadChunks函数】当所有分片发送完成后前端给后端以文件整体哈希做为参数调用后端/merge接口提示后端可以进行合并操作后端返回成功消息即完成大文件分片上传断点续传功能。【mergeFile函数】 演示图 选择文件 秒传 分片上传 代码内容
在后续操作中完成了对请求的封装以及分片上传的hook的编写主处理逻辑部分仅仅为下方所示
const submitUpload () {const file FileInfo.value;if(!file) {return;}fileName.value file.name;const { mainDeal } useUpload(file, fileName.value)mainDeal()
}api的封装为下方所示
import request from /utils/request;export async function initSend(uploadId, fileName, totalChunk) {return request({url: /init,method: POST,data: {uploadId,fileName,totalChunk,},});
}export async function verifyInfo(uploadId) {return request({url: /status,method: POST,data: {uploadId,},headers: { Content-Type: application/x-www-form-urlencoded },});
}
export async function uploadSingle(formData) {return request({url: /chunk,method: POST,data: {formData,},headers: {Content-Type: multipart/form-data,},});
}
export async function mergeFile(uploadId) {return request({url: /merge,method: POST,data: {uploadId,},headers: {Content-Type: application/x-www-form-urlencoded,},});
}useUpload钩子函数为
import { ref } from vue;
import axios from axios;
import type { AxiosResponse } from axios;
import {initSend,verifyInfo,mergeFile,uploadSingle,
} from /apis/uploadApi;
export function useUpload(fileInfo, filename) {const FileInfo refFile(fileInfo);const fileName ref(filename); // 文件名称const fileHash ref(); // 文件hashconst fileHashArr ref([]);const chunks ref([]);interface Verify {id?: Number;uploadId?: String;chunkIndex?: Number;status?: String;}const mainDeal async () {const worker new Worker(new URL(/utils/worker.js, import.meta.url), {type: module,});const file FileInfo.value;console.log(worker);console.log(file_info, file);worker.postMessage({ file: file });worker.onmessage async (e) {const { data } e;chunks.value data.fileChunkList;fileHashArr.value data.fileChunkHashList;fileHash.value data.fileMd5;console.log(uploadid, fileHash.value);const res_init await initSend(fileHash.value,fileName.value,chunks.value.length);console.log(res_init, res_init);const { existFile, existChunks } await verify(fileHash.value);if (existFile) return;uploadChunks(chunks.value, existChunks, fileHashArr.value);worker.terminate();};};// 控制请求并发const concurRequest (taskPool: Array() PromiseResponse,max: number): PromiseArrayResponse | unknown {return new Promise((resolve) {if (taskPool.length 0) {resolve([]);return;}console.log(taskPool, taskPool);const results: ArrayResponse | unknown [];let index 0;let count 0;console.log(results_before, results);const request async () {if (index taskPool.length) return;const i index;const task taskPool[index];index;try {results[i] await task();console.log(results_try, results);} catch (err) {results[i] err;} finally {count;if (count taskPool.length) {resolve(results);}request();}};const times Math.min(max, taskPool.length);for (let i 0; i times; i) {request();}});};// 合并分片请求const mergeRequest async () {return mergeFile(fileHash.value);};// 上传文件分片const uploadChunks async (chunks: ArrayBlob,existChunks: Arraystring,md5Arr: Arraystring) {const formDatas chunks.map((chunk, index) ({fileHash: fileHash.value,chunkHash: fileHash.value - index,chunkIndex: index,checksum: md5Arr[index],chunk,})).filter((item) !existChunks.includes(item.chunkHash));console.log(formDatas, formDatas);const form_Datas formDatas.map((item) {console.log(!, item.chunkIndex);const formData new FormData();formData.append(uploadId, item.fileHash);formData.append(chunkIndex, String(item.chunkIndex));formData.append(checksum, item.checksum);formData.append(file, item.chunk);return formData;});console.log(formDatas, form_Datas);const taskPool form_Datas.map((formData) () fetch(http://10.184.131.57:8101/ferret/upload/chunk, {method: POST,body: formData,}));//控制请求并发const response await concurRequest(taskPool, 6);console.log(response, response);// 合并分片请求const res_merge await mergeRequest();console.log(res_merge, res_merge);};// 校验文件、文件分片是否存在const verify async (uploadId: string) {const res await verifyInfo(uploadId);const { data } res.data;console.log(verify, res);// 看服务器是不是已经有文件所有信息const existFile data.every((item: Verify) item.status Uploaded);const existChunks: string[] [];data.filter((item: Verify) {if (item.status Uploaded) {existChunks.push(${item.uploadId}-${item.chunkIndex});}});console.log(existFile, existFile, existChunks, existChunks);return {existFile,existChunks,};};return {mainDeal,};
}web worker实现方式
import SparkMD5 from spark-md5;
let DefaultChunkSize 1024 * 1024 * 5; // 5MBself.onmessage (e) {console.log(!!, e.data)if (e.data.file.size 1024 * 1024 * 100 e.data.file.size 1024 * 1024 * 512) {DefaultChunkSize 1024 * 1024 * 10}else if (e.data.file.size 1024 * 1024 * 512) {DefaultChunkSize 1024 * 1024 * 50}const { file, chunkSize DefaultChunkSize } e.data;let blobSlice File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,chunks Math.ceil(file.size / chunkSize),currentChunk 0,spark new SparkMD5.ArrayBuffer(),fileChunkHashList [],fileChunkList [],fileReader new FileReader();loadNext();function loadNext() {let start currentChunk * chunkSize,end ((start chunkSize) file.size) ? file.size : start chunkSize;let chunk blobSlice.call(file, start, end);fileChunkList.push(chunk);fileReader.readAsArrayBuffer(chunk);}function getChunkHash(e) {const chunkSpark new SparkMD5.ArrayBuffer();chunkSpark.append(e.target.result);fileChunkHashList.push(chunkSpark.end());}// 处理每一块的分片fileReader.onload function (e) {spark.append(e.target.result);currentChunk;getChunkHash(e)if (currentChunk chunks) {loadNext();} else {// 计算完成后返回结果self.postMessage({fileMd5: spark.end(),fileChunkList,fileChunkHashList,});fileReader.abort();fileReader null;}}// 读取失败fileReader.onerror function () {self.postMessage({error: wrong});}
};