let stats = fs.statSync(filepath);//读取文件信息
let chunkSize = 3*1024*1024;//每片分块的大小3M
let size = stats.size;//文件大小
let pieces = Math.ceil(size / chunkSize);//总共的分片数
function uploadPiece (i){
//计算每块的结束位置
let startdata = i * chunkSize;
let enddata = Math.min(size, (i + 1) * chunkSize);
let arr = [];
//创建一个readStream对象,根据文件起始位置和结束位置读取固定的分片
let readStream = fs.createReadStream(filepath,
{start: startdata, end: enddata - 1}
);
//on data读取数据
readStream.on('data', (data)=>{
arr.push(data)
})
//on end在该分片读取完成时触发
readStream.on('end', ()=>{
//这里服务端只接受blob对象,需要把原始的数据流转成blob对象,这块为了配合后端才转
let blob = new Blob(arr)
//新建formdata数据对象
var formdata = new FormData();
let md5Val = md5(Buffer.concat(arr));
formdata.append("file", blob);
console.log('blob.size',blob.size)
formdata.append("md5", md5Val);
formdata.append("size", size + ''); // 数字30被转换成字符串"30"
formdata.append("chunk", i + '');//第几个分片,从0开始
formdata.append("chunks", pieces + '');//分片数
formdata.append("name", name);//文件名
post(formdata)//这里是伪代码,实现上传,开发者自己实现
})
}
const hashFile = (file) => {
return new Promise((resolve, reject) => {
const chunks = Math.ceil(file.size / chunkSize);
let currentChunk = 0;
const spark = new SparkMD5.ArrayBuffer();
const fileReader = new FileReader();
function loadNext() {
const start = currentChunk * chunkSize;
const end = start + chunkSize >= file.size ? file.size : start + chunkSize;
fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
}
fileReader.onload = e => {
spark.append(e.target.result); // Append array buffer
currentChunk += 1;
if (currentChunk < chunks) {
loadNext();
} else {
console.log('finished loading');
const result = spark.end();
// 如果单纯的使用result 作为hash值的时候, 如果文件内容相同,而名称不同的时候
// 想保留两个文件无法保留。所以把文件名称加上。
const sparkMd5 = new SparkMD5();
sparkMd5.append(result);
sparkMd5.append(file.name);
const hexHash = sparkMd5.end();
resolve(hexHash);
}
};
fileReader.onerror = () => {
console.warn('文件读取失败!');
};
loadNext();
}).catch(err => {
console.log(err);
});
}
const chunkSize = 2 * 1024 * 1024; // 每个chunk的大小,设置为2兆
// 使用Blob.slice方法来对文件进行分割。
// 同时该方法在不同的浏览器使用方式不同。
const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
let file = new File();
if (!file) {
alert('没有获取文件');
return;
}
const blockCount = Math.ceil(file.size / chunkSize); // 分片总数
const axiosPromiseArray = []; // axiosPromise数组
const hash = await hashFile(file); //文件 hash
// 获取文件hash之后,如果需要做断点续传,可以根据hash值去后台进行校验。
// 看看是否已经上传过该文件,并且是否已经传送完成以及已经上传的切片。
console.log(hash);
for (let i = 0; i < blockCount; i++) {
const start = i * chunkSize;
const end = Math.min(file.size, start + chunkSize);
// 构建表单
const form = new FormData();
form.append('file', blobSlice.call(file, start, end));
form.append('identifier', file.name);
form.append('filename', file.name);
form.append('chunkNumber', i+1);
form.append('size', file.size);
form.append('hash', hash);
// ajax提交 分片,此时 content-type 为 multipart/form-data
const axiosOptions = {
onUploadProgress: e => {
// 处理上传的进度
console.log(blockCount, i, e, file);
},
};
// 加入到 Promise 数组中
axiosPromiseArray.push(axios.post('/file/upload', form, axiosOptions));
}
// 所有分片上传后,请求合并分片文件
await axios.all(axiosPromiseArray).then(() => {
// 合并chunks
const data = {
size: file.size,
name: file.name,
total: blockCount,
hash
};
axios.post('/file/merge_chunks', data).then(res => {
console.log('上传成功');
console.log(res.data, file);
alert('上传成功');
}).catch(err => {
console.log(err);
});
});
async uploadfile() {
let that = this;
let filepath = this.filepath;
let filename = filepath.split("/").reverse()[0];
let stats = fs.statSync(filepath);//读取文件信息
let chunkSize = 3 * 1024 * 1024;//每片分块的大小3M
let size = stats.size;//文件大小
let piecesAll = Math.ceil(size / chunkSize);//总共的分片数
let identifier = parseInt(new Date().getTime() / 1000 + "");
let savefolder = this.userInfo.schoolid + "/live";
const axiosPromiseArray = []; // axiosPromise数组
console.info("文件大小:" + size);
console.info("文件分片:" + piecesAll);
console.info("identifier", identifier)
console.info("filename", filename)
console.info("savefolder", this.userInfo.schoolid + "/live")
let readPieces = 0;//已读取的文件片数
for (let i = 0; i < piecesAll; i++) {
let startdata = i * chunkSize;
let enddata = Math.min(size, (i + 1) * chunkSize);
let arr = [];
//创建一个readStream对象,根据文件起始位置和结束位置读取固定的分片
let readStream = fs.createReadStream(filepath,
{start: startdata, end: enddata - 1}
);
//on data读取数据
readStream.on('data', (data) => {
arr.push(data)
})
//on end在该分片读取完成时触发
readStream.on('end', () => {
//这里服务端只接受blob对象,需要把原始的数据流转成blob对象,这块为了配合后端才转
let blob = new Blob(arr)
//新建formdata数据对象
const form = new FormData();
form.append('file', blob);
form.append('identifier', identifier);
form.append('filename', filename);
form.append('chunkNumber', i + 1);
form.append('savefolder', savefolder);
//加入到 Promise 数组中
axiosPromiseArray.push(axios.post(fileuploadUrl + 'chunk/up', form, {
headers:
{'Content-Type': 'application/x-www-form-urlencoded'}
}));
readPieces += 1;
that.tipmsg.progress = parseInt((readPieces * 100 / piecesAll) + "")
if (readPieces === piecesAll) {
axios.all(axiosPromiseArray).then(() => {
// 合并chunks
const form = new FormData();
form.append('savefolder', savefolder);
form.append('identifier', identifier + "");
form.append('filename', filename);
form.append('thumed', 1 + "");
form.append('rename', 1 + "");
axios.post(fileuploadUrl + 'chunk/mergevideo', form, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
).then(res => {
console.log('上传成功');
console.log("res.data", res.data);
axios.post(apiUrl + "section/update_playback", {
sectionid: that.sectionid,
mp4code: res.data.obj.mp4code || "h264",
playback: res.data.obj.videopath
}).then(res3 => {
win.close()
}).catch(err3 => {
win.close()
})
// win.close()
}).catch(err => {
console.log(err);
win.close()
});
});
}
})
}
}
关键点
记录已经读取的分片数量 当所有分片都已经读取后再调用合并接口