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

news2024/11/25 22:25:07

上节我们学了在 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 * 1024
        async function formData() {
           const file = fileInput.files[0]
           const chunks = []
           let startPos = 0
           while(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.coloradmin.cn/o/1872155.html

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

相关文章

BFS 解决拓扑排序

例题一 解法&#xff1a; 算法思路&#xff1a; 原问题可以转换成⼀个拓扑排序问题。⽤ BFS 解决拓扑排序即可。 拓扑排序流程&#xff1a; a. 将所有⼊度为 0 的点加⼊到队列中&#xff1b; b. 当队列不空的时候&#xff0c;⼀直循环&#xff1a; i. 取出队头元素&am…

Transformers 安装与基本使用

文章目录 Github文档推荐文章简介安装官方示例中文情感分析模型分词器 Tokenizer填充 Padding截断 Truncation google-t5/t5-small使用脚本进行训练Pytorch 机器翻译数据集下载数据集格式转换 Github https://github.com/huggingface/transformers 文档 https://huggingface…

上海亚商投顾:沪指震荡下跌 多只银行股创年内新高

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 三大指数昨日震荡调整&#xff0c;沪指尾盘跌近1%&#xff0c;深成指、创业板指均跌超1.5%。 板块概念方面&a…

六西格玛项目实战:数据驱动,手机PCM率直线下降

在当前智能手机市场日益竞争激烈的背景下&#xff0c;消费者对手机质量的要求达到了前所未有的高度。PCM&#xff08;可能指生产过程中的某种不良率或缺陷率&#xff09;作为影响手机质量的关键因素&#xff0c;直接关联到消费者满意度和品牌形象。为了应对这一挑战&#xff0c…

ICCV2023知识蒸馏相关论文速览

Paper1 Spatial Self-Distillation for Object Detection with Inaccurate Bounding Boxes 摘要原文: Object detection via inaccurate bounding box supervision has boosted a broad interest due to the expensive high-quality annotation data or the occasional inevit…

Windows CMD:快速入门

文字目录 一、概述二、常用命令2.1 切换盘符2.2 查看当前盘符下的所有文件2.3 进入单级目录2.4 返回上一级目录2.5 进入多级目录2.6 回到盘符目录2.7 清屏2.8 退出 三、练习 一、概述 CMD 是 Command的缩写&#xff0c;即命令的意思&#xff0c;它的作用是利用命令的方式来操作…

Linux-笔记 使用SCP命令传输文件报错 :IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!

前言 使用scp命令向开发板传输文件发生报错&#xff0c;报错见下图; 解决 rm -rf /home/<用户名>/.ssh/known_hosts 此方法同样适用于使用ssh命令连接开发板报错的情况。 参考 https://blog.csdn.net/westsource/article/details/6636096

大模型赋能全链路可观测性:运维效能的革新之旅

目录 全链路可观测工程与大模型结合---提升运维效能 可观测性&#xff08;Observability&#xff09;在IT系统中的应用及其重要性 统一建设可观测数据 统一建设可观测数据的策略与流程 全链路的构成和监控形态 云上的全链路可视方案 为什么一定是Copilot 大模型的Copilo…

基于iview.viewUI实现行合并(无限制/有限制合并)【已验证可正常运行】

1.基于iview.viewUI实现行合并&#xff08;列之间没有所属对应关系&#xff0c;正常合并&#xff09; 注&#xff1a;以下代码来自于GPT4o&#xff1a;国内直连GPT4o 只需要修改以下要合并的列字段&#xff0c;就可以方便使用啦 mergeFields: [majorNo, devNam, overhaulAdvic…

【EXCEL技巧】Excel如何将数字前面的0去掉

Excel文件中经常会遇到数据是0001345这种&#xff0c;那么&#xff0c;如何将数字前面的0去掉呢&#xff1f;今天和大家分享方法。 首先&#xff0c;选中一列空的单元格&#xff0c;然后在单元格中输入公式TEXT(D3,0)&#xff0c;这里的D3指的是前面带有0的数据的位置 回车之后…

Linux基础- 使用 Apache 服务部署静态网站

目录 零. 简介 一. linux安装Apache 二. 创建网页 三. window访问 修改了一下默认端口 到 8080 零. 简介 Apache 是世界使用排名第一的 Web 服务器软件。 它具有以下一些显著特点和优势&#xff1a; 开源免费&#xff1a;可以免费使用和修改&#xff0c;拥有庞大的社区支…

小程序备案小程序认证双系统

​打造安全合规的线上平台 &#x1f50d; 一、引言&#xff1a;为何需要小程序备案与认证&#xff1f; 在数字化快速发展的今天&#xff0c;小程序已成为企业、个人展示自身、提供服务的重要窗口。然而&#xff0c;随着小程序数量的快速增长&#xff0c;安全、合规等问题也逐渐…

jenkins设置定时构建语法

一、设置定时 定时构建的语法是*** * * * ***。 第一个*表示分钟&#xff0c;取值范围是0~59。例如&#xff0c;5 * * * *表示每个小时的第5分钟会构建一次&#xff1b;H/15 * * * 或/15 * * * 表示每隔15分钟构建一次&#xff1b; 第2个表示小时&#xff0c;取值范围是0~23。…

深度解析RocketMq源码-IndexFile

1.绪论 在工作中&#xff0c;我们经常需要根据msgKey查询到某条日志。但是&#xff0c;通过前面对commitLog分析&#xff0c;producer将消息推送到broker过后&#xff0c;其实broker是直接消息到达broker的先后顺序写入到commitLog中的。我们如果想根据msgKey检索一条消息无疑…

Embedding 、词嵌入、向量模型说的是一回事么?AI是如何理解世界?AI人不能不看的Embedding白话科普!

在AI理解世界的过程中&#xff0c;向量模型扮演着一个至关重要的角色&#xff0c;甚至可以说它是AI大模型用以构建和理解复杂数据的基础&#xff0c;也是对不同形态数据的一种标准化的“浓缩”。它能够将语言、图像、声音等多样化的信息&#xff0c;转化为一种通用的、数学化的…

知乎正通过乱码来干扰必应/谷歌等爬虫,从而限制中文数据集被用于AI训练

有用户反馈称使用微软必应搜索和谷歌搜索发现存在不少知乎乱码内容&#xff0c;即搜索结果里知乎内容的标题和正文内容都可能是乱码的&#xff0c;但抓取的正文前面一些段落内容可以正常查看。考虑到此前知乎已经屏蔽除百度和搜狗以外的所有搜索引擎爬虫 (蜘蛛 / 机器人)&#…

《数字图像处理与机器视觉》案例二(基于边缘检测和数学形态学焊缝图像处理)

一、前言 焊缝是评价焊接质量的重要标志&#xff0c;人工检测方法存在检测标准不统一&#xff0c;检测精度低&#xff0c;焊缝视觉检测技术作为一种重要的质量检测方法&#xff0c;正逐渐在各行各业中崭露头角。把焊缝准确的从焊接工件中准确分割出来是焊缝评价的关键一步&…

使用模板方法设计模式封装 socket 套接字并实现Tcp服务器和客户端 简单工厂模式设计

文章目录 使用模板方法设计模式封装套接字使用封装后的套接字实现Tcp服务器和客户端实现Tcp服务器实现Tcp客户端 工厂模式 使用模板方法设计模式封装套接字 可以使用模块方法设计模式来设计套接字 socket 的封装 模板方法&#xff08;Template Method&#xff09;设计模式是一…

百度ueditor如何修改图片的保存位置

背景 编辑器的保存图片是设置有默认规则的&#xff0c;但是服务器上一般会把图片路径设置为软连接&#xff0c;所以我就需要更改编辑器保存图片的路径&#xff0c;要不然&#xff0c;每次有新的部署&#xff0c;上一次上传的图片就会失效。先来看看编辑器默认的保存路径吧&…

目标检测算法之RT-DETR

RT-DETR算法理解 BackgroundModel ArchitectureEfficient Hybrid EncoderUncertainty-minimal Query Selection 总结 Background Real-time Detection Transformer&#xff08;RT-DETR&#xff09;是一个基于tranformer的实时推理目标检测模型。RT-DETR是2023年百度发布的一个…