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

csdn推荐

npx http-server

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

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

再来试下多文件上传:

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

前端代码:


  
  
    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;
  

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

如果有多个文件的字段:

@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, @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,@Body() body) {
    console.log('body' ,body)
    console.log('files',files) 
  }

前端代码我们这样写:




    
    
    
    Document
    


    
    
        /*
        对拿到的文件进行分片,然后单独上传每个分片,分片名称为文件名+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  {
                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;
    

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

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

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

@Post('upload')
  @UseInterceptors(FilesInterceptor('files',20,{
    dest:'uploads'
  }))
  uploadFiles(@UploadedFiles() files :Array,@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;
    });
  }

测试一下:

接收到的文件分片:

合并之后:

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

文章来源:https://blog.csdn.net/m0_65789515/article/details/139856687



微信扫描下方的二维码阅读本文

© 版权声明
THE END
喜欢就支持一下吧
点赞7 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容