nestjs fileupload s3 naver cloud aws object bucket nhn cloud express multer

agnusdei·2023년 7월 19일
0
// 필요한 NestJS 모듈과 라이브러리를 임포트합니다.
import {
  Controller,
  Delete,
  Post,
  Query,
  UploadedFile,
  UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { ApiTags } from '@nestjs/swagger';
import dayjs from 'dayjs'; // 날짜 및 시간 조작 라이브러리인 'dayjs'를 임포트합니다.
import 'multer'; // Multer 라이브러리를 임포트합니다.
import { diskStorage } from 'multer'; // Multer의 diskStorage를 사용하기 위해 임포트합니다.
import { join } from 'path'; // 파일 경로를 조작하기 위한 Node.js의 'path' 모듈을 임포트합니다.
import { UploadService } from './upload.service'; // UploadService를 임포트합니다.

@ApiTags('파일 업로드') // NestJS의 Swagger 데코레이터로, API 문서에서 태그를 표시합니다.
@Controller({ version: '1' }) // NestJS의 컨트롤러 데코레이터로, API 버전을 설정합니다.
export class UploadController {
  constructor(private readonly uploadService: UploadService) {}

  @Post() // HTTP POST 요청 핸들러 데코레이터로, 파일 업로드를 처리하는 엔드포인트입니다.
  @UseInterceptors( // NestJS의 Interceptor를 사용하여 요청을 가로채고 처리하는데 사용합니다.
    FileInterceptor('file', { // 파일 업로드를 위한 FileInterceptor를 사용합니다. 'file'은 업로드할 파일 필드의 이름입니다.
      dest: join(__dirname, 'temp'), // 업로드된 파일이 저장될 디렉토리 경로를 지정합니다.
      storage: diskStorage({ // 파일 저장을 위한 diskStorage를 사용합니다.
        destination: join(__dirname, 'temp'), // 업로드된 파일의 저장 경로를 지정합니다.
        filename: (req, file, cb) => { // 파일의 저장 파일명을 지정하는 함수입니다.
          cb(null, `${dayjs().unix()}_${file.originalname}`);
          // dayjs().unix()를 사용하여 현재 시간을 Unix 타임스탬프로 변환한 값과 업로드된 파일의 원래 이름을 조합하여 저장 파일명을 생성합니다.
        },
      }),
    })
  )
  uploadFile(@UploadedFile() file: Express.Multer.File) { // 업로드된 파일을 핸들링하는 메서드입니다.
    return this.uploadService.uploadFile(file); // UploadService를 사용하여 파일을 업로드하는 비즈니스 로직을 호출합니다.
  }

  @Delete() // HTTP DELETE 요청 핸들러 데코레이터로, 파일 삭제를 처리하는 엔드포인트입니다.
  deleteFile(@Query('name') name: string) { // 쿼리 파라미터로 전달된 파일 이름을 받아옵니다.
    return this.uploadService.deleteFile(name); // UploadService를 사용하여 파일을 삭제하는 비즈니스 로직을 호출합니다.
  }
}

이 코드는 NestJS를 사용하여 파일 업로드와 삭제를 처리하는 UploadController 클래스를 정의한 것입니다. NestJS의 데코레이터와 Interceptor를 활용하여 파일 업로드를 쉽게 구현하고 있습니다. 업로드된 파일은 'temp' 디렉토리에 저장되며, 이후 UploadService에서 실제로 파일을 업로드하고 삭제하는 로직이 구현될 것으로 예상됩니다.

FileInterceptor는 NestJS에서 제공하는 파일 업로드를 처리하는 Interceptor 중 하나입니다. Interceptor는 HTTP 요청을 가로채고 조작하는 데 사용되며, 컨트롤러의 엔드포인트로 들어오는 요청을 가로채서 파일 업로드와 같은 추가 작업을 수행할 수 있게 해줍니다.

FileInterceptor의 파라미터와 기능을 상세히 설명하겠습니다:

FileInterceptor(
  'file', // 업로드할 파일 필드의 이름을 지정합니다. 'file'은 업로드할 파일이 들어올 필드의 이름입니다.
  {
    dest: join(__dirname, 'temp'), // 업로드된 파일이 저장될 디렉토리 경로를 지정합니다.
    storage: diskStorage({ // 파일 저장을 위한 diskStorage를 사용합니다.
      destination: join(__dirname, 'temp'), // 업로드된 파일의 저장 경로를 지정합니다.
      filename: (req, file, cb) => { // 파일의 저장 파일명을 지정하는 함수입니다.
        cb(null, `${dayjs().unix()}_${file.originalname}`);
        // dayjs().unix()를 사용하여 현재 시간을 Unix 타임스탬프로 변환한 값과 업로드된 파일의 원래 이름을 조합하여 저장 파일명을 생성합니다.
      },
    }),
)

파라미터 설명:

  1. 'file': 업로드할 파일이 들어올 필드의 이름을 지정합니다. HTTP 요청의 멀티파트(form-data) 형식으로 파일이 전송되는 경우, 이 이름으로 파일을 찾아서 업로드합니다.

  2. options: 파일 업로드에 관련된 옵션을 설정하는 객체입니다.

    • dest: 업로드된 파일이 저장될 디렉토리 경로를 지정합니다. __dirname은 현재 파일이 위치한 디렉토리를 나타내며, 'temp'는 'temp' 디렉토리를 의미합니다. 업로드된 파일은 이 경로에 저장됩니다.

    • storage: 파일 저장을 위한 스토리지 객체를 설정합니다. diskStorage를 사용하면 업로드된 파일을 디스크에 저장할 수 있습니다. destination 속성은 업로드된 파일의 저장 경로를 지정하며, 위에서 설정한 dest와 동일합니다. filename 속성은 저장될 파일명을 지정하는 함수를 받습니다. 이 함수는 req (HTTP 요청 객체), file (업로드된 파일 객체), cb (콜백 함수)를 매개변수로 받습니다. 함수 내에서 콜백 함수 cb를 호출하여 저장될 파일명을 반환합니다. 이 코드에서는 현재 시간을 Unix 타임스탬프로 변환한 값과 업로드된 파일의 원래 이름을 조합하여 고유한 저장 파일명을 생성합니다.

이렇게 FileInterceptor를 사용하면 클라이언트가 업로드한 파일을 dest에 지정한 디렉토리에 저장하면서 저장 파일명을 커스터마이징할 수 있습니다. 이후 업로드된 파일은 @UploadedFile() 데코레이터를 사용하여 컨트롤러 메서드에서 사용할 수 있게 됩니다.

// 필요한 NestJS 모듈과 라이브러리를 임포트합니다.
import { File } from '@aresa/interface'; // @aresa/interface에서 File 인터페이스를 임포트합니다.
import { BadGatewayException, Injectable, Logger } from '@nestjs/common'; // NestJS의 데코레이터와 예외 클래스, 로거를 임포트합니다.
import * as AWS from 'aws-sdk'; // AWS SDK를 사용하기 위해 'aws-sdk'를 임포트합니다.
import fs from 'fs'; // Node.js의 파일 시스템 모듈을 임포트합니다.
import 'multer'; // Multer 라이브러리를 임포트합니다.
import { join } from 'path'; // 파일 경로를 조작하기 위한 Node.js의 'path' 모듈을 임포트합니다.

@Injectable() // NestJS의 서비스(Injectable) 데코레이터로 서비스 클래스임을 선언합니다.
export class UploadService {
  private logger: Logger = new Logger(UploadService.name); // 로거 인스턴스를 생성하여 사용합니다.

  /** 전역 S3 객체 생성 */
  private s3 = new AWS.S3({
    endpoint: 'https://kr.object.ncloudstorage.com', // 네이버 클라우드 객체 스토리지 엔드포인트를 설정합니다.
    region: 'kr-standard', // 지역을 'kr-standard'로 설정합니다.
    credentials: {
      accessKeyId: process.env.NAVER_CLOUD_KEY, // 네이버 클라우드의 액세스 키를 환경 변수에서 가져옵니다.
      secretAccessKey: process.env.NAVER_CLOUD_SECRET, // 네이버 클라우드의 비밀 액세스 키를 환경 변수에서 가져옵니다.
    },
  });

  /**
   * 파일을 업로드합니다.
   * @param file 업로드할 파일 객체
   * @returns 업로드된 파일 정보 (url, size, name)
   */
  async uploadFile(file: Express.Multer.File): Promise<File> {
    // 업로드된 파일을 읽어와서 체크합니다.
    const check = fs.readFileSync(join(__dirname, 'temp', file.filename));
    if (!check) {
      // 파일이 존재하지 않으면 BadGatewayException을 발생시킵니다.
      throw new BadGatewayException();
    }

    try {
      /** 네이버 클라우드에 파일을 업로드합니다. */
      await this.s3
        .putObject({
          Bucket: 'aresa-assets', // 업로드할 버킷 이름을 설정합니다.
          Key: file.filename, // 업로드할 파일의 키 (이름)를 설정합니다.
          ACL: 'public-read', // 업로드된 파일에 대한 접근 권한을 설정합니다.
          Body: fs.createReadStream(join(__dirname, 'temp', file.filename)), // 업로드할 파일의 스트림을 설정합니다.
        })
        .promise();

      this.logger.log(`${file.filename}을 업로드했습니다.`); // 업로드 로그를 남깁니다.

      /** 파일을 삭제합니다. */
      fs.rmSync(join(__dirname, 'temp', file.filename)); // 업로드 후 임시로 저장한 파일을 삭제합니다.

      // 업로드된 파일 정보를 반환합니다.
      return {
        url: `${process.env.NAVER_CLOUD_BUCKET_ENDPOINT}/${file.filename}`,
        size: file.size,
        name: file.filename,
      };
    } catch (error) {
      // 업로드 중 오류가 발생하면 BadGatewayException을 발생시킵니다.
      this.logger.error(
        `파일 ${file.filename}을 업로드 하는 중 오류가 발생했습니다.`
      );
      throw new BadGatewayException(
        '파일을 업로드하는 도중 오류가 발생했습니다.'
      );
    }
  }

  /**
   * 파일을 삭제합니다.
   * @param name 삭제할 파일의 이름 (키)
   */
  async deleteFile(name: string) {
    try {
      // 네이버 클라우드에서 파일을 삭제합니다.
      await this.s3
        .deleteObject({
          Bucket: 'aresa-assets', // 삭제할 파일이 있는 버킷 이름을 설정합니다.
          Key: name, // 삭제할 파일의 키 (이름)를 설정합니다.
        })
        .promise();
    } catch (error) {
      // 파일 삭제 중 오류가 발생하면 BadGatewayException을 발생시킵니다.
      throw new BadGatewayException('파일을 삭제하는 중 오류가 발생했습니다.');
    }
  }
}

Express Multer는 Node.js의 웹 프레임워크인 Express에서 파일 업로드를 쉽게 처리하기 위해 사용되는 미들웨어(Middleware)입니다. Multer는 멀티파트(form-data) 형식으로 전송된 파일을 처리하고 업로드할 때 유용한 기능들을 제공합니다.

Express Multer의 주요 목적:
Express Multer의 주요 목적은 클라이언트에서 서버로 파일을 업로드하는 요청을 처리하는 것입니다. 파일 업로드는 기본적으로 HTTP POST 요청과 멀티파트(form-data) 형식으로 파일 데이터를 전송합니다. Multer는 이러한 파일 데이터를 읽고 파싱하여 서버로 전달하는 일을 담당합니다.

Express Multer의 내부 작동 원리:
Multer는 내부적으로 Busboy 라이브러리를 기반으로 동작합니다. Busboy는 Node.js의 스트림을 사용하여 멀티파트 요청을 처리하는 데 사용되는 라이브러리로, 데이터를 조각조각으로 읽고 처리하는 데 매우 효율적입니다.

  1. 클라이언트가 파일을 업로드하는 요청이 서버로 전달됩니다.
  2. Express Multer는 해당 요청의 본문(body)을 파싱하여 멀티파트 데이터를 읽습니다.
  3. Multer는 Busboy를 사용하여 멀티파트 데이터를 스트림으로 처리하고, 파일의 바이너리 데이터를 임시 디렉토리에 저장합니다.
  4. 업로드된 파일에 대한 정보(파일명, 크기, MIME 타입 등)와 함께 업로드 결과를 Express의 요청 객체(request)에 추가합니다.
  5. 이후 미들웨어 또는 라우터 핸들러에서 req.file을 통해 업로드된 파일에 접근할 수 있게 됩니다.
  6. 개발자는 req.file을 사용하여 업로드된 파일을 원하는 대로 처리하거나 저장할 수 있습니다.

Express Multer 사용 예시:
아래는 Express Multer를 사용하여 파일 업로드를 처리하는 간단한 예제입니다.

const express = require('express');
const multer = require('multer');
const app = express();

// 파일을 임시 디렉토리에 저장하기 위한 Multer 미들웨어 설정
const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, 'uploads/');
  },
  filename: function (req, file, cb) {
    cb(null, file.fieldname + '-' + Date.now());
  },
});
// multer 미들웨어 생성
const upload = multer({ storage: storage });

// 파일 업로드 처리 라우터
app.post('/upload', upload.single('file'), (req, res) => {
  // 업로드된 파일 정보는 req.file을 통해 접근 가능
  console.log(req.file);
  res.send('파일 업로드 성공!');
});

app.listen(3000, () => {
  console.log('서버가 3000번 포트에서 실행중입니다.');
});

위 예제에서 upload.single('file') 미들웨어는 하나의 파일만 업로드하는 경우를 처리합니다. 파일은 'uploads/' 디렉토리에 임시로 저장되며, 업로드된 파일 정보는 req.file을 통해 접근할 수 있습니다. 이렇게 간단히 Express Multer를 사용하여 파일 업로드 기능을 구현할 수 있습니다.

join은 Node.js의 path 모듈에서 제공하는 메서드입니다. path.join() 메서드는 하나 이상의 경로 세그먼트를 전달하여 이를 하나의 경로로 합치는 역할을 수행합니다. 파일 시스템 경로를 조작하는 데 사용되며, 경로 세그먼트들을 올바른 구분자(/ 또는 \)를 사용하여 연결해줍니다.

path.join(...paths) 메서드 사용 방법:

  • ...paths: 하나 이상의 문자열 매개변수를 전달합니다. 이 매개변수들은 합쳐질 경로 세그먼트들을 나타냅니다.

path.join() 메서드 작동 원리:
1. path.join() 메서드는 전달받은 모든 경로 세그먼트를 합치기 위해 운영 체제별로 올바른 구분자(/ 또는 \)를 적절히 처리합니다. 이렇게 하면 OS에 상관없이 항상 올바른 경로가 생성됩니다.
2. 경로 세그먼트들을 합칠 때, 중복되는 구분자가 있는 경우 하나로 합쳐집니다. 예를 들어, 'dir1/''dir2/'라는 두 개의 세그먼트를 합칠 때, 'dir1/dir2/'와 같이 경로가 생성됩니다.
3. 경로 세그먼트들 중 앞쪽에서부터 순서대로 합쳐지며, 합쳐진 결과가 최종 경로가 됩니다.

예시:

const path = require('path');

const directory = '/home/user'; // 기본 디렉토리
const filename = 'file.txt'; // 파일 이름

// 경로 세그먼트들을 합쳐서 완전한 파일 경로를 생성합니다.
const fullPath = path.join(directory, 'documents', filename);

console.log(fullPath); // 출력: /home/user/documents/file.txt

위 예시에서 path.join() 메서드는 '/home/user', 'documents', 'file.txt' 세 개의 인자를 받아서 /home/user/documents/file.txt와 같은 완전한 파일 경로를 생성합니다. OS별로 구분자를 올바르게 처리하여 경로를 합치는 것이 주요한 역할입니다.

0개의 댓글