بالاختيار بين منصة NestJS + Fastify و NestJS + Express ، قمت باختيار NestJS + Fastify. مع العلم بميل المطورين في أي موقف غير مفهوم لتعليق خصائص إضافية على كائن req في Express وبالتالي التواصل بين أجزاء مختلفة من التطبيق ، قررت بشدة أن Express لن يكون في المشروع التالي.
كنت بحاجة فقط إلى حل مشكلة فنية مع نوع المحتوى: بيانات متعددة / نموذجية. أيضًا ، خططت لحفظ الملفات المستلمة عبر نوع المحتوى: طلبات بيانات متعددة الأجزاء / نماذج في وحدة تخزين S3. في هذا الصدد ، فإن تنفيذ نوع المحتوى: طلبات بيانات متعددة الأجزاء / استمارة على منصة NestJS + Express أربكني أنها لا تعمل مع التدفقات.
إطلاق S3 Local Storage
S3 عبارة عن مخزن بيانات (قد يقول المرء ، وإن لم يكن بالمعنى الدقيق للكلمة ، مخزن ملفات) يمكن الوصول إليه عبر بروتوكول http. تم توفير S3 في الأصل بواسطة AWS. يتم دعم S3 API حاليًا بواسطة خدمات سحابية أخرى أيضًا. ولكن ليس فقط. هناك تطبيقات خادم S3 يمكنك رفعها محليًا لاستخدامها أثناء التطوير ، وربما تشغيل خوادم S3 وتشغيلها في الإنتاج.
أولاً ، عليك تحديد الدافع لاستخدام تخزين بيانات S3. في بعض الحالات ، يمكن أن يقلل هذا من التكاليف. على سبيل المثال ، يمكنك أن تأخذ أبطأ وأرخص تخزين S3 لتخزين النسخ الاحتياطية. من المحتمل أن تكلف التخزين السريع مع حركة المرور العالية (يتم شحن حركة المرور بشكل منفصل) لتحميل البيانات من التخزين مقارنة بأقراص SSD من نفس الحجم.
الدافع الأكثر قوة هو 1) قابلية التوسع - لا تحتاج إلى التفكير في حقيقة أن مساحة القرص قد تنفد ، و 2) الموثوقية - تعمل الخوادم في مجموعة ولا تحتاج إلى التفكير في النسخ الاحتياطي ، نظرًا لأن العدد المطلوب من النسخ متاح دائمًا.
لرفع مستوى تنفيذ خوادم S3 - minio - محليًا ، ما عليك سوى تثبيت عامل الإرساء والتركيب على الكمبيوتر. ملف 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. كيفية تثبيت Fastify موصوفة في الوثائق docs.nestjs.com/techniques/performance . بالإضافة إلى ذلك ، نحتاج إلى تثبيت مكون إضافي للتعامل مع نوع المحتوى: متعدد الأجزاء / بيانات النموذج - 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);
}
});
}
}
من بين الميزات ، تجدر الإشارة إلى أننا نكتب دفق إدخال في دفقين إخراج إذا تم تحميل صورة. يقوم أحد التدفقات بضغط الصورة إلى حجم 200 × 200. في جميع الحالات ، يتم استخدام أسلوب العمل مع التدفقات. ولكن من أجل اكتشاف الأخطاء المحتملة وإعادتها إلى وحدة التحكم ، فإننا نطلق على طريقة الوعد () ، المحددة في مكتبة aws-sdk. نجمع الوعود الواردة في مجموعة الوعود:
const promise = s3Stream
.upload({ Bucket: 'test', Key: filename, Body: file })
.promise();
promises.push(promise);
علاوة على ذلك ، نتوقع حلها في الطريقة
Promise.all(promises).
رمز وحدة التحكم ، الذي لا يزال يتعين علينا إعادة توجيه طلب Fastify فيه إلى الخدمة:
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
13 أغسطس 2020