Nest使用multer实现文件上传,并实现大文件分片上传(下)

上节我们学了在 Express 里用 multer 包处理 multipart/form-data 类型的请求中的 file

单个、多个字段的单个、多个 file 都能轻松取出来。

接下来我们就来学习一下在Nest 里使用multer。

一,Nest如何使用multer实现文件上传

首先我们先创建一个Nest项目:

nest new nest-multer-upload -p npm

还需要安装下 multer 的 ts 类型的包:

npm install -D @types/multer

我们在AppController 添加这样一个 handler:

import { Controller, Get, Post, UseInterceptors,UploadedFile,Body } from '@nestjs/common';
import { AppService } from './app.service';
import { FileInterceptor } from '@nestjs/platform-express';@Controller()
export class AppController {constructor(private readonly appService: AppService) {}@Post('file')@UseInterceptors(FileInterceptor('file',{dest:'uploads'}))uploadFile(@UploadedFile() file:Express.Multer.File,@Body() body){console.log('body', body);console.log('file', file);}
}

使用 FileInterceptor 来提取 file 字段,然后通过 UploadedFile 装饰器把它作为参数传入。

然后 npm run start:dev 把服务跑起来,一保存,就可以看到这个目录被创建了:

然后我们来写前端代码,让 nest 服务支持静态文件的访问,然后让 nest 服务支持跨域,再单独跑个 http-server 来提供静态服务。

在根目录创建 index.html,编写前端代码:

<!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><script src="https://unpkg.com/axios@0.24.0/dist/axios.min.js"></script>
</head>
<body><input id="fileInput" type="file" multiple/><script>const fileInput = document.querySelector('#fileInput');async function formData() {const data = new FormData();data.set('name','张三');data.set('age', 24);data.set('file', fileInput.files[0]);const res = await axios.post('http://localhost:3000/file', data);console.log(res);}fileInput.onchange = formData;</script>
</body>
</html>

先单独跑个 http-server 来提供静态服务:

npx http-server

接下来我们在页面选择一个文件上传:

服务端就打印了file对象并存到uploads文件夹:

 再来试下多文件上传:

// 多文件上传@Post('files')@UseInterceptors(FilesInterceptor('files',3,{dest:'uploads'}))uploadFiles(@UploadedFiles() files:Array<Express.Multer.File>,@Body() body) {console.log('body', body);console.log('files', files);}
//把 FileInterceptor 换成 FilesInterceptor,把 UploadedFile 换成 UploadedFiles,都是多加一个 s。

 前端代码:

<body><input id="fileInput" type="file" multiple /><script>const fileInput = document.querySelector('#fileInput');async function formData() {const data = new FormData();data.set('name', '张三');data.set('age', 24);[...fileInput.files].forEach(item => {data.append('files', item)})const res = await axios.post('http://localhost:3000/files', data, {headers: { 'content-type': 'multipart/form-data' }});console.log(res);}fileInput.onchange = formData;</script>
</body>

这样就可以上传多文件了:

 

如果有多个文件的字段:

@Post('filesA')@UseInterceptors(FileFieldsInterceptor([{ name: 'file1', maxCount: 2 },{ name: 'file2', maxCount: 3 }], {dest: 'uploads'}))uploadFileFields(@UploadedFiles() files: { file1?: Express.Multer.File[], file2?: Express.Multer.File[] }, @Body() body) {console.log('body', body);console.log('files', files);}

前端代码和之前都差不多,只是字段名和接口不一样,在这里就不一一赘述了

如果并不知道有哪些字段是 file :

@Post('filesB')@UseInterceptors(AnyFilesInterceptor({dest: 'uploads'}))uploadAnyFiles(@UploadedFiles() files: Array<Express.Multer.File>, @Body() body) {console.log('body', body);console.log('files', files);}

文件的校验:

像文件大小、类型的校验这种逻辑太过常见,Nest 给封装好了,可以直接用:

@Post('filesC')@UseInterceptors(FileInterceptor('file', {dest: 'uploads'}))uploadFile3(@UploadedFile(new ParseFilePipe({validators: [new MaxFileSizeValidator({ maxSize: 1000 }),new FileTypeValidator({ fileType: 'image/jpeg' }),],})) file: Express.Multer.File, @Body() body) {console.log('body', body);console.log('file', file);}//ParseFilePipe:它的作用是调用传入的 validator 来对文件做校验//比如 MaxFileSizeValidator 是校验文件大小、FileTypeValidator 是校验文件类型

 我们来试试:

可以看到,返回的也是 400 响应,并且 message 说明了具体的错误信息

而且这个错误信息可以自己修改:

@Post('filesC')@UseInterceptors(FileInterceptor('file', {dest: 'uploads'}))uploadFile3(@UploadedFile(new ParseFilePipe({exceptionFactory:err => {throw new HttpException('错误信息:' + err ,400)},validators: [new MaxFileSizeValidator({ maxSize: 1000 }),new FileTypeValidator({ fileType: 'image/jpeg' }),],})) file: Express.Multer.File, @Body() body) {console.log('body', body);console.log('file', file);}

看看效果:

 二,大文件分片上传

当文件很大的时候,上传就会变得比较慢。

假设传一个 100M 的文件需要 3 分钟,那传一个 1G 的文件就需要 30 分钟。

这样是能完成功能,但是产品的体验会很不好。

所以大文件上传的场景,需要做专门的优化。

把 1G 的大文件分割成 10 个 100M 的小文件,然后这些文件并行上传,不就快了?

然后等 10 个小文件都传完之后,再发一个请求把这 10 个小文件合并成原来的大文件。

这就是大文件分片上传的方案。

那如何拆分和合并呢?

浏览器里 Blob 有 slice 方法,可以截取某个范围的数据,而 File 就是一种 Blob。

所以可以在 input 里选择了 file 之后,通过 slice 对 File 分片。

那合并呢?

fs 的 createWriteStream 方法支持指定 start,也就是从什么位置开始写入。

这样把每个分片按照不同位置写入文件里,就完成合并了。

创建个 Nest 项目:

nest new large-file-sharding-upload

在 AppController 添加一个路由:

@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) }

前端代码我们这样写:

<!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><script src="https://unpkg.com/axios@0.24.0/dist/axios.min.js"></script>
</head>
<body><input id="fileInput" type="file"/><script>/*对拿到的文件进行分片,然后单独上传每个分片,分片名称为文件名+index*/const fileInput = document.querySelector('#fileInput');const chunkSize = 20 * 1024async function formData() {const file = fileInput.files[0]const chunks = []let startPos = 0while(startPos < file.size) {chunks.push(file.slice(startPos, startPos + chunkSize));startPos += chunkSize;}chunks.map((chunk, index) => {const data = new FormData();data.set('name', file.name + '-' + index)data.append('files', chunk);axios.post('http://localhost:3000/upload', data);})console.log(res);}fileInput.onchange = formData;</script>
</body>
</html>

接下来我们来测试一下,这里我测试用的图片是 40k:

 每 20k 一个分片,一共是 2 个分片,服务端接收到了这 2 个分片:

接下来我们来进行合并操作:

@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);// 然后我们来合并文件const chunkDirMerge = 'uploads/chunks_'+ fileName;const filesMerge = fs.readdirSync(chunkDirMerge);let count = 0;let startPos = 0;filesMerge.map(file => {const filePath = chunkDirMerge + '/' + file;const stream = fs.createReadStream(filePath);stream.pipe(fs.createWriteStream('uploads/' + fileName, {start: startPos})).on('finish', () => {count ++;// 然后我们在合并完成之后把 chunks 目录删掉。if(count === files.length) {fs.rm(chunkDir, {recursive: true}, () =>{}); }})startPos += fs.statSync(filePath).size;});}

 测试一下:

接收到的文件分片:

合并之后:

至此,大文件分片上传就完成了。

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

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

相关文章

性能测试4【搬代码】

性能测试4与性能测试3最后的 三、性能瓶颈分析和性能调优 (1)基准测试 (2)负载测试 (3)压力测试 (4)浪涌测试 (5)容量测试 有关&#xff0c;需要结合看 性能瓶颈分析和性能调优 (1)基准测试 一般是单接口&#xff08;单交易&#xff09;&#xff1a;使用一个用持续压测1min以…

【Linux系列】Fedora40安装VMware Workstation Pro报错

问题描述 由于Fedora 40使用的Linux内核是6.9,导致安装VMware Workstation Pro 时&#xff0c;安装依赖无法成功&#xff0c;具体报错如下 ..................CC [M] /tmp/modconfig-a8Fcf5/vmnet-only/smac.oCC [M] /tmp/modconfig-a8Fcf5/vmnet-only/vnetEvent.oCC [M] …

液体粒子计数器的原理及常见型号选择 lighthouse代理商北京中邦兴业

​液体颗粒计数用于测量液体样品中颗粒的大小和分布。通过用激光二极管照射液体样品并检测散射光来测量颗粒分布和尺寸。散射光的性质与粒子大小的大小有关。液体颗粒计数器可用于批量取样或在线&#xff08;连续监测&#xff09;应用&#xff0c;如水处理厂&#xff0c;或用于…

【封装】Unity编辑器模式GUID加载资源

介绍 在编辑器模式下通过GUID获取工程目录下的指定资源的接口工具封装 工具原理 借助AssetDatabaseAPI FindAssets : 获取 GUID GUIDToAssetPath : 通过GUID获取路径LoadAssetAtPath<T>: 通过路径加载资源 代码&#xff1a; public static class GetAssetUtil {pub…

ADC位数、增益调制与参考电压

位数&#xff1a;12bit、10bit、8bit 一般就是对应的ADC值分别为&#xff1a;4095、1023、255&#xff0c;也就选用对应位数时ADC的最大值。 增益的作用 增益设置用于放大或缩小输入信号&#xff0c;使其适配到ADC的输入范围。增益设置可以通过配置SAADC的通道配置寄存器来实…

Vscode lanuch.json

Intro 使用launch.json 能够方便的运行需要传很多参数的代码文件 如下&#xff1a; import math import argparse # 1、导入argpase包def parse_args():parse argparse.ArgumentParser(descriptionCalculate cylinder volume) # 2、创建参数对象parse.add_argument(--rad…

怎么处理整合了shiro的应用的RPC接口鉴权问题

这篇文章分享一下&#xff1a;当一个服务提供者整合了shiro安全框架来实现权限访问控制时&#xff0c;服务消费者通过feign请求服务提供者的接口时的鉴权不通过问题。 问题描述 博主有一个项目pms&#xff08;权限管理系统&#xff09;&#xff0c;使用了shiro框架来实现鉴权功…

【免费可视化工具】智慧港口全景监测大屏引领行业变革

在传统的港口运营中&#xff0c;人们往往要面对繁琐的数据、复杂的流程和不确定的风险。但随着科技的发展&#xff0c;智慧港口全景监测大屏&#xff0c;集数据整合、实时监控、智能分析于一体&#xff0c;为港口运营提供了全新的解决方案。 今天要说的是山海鲸可视化搭建的智慧…

Android 通知组

一. 通知组简介 从 Android 7.0&#xff08;API 级别 24&#xff09;开始&#xff0c;您可以在一个组中显示相关通知。如下所示: 图 1. 收起&#xff08;顶部&#xff09;和展开&#xff08;底部&#xff09;的通知组。 注意 &#xff1a;如果应用发出 4 条或更多条通知且未…

第十一次Javaweb作业

4.登录校验 4.1会话 --用户打开浏览器&#xff0c;访问web服务器的资源&#xff0c;会话建立&#xff0c;直到有一方断开连接&#xff0c;会话结束。在一次会话中可以包含多次请求和响应。 会话跟踪&#xff1a;一种维护浏览器状态的方法&#xff0c;服务器需要识别多次请求…

Django(根据Models中模型类反向生成数据库表)—— python篇

一、数据库的配置 1、 django默认支持 sqlite&#xff0c;mysql, oracle,postgresql数据库。 sqlite&#xff1a;django默认使用sqlite的数据库&#xff0c;默认自带sqlite的数据库驱动 , 引擎名称&#xff1a;django.db.backends.sqlite3 mysql&#xff1a;引擎名称&#xff…

解决Transformer根本缺陷,所有大模型都能获得巨大改进

即使最强大的 LLM 也难以通过 token 索引来关注句子等概念&#xff0c;现在有办法了。 最近两天&#xff0c;马斯克和 LeCun 的口水战妥妥成为大家的看点。这两位 AI 圈的名人你来我往&#xff0c;在推特&#xff08;现为 X&#xff09;上相互拆对方台。 LeCun 在宣传自家最新论…

leetcode 动态规划(基础版)单词拆分

题目&#xff1a; 题解&#xff1a; 一种可行的dp做法是基于完全背包问题&#xff0c;将s看成是一个背包&#xff0c;wordDict看作是物品&#xff0c;然后往s中放入物品判断最终是否可以变为给定的s即可。这道题和上一题都用到了在dp如何枚举连续子串和状态表示&#xff1a;枚…

【C++】C++拷贝构造引发的无限递归问题及其解决方法

了解C拷贝构造引发的无限递归问题及其解决方法 在C中&#xff0c;拷贝构造函数用于创建一个对象的副本。正确使用拷贝构造函数非常重要&#xff0c;否则可能会引发无限递归问题&#xff0c;导致程序崩溃。 什么是拷贝构造函数&#xff1f; 拷贝构造函数是一种特殊的构造函数…

Golang 百题(实战快速掌握语法)_2

返回集合中满足指定条件的最后一个元素 本实验将实现判断给定集合中的元素是否符合&#xff0c;并返回符合的最后一个元素。 知识点 forfmt.Error 适合人群 本课程属于基础课程。需要用户掌握 Go 语言编程基础知识、计算机基础知识和 Linux 环境的基本用法。 许可证 内容…

潮玩手办盲盒前端项目模版的技术探索与应用案例

一、引言 在数字化时代&#xff0c;随着消费者对个性化和艺术化产品的需求日益增长&#xff0c;潮玩手办和盲盒市场逐渐崭露头角。为了满足这一市场需求&#xff0c;前端技术团队需要构建一个功能丰富、用户友好的在线平台。本文旨在探讨潮玩手办盲盒前端项目模版的技术实现&a…

MySQL入门学习-MySQL日志.查询日志

MySQL 日志包括错误日志、通用查询日志、二进制日志、中继日志、慢查询日志等&#xff0c;以下是关于查询日志的介绍&#xff1a; 一、概念&#xff1a; 查询日志记录了客户端连接 MySQL 后执行的所有语句&#xff0c;包括执行失败的语句。 二、特点&#xff1a; - 记录详细…

Postman接口工具实战

为了更好地展示Postman接口测试的实战过程&#xff0c;我将以一个简单的实战示例来说明如何使用Postman完成一个API的测试。假设我们要测试一个假想的天气查询API&#xff0c;该API允许用户通过城市名查询天气情况。我们将执行以下步骤&#xff1a; 1. 准备工作 确保已经安装…

FuTalk设计周刊-Vol.025

&#x1f525;&#x1f525;AI漫谈 热点捕手&#x1f525;&#x1f525; 1、耗时半年&#xff0c;实地调研&#xff01;泣血2万字&#xff0c;破除你的人工智能焦虑《2023最全AI商业落地调研报告》 链接https://www.bilibili.com/video/BV1YB4y1f7GE?share_sourcecopy_web &…

Vue3路由守卫的理解

官网:导航守卫 | Vue Router 目录 1.路由类型 1.1导航守卫 1.2路由独享 1.3组件内守卫 2.路由元信息 1.路由类型 1.1导航守卫 全局 :所有路由都会触发 router.beforeEach((to,from,next)>{}) router.afterEach((to,form,next)>{}) 1.2路由独享 路由表中的组件 be…