NestJS。将文件上传到S3存储(Minio)

NestJS是用于在Node.js平台上构建高效,可扩展的服务器应用程序的框架。您可能会发现NestJS是一个独立于平台的框架。这意味着它可以在您选择的两个框架之一的基础上工作:NestJS + Express或NestJS + Fastify。确实如此,或几乎如此。该平台的独立性以Content-Type:多部分/表单数据请求的处理结束。也就是说,实际上是在开发的第二天。如果您使用的是NestJS + Express平台,这不是一个大问题-文档中提供了一个Content-Type:multipart / form-data工作方式的示例。 NestJS + Fastify没有这样的示例,网上也没有那么多示例。其中一些示例遵循非常复杂的路径。



在NestJS + Fastify和NestJS + Express平台之间进行选择,我选择了NestJS + Fastify。知道开发人员在任何无法理解的情况下倾向于在Express中的req对象上挂起附加属性并在应用程序的不同部分之间进行通信的趋势后,我坚决决定Express将不在下一个项目中。



我只需要解决Content-Type的技术问题:multipart / form-data。另外,我计划将通过Content-Type:multipart / form-data请求收到的文件保存在S3存储中。在这方面,NestJS + Express平台上Content-Type:multipart / form-data请求的实现使我感到困惑,因为它不适用于流。



启动S3本地存储



S3是可通过http协议访问的数据存储(虽然不是严格地说,可能是一个文件存储)。 S3最初由AWS提供。当前,其他云服务也支持S3 API。但不仅如此。您可以在开发过程中本地使用S3服务器实现,并可能将S3服务器投入生产。



首先,您需要确定使用S3数据存储的动机。在某些情况下,这可以降低成本。例如,您可以使用最慢和最便宜的S3存储来存储备份。用于从存储中加载数据的高流量快速存储(流量单独收费)的成本可能与相同大小的SSD驱动器相当。



一个更强大的动机是1)可伸缩性-您无需考虑磁盘空间可能用完的事实,以及2)可靠性-服务器在群集中工作并且您无需考虑备份,因为所需的副本数始终可用。



要提高S3服务器的实现-minio-在本地只需在计算机上安装docker和docker-compose即可。对应的docker-compose.yml文件:



version: '3'
services:
  minio1:
    image: minio/minio:RELEASE.2020-08-08T04-50-06Z
    volumes:
      - ./s3/data1-1:/data1
      - ./s3/data1-2:/data2
    ports:
      - '9001:9000'
    environment:
      MINIO_ACCESS_KEY: minio
      MINIO_SECRET_KEY: minio123
    command: server http://minio{1...4}/data{1...2}
    healthcheck:
      test: ['CMD', 'curl', '-f', 'http://localhost:9000/minio/health/live']
      interval: 30s
      timeout: 20s
      retries: 3

  minio2:
    image: minio/minio:RELEASE.2020-08-08T04-50-06Z
    volumes:
      - ./s3/data2-1:/data1
      - ./s3/data2-2:/data2
    ports:
      - '9002:9000'
    environment:
      MINIO_ACCESS_KEY: minio
      MINIO_SECRET_KEY: minio123
    command: server http://minio{1...4}/data{1...2}
    healthcheck:
      test: ['CMD', 'curl', '-f', 'http://localhost:9000/minio/health/live']
      interval: 30s
      timeout: 20s
      retries: 3

  minio3:
    image: minio/minio:RELEASE.2020-08-08T04-50-06Z
    volumes:
      - ./s3/data3-1:/data1
      - ./s3/data3-2:/data2
    ports:
      - '9003:9000'
    environment:
      MINIO_ACCESS_KEY: minio
      MINIO_SECRET_KEY: minio123
    command: server http://minio{1...4}/data{1...2}
    healthcheck:
      test: ['CMD', 'curl', '-f', 'http://localhost:9000/minio/health/live']
      interval: 30s
      timeout: 20s
      retries: 3

  minio4:
    image: minio/minio:RELEASE.2020-08-08T04-50-06Z
    volumes:
      - ./s3/data4-1:/data1
      - ./s3/data4-2:/data2
    ports:
      - '9004:9000'
    environment:
      MINIO_ACCESS_KEY: minio
      MINIO_SECRET_KEY: minio123
    command: server http://minio{1...4}/data{1...2}
    healthcheck:
      test: ['CMD', 'curl', '-f', 'http://localhost:9000/minio/health/live']
      interval: 30s
      timeout: 20s
      retries: 3


我们开始-毫无问题,我们得到了由4台S3服务器组成的集群。



NestJS + Fastify + S3



我将从头开始描述使用NestJS服务器的过程,尽管其中一些内容在文档中已得到很好的描述。安装CLI NestJS:



npm install -g @nestjs/cli


创建一个新的NestJS项目:



nest new s3-nestjs-tut


已安装必需的软件包(包括与S3配合使用所需的软件包):




npm install --save @nestjs/platform-fastify fastify-multipart aws-sdk sharp
npm install --save-dev @types/fastify-multipart  @types/aws-sdk @types/sharp


默认情况下,该项目会安装NestJS + Express平台。docs.nestjs.com/techniques/performance文档中介绍了如何安装Fastify 另外,我们需要安装一个用于处理Content-Type的插件:multipart / form-data-fastify-multipart



import { NestFactory } from '@nestjs/core';
import {
  FastifyAdapter,
  NestFastifyApplication,
} from '@nestjs/platform-fastify';
import fastifyMultipart from 'fastify-multipart';
import { AppModule } from './app.module';

async function bootstrap() {
  const fastifyAdapter = new FastifyAdapter();
  fastifyAdapter.register(fastifyMultipart, {
    limits: {
      fieldNameSize: 1024, // Max field name size in bytes
      fieldSize: 128 * 1024 * 1024 * 1024, // Max field value size in bytes
      fields: 10, // Max number of non-file fields
      fileSize: 128 * 1024 * 1024 * 1024, // For multipart forms, the max file size
      files: 2, // Max number of file fields
      headerPairs: 2000, // Max number of header key=>value pairs
    },
  });
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    fastifyAdapter,
  );
  await app.listen(3000, '127.0.0.1');
}

bootstrap();


现在,我们将描述将文件上传到S3存储库的服务,减少了处理某些类型的错误的代码(全文在文章存储库中):



import { Injectable, HttpException, BadRequestException } from '@nestjs/common';
import { S3 } from 'aws-sdk';
import fastify = require('fastify');
import { AppResponseDto } from './dto/app.response.dto';
import * as sharp from 'sharp';

@Injectable()
export class AppService {
  async uploadFile(req: fastify.FastifyRequest): Promise<any> {

    const promises = [];

    return new Promise((resolve, reject) => {

      const mp = req.multipart(handler, onEnd);

      function onEnd(err) {
        if (err) {
          reject(new HttpException(err, 500));
        } else {
          Promise.all(promises).then(
            data => {
              resolve({ result: 'OK' });
            },
            err => {
              reject(new HttpException(err, 500));
            },
          );
        }
      }

      function handler(field, file, filename, encoding, mimetype: string) {
        if (mimetype && mimetype.match(/^image\/(.*)/)) {
          const imageType = mimetype.match(/^image\/(.*)/)[1];
          const s3Stream = new S3({
            accessKeyId: 'minio',
            secretAccessKey: 'minio123',
            endpoint: 'http://127.0.0.1:9001',
            s3ForcePathStyle: true, // needed with minio?
            signatureVersion: 'v4',
          });
          const promise = s3Stream
            .upload(
              {
                Bucket: 'test',
                Key: `200x200_${filename}`,
                Body: file.pipe(
                  sharp()
                    .resize(200, 200)
                    [imageType](),
                ),
              }
            )
            .promise();
          promises.push(promise);
        }
        const s3Stream = new S3({
          accessKeyId: 'minio',
          secretAccessKey: 'minio123',
          endpoint: 'http://127.0.0.1:9001',
          s3ForcePathStyle: true, // needed with minio?
          signatureVersion: 'v4',
        });
        const promise = s3Stream
          .upload({ Bucket: 'test', Key: filename, Body: file })
          .promise();
        promises.push(promise);
      }
    });
  }
}


在这些功能中,应注意,如果加载了图片,我们会将输入流写入两个输出流。其中一个流将图片压缩为200x200的大小。在所有情况下,都使用流样式。但是为了捕获可能的错误并将其返回给控制器,我们调用了aws-sdk库中定义的promise()方法。我们将已收到的承诺累积在promises数组中:



        const promise = s3Stream
          .upload({ Bucket: 'test', Key: filename, Body: file })
          .promise();
        promises.push(promise);


并且,进一步,我们期望它们在方法中得到解决Promise.all(promises)



控制器代码,在其中我仍然必须将FastifyRequest转发给服务:



import { Controller, Post, Req } from '@nestjs/common';
import { AppService } from './app.service';
import { FastifyRequest } from 'fastify';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Post('/upload')
  async uploadFile(@Req() req: FastifyRequest): Promise<any> {
    const result = await this.appService.uploadFile(req);
    return result;
  }
}


项目启动:



npm run start:dev


文章 资料库github.com/apapacy/s3-nestjs-tut



apapacy@gmail.com

2020年8月13日



All Articles