import SparkMD5 from "spark-md5"; import { mergeFilePart } from "@/api/common.js"; import store from "@/store"; import UI from "@/utils/ui"; let ui = new UI(); //自定义文件分片上传 export const fileSlice = async (options) => { return new Promise((resolve) => { ui.showLoading(); console.log(options, "options"); const action = options.action; // 文件上传上传路径 应该用不上 const optionFile = options.file; // 需要分片的文件 const spark = new SparkMD5.ArrayBuffer(); // md5的ArrayBuffer加密类 const fileReader = new FileReader(); // 文件读取类 const chunkSize = 1024 * 200; // 单个分片大小 设置为100kb let md5 = ""; // 文件的唯一标识 let fileChunkedList = []; // 文件分片完成之后的数组
//文件开始分片,push到fileChunkedList数组中, 并用第一个分片去计算文件的md5
for (let i = 0; i < optionFile.size; i = i + chunkSize) {
const tmp = optionFile.slice(i, Math.min(i + chunkSize, optionFile.size));
if (i === 0) {
fileReader.readAsArrayBuffer(tmp); // 读取第一个切片方便生成唯一标识
}
fileChunkedList.push(tmp);
}
// fileReader.readAsDataURL(optionFile);
// 在文件读取完毕之后,开始计算文件md5,作为文件唯一标识
fileReader.onload = async (e) => {
// console.log(e.target.result); //base64数据
// var imgBase64Data = e.target.result;
// var head = imgBase64Data.indexOf('4') + 2;
// var base64Data = imgBase64Data.substring(head, imgBase64Data.length - head);
// console.log(base64Data.length);
// for (let i = 0; i < base64Data.length; i = i + chunkSize) {
// const tmp = base64Data.slice(i, Math.min(i + chunkSize, base64Data.length));
// fileChunkedList.push(tmp);
// }
spark.append(e.target.result); //base64数据 转位 标识
md5 = spark.end() + new Date().getTime();
console.log("文件md5为--------", md5);
//整合 合并文件参数
let mergeFileData = {
identifier: md5,
filename: options.file.name,
totalSize: optionFile.size,
totalChunks: fileChunkedList.length,
};
// 将fileChunkedList转成FormData对象,并加入上传时需要的数据
fileChunkedList = fileChunkedList.map((item, index) => {
const formData = new FormData();
// if (options.data) {
// // 额外加入外面传入的data数据
// Object.keys(options.data).forEach(key => {
// formData.append(key, option.data[key]);
// });
// }
// 这些字段看后端需要哪些,就传哪些,也可以自己追加额外参数
// formData.append('chunkSize', chunkSize); // 单个分块大小
formData.append("chunk", item); // 文件
formData.append("chunkNumber", index + 1); // 当前文件块索引
formData.append("currentChunkSize", item.size); // 当前分块大小
formData.append("identifier", md5); // 文件标识
formData.append("filename", options.file.name); // 文件名
formData.append("totalSize", optionFile.size); // 文件总大小
formData.append("totalChunks", fileChunkedList.length); // 总块数
return formData;
});
// 创建队列上传任务
// const sendRequest = chunks => {
// return Promise.all(
// //同时发送请求
// chunks.map(async v => {
// return uploadFileRedis(v);
// })
// );
// };
// 创建队列上传任务,limit是上传并发数
function sendRequest(chunks, limit = 5) {
return new Promise((resolve, reject) => {
let resList = chunks.length;
let isStop = false;
const start = async () => {
if (isStop) return;
const params = chunks.shift();
if (params) {
const xhr = new XMLHttpRequest();
if (options.withCredentials && "withCredentials" in xhr) {
xhr.withCredentials = true;
}
// const headers = option.headers || {};
// for (const item in headers) {
// if (headers.hasOwnProperty(item) && headers[item] !== null) {
// xhr.setRequestHeader(item, headers[item]);
// }
// }
// 文件开始上传
// 分片上传成功回调
xhr.onload = function onload(e) {
if (xhr.status < 200 || xhr.status >= 300) {
isStop = true;
reject("上传图片失败");
}
let res = JSON.parse(e.target.response);
if (res.code !== 200) {
console.log(chunks.length, "res.code");
isStop = true;
reject(res.message);
}
resList--;
//控制所有的请求完成响应后 resolve
resList <= 0 ? resolve() : start();
};
// 分片上传失败回调
xhr.onerror = function error(e) {
isStop = true;
reject(e);
};
xhr.open("post", action, true);
xhr.send(params);
//这里是把所有分片上传的xhr存到全局中,如果用户手动取消上传,或者上传出现错误,则要调用xhr.abort()把store中所有xhr的停止,不然文件还会继续上传
store.dispatch("setChunkedUploadXhr", xhr);
}
};
while (limit > 0) {
setTimeout(() => {
start();
}, Math.random() * 100);
limit -= 1;
}
});
}
try {
// 调用上传队列方法 等待所有文件上传完成
await sendRequest(fileChunkedList).then(async () => {
setTimeout(async () => {
// 给后端发送文件合并请求
let fileSliceInfo = await mergeFilePart(mergeFileData);
if (fileSliceInfo.code == 200)
resolve({
response: fileSliceInfo?.data,
file: optionFile,
data: options.data,
});
}, 0);
});
} catch (error) {
$ui.hideLoading();
store.state.chunkedUploadXhr.forEach((item) => {
item.abort();
});
store.dispatch("delChunkedUploadXhr", []);
options.onError(error);
}
};
}); };