当前位置: 首页 > 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…...

在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:

在 HarmonyOS 应用开发中&#xff0c;手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力&#xff0c;既支持点击、长按、拖拽等基础单一手势的精细控制&#xff0c;也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档&#xff0c…...

HTML 列表、表格、表单

1 列表标签 作用&#xff1a;布局内容排列整齐的区域 列表分类&#xff1a;无序列表、有序列表、定义列表。 例如&#xff1a; 1.1 无序列表 标签&#xff1a;ul 嵌套 li&#xff0c;ul是无序列表&#xff0c;li是列表条目。 注意事项&#xff1a; ul 标签里面只能包裹 li…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战

“&#x1f916;手搓TuyaAI语音指令 &#x1f60d;秒变表情包大师&#xff0c;让萌系Otto机器人&#x1f525;玩出智能新花样&#xff01;开整&#xff01;” &#x1f916; Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制&#xff08;TuyaAI…...

【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)

1.获取 authorizationCode&#xff1a; 2.利用 authorizationCode 获取 accessToken&#xff1a;文档中心 3.获取手机&#xff1a;文档中心 4.获取昵称头像&#xff1a;文档中心 首先创建 request 若要获取手机号&#xff0c;scope必填 phone&#xff0c;permissions 必填 …...

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中&#xff0c;新增了一个本地验证码接口 /code&#xff0c;使用函数式路由&#xff08;RouterFunction&#xff09;和 Hutool 的 Circle…...

10-Oracle 23 ai Vector Search 概述和参数

一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI&#xff0c;使用客户端或是内部自己搭建集成大模型的终端&#xff0c;加速与大型语言模型&#xff08;LLM&#xff09;的结合&#xff0c;同时使用检索增强生成&#xff08;Retrieval Augmented Generation &#…...

【JVM面试篇】高频八股汇总——类加载和类加载器

目录 1. 讲一下类加载过程&#xff1f; 2. Java创建对象的过程&#xff1f; 3. 对象的生命周期&#xff1f; 4. 类加载器有哪些&#xff1f; 5. 双亲委派模型的作用&#xff08;好处&#xff09;&#xff1f; 6. 讲一下类的加载和双亲委派原则&#xff1f; 7. 双亲委派模…...

快刀集(1): 一刀斩断视频片头广告

一刀流&#xff1a;用一个简单脚本&#xff0c;秒杀视频片头广告&#xff0c;还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农&#xff0c;平时写代码之余看看电影、补补片&#xff0c;是再正常不过的事。 电影嘛&#xff0c;要沉浸&#xff0c;…...

面试高频问题

文章目录 &#x1f680; 消息队列核心技术揭秘&#xff1a;从入门到秒杀面试官1️⃣ Kafka为何能"吞云吐雾"&#xff1f;性能背后的秘密1.1 顺序写入与零拷贝&#xff1a;性能的双引擎1.2 分区并行&#xff1a;数据的"八车道高速公路"1.3 页缓存与批量处理…...

大数据治理的常见方式

大数据治理的常见方式 大数据治理是确保数据质量、安全性和可用性的系统性方法&#xff0c;以下是几种常见的治理方式&#xff1a; 1. 数据质量管理 核心方法&#xff1a; 数据校验&#xff1a;建立数据校验规则&#xff08;格式、范围、一致性等&#xff09;数据清洗&…...