前端上传大文件时,由于持续时间较长,容易受网络影响从而导致最终上传失败,因此将大文件切成一个个的小块并行上传会显著提升上传的成功率。
这是我参与8月更文挑战的第2天,活动详情查看:8月更文挑战
要做什么
首先分析需要做哪些事情:
- 读取文件内容
- 把文件内容切片
- 上传切片
- 全部上传完毕,通知服务端合并文件
我们用FileReader
读取文件,用Blob.slice
切片文件,通过Ajax
上传切片,最后再通调取切片上传完毕的API告诉服务端上传完毕可以合并切片.
好的,开始代码演示(实战)
项目实战
首先看html
内容,很简单就是一个类型为文件的input标签:
<input type="file" onChange="handleFileChange()">
复制代码
js逻辑部分
//切片大小:5M
const pieceSize = 5*1024*1024;
//切片起点
let chunkBeginIndex = 0;
//执行切片的函数
function handleFileChange(e){
const file = e.target.files[0];
const chunkList = [];
//当切片起点在范围内时
while(chunkBeginIndex*pieceSize <= file.size){
const startPos = chunkBeginIndex*pieceSize;
//对切片终点做处理,防止出轨
const endPos = (chunkBeginIndex+1)*pieceSize <= file.size ? (chunkBeginIndex+1)*pieceSize:file.size;
chunkList.push(file.slice(startPos,endPos));
chunkBeginIndex++;
}
doUpload(chunkList,()=>{
//console.log("上完完毕!")
})
}
//上传切片,每个上传需要附带切片的索引位置(第几块)
function doUpload(chunkList,onFinshed){
const maxConnect = 6;
let connectCount = 0;
const uploadChunkIdxPool = chunkList.map((item,idx)=>idx);
function request(chunkIdx,chunkData,cb){
//API request
//cb will be called when request finished
}
function beginUpload(){
connectCount++;
const chunkIdx = uploadChunkIdxPool.splice(0,1)[0];
request(chunkIdx,chunkList[chunkIdx],()=>{
connectCount--;
if(uploadChunkIdxPool.length > 0){
beginUpload()
}else{
//全部上传完毕
onFinshed()
}
})
}
//并发6个
for(var i=0;i< maxConnect;i++){
beginUpload();
}
}
复制代码
看到我们再发送每一个块是附带了位置索引的。服务端(以nodejs为例)可以将位置索引与块数据关联起来临时存储硬盘上:
const queryMap = extraQuery(url);
const index = queryMap.index;//从URL里拿到索引
const chunk = [];
req.on('data',(buffer)=>{
//buffer 为16进制的字节码
chunk.push(buffer);
})
req.on('end',()=>{
const buffer = Buffer.concat(chunk);
//保存格式为:/files/块索引
fs.writeFile(path.join(__dirname, "./files/"+index), buffer, (err) => {
if(err){
throw err;
}
console.log('file has been saved!');
})
})
复制代码
最后我们再把分块合并即可:
const fs = require('fs')
const { appendFileSync } = fs;
const directory = fs.readdirSync("./files").filter(fileName=>{
return /^\d+$/.test(fileName)
})
directory.forEach(fileName=>{
const buffer = fs.readFileSync("./files/"+fileName)
appendFileSync('movie.zip', buffer);
})
复制代码
好,我们代码演示部分就到此为止。
最后
其实上面的方案有很多优化的空间:
- 支持多用户上传的场景(存储在硬盘的临时命名必须规范处理,可以用MD5加密处理)
- 失败后重试的机制
- 大文件秒传(将文件内容MD5加密后发送到服务端进行检查,如果服务端已存在类似的记录则直接返回成功。很多云盘厂商就是这么做的)
最好,感谢阅读,如果任何疑问,欢迎留言讨论,谢谢!
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END