نيستجس. تحميل الملفات إلى تخزين S3 (minio)

NestJS هو إطار عمل لبناء تطبيقات خادم فعالة وقابلة للتطوير على منصة Node.js. قد تصادف الادعاء بأن NestJS هو إطار عمل مستقل للمنصة. هذا يعني أنه يمكن أن يعمل على أساس أحد إطارين من اختيارك: NestJS + Express أو NestJS + Fastify. هذا حقاً كذلك ، أو تقريباً كذلك. تنتهي استقلالية النظام الأساسي هذه بمعالجة نوع المحتوى: طلبات بيانات متعددة / نموذجية. هذا هو عمليا في اليوم الثاني من التطوير. وهذه ليست مشكلة كبيرة إذا كنت تستخدم منصة NestJS + Express - فهناك مثال على كيفية عمل Content-Type: multipart / form-data في الوثائق. لا يوجد مثل هذا المثال لـ NestJS + Fastify ، ولا توجد أمثلة كثيرة على الشبكة. وبعض هذه الأمثلة تتبع مسارًا معقدًا للغاية.



بالاختيار بين منصة 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



All Articles