前言
大文件上传,一般时间都比较长,这么长的时间内,可能会出现各种各样的问题,比如断网,一旦出错,我们的文件就需要重新上传,这样造成资源浪费,如果我们使用了断点续传继续就不会造成资源浪费了,因为当出现错误的时候,我们再重新上传文件,就会从我们出现错误的地方开始上传了,对于出错前上传的内容就不用再上传了,对于已经上传过的文件,就可以实现秒传的效果了。
完整仓库地址
流程
1、上传方法
const onUpload = async ({ file }) => {if (!file) return;// 文件切片const chunks = createChunks(file);// 计算哈希值const hash = await calculateHash(chunks);// 校验是否曾经上传过const { data } = await verifyFile(hash, file.name);if (!data.shouldUpload) {// 这里就相当于是秒传了,只需要将提示语改一下即可ElMessage.warning("文件已存在");return;}// 上传文件切片await uploadChunks(chunks, file.name, hash, data.existsChunks);// 发送合并请求await mergeRequest(hash, file.name);
};
2、将文件切片
const createChunks = (file) => {const chunks = [];let cur = 0;while (cur < file.size) {chunks.push(file.slice(cur,cur + CHUNK_SIZE > file.size ? file.size : cur + CHUNK_SIZE));cur += CHUNK_SIZE;}return chunks;
};
3、计算文件哈希值
const calculateHash = (chunks) => {// 为什么要用promise? 因为reader.onload是异步的return new Promise((resolve) => {// 文件如果太大了,每个切片都计算一遍哈希值,时间会很长// 所以可以这样来计算,第一及最后一个切片计算完整哈希值,其他切片只计算前2个字节、中间两个、后面2个字节const newChunks = [];const MID = CHUNK_SIZE / 2;chunks.forEach((chunk, index) => {if (index === 0 || index === chunks.length - 1) {newChunks.push(chunk);} else {newChunks.push(chunk.slice(0, 2));newChunks.push(chunk.slice(MID, MID + 2));newChunks.push(chunk.slice(CHUNK_SIZE - 2, CHUNK_SIZE));}});const spark = new SparkMD5.ArrayBuffer();const reader = new FileReader();// 由于onload是异步的所以需要用Promise,读取文件切片读取完毕就把哈希值返回出去reader.onload = (e) => {spark.append(e.target.result);resolve(spark.end());};// 读取文件切片 返回一个ArrayBuffer数据对象,为了就是传给 spark// 因为spark new SparkMD5.ArrayBuffer()创建的 只接收ArrayBuffer数据对象reader.readAsArrayBuffer(new Blob(newChunks));});
};
4、校验文件是否上传过
这块主要是调用后端接口,具体数据可以与后端商议(实际开发过程中一般是java后端与我们这里的nodejs不一样)
// 校验文件是否已经上传过
const verifyFile = async (fileHash, fileName) => {const res = await axios.post("http://localhost:3000/verify",{fileHash,fileName,size: CHUNK_SIZE,},{headers: {"Content-Type": "application/json",},});return res;
};
5、上传切片
const uploadChunks = async (chunks, fileName, hash, existsChunks) => {// 将切片转成formDatas数据,过滤已经上传的切片let formDatas = chunks.map((chunk, index) => {const formData = new FormData();formData.append("fileHash", hash);formData.append("chunkHash", hash + "-" + index);formData.append("chunk", chunk);return formData;});// 过滤掉已经上传的切片formDatas = formDatas.filter((item) => !existsChunks.includes(item.get("chunkHash")));const requestPool = [];let index = 0;while (index < formDatas.length) {const request = axios.post("http://localhost:3000/upload",formDatas[index]);// 将当前请求添加到请求池中requestPool.push(request);// 请求数量加1index++;}await Promise.all(requestPool);
};
6、发送合并请求
// 合并请求
const mergeRequest = async (fileHash, fileName) => {const res = await axios.post("http://localhost:3000/merge",{fileHash,fileName,size: CHUNK_SIZE,},{headers: {"Content-Type": "application/json",},});if (res.data.ok) {ElMessage.success("上传成功");}
};
以上便是大文件上传全部内容了,这是一个简略的demo, 如果有大佬指教一下就更好了