Node.js实现大文件断点续传—浅析
Node.js简介:
当谈论Node.js时,通常指的是一个基于Chrome V8 JavaScript引擎构建的开源、跨平台的JavaScript运行时环境。以下是一些Node.js的内容:
-
事件驱动编程:Node.js采用了事件驱动的编程范式,这意味着它可以异步地执行I/O操作,而无需阻塞进程或线程。这种方法可以提高应用程序的性能和响应性。
-
模块化:Node.js支持模块化编程,因此开发人员可以将代码分解为可重用的模块。Node.js中的每个文件都被视为一个模块,可以使用require()函数将其导入到其他文件中。
-
NPM:Node.js拥有一个名为NPM(Node Package Manager)的包管理器,它允许开发人员轻松地查找、安装和管理他们的依赖项。
-
HTTP模块:Node.js包含一个内置的HTTP模块,使开发人员能够轻松地创建Web服务器和客户端。
-
文件系统模块:Node.js还包括一个内置的文件系统模块,允许开发人员访问和操作文件系统。
-
单线程:虽然Node.js是单线程的,但它可以通过异步编程技术来避免阻塞。这意味着在单个线程中,可以同时处理多个请求,从而提高应用程序的性能。
-
跨平台:由于Node.js是基于JavaScript编写的,因此可以在Windows、Linux、Mac OS等多个平台上运行。
这只是Node.js的一小部分内容,但它们提供了Node.js如此流行的一些关键功能和特点。
前言
平常业务需求:上传图片、Excel等,毕竟几M的大小可以很快就上传到服务器。
针对于上传视频等大文件几百M或者几G的大小,就需要等待比较长的时间。
这就产生了对应的解决方法,对于大文件上传时的暂停、断网、网络较差的情况下, 使用切片+断点续传就能够很好的应对上述的情况,
方案分析
-
切片
- 就是对上传视频进行切分,具体操作为:
-
File.slice(start,end):返回新的blob对象
- 拷贝blob的起始字节
- 拷贝blob的结束字节
-
断点续传
- 每次切片上传之前,请求服务器接口,读取相同文件的已上传切片数
- 上传的是新文件,服务端则返回0,否则返回已上传切片数
具体解决流程
该demo提供关键点思路及方法,其他功能如:文件限制,lastModifiedDate校验文件重复性,缓存文件定期清除等功能扩展都可以在此代码基础上添加。
html 部分
<input class="video" type="file" />
<button type="submit" onclick="handleVideo(event, '.video', 'video')">提交
</button>
script 部分
let count = 0; // 记录需要上传的文件下标
const handleVideo = async (event, name, url) => {
// 阻止浏览器默认表单事件
event.preventDefault();
let currentSize = document.querySelector("h2");
let files = document.querySelector(name).files;
// 默认切片数量
const sectionLength = 100;
// 首先请求接口,获取服务器是否存在此文件
// count为0则是第一次上传,count不为0则服务器存在此文件,返回已上传的切片数
count = await handleCancel(files[0]);// 申明存放切片的数组对象
let fileCurrent = [];
// 循环file文件对象
for (const file of [...files]) {// 得出每个切片的大小let itemSize = Math.ceil(file.size / sectionLength);// 循环文件size,文件blob存入数组let current = 0;for (current; current < file.size; current += itemSize) {fileCurrent.push({ file: file.slice(current, current + itemSize) });}// axios模拟手动取消请求const CancelToken = axios.CancelToken;const source = CancelToken.source();// 当断点续传时,处理切片数量,已上传切片则不需要再次请求上传fileCurrent =count === 0 ? fileCurrent : fileCurrent.slice(count, sectionLength);// 循环切片请求接口for (const [index, item] of fileCurrent.entries()) {// 模拟请求暂停 || 网络断开if (index > 90) {source.cancel("取消请求");}// 存入文件相关信息// file为切片blob对象// filename为文件名// index为当前切片数// total为总切片数let formData = new FormData();formData.append("file", item.file);formData.append("filename", file.name);formData.append("total", sectionLength);formData.append("index", index + count + 1);await axios({url: `http://localhost:8080/${url}`,method: "POST",data: formData,cancelToken: source.token,}).then((response) => {// 返回数据显示进度currentSize.innerHTML = `进度${response.data.size}%`;}).catch((err) => {console.log(err);});}
}
};// 请求接口,查询上传文件是否存在
// count为0表示不存在,count不为0则已上传对应切片数
const handleCancel = (file) => {
return axios({method: "post",url: "http://localhost:8080/getSize",headers: { "Content-Type": "application/json; charset = utf-8" },data: {fileName: file.name,},
}).then((res) => {return res.data.count;}).catch((err) => {console.log(err);});
};
node服务端 部分
// 使用express构建服务器api
const express = require("express");
// 引入上传文件逻辑代码
const upload = require("./upload_file");
// 处理所有响应,设置跨域
app.all("*", (req, res, next) => {res.header("Access-Control-Allow-Origin", "*");res.header("Access-Control-Allow-Headers", "X-Requested-With");res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");res.header("Access-Control-Allow-Headers", "Content-Type, X-Requested-With ");res.header("X-Powered-By", " 3.2.1");res.header("Content-Type", "application/json;charset=utf-8");next();
});
const app = express();app.use(bodyParser.json({ type: "application/*+json" }));
// 视频上传(查询当前切片数)
app.post("/getSize", upload.getSize);
// 视频上传接口
app.post("/video", upload.video);// 开启本地端口侦听
app.listen(8080);
- upload_file
// 文件上传模块
const formidable = require("formidable");
// 文件系统模块
const fs = require("fs");
// 系统路径模块
const path = require("path");// 操作写入文件流
const handleStream = (item, writeStream) => {// 读取对应目录文件bufferconst readFile = fs.readFileSync(item);// 将读取的buffer || chunk写入到stream中writeStream.write(readFile);// 写入完后,清除暂存的切片文件fs.unlink(item, () => {});
};// 视频上传(切片)
module.exports.video = (req, res) => {// 创建解析对象const form = new formidable.IncomingForm();// 设置视频文件上传路径let dirPath = path.join(__dirname, "video");form.uploadDir = dirPath;// 是否保留上传文件名后缀form.keepExtensions = true;// err 错误对象 如果解析失败包含错误信息// fields 包含除了二进制以外的formData的key-value对象// file 对象类型 上传文件的信息form.parse(req, async (err, fields, file) => {// 获取上传文件blob对象let files = file.file;// 获取当前切片indexlet index = fields.index;// 获取总切片数let total = fields.total;// 获取文件名let filename = fields.filename;// 重写上传文件名,设置暂存目录let url =dirPath +"/" +filename.split(".")[0] +`_${index}.` +filename.split(".")[1];try {// 同步修改上传文件名fs.renameSync(files.path, url);console.log(url);// 异步处理setTimeout(() => {// 判断是否是最后一个切片上传完成,拼接写入全部视频if (index === total) {// 同步创建新目录,用以存放完整视频let newDir = __dirname + `/uploadFiles/${Date.now()}`;// 创建目录fs.mkdirSync(newDir);// 创建可写流,用以写入文件let writeStream = fs.createWriteStream(newDir + `/${filename}`);let fsList = [];// 取出所有切片文件,放入数组for (let i = 0; i < total; i++) {const fsUrl =dirPath +"/" +filename.split(".")[0] +`_${i + 1}.` +filename.split(".")[1];fsList.push(fsUrl);}// 循环切片文件数组,进行stream流的写入for (let item of fsList) {handleStream(item, writeStream);}// 全部写入,关闭stream写入流writeStream.end();}}, 100);} catch (e) {console.log(e);}res.send({code: 0,msg: "上传成功",size: index,});});
};// 获取文件切片数
module.exports.getSize = (req, res) => {let count = 0;req.setEncoding("utf8");req.on("data", function (data) {let name = JSON.parse(data);let dirPath = path.join(__dirname, "video");// 计算已上传的切片文件个数let files = fs.readdirSync(dirPath);files.forEach((item, index) => {let url =name.fileName.split(".")[0] +`_${index + 1}.` +name.fileName.split(".")[1];if (files.includes(url)) {++count;}});res.send({code: 0,msg: "请继续上传",count,});});
};
逻辑分析
-
前端
-
首先请求上传查询文件是否第一次上传,或已存在对应的切片
- 文件第一次上传,则切片从0开始
- 文件已存在对应的切片,则从切片数开始请求上传
-
循环切片数组,对每块切片文件进行上传
- 其中使用了模拟手动暂停请求,当切片数大于90取消请求
-
-
服务端
-
接收查询文件filename,查找临时存储的文件地址,判断是否存在对应上传文件
- 从未上传过此文件,则返回0,切片数从0开始
- 已上传过文件,则返回对应切片数
-
接收上传文件切片,文件存入临时存储目录
- 通过count和total判断切片是否上传完毕
- 上传完毕,创建文件保存目录,并创建可写流,进行写入操作
- 提取对应临时文件放入数组,循环文件目录数组,依次读取并写入文件buffer
- 写入完毕,关闭可写流。
-
小结
以上代码涉及到具体的业务流程会有所更改或偏差,这只是其中一种具体实现的方式。
希望这篇文章能对大家有所帮助,如果有写的不对的地方也希望指点一二。
相关文章:
Node.js实现大文件断点续传—浅析
Node.js简介: 当谈论Node.js时,通常指的是一个基于Chrome V8 JavaScript引擎构建的开源、跨平台的JavaScript运行时环境。以下是一些Node.js的内容: 事件驱动编程:Node.js采用了事件驱动的编程范式,这意味着它可以异步…...
Spring Cloud Nacos源码讲解(九)- Nacos客户端本地缓存及故障转移
Nacos客户端本地缓存及故障转移 在Nacos本地缓存的时候有的时候必然会出现一些故障,这些故障就需要进行处理,涉及到的核心类为ServiceInfoHolder和FailoverReactor。 本地缓存有两方面,第一方面是从注册中心获得实例信息会缓存在内存当…...
MySQL知识点小结
事务 进行数据库提交操作时使用事务就是为了保证四大特性,原子性,一致性,隔离性,持久性Durability. 持久性:事务一旦提交,对数据库的改变是永久的. 事务的日志用于保存对数据的更新操作. 这个操作T1事务操作的会发生丢失,因为最后是T2提交的修改,而且T2先进行一次查询,按照A…...
MySQL关于NULL值,常见的几个坑
数据库版本MySQL8。 1.count 函数 觉得 NULL值 不算数 ,所以开发中要避免count的时候丢失数据。 如图所示,以下有7条记录,但是count(name)却只有6条。 为什么丢失数据?因为MySQL的count函数觉得 Null值不算数,就是说…...
OllyDbgqaqazazzAcxsaZ
本文通过吾爱破解论坛上提供的OllyDbg版本为例,讲解该软件的使用方法 F2对鼠标所处的位置打下断点,一般表现为鼠标所属地址位置背景变红F3加载一个可执行程序,进行调试分析,表现为弹出打开文件框F4执行程序到光标处F5缩小还原当前…...
Elasticsearch7.8.0版本进阶——自定义分析器
目录一、自定义分析器的概述二、自定义的分析器的测试示例一、自定义分析器的概述 Elasticsearch 带有一些现成的分析器,然而在分析器上 Elasticsearch 真正的强大之 处在于,你可以通过在一个适合你的特定数据的设置之中组合字符过滤器、分词器、词汇单 …...
spring事务-创建代理对象
用来开启事务的注解EnableTransactionManagement上通过Import导入了TransactionManagementConfigurationSelector组件,TransactionManagementConfigurationSelector类的父类AdviceModeImportSelector实现了ImportSelector接口,因此会调用public final St…...
Linux 配置NFS与autofs自动挂载
目录 配置NFS服务器 安装nfs软件包 配置共享目录 防火墙放行相关服务 配置NFS客户端 autofs自动挂载 配置autofs 配置NFS服务器 nfs主配置文件参数(/etc/exports) 共享目录 允许地址1访问(选项1,选项2) 循序地…...
【编程入门】应用市场(Python版)
背景 前面已输出多个系列: 《十余种编程语言做个计算器》 《十余种编程语言写2048小游戏》 《17种编程语言10种排序算法》 《十余种编程语言写博客系统》 《十余种编程语言写云笔记》 《N种编程语言做个记事本》 目标 为编程初学者打造入门学习项目,使…...
异常信息记录入库
方案介绍 将异常信息放在日志里面,如果磁盘定期清理,会导致很久之前的日志丢失,因此考虑将日志中的异常信息存在表里,方便后期查看定位问题。 由于项目是基于SpringBoot构架的,所以采用AdviceControllerExceptionHand…...
Spring Batch 高级篇-分区步骤
目录 引言 概念 分区器 分区处理器 案例 转视频版 引言 接着上篇:Spring Batch 高级篇-并行步骤了解Spring Batch并行步骤后,接下来一起学习一下Spring Batch 高级功能-分区步骤 概念 分区:有划分,区分意思,在…...
ES数据迁移_snapshot(不需要安装其他软件)
参考文章: 三种常用的 Elasticsearch 数据迁移方案ES基于Snapshot(快照)的数据备份和还原CDH修改ElasticSearch配置文件不生效问题 目录1、更改老ES和新ES的config/elasticsearch.yml2、重启老ES,在老ES执行Postman中创建备份目录…...
【Vue3 第二十章】异步组件 代码分包 Suspense内置组件 顶层 await
异步组件 & 代码分包 & Suspense内置组件 & 顶层 await 一、概述 在大型项目中,我们可能需要拆分应用为更小的块,以减少主包的体积,并仅在需要时再从服务器加载相关组件。这时候就可以使用异步组件。 Vue 提供了 defineAsyncC…...
「媒体邀约」四川有哪些媒体,成都活动媒体邀约
传媒如春雨,润物细无声,四川省位于中国西南地区,是中国的一个省份。成都市是四川省的省会,成都市是中国西部地区的政治、经济、文化和交通中心,也是著名的旅游胜地。每年的文化交流活动很多,也有许多的大企…...
@Autowired和@Resource的区别
文章目录1. Autowired和Resource的区别2. 一个接口多个实现类的处理2.1 注入时候报错情况2.2 使用Primary注解处理2.3 使用Qualifer注解处理2.4 根据业务情况动态的决定注入哪个serviceImpl1. Autowired和Resource的区别 Aurowired是根据type来匹配;Resource可以根…...
Linux系列:glibc程序设计规范与内存管理思想
文章目录前言命名规范说明版式风格内存管理与智能指针关于UML前言 这是一个基于lightdm、glibc、gobject、gtk、qt、glibc、x11、wayland等多个高质量开源项目总结而来的规范。 glibc处于内核态与用户态的边界,承上启下,对用户的体验影响非常大。其在系…...
Redis 集群
文章目录一、集群简介二、Redis集群结构设计🍉2.1 数据存储设计🍉2.2 内部通信设计三、cluster 集群结构搭建🍓3-1 cluster配置 .conf🍓3-2 cluster 节点操作命令🍓3-3 redis-trib 命令🍓3-4 搭建 3主3从结…...
EF 框架的简介、发展历史;ORM框架概念
一、EF 框架简介EF 全称是 EntityFramework 。Entity Framework是ADO.NET 中的一套支持开发面向数据的软件应用程序的技术,是微软的一个ORM框架。ORM框架(Object Relational Mapping) 翻译过来就是对象关系映射。如果不用ORM框架,我们一般这样…...
注解原理剖析与实战
一、注解及其原理 1.注解的基本概念 注解,可以看作是对 一个类/方法的一个扩展的模版,每个类/方法按照注解类中的规则,来为类/方法注解不同的参数,在用到的地方可以得到不同的类/方法中注解的各种参数与值。 从JDK5开始ÿ…...
《STL源码剖析》理解之将类成员函数和for_each等算法结合
类成员函数可以通过函数适配器(function adapters)包装成一个仿函数(重载了operator()的类),将其搭配于STL算法一起使用。#include <algorithm> #include <functional> #include <vector> #include <iostream>using namespace std;class In…...
网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...
el-switch文字内置
el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...
c# 局部函数 定义、功能与示例
C# 局部函数:定义、功能与示例 1. 定义与功能 局部函数(Local Function)是嵌套在另一个方法内部的私有方法,仅在包含它的方法内可见。 • 作用:封装仅用于当前方法的逻辑,避免污染类作用域,提升…...
Python 高效图像帧提取与视频编码:实战指南
Python 高效图像帧提取与视频编码:实战指南 在音视频处理领域,图像帧提取与视频编码是基础但极具挑战性的任务。Python 结合强大的第三方库(如 OpenCV、FFmpeg、PyAV),可以高效处理视频流,实现快速帧提取、压缩编码等关键功能。本文将深入介绍如何优化这些流程,提高处理…...
深度解析云存储:概念、架构与应用实践
在数据爆炸式增长的时代,传统本地存储因容量限制、管理复杂等问题,已难以满足企业和个人的需求。云存储凭借灵活扩展、便捷访问等特性,成为数据存储领域的主流解决方案。从个人照片备份到企业核心数据管理,云存储正重塑数据存储与…...
板凳-------Mysql cookbook学习 (十--2)
5.12 模式匹配中的大小写问题 mysql> use cookbook Database changed mysql> select a like A, a regexp A; ------------------------------ | a like A | a regexp A | ------------------------------ | 1 | 1 | --------------------------…...
01-VMware16虚拟机详细安装
官网地址:https://www.vmware.com/cn.html 1.1 打开下载好的 .exe 文件, 双击安装。 1.2 点击下一步 1.3 先勾选我接受许可协议中的条款,然后点击下一步 1.4 自定义安装路径,注意这里的文件路径尽量不要包含中文,完成…...
OD 算法题 B卷【删除字符串中出现次数最少的字符】
文章目录 删除字符串中出现次数最少的字符 删除字符串中出现次数最少的字符 实现删除字符串中出现次数最少的字符,若(最少的)有多个字符出现次数一样,则都删除。输出删除后的字符串,其他字符保持原有顺序;…...
OGG-01635 OGG-15149 centos服务器远程抽取AIX oracle11.2.0.4版本
背景描述 有一套ogg远程抽取的环境,源端是AIX7.1环境的oracle 11.2.0.4版本的数据库,中间是OGG抽取服务器,目标端是centos 7.9环境的oracle 19c。 采用集成模式远程抽取源端数据正常,但是经典模式远程抽取源数据的时候抽取进程启…...
