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…...
为什么92%的Sora 2初学者卡在第4步?——帧一致性崩塌诊断工具包+时间轴锚点校准法
更多请点击: https://kaifayun.com 第一章:Sora 2视频生成的核心原理与环境准备 Sora 2并非OpenAI官方发布的模型,而是社区基于Sora技术理念构建的开源复现与增强框架,其核心依托于时空联合建模的扩散变换器(Spacetim…...
自制射频功率计:基于AD8317芯片,成本43欧元实现1MHz-10GHz测量
1. 项目概述:为什么我要亲手打造一台射频功率计在无人机和模型飞行器的圈子里,尤其是在我们荷兰FMS Spaarnwoude俱乐部,合规飞行是头等大事。我给我的八轴飞行器加装了云台相机和图传系统,工作在5.8GHz频段。根据本地法规…...
QMCDecode终极指南:3步解锁QQ音乐加密格式,实现跨平台音乐自由
QMCDecode终极指南:3步解锁QQ音乐加密格式,实现跨平台音乐自由 【免费下载链接】QMCDecode QQ音乐QMC格式转换为普通格式(qmcflac转flac,qmc0,qmc3转mp3, mflac,mflac0等转flac),仅支持macOS,可自动识别到QQ音乐下载目…...
[智能体-81]:工程化智能体 = 模型做脑力拆解 + 框架做流程落地。前者是决策者,后者是管理者,tools/function call是内部员工;mcp server是外部资源;
一、全角色人设 & 对应技术组件角色定位对应技术模块核心职责决策者(脑力大脑)大模型 LLM理解目标、任务拆解、逻辑判断、分支决策、内容生成,负责 “想方案、定步骤”管理者(流程总管)智能体编排框架(…...
基于MAX78000的医疗紧急呼叫系统:边缘AI与低功耗设计实战
1. 项目概述与核心价值大家好,我是Victor Hugo,一名电子工程师。今天我想和大家分享一个我最近完成并参与设计竞赛的项目:一个基于MAX78000 FTHR开发板的医疗紧急呼叫辅助系统。这个项目的核心,不是从零开始造一个新轮子ÿ…...
约束感知图缩减算法在量子优化中的应用
1. 约束感知图缩减算法概述在量子计算领域,资源受限一直是制约算法实际应用的主要瓶颈。以当前主流的超导量子计算机为例,其量子比特数通常在50-100个之间,且存在显著的噪声干扰。这种硬件限制使得许多经典优化问题难以直接映射到量子设备上求…...
告别鼠标点击,微博图片批量下载的轻松方案
告别鼠标点击,微博图片批量下载的轻松方案 【免费下载链接】weiboPicDownloader Download weibo images without logging-in 项目地址: https://gitcode.com/gh_mirrors/we/weiboPicDownloader 还记得那个周末的下午吗?你喜欢的博主发布了九宫格美…...
3步快速上手Whisper-WebUI:轻松实现语音转字幕的完整指南
3步快速上手Whisper-WebUI:轻松实现语音转字幕的完整指南 【免费下载链接】Whisper-WebUI A Web UI for easy subtitle using whisper model. 项目地址: https://gitcode.com/gh_mirrors/wh/Whisper-WebUI 还在为视频制作繁琐的字幕而烦恼吗?Whis…...
AutoPentest:面向红队的渗透测试决策引擎架构解析
1. 这不是又一个“自动化扫描器”,而是一套能替你做决策的渗透测试工作流引擎AutoPentest这个名字,第一眼容易让人联想到Nmap加个for循环、或者Burp Suite里点几下Intruder——但实际用过的人很快会意识到:它根本不在同一个维度上。我第一次在…...
CA-CFAR、GO-CFAR、SO-CFAR怎么选?一张图看懂三种恒虚警检测算法的适用场景与避坑指南
CA-CFAR、GO-CFAR、SO-CFAR工程选型指南:从算法原理到场景适配 雷达信号处理工程师常常面临一个经典难题:在复杂环境中如何选择合适的恒虚警检测算法?当海面杂波、多目标干扰或低信噪比条件同时出现时,CA、GO、SO三种CFAR变体的性…...
