0
点赞
收藏
分享

微信扫一扫

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

然后我们来写前端代码,让 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);
  }

文件的校验:

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

看看效果:

 二,大文件分片上传

创建个 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;
    });
  }

 测试一下:

接收到的文件分片:

合并之后:

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

举报

相关推荐

0 条评论