大文件上传,一般时间都比较长,这么长的时间内,可能会出现各种各样的问题,比如断网,一旦出错,我们的文件就需要重新上传,这样造成资源浪费,如果我们使用了断点续传继续就不会造成资源浪费了,因为当出现错误的时候,我们再重新上传文件,就会从我们出现错误的地方开始上传了,对于出错前上传的内容就不用再上传了,对于已经上传过的文件,就可以实现秒传的效果了。
完整仓库地址
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);
};
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;
};
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));
});
};
这块主要是调用后端接口,具体数据可以与后端商议(实际开发过程中一般是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;
};
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);
// 请求数量加1
index++;
}
await Promise.all(requestPool);
};
// 合并请求
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, 如果有大佬指教一下就更好了