在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日