分类信息网站制作,建筑网论坛,做任务有q币的网站,济南网络推广外包公司在上一篇文章中#xff0c;我们讲解了从视频上传到保存在服务端的整个过程#xff0c;在这个过程中#xff0c;我们又细分了前端上传视频的几种方式#xff0c;前端处理视频的几种方式#xff0c;在前后端通信过程中需要注意的哪些点等等。有不清楚的小伙伴可以看看 上篇文…在上一篇文章中我们讲解了从视频上传到保存在服务端的整个过程在这个过程中我们又细分了前端上传视频的几种方式前端处理视频的几种方式在前后端通信过程中需要注意的哪些点等等。有不清楚的小伙伴可以看看 上篇文章。
紧接上文我们来讲下文件的分片上传。
我们都知道分片上传是为了提升文件保存的速度。那它是如何实现的呢下面的流程图会是一个很好的解释 接下来我们一步步的拆解。
前端如何进行分割操作
在上篇文章我们知道通过 Element.files属性 拿到的是FileList集合。FileList集合由File对象组成。File对象又继承Blob对象。所以File对象可以使用slice方法来完成对文件对象的切割。
slice方法具体明细如下 含义Blob.slice() 方法用于创建一个包含源 Blob的指定字节范围内的数据的新 Blob 对象。 返回值一个新的 Blob 对象它包含了原始 Blob 对象的某一个段的数据。 参数3个参数分别如下 start。第一个会被拷贝进新的 Blob 的字节的起始位置。end。这个下标的对应的字节将会是被拷贝进新的Blob 的最后一个字节。contentType。给新的 Blob 赋予一个新的文档类型。这将会把它的 type 属性设为被传入的值。它的默认值是一个空的字符串。
说了一大堆理论该是实战了先说一下思路
先通过input标签上传文件。上传文件后会触发input标签的change事件在这个事件里通过event.target.files可以获取到上传的文件对象并且将它保存在state里。定义每个文件块的大小然后使用slice进行分割。
代码如下
class Video extends React.Component {constructor(props){super(props);this.state {fileObj: {}}}// 分片上传uploadChunkFile async () {// 定义每块体积大小为20MBlet chunk_size 20 * 1024 * 1024;// 获取上传的文件对象let fileObj this.state.fileObj;// 获取上传的文件对象的体积let allSize this.state.fileObj.size;// 获取文件对应的总的分片的数量let allChunkCount Math.ceil(allSize / chunk_size);// chunk文件集合let chunkArr [];for (let index 0; index allChunkCount; index){let startIndex index * chunk_size;let endIndex Math.min(startIndex chunk_size, allSize);chunkArr.push({data: fileObj.slice(index * chunk_size,endIndex),filename: chunk-${index},chunkIndex: index});}}// 上传文件触发inputChange async (event) {let self this;let uploadFileObj event.target.files[0] || {};this.setState(state {return {...state,fileObj: uploadFileObj}});return}render(){return divbutton onClick{this.testConnect}测试连接/buttoninputtypefileonChange{(event) this.inputChange(event)}/button onClick{this.uploadChunkFile}分片上传/button/div}
}当我们上传一个66M的视频时我们会发现总的分片数量是4。符合预期。
发送chunk的几种方式
这块无非就2种分别如下
将这些分片按照顺序发送给后端。将这些分片并发的方式发送给后端。这种方式下需要考虑浏览器一次只能并发6个请求的情况并且这种方式也是面试中高频考点如何控制并发。
在这个功能点里我们采用按顺序的方式上传分片因为这种方式是最直观的会了这种方式相信分片上传你就完全会了。
但是在实际的项目中更多的还是并发的场景我们下篇文章再讲。
按照顺序发送 这个就是第一个请求成功后再去发送第二个请求以此类推… 它也是面试中的一个常考点如何按照顺序发送请求 、 如何实现红绿灯效果等等。
按顺序发送2种思路一种是循环一种是递归。
递归这里不用说重点讲一下循环。
普通的for循环可以做到吗
答案是可以的。
let arr [{name: 1},{name: 2},{name: 3},{name: 4},{name: 5}
];async function ax(){for (let index 0; index arr.length; index){let result await new Promise((resolve, reject) {setTimeout(() {resolve(arr[index].name);}, 1000);});console.log(result:, result);}
}ax();for…in… 能做到吗
答案也是可以的。
let arr [{name: 1},{name: 2},{name: 3},{name: 4},{name: 5}
];async function ax(){for (let index in arr){let result await new Promise((resolve, reject) {setTimeout(() {resolve(arr[index].name);}, 1000);});console.log(result:, result);}}ax();for…of…能做到吗
答案也是可以的。
let arr [{name: 1},{name: 2},{name: 3},{name: 4},{name: 5}
];async function ax(){for (let index of arr){let result await new Promise((resolve, reject) {setTimeout(() {resolve(index.name);}, 1000);});console.log(result:, result);}}ax();forEach可以做到吗
不行绝对不行。
let arr [{name: 1},{name: 2},{name: 3},{name: 4},{name: 5}
];async function ax(){arr.forEach(async item {let result await new Promise((resolve, reject) {setTimeout(() {resolve(item.name);}, 1000);});console.log(result:, result);});
}ax();MDN上也是这么说的但是你要问具体原因那就只能看forEach源码了。我感觉啊forEach应该是个while循环实现的外层的函数是个同步函数所以导致forEach不能按照顺序发送Promise请求。
forEach伪代码如下
Array.prototype.myForEach function (cb){let originArr this;let index 0;while(index originArr.length){cb(originArr[index], index);}
}/**即使cb内部是异步操作但是cb外面的调用方不是异步的所以导致这种写法并不能按顺序发送Promise请求。
*/
while循环可以做到吗
答案是可以的。
let arr [{name: 1},{name: 2},{name: 3},{name: 4},{name: 5}
];async function ax(){let index 0;while(index arr.length){let result await new Promise((resolve, reject) {setTimeout(() {resolve(arr[index].name);}, 1000);});index;console.log(result:, result);}}ax();数组里哪些方法能做到
这个就要看数组方法的源码了但是分析过程跟forEach一样这里就不一一例举了。
代码实践
在上面的分割章节里我们讲解了File对象的分割我们继续在原方法里进行改造从而添加按顺序发送chunk块的需求。 // 分片上传请求
uploadChunkReq async (fileBlob, chunkIndex, type) {let formData new FormData();let result await axiosInstance.post(/video/uploadChunk,{chunkIndex,type,videoDict: fileBlob,},{headers: {Content-Type: multipart/form-data}});return result;
}// 分片上传动作
uploadChunkFile async () {// 定义每块体积大小为20MBlet chunk_size 20 * 1024 * 1024;// 获取上传的文件对象let fileObj this.state.fileObj;// 获取上传的文件对象的体积let allSize this.state.fileObj.size;// 获取文件对应的总的分片的数量let allChunkCount Math.ceil(allSize / chunk_size);// chunk文件集合let chunkArr [];for (let index 0; index allChunkCount; index){let startIndex index * chunk_size;let endIndex Math.min(startIndex chunk_size, allSize);/**每个分片信息都包含分片的数据、分片的编号、分片的名称*/chunkArr.push({data: fileObj.slice(index * chunk_size,endIndex),filename: chunk-${index},chunkIndex: index});}// 按顺序发送chunk分片for (let item of chunkArr){let result await this.uploadChunkReq(item.data, item.chunkIndex, chunk);console.log(分片上传的结果:, result);}
}后端如何合并chunk
主要是3件事
首先要新增一个接口用来保存分片数据
其次当所有的分片都保存成功了应该去合并分片最终形成文件合并chunk的时机可以是后端自己判断也可以是前端触发具体要看场景。
最后删除分片数据。
单独保存chunk
这里我们需要改造原有的方法看过上一篇的小伙伴都知道如果express是通过multer第三方库来解析的form-data数据那它就一定会经过multer里定义的中间件我们要在这里去将不同类型的文件存放到不同的文件夹里。
// 定义chunk的临时存放路径
var tempChunkPosition multer({// dest: tempChunkstorage: multer.diskStorage({destination: function (req, file, cb) {if (req.body.type chunk){// 说明是分片上传cb(null, path.join(__dirname, ../tempChunk));} else {// 说明上传的是小文件cb(null, path.join(__dirname, ../videoDest));}},filename: function (req, file, cb) {if (req.body.type chunk){// 如果是分片上传cb(null, file.fieldname - ${req.body.chunkIndex} - Date.now());} else {// 说明上传的是小文件cb(null, file.fieldname - Date.now() .mp4);}}})
});/ 上传切片
router.post(/uploadChunk, tempChunkPosition.single(videoDict),(req, res, next) {return res.send({success: true,msg: 上传成功});
});合并chunk
这一步就是将临时的分片数据全都读取出来然后依次将他们写入到文件中。
因为我们在上传分片的过程中已经将分片的标识索引传给了后端所以后端无需再对读出来的chunk集合进行顺序排序。
router.post(/mergeChunk,(req, res, next) {// 获取文件切片的路径let chunkPath path.join(__dirname, ../tempChunk);// 开始读取切片const chunkArr fs.readdirSync(chunkPath);chunkArr.forEach(file {fs.appendFileSync(path.join(__dirname, ../videoDest/${req.body.originFileName}.mp4),fs.readFileSync(${chunkPath}/${file}));});return res.send({success: true,msg: 文件合并成功});
});此时我们再对前端的上传分片的函数进行改造主要就是新增 “合并分片”的动作触发。 // 合并分片请求
mergeChunk async () {let result await axiosInstance.post(/video/mergeChunk,{originFileName: 11},{headers: {Content-Type: application/json}});return result;
}//分片上传
uploadChunkFile async () {// 前面的都不变......for (let item of chunkArr){let result await this.uploadChunkReq(item.data, item.chunkIndex, chunk);console.log(分片上传的结果:, result);}// 前面的都不变......// 合并请求(新增的)this.mergeChunk();
}到这一步我们的分片上传的流程就已经全部打通了此时大家上传文件后就会看到后端的目录里不仅有分片数据而且还有完整的视频文件。
敬请期待
我们这次讲解了文件的分片上传但是整体跑下来你会发现有点太顺风顺水没有包含错误机制所以下篇文章我们不仅会将如何并发控制分片的上传还会有上传过程中的错误控制。
最后
好啦本篇文章到这里就结束啦如果上述过程中有错误的地方欢迎各位大神指出。希望我的文章对你有帮助我们下期再见啦