当前位置: 首页 > news >正文

Node.js实现大文件断点续传—浅析

Node.js简介:

当谈论Node.js时,通常指的是一个基于Chrome V8 JavaScript引擎构建的开源、跨平台的JavaScript运行时环境。以下是一些Node.js的内容:

  1. 事件驱动编程:Node.js采用了事件驱动的编程范式,这意味着它可以异步地执行I/O操作,而无需阻塞进程或线程。这种方法可以提高应用程序的性能和响应性。

  2. 模块化:Node.js支持模块化编程,因此开发人员可以将代码分解为可重用的模块。Node.js中的每个文件都被视为一个模块,可以使用require()函数将其导入到其他文件中。

  3. NPM:Node.js拥有一个名为NPM(Node Package Manager)的包管理器,它允许开发人员轻松地查找、安装和管理他们的依赖项。

  4. HTTP模块:Node.js包含一个内置的HTTP模块,使开发人员能够轻松地创建Web服务器和客户端。

  5. 文件系统模块:Node.js还包括一个内置的文件系统模块,允许开发人员访问和操作文件系统。

  6. 单线程:虽然Node.js是单线程的,但它可以通过异步编程技术来避免阻塞。这意味着在单个线程中,可以同时处理多个请求,从而提高应用程序的性能。

  7. 跨平台:由于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开始
      • 已上传过文件,则返回对应切片数
    • 接收上传文件切片,文件存入临时存储目录

      • 通过counttotal判断切片是否上传完毕
      • 上传完毕,创建文件保存目录,并创建可写流,进行写入操作
      • 提取对应临时文件放入数组,循环文件目录数组,依次读取并写入文件buffer
      • 写入完毕,关闭可写流。

小结

以上代码涉及到具体的业务流程会有所更改或偏差,这只是其中一种具体实现的方式。
希望这篇文章能对大家有所帮助,如果有写的不对的地方也希望指点一二。

相关文章:

Node.js实现大文件断点续传—浅析

Node.js简介&#xff1a; 当谈论Node.js时&#xff0c;通常指的是一个基于Chrome V8 JavaScript引擎构建的开源、跨平台的JavaScript运行时环境。以下是一些Node.js的内容&#xff1a; 事件驱动编程&#xff1a;Node.js采用了事件驱动的编程范式&#xff0c;这意味着它可以异步…...

Spring Cloud Nacos源码讲解(九)- Nacos客户端本地缓存及故障转移

Nacos客户端本地缓存及故障转移 ​ 在Nacos本地缓存的时候有的时候必然会出现一些故障&#xff0c;这些故障就需要进行处理&#xff0c;涉及到的核心类为ServiceInfoHolder和FailoverReactor。 ​ 本地缓存有两方面&#xff0c;第一方面是从注册中心获得实例信息会缓存在内存当…...

MySQL知识点小结

事务 进行数据库提交操作时使用事务就是为了保证四大特性,原子性,一致性,隔离性,持久性Durability. 持久性:事务一旦提交,对数据库的改变是永久的. 事务的日志用于保存对数据的更新操作. 这个操作T1事务操作的会发生丢失,因为最后是T2提交的修改,而且T2先进行一次查询,按照A…...

MySQL关于NULL值,常见的几个坑

数据库版本MySQL8。 1.count 函数 觉得 NULL值 不算数 &#xff0c;所以开发中要避免count的时候丢失数据。 如图所示&#xff0c;以下有7条记录&#xff0c;但是count(name)却只有6条。 为什么丢失数据&#xff1f;因为MySQL的count函数觉得 Null值不算数&#xff0c;就是说…...

OllyDbgqaqazazzAcxsaZ

本文通过吾爱破解论坛上提供的OllyDbg版本为例&#xff0c;讲解该软件的使用方法 F2对鼠标所处的位置打下断点&#xff0c;一般表现为鼠标所属地址位置背景变红F3加载一个可执行程序&#xff0c;进行调试分析&#xff0c;表现为弹出打开文件框F4执行程序到光标处F5缩小还原当前…...

Elasticsearch7.8.0版本进阶——自定义分析器

目录一、自定义分析器的概述二、自定义的分析器的测试示例一、自定义分析器的概述 Elasticsearch 带有一些现成的分析器&#xff0c;然而在分析器上 Elasticsearch 真正的强大之 处在于&#xff0c;你可以通过在一个适合你的特定数据的设置之中组合字符过滤器、分词器、词汇单 …...

spring事务-创建代理对象

用来开启事务的注解EnableTransactionManagement上通过Import导入了TransactionManagementConfigurationSelector组件&#xff0c;TransactionManagementConfigurationSelector类的父类AdviceModeImportSelector实现了ImportSelector接口&#xff0c;因此会调用public final St…...

Linux 配置NFS与autofs自动挂载

目录 配置NFS服务器 安装nfs软件包 配置共享目录 防火墙放行相关服务 配置NFS客户端 autofs自动挂载 配置autofs 配置NFS服务器 nfs主配置文件参数&#xff08;/etc/exports&#xff09; 共享目录 允许地址1访问&#xff08;选项1&#xff0c;选项2&#xff09; 循序地…...

【编程入门】应用市场(Python版)

背景 前面已输出多个系列&#xff1a; 《十余种编程语言做个计算器》 《十余种编程语言写2048小游戏》 《17种编程语言10种排序算法》 《十余种编程语言写博客系统》 《十余种编程语言写云笔记》 《N种编程语言做个记事本》 目标 为编程初学者打造入门学习项目&#xff0c;使…...

异常信息记录入库

方案介绍 将异常信息放在日志里面&#xff0c;如果磁盘定期清理&#xff0c;会导致很久之前的日志丢失&#xff0c;因此考虑将日志中的异常信息存在表里&#xff0c;方便后期查看定位问题。 由于项目是基于SpringBoot构架的&#xff0c;所以采用AdviceControllerExceptionHand…...

Spring Batch 高级篇-分区步骤

目录 引言 概念 分区器 分区处理器 案例 转视频版 引言 接着上篇&#xff1a;Spring Batch 高级篇-并行步骤了解Spring Batch并行步骤后&#xff0c;接下来一起学习一下Spring Batch 高级功能-分区步骤 概念 分区&#xff1a;有划分&#xff0c;区分意思&#xff0c;在…...

ES数据迁移_snapshot(不需要安装其他软件)

参考文章&#xff1a; 三种常用的 Elasticsearch 数据迁移方案ES基于Snapshot&#xff08;快照&#xff09;的数据备份和还原CDH修改ElasticSearch配置文件不生效问题 目录1、更改老ES和新ES的config/elasticsearch.yml2、重启老ES&#xff0c;在老ES执行Postman中创建备份目录…...

【Vue3 第二十章】异步组件 代码分包 Suspense内置组件 顶层 await

异步组件 & 代码分包 & Suspense内置组件 & 顶层 await 一、概述 在大型项目中&#xff0c;我们可能需要拆分应用为更小的块&#xff0c;以减少主包的体积&#xff0c;并仅在需要时再从服务器加载相关组件。这时候就可以使用异步组件。 Vue 提供了 defineAsyncC…...

「媒体邀约」四川有哪些媒体,成都活动媒体邀约

传媒如春雨&#xff0c;润物细无声&#xff0c;四川省位于中国西南地区&#xff0c;是中国的一个省份。成都市是四川省的省会&#xff0c;成都市是中国西部地区的政治、经济、文化和交通中心&#xff0c;也是著名的旅游胜地。每年的文化交流活动很多&#xff0c;也有许多的大企…...

@Autowired和@Resource的区别

文章目录1. Autowired和Resource的区别2. 一个接口多个实现类的处理2.1 注入时候报错情况2.2 使用Primary注解处理2.3 使用Qualifer注解处理2.4 根据业务情况动态的决定注入哪个serviceImpl1. Autowired和Resource的区别 Aurowired是根据type来匹配&#xff1b;Resource可以根…...

Linux系列:glibc程序设计规范与内存管理思想

文章目录前言命名规范说明版式风格内存管理与智能指针关于UML前言 这是一个基于lightdm、glibc、gobject、gtk、qt、glibc、x11、wayland等多个高质量开源项目总结而来的规范。 glibc处于内核态与用户态的边界&#xff0c;承上启下&#xff0c;对用户的体验影响非常大。其在系…...

Redis 集群

文章目录一、集群简介二、Redis集群结构设计&#x1f349;2.1 数据存储设计&#x1f349;2.2 内部通信设计三、cluster 集群结构搭建&#x1f353;3-1 cluster配置 .conf&#x1f353;3-2 cluster 节点操作命令&#x1f353;3-3 redis-trib 命令&#x1f353;3-4 搭建 3主3从结…...

EF 框架的简介、发展历史;ORM框架概念

一、EF 框架简介EF 全称是 EntityFramework 。Entity Framework是ADO.NET 中的一套支持开发面向数据的软件应用程序的技术,是微软的一个ORM框架。ORM框架&#xff08;Object Relational Mapping&#xff09; 翻译过来就是对象关系映射。如果不用ORM框架&#xff0c;我们一般这样…...

注解原理剖析与实战

一、注解及其原理 1.注解的基本概念 注解&#xff0c;可以看作是对 一个类/方法的一个扩展的模版&#xff0c;每个类/方法按照注解类中的规则&#xff0c;来为类/方法注解不同的参数&#xff0c;在用到的地方可以得到不同的类/方法中注解的各种参数与值。 从JDK5开始&#xff…...

《STL源码剖析》理解之将类成员函数和for_each等算法结合

类成员函数可以通过函数适配器(function adapters)包装成一个仿函数(重载了operator()的类)&#xff0c;将其搭配于STL算法一起使用。#include <algorithm> #include <functional> #include <vector> #include <iostream>using namespace std;class In…...

如何构建应用标准化体系

标准化的过程实际上就是对运维对象的识别和建模过程。形成统一的对象模型后&#xff0c;各方在统一的认识下展开有效协作&#xff0c;然后针对不同的运维对象&#xff0c;再抽取出它们所对应的运维场景&#xff0c;接下来才是运维场景的自动化实现。 在标准化的过程中&#xf…...

【RabbitMQ笔记03】消息队列RabbitMQ七种模式之WorkQueues工作队列模式

这篇文章&#xff0c;主要介绍消息队列RabbitMQ七种模式之WorkQueues工作队列模式。 目录 一、工作队列模式 1.1、什么是Work Queues模式 1.2、工作队列模式的使用 &#xff08;1&#xff09;引入依赖 &#xff08;2&#xff09;编写生产者 &#xff08;3&#xff09;编写…...

认识html

1.html的特点先看一段简单的html代码<html><head></head><body>hello world</body> </html>如果将这段带有这段代码的.html文件拉进浏览器中,就会出现一个页面,内容就是hello world,如下图:由上面的代码,我们可以了解到一些html代码的特点…...

在外包公司熬了 3 年终于进了字节,竭尽全力....

其实两年前校招的时候就往字节投了一次简历&#xff0c;结果很明显凉了&#xff0c;随后这个理想就被暂时放下了&#xff0c;但是这个种子一直埋在心里这两年除了工作以外&#xff0c;也会坚持写博客&#xff0c;也因此结识了很多优秀的小伙伴&#xff0c;从他们身上学到了特别…...

绝对让你明明白白,脚把脚带你盯着 I2C 时序图将 I2C 程序给扣出来(基于STM32的模拟I2C)

目录前言一、关于STM32 I/O端口位的基本结构讲解二、模拟I2C编写前的需知道的知识1、I2C简介2、根据时序编写模拟I2C程序重要的两点Ⅰ、主机发送数据给从机时的时序控制Ⅱ、主机接收来自从机的数据时的时序控制Ⅲ、完整的I2C时序图&#xff08;按写程序的思想分割时序&#xff…...

2023年全国最新工会考试精选真题及答案5

百分百题库提供工会考试试题、工会考试预测题、工会考试真题、工会证考试题库等&#xff0c;提供在线做题刷题&#xff0c;在线模拟考试&#xff0c;助你考试轻松过关。 一、单选题 1.企业工会委员会实行&#xff08;&#xff09;&#xff0c;重要问题须经&#xff08;&#x…...

一文2000字手把手教你自动化测试Selenium+pytest+数据驱动

主流自动化框架 selenium &#xff1a;web端自动化框架 &#xff0c;&#xff08;行业里面最核心的框架&#xff09; appium &#xff1a;手机app端框架 requests &#xff1a;接口测试 selenium 工具类封装 selenium提供了很多方法供我们去完成网页元素的操作&#xff0c; …...

windows安装Ubuntu子系统以及图形化界面记录

文章目录1. windows环境设置2. 开始安装3. ubuntu使用3.1 启动和退出 Linux 子系统3.2 安装位置3.3 更换源4. 安装图形化界面4.1 安装VcXsrv4.2 安装桌面环境&#xff08;1&#xff09;方法1&#xff1a;VcXsrv Gnome&#xff08;2&#xff09;方法2&#xff1a;VcXsrv Xfce4…...

通俗易懂,十分钟读懂DES,详解DES加密算法原理,DES攻击手段以及3DES原理。Python DES实现源码

文章目录1、什么是DES2、DES的基本概念3、DES的加密流程4、DES算法步骤详解4.1 初始置换(Initial Permutation&#xff0c;IP置换)4.2 加密轮次4.3 F轮函数4.3.1 拓展R到48位4.3.2 子密钥K的生成4.3.3 当前轮次的子密钥与拓展的48位R进行异或运算4.3.4 S盒替换&#xff08;Subs…...

为多态基类声明virtual析构函数

我们知道&#xff0c;有时会让一个基类指针指向用 new 运算符动态生成的派生类对象&#xff08;类似接口的作用&#xff09;&#xff1b;同时&#xff0c;用 new 运算符动态生成的对象都是通过 delete 指向它的指针来释放的。如果一个基类指针指向用 new 运算符动态生成的派生类…...