说说前端切片上传的实现原理

前端上传大文件时,由于持续时间较长,容易受网络影响从而导致最终上传失败,因此将大文件切成一个个的小块并行上传会显著提升上传的成功率。

这是我参与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
喜欢就支持一下吧
点赞0 分享