视频上传-分片上传那点事

在上一篇文章中,我们讲解了从视频上传到保存在服务端的整个过程,在这个过程中,我们又细分了前端上传视频的几种方式,前端处理视频的几种方式,在前后端通信过程中需要注意的哪些点等等。有不清楚的小伙伴可以看看 上篇文章。

紧接上文,我们来讲下文件的分片上传。

我们都知道分片上传是为了提升文件保存的速度。那它是如何实现的呢?下面的流程图会是一个很好的解释:

在这里插入图片描述

接下来,我们一步步的拆解。

前端如何进行分割操作

在上篇文章我们知道,通过 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 <div><button onClick={this.testConnect}>测试连接</button><inputtype='file'onChange={(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: 'tempChunk'storage: 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();
}

到这一步,我们的分片上传的流程就已经全部打通了,此时大家上传文件后,就会看到后端的目录里不仅有分片数据,而且还有完整的视频文件。

敬请期待

我们这次讲解了文件的分片上传,但是整体跑下来你会发现,有点太顺风顺水,没有包含错误机制,所以下篇文章,我们不仅会将如何并发控制分片的上传,还会有上传过程中的错误控制。

最后

好啦,本篇文章到这里就结束啦,如果上述过程中有错误的地方,欢迎各位大神指出。希望我的文章对你有帮助,我们下期再见啦~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/672690.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JVM 性能调优 - 四种引用(4)

为什么会有四种引用 我们先回顾下在 Java 虚拟机内存体系(1) 中提到了的垃圾回收算法 1、引用计数法 原理:给对象添加一个引用计数器,每当有一个地方引用它,计数器的值就加一。每当有一个引用失效,计数器的值就减一。当计数器值为零时,这个对象被认为没有其他对象引用,…

制作离线版element ui文档

链接&#xff1a;https://pan.baidu.com/s/1k5bsCK9WUlZobhFBLItw1g?pwdgeyk 提取码&#xff1a;geyk --来自百度网盘超级会员V4的分享 https://github.com/ElemeFE/element 克隆官方代码 使用nvm切换node版本&#xff0c;推荐使用14.0.0 http://doc.xutongbao.top/doc/#/zh…

Verilog刷题笔记20

题目&#xff1a; Case statements in Verilog are nearly equivalent to a sequence of if-elseif-else that compares one expression to a list of others. Its syntax and functionality differs from the switch statement in C. 解题&#xff1a; module top_module ( …

qt QMessagbox的按钮的顺序

在 Qt 中&#xff0c;QMessageBox::StandardButtons 枚举类型定义了标准按钮的集合&#xff0c;包括如"OK"、"Cancel"、"Yes"、"No"等按钮。这些按钮的排列顺序是由 Qt 在不同操作系统下的风格和用户界面准则所决定的&#xff0c;以保…

Python HTTP隧道在远程通信中的应用:穿越网络的“魔法门”

在这个数字化时代&#xff0c;远程通信就像是我们日常生活中的“魔法门”&#xff0c;让我们可以随时随地与远方的朋友、同事或服务器进行交流。而在这扇“魔法门”的背后&#xff0c;Python HTTP隧道技术发挥着举足轻重的作用。 想象一下&#xff0c;你坐在家里的沙发上&…

【JAVA WEB】Web标签

目录 注释标签 标题标签 h1-h6 段落标签 换行标签 格式化标签 加粗&#xff1a;strong 标签和 b 标签 倾斜&#xff1a;em 标签和 i 标签 删除线&#xff1a; del 标签 和 s 标签 下划线&#xff1a;ins 标签 和 u 标签 图片标签&#xff1a;img 单标签 src属性&#…

vector类的模拟实现

实现基本的vector框架 参考的是STL的一些源码&#xff0c;实现的vector也是看起来像是一个简略版的&#xff0c;但是看完能对vector这个类一些接口函数更好的认识。 我们写写成员变量&#xff0c;先来看看STL的成元变量是那些 namespace tjl {template<class T>class …

11.Swift数组

Swift 数组 在 Swift 中&#xff0c;数组是一种用于存储相同类型数据的有序集合。Swift 的数组是类型安全的&#xff0c;可以存储任意类型的数据&#xff0c;但数组中的所有元素类型必须相同。以下是 Swift 中常用的数组操作&#xff1a; 1. 创建数组 可以使用数组字面量语法…

c#内置委托

C#语言中有许多内置的委托&#xff0c;其中一些是常用的&#xff0c;包括&#xff1a; Action&#xff1a;表示不带返回值的方法的委托。它可以接受多个参数&#xff0c;但不返回任何值。 Action<int, string> actionDelegate (x, y) > Console.WriteLine("Ac…

Guitar Pro正版多少钱 Guitar Pro购买后永久使用吗

相信很多玩吉他的小伙伴都听说过Guitar Pro这款软件&#xff0c;Guitar Pro是一款传奇的吉他谱软件&#xff0c;可以用来打谱&#xff0c;看谱&#xff0c;midi音序制作等等&#xff0c;同时做为一款吉他学习辅助软件有着强大的优势&#xff0c;那大家知道Guitar Pro正版多少钱…

C++进阶(十二)lambda可变参数包装器

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、新的类功能1、默认成员函数2、类成员变量初始化3、 强制生成默认函数的关键字default:4、…

【数据结构】链表OJ面试题2《分割小于x并排序链表、回文结构、相交链表》+解析

1.前言 前五题在这http://t.csdnimg.cn/UeggB 休息一天&#xff0c;今天继续刷题&#xff01; 2.OJ题目训练 1. 编写代码&#xff0c;以给定值x为基准将链表分割成两部分&#xff0c;所有小于x的结点排在大于或等于x的结点之前 。链表分割_牛客题霸_牛客网 思路 既然涉及…

(35)IP地址无效化

文章目录 每日一言题目解题思路代码结语 每日一言 台阶是一层一层筑起的&#xff0c;目前的现实是未来理想的基础。只想将来&#xff0c;不从近处现实着手&#xff0c;就没有基础&#xff0c;就会流于幻想。——徐特立 题目 题目链接&#xff1a;IP地址无效化 给你一个有效的…

什么是网络渗透,应当如何防护?

什么是网络渗透 网络渗透是攻击者常用的一种攻击手段&#xff0c;也是一种综合的高级攻击技术&#xff0c;同时网络渗透也是安全工作者所研究的一个课题&#xff0c;在他们口中通常被称为"渗透测试(Penetration Test)"。无论是网络渗透(Network Penetration)还是渗透…

C++初阶之类与对象(上)详细解析

个人主页&#xff1a;点我进入主页 专栏分类&#xff1a;C语言初阶 C语言进阶 数据结构初阶 Linux C初阶 欢迎大家点赞&#xff0c;评论&#xff0c;收藏。 一起努力&#xff0c;一起奔赴大厂 目录 一.前言 二.类的定义和使用 2.1类的引入 2.2类的定义和访问限定…

Java学习-常用API(一)

Object类 Object类及其常用方法&#xff1a; 代码示例&#xff1a; Objects Objects类的引入&#xff0c;定义及其常见的方法&#xff1a; 示例 包装类 什么是包装类&#xff1f; 自动装箱和自动拆箱&#xff1a; 常用方法&#xff1a; 注意&#xff1a;字符串的 数值&#xf…

1Panel面板如何安装并结合内网穿透实现远程访问本地管理界面

文章目录 前言1. Linux 安装1Panel2. 安装cpolar内网穿透3. 配置1Panel公网访问地址4. 公网远程访问1Panel管理界面5. 固定1Panel公网地址 前言 1Panel 是一个现代化、开源的 Linux 服务器运维管理面板。高效管理,通过 Web 端轻松管理 Linux 服务器&#xff0c;包括主机监控、…

写函数判断闰年

实现函数判断year是不是润年。 下表显示了 C 语言支持的所有算术运算符。假设变量 A 的值为 10&#xff0c;变量 B 的值为 20&#xff0c;则&#xff1a; 运算符描述实例/分子除以分母B / A 将得到 2%取模运算符&#xff0c;整除后的余数B % A 将得到 0 已经知道判断闰年标准…

2024 Google Chrome 浏览器回退安装旧版本

2024 Google Chrome 浏览器回退安装旧版本 查看当前谷歌版本备份浏览器数据卸载浏览器双击重新安装旧版本浏览器 查看当前谷歌版本 详细参考&#xff1a;参考 笔记&#xff1a;最近谷歌浏览器更新后&#xff0c;用着总感觉别扭&#xff1a;不习惯 备份浏览器数据 &#xff…

微服务-微服务Alibaba-Nacos 源码分析 (源码流程图)-2.0.1

客户端注册临时实例&#xff0c;GRPC处理 客户端服务发现 及订阅处理