第二十章 Nest 大文件分片上传


在前端的文件上传功能中,只要请求头里定义 content-type 为 multipart/form-data,内容就会以下面形式传递到服务端,接着服务器再按照multipart/form-data的格式去提取数据 获取文件数据
1719751955317.png
但是当文件体积很大时 就会出现一个问题 文件越大 请求的时间会越长,会导致产品的体验很不好。
所以大文件上传时 我们要对齐进行优化 ,例如把1G的大文件 分割成10个100M的小文件,接着并行上传这些文件,服务端接收到10个文件之后再合并这10个小文件 成为一个大文件 这就是大文件分片上传的方案。

1、前端分片大文件方法

在游览器中 Blob 有着一个slice方法 可以截取特定范围的数据 File就是Blob中的一种,我们在选择文件之后可以通过slice 对 File 分片

地址:https://developer.mozilla.org/zh-CN/docs/Web/API/Blob/slice
1719752678591.png

2、后端合并分片文件的方法

在前端分片发送文件到后端全接收之后,在后端我们可以使用fs 的 createWriteStream 方法把每个分片按照不同位置写入文件

地址:https://nodejs.cn/api-v12/fs.html#fscreatewritestreampath-options
1719753140827.png

接下来我们创建项目来测试一下:

nest new large-file-sharding-upload

1719753353544.png
安装 multer 的 ts 类型的包

npm install -D @types/multer

在app.controller.ts中增加一个路由:

import { Body, Controller, Get, Post, UploadedFiles, UseInterceptors } from '@nestjs/common';
import { AppService } from './app.service';
import { FilesInterceptor } from '@nestjs/platform-express';@Controller()
export class AppController {constructor(private readonly appService: AppService) {}@Post('upload')@UseInterceptors(FilesInterceptor('files', 20, {dest: 'uploads'}))uploadFiles(@UploadedFiles() files: Array<Express.Multer.File>, @Body() body) {console.log('body', body);console.log('files', files);}
}

接着启动服务:

pnpm run start:dev

可以看到根目录下,生成了uploads 目录:
1719753839814.png
然后我们在main.ts里面开启跨域支持:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';async function bootstrap() {const app = await NestFactory.create(AppModule);app.enableCors();await app.listen(3000);
}
bootstrap();

在根目录下新建index.html:(其中input 指定 multiple,可以选择多个文件)

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><!-- 引入axios库,用于发送HTTP请求 --><script src="https://unpkg.com/axios@0.24.0/dist/axios.min.js"></script>
</head><body><input id="fileInput" type="file" /><script>/* 获取文件输入元素 */const fileInput = document.querySelector('#fileInput');/* 定义每块的大小,单位为字节 */const chunkSize = 20 * 1024;/* 当文件输入改变时,处理文件分块上传 */fileInput.onchange = async function () {/* 获取选中的文件 */const file = fileInput.files[0];console.log(file);/* 分割文件为多个块 */const chunks = [];let startPos = 0;while (startPos < file.size) {chunks.push(file.slice(startPos, startPos + chunkSize));startPos += chunkSize;}/* 对每个分块创建表单数据并发送POST请求 */chunks.map((chunk, index) => {const data = new FormData();/* 为每个分块设置唯一的名称 */data.set('name', file.name + '-' + index);/* 添加分块到表单数据 */data.append('files', chunk);/* 使用axios发送POST请求上传分块 */axios.post('http://localhost:3000/upload', data);})}</script>
</body></html>

启动前端index.html

npx http-server

1719754178991.png
游览器访问:http://127.0.0.1:8080/
1719754275979.png
接着我们上传个文件 这里我上传的文件大小为20多kb 我的切片是20kb分片一个 所以一共会分片成2给
1719754989392.png
1719755058410.png
可以看到服务端接收了2给分片
1719755090845.png
接下来 我们在服务端实现接收到服务端的数据之后 实现将同一个文件的分片 放置到单独目录中

import { Body, Controller, Get, Post, UploadedFiles, UseInterceptors } from '@nestjs/common';
import { AppService } from './app.service';
import { FilesInterceptor } from '@nestjs/platform-express';
import * as fs from 'fs';@Controller()
export class AppController {constructor(private readonly appService: AppService) { }@Post('upload')@UseInterceptors(FilesInterceptor('files', 20, {dest: 'uploads'}))uploadFiles(@UploadedFiles() files: Array<Express.Multer.File>, @Body() body) {console.log('body', body);console.log('files', files);const fileName = body.name.match(/(.+)\-\d+$/)[1];const chunkDir = 'uploads/chunks_' + fileName;if (!fs.existsSync(chunkDir)) {fs.mkdirSync(chunkDir);}fs.cpSync(files[0].path, chunkDir + '/' + body.name);fs.rmSync(files[0].path);}
}

再次上传文件 可以看到创建了目录 文件夹下有2个文件
1719755431335.png
我们在上传的时候 给文件名增加一个随机的字符串 防止重复

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><!-- 引入axios库,用于发送HTTP请求 --><script src="https://unpkg.com/axios@0.24.0/dist/axios.min.js"></script>
</head><body><input id="fileInput" type="file" /><script>/* 获取文件输入元素 */const fileInput = document.querySelector('#fileInput');/* 定义每块的大小,单位为字节 */const chunkSize = 20 * 1024;/* 当文件输入改变时,处理文件分块上传 */fileInput.onchange = async function () {/* 获取选中的文件 */const file = fileInput.files[0];console.log(file);/* 分割文件为多个块 */const chunks = [];let startPos = 0;while (startPos < file.size) {chunks.push(file.slice(startPos, startPos + chunkSize));startPos += chunkSize;}const randomStr = Math.random().toString().slice(2, 8);/* 对每个分块创建表单数据并发送POST请求 */chunks.map((chunk, index) => {const data = new FormData();/* 为每个分块设置唯一的名称 */data.set('name', randomStr + '_' + file.name + '-' + index);/* 添加分块到表单数据 */data.append('files', chunk);/* 使用axios发送POST请求上传分块 */axios.post('http://localhost:3000/upload', data);})}</script>
</body></html>

最后就是合并分片请求:

/*** 合并上传的文件片段。* * 该方法用于处理文件分片上传后的合并操作。通过读取指定目录下的所有文件片段,* 将它们按顺序拼接成完整的文件,并删除已经合并的文件片段。* * @param name 文件名,用于定位和识别待合并的文件片段。*/@Get('merge')merge(@Query('name') name: string) {// 构建存储文件片段的目录路径const chunkDir = 'uploads/chunks_' + name;// 读取文件片段目录中的所有文件const files = fs.readdirSync(chunkDir);// 初始化用于跟踪已合并文件数量的变量let startPos = 0;let count = 0;// 遍历文件片段,逐个进行合并files.map(file => {// 构建当前文件片段的完整路径const filePath = chunkDir + '/' + file;// 创建读取当前文件片段的流const stream = fs.createReadStream(filePath);// 将文件片段流式传输到目标文件,从上一个片段的结束位置开始写入stream.pipe(fs.createWriteStream('uploads/' + name, {start: startPos})).on('finish', () => {// 合并完成一个文件片段后,增加计数count++;// 如果所有文件片段都已合并完成,删除已合并的文件片段目录if (count === files.length) {fs.rmSync(chunkDir, { recursive: true });}})// 更新下一个文件片段的写入起始位置startPos += fs.statSync(filePath).size;})}

然后在前端代码里,当分片全部上传完之后,调用 merge 接口:

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><!-- 引入axios库,用于发送HTTP请求 --><script src="https://unpkg.com/axios@0.24.0/dist/axios.min.js"></script>
</head><body><input id="fileInput" type="file" /><script>/* 获取文件输入元素 */const fileInput = document.querySelector('#fileInput');/* 定义每块的大小,单位为字节 */const chunkSize = 20 * 1024;/* 当文件输入改变时,处理文件分块上传 */fileInput.onchange = async function () {/* 获取选中的文件 */const file = fileInput.files[0];console.log(file);/* 分割文件为多个块 */const chunks = [];let startPos = 0;while (startPos < file.size) {chunks.push(file.slice(startPos, startPos + chunkSize));startPos += chunkSize;}const randomStr = Math.random().toString().slice(2, 8);let tasks = []/* 对每个分块创建表单数据并发送POST请求 */chunks.map((chunk, index) => {const data = new FormData();/* 为每个分块设置唯一的名称 */data.set('name', randomStr + '_' + file.name + '-' + index);/* 添加分块到表单数据 */data.append('files', chunk);tasks.push(axios.post('http://localhost:3000/upload', data));})await Promise.all(tasks)axios.get('http://localhost:3000/merge?name=' + randomStr + '_' + file.name)}</script>
</body></html>

1719757051775.png
可以看到文件分片上传之后 会执行 merge 接口 合并生成了下面文件
1719757116857.png

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

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

相关文章

QT使用QPainter绘制多边形维度图

多边形统计维度图是一种用于展示多个维度的数据的图表。它通过将各个维度表示为图表中的多边形的边&#xff0c;根据数据的大小和比例来确定各个维度的长度。 一、简述 本示例实现六边形战力统计维度图&#xff0c;一种将六个维度的战力统计以六边形图形展示的方法。六个维度是…

怎样在 PostgreSQL 中优化对复合索引的选择性?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01;&#x1f4da;领书&#xff1a;PostgreSQL 入门到精通.pdf 文章目录 怎样在 PostgreSQL 中优化对复合索引的选择性一、理解复合索引的概念二、选择性的重要性三、优化复合索…

shell脚本-linux如何在脚本中远程到一台linux机器并执行命令

需求&#xff1a;我们需要从11.0.1.17远程到11.0.1.16上执行命令 实现&#xff1a; 1.让11.0.1.17 可以免密登录到11.0.1.16 [rootlocalhost ~]# ssh-keygen Generating public/private rsa key pair. Enter file in which to save the key (/root/.ssh/id_rsa): Created d…

【问题记录】Docker配置mongodb副本集实现数据流实时获取

配置mongodb副本集实现数据流实时获取 前言操作步骤1. docker拉取mongodb镜像2. 连接mongo1镜像的mongosh3. 在mongosh中初始化副本集 注意点 前言 由于想用nodejs实现实时获取Mongodb数据流&#xff0c;但是报错显示需要有副本集的mongodb才能实现实时获取信息流&#xff0c;…

27.js实现鼠标拖拽

e.offsetX是鼠标距离准确事件源的左上角距离 e.clientX是鼠标距离浏览器可视窗口左上角的距离 e.pageX是鼠标距离文档左上角的距离 /* 当鼠标点击div时开始挪动&#xff0c;当鼠标抬起&#xff0c;div静止——事件源是div 当鼠标点击后,鼠标在移动——事件源…

SpringCache介绍

SpringCache是Spring提供的缓存框架。提供了基于注解的缓存功能。 SpringCache提供了一层抽象&#xff0c;底层可以切换不同的缓存实现&#xff08;只需要导入不同的Jar包即可&#xff09;&#xff0c;如EHCache&#xff0c;Caffeine&#xff0c;Redis。 2个重要依赖已经导入&a…

简单一阶滤波器设计:matlab和C实现

一、简单一阶滤波器的模型 二、示例 得: y(n)-0.9y(n-1)=x(n)+0.05x(n-1),即:y(n)=0.9y(n-1)+x(n)+0.05x(n-1) 已知:,并且有: A. 假设输入序列有N=100个点 B. 系统初始状态为0,即y(-1)=0 C. 输入序列是因果序列,

【OpenRecall】超越 Windows Recall,OpenRecall 为你的隐私和自由而战

引言 随着 Windows 11 的 Recall 功能推出&#xff0c;我们看到了数字记忆回顾的全新可能性。然而&#xff0c;这项功能受限于特定的硬件——Copilot 认证的 Windows 硬件&#xff0c;并且仅在 Windows 平台上可用。对于追求隐私和硬件灵活性的用户来说&#xff0c;这无疑是个…

长按加速- 解决react - setInterval下无法更新问题

最开始直接setInterval里&#xff0c;useState硬写&#xff0c;发现更新不&#xff0c;固定值 换let&#xff0c;发现dom更新不了 正确做法是用ref 并且pc端可以长按的&#xff0c;只是要用onTouchStart&#xff0c;不要用onMouseDown onTouchStart{handleMouseDown} onTou…

在设计电气系统时,电气工程师需要考虑哪些关键因素?

在设计电气系统时&#xff0c;电气工程师需要考虑多个关键因素&#xff0c;以确保系统的安全性、可靠性、效率和经济性。我收集归类了一份plc学习包&#xff0c;对于新手而言简直不要太棒&#xff0c;里面包括了新手各个时期的学习方向编程教学、问题视频讲解、毕设800套和语言…

word 设置多级混合标题自动更新

目录预览 一、问题描述二、原因分析三、解决方案四、参考链接 一、问题描述 有没有体会过多级标题&#xff0c;怎么设置都不听使唤的情况&#xff1f; 我想要的格式是&#xff1a; 二、原因分析 多级标题中发现&#xff0c;输入编号格式这里有个数字没有底纹,是了&#xff0…

系统架构设计师教程 第3章 信息系统基础知识-3.1 信息系统概述

系统架构设计师教程 第3章 信息系统基础知识-3.1 信息系统概述 3.1.1 信息系统的定义3.1.1.1 信息系统3.1.1.2 信息化3.1.2 信息系统的发展3.1.2.1 初始阶段3.1.2.2 传播阶段3.1.2.3 控制阶段3.1.2.4 集成阶段3.1.2.5 数据管理阶段3.1.2.6 成熟阶段3.1.3 信息系统的分类3.…

Redis-基础概念

目录 概念 Redis是什么 Redis 和 MySQL 的区别&#xff1f; Redis单线程有什么极端场景的瓶颈 Redis为什么快? 为什么Redis是单线程? Redis是单线程还是多线程 Redis为什么选择单线程做核心处理 Redis6.0之后引入了多线程&#xff0c;你知道为什么吗? 瓶颈是内存和I…

C#进阶-基于.NET Framework 4.x框架实现ASP.NET WebForms项目IP拦截器

在这篇文章中&#xff0c;我们将探讨如何在 ASP.NET WebForms 中实现IP拦截器&#xff0c;以便在 ASMX Web 服务方法 和 HTTP 请求 中根据IP地址进行访问控制。我们将使用自定义的 SoapExtension 和 IHttpModule 来实现这一功能&#xff0c;并根据常用的两种文本传输协议&#…

大模型“重构”教育:解构学习奥秘,推动教育普惠

大模型“重构”千行百业系列选题 生成式人工智能的热潮&#xff0c;为AI领域的发展注入新的活力&#xff0c;而“赋能千行百业”已经成为人们普遍对于人工智能和大模型的全新理解。 人工智能和大模型技术的迅猛发展正在以前所未有的速度深刻改变着各个行业。正如专家所预测&a…

【Apache POI】Java解析Excel文件并处理合并单元格-粘贴即用

同为牛马&#xff0c;点个赞吧&#xff01; 一、Excel文件样例 二、工具类源码 import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.xssf.usermodel.XSSFWorkbookFactory; import org.springframework.web.multip…

windows实现自动化按键

1.选择目标窗口 获取窗口句柄 void KeyPresser::selectWindow() {SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, NULL, WinEventProc, 0, 0, WINEVENT_OUTOFCONTEXT);selectedWindowLabel->setText("请点击目标窗口..."); }void CALLBACK …

ZStack Cloud 5.1.8正式发布

2024年7月5日&#xff0c;ZStack Cloud正式发布最新版本——ZStack Cloud 5.1.8&#xff0c;涵盖一系列重要功能&#xff0c;以下为您进行详细介绍。 亮点速览 GPU运维管理增强&#xff1a;新增GPU设备统一管理界面&#xff1b;支持GPU工作状态和实时负载监控报警三层网络运维…

套用BI方案做数据可视化是种什么体验?

在数字化转型的浪潮中&#xff0c;数据可视化作为连接数据与决策的桥梁&#xff0c;其重要性日益凸显。近期&#xff0c;我有幸体验了奥威BI方案进行数据可视化的全过程&#xff0c;这不仅是一次技术上的探索&#xff0c;更是一次对高效、智能数据分析的深刻感受。 初识奥威&a…

嵌入式物联网在教育行业的应用——案例分析

作者主页: 知孤云出岫 嵌入式物联网在教育行业的应用——案例分析 目录 作者主页:嵌入式物联网在教育行业的应用——案例分析一、引言二、智能教室&#xff1a;环境监测系统1. 硬件需求2. 电路连接3. 代码实现 三、个性化学习&#xff1a;智能学习平台1. 数据处理与分析2. 代…