NestJS, AWS-SDK 활용한 NCP Object Storage 업로드, 다운로드

박재하·2023년 11월 24일
0

목표

  • 트러블 슈팅 : Object Storage 에러 => 해결
  • AWS-SDK를 이용한 NCP Object Storage 파일 업로드, 다운로드

기본적인 사용법

API 키 발급 (NCP)

스크린샷 2023-11-22 오후 9 21 20 스크린샷 2023-11-22 오후 9 36 17

Object Storage 이용신청, bucket 생성

스크린샷 2023-11-22 오후 9 16 04 스크린샷 2023-11-22 오후 9 19 05 스크린샷 2023-11-22 오후 10 47 35

S3와 호환되므로 AWS-SDK 모듈 등에서 Endpoint를 kr.object.ncloudstorage.com으로 설정해서 사용하면 된다.

config

import * as AWS from 'aws-sdk';

import { configDotenv } from 'dotenv';
configDotenv();

export const awsConfig = {
	endpoint: new AWS.Endpoint(process.env.AWS_S3_ENDPOINT),
	region: process.env.AWS_REGION,
	credentials: {
		accessKeyId: process.env.AWS_ACCESS_KEY_ID,
		secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
	},
};

export const bucketName = process.env.AWS_BUCKET_NAME;

upload

// NCP Object Storage 업로드
AWS.config.update(awsConfig);
const result = await new AWS.S3()
	.putObject({
		Bucket: bucketName,
		Key: filename,
		Body: buffer,
		ACL: 'public-read',
	})
	.promise();
Logger.log('uploadFile result:', result);

download

// NCP Object Storage 다운로드
AWS.config.update(awsConfig);
const result = await new AWS.S3()
	.getObject({
		Bucket: bucketName,
		Key: filename,
	})
	.promise();
Logger.log(`downloadFile result: ${result.ETag}`);

트러블 슈팅 : Object Storage 에러 => 해결

S3 Endpoint 주소 문제

NCloud 공식문서에 나와있는 S3 Endpoint 주소가 문제였다..

스크린샷 2023-11-23 오후 12 42 10

여기 나와있는 주소를 썼는데 ACCESS KEY가 없다고 나와 뭔가 잘못한 줄 알고 한참을 헤맸는데

스크린샷 2023-11-23 오후 12 42 49

혹시나 싶어 다른 문서에 나와있는

스크린샷 2023-11-23 오후 12 42 54

이 주소를 보니까 다른거야.. 그래서 이걸로 했더니 됨. ㅋ

알고보니 내가 보던 공식문서는 financial용 ncloud로 서버도 인증키도 따로 관리되는 거였다... 내가 바보 ㅇㅈ

업로드 로직 추가

이제 주석처리해 두었던 업로드 로직을 다시 추가해줬다.

async createBoard(
  createBoardDto: CreateBoardDto,
  userData: UserDataDto,
  files: Express.Multer.File[],
): Promise<Board> {
  const { title, content } = createBoardDto;

  const user = await this.userRepository.findOneBy({ id: userData.userId });

  const images: Image[] = [];
  for (const file of files) {
    const image = await this.uploadFile(file);
    images.push(image);
  }

  const board = this.boardRepository.create({
    title,
    content: encryptAes(content), // AES 암호화하여 저장
    user,
    images,
  });
  const createdBoard: Board = await this.boardRepository.save(board);

  createdBoard.user.password = undefined; // password 제거하여 반환
  return createdBoard;
}

파일 목록에서 순서대로 읽어와 uploadFile() 호출

async uploadFile(file: Express.Multer.File): Promise<Image> {
  if (!file.mimetype.includes('image')) {
    throw new BadRequestException('Only image files are allowed');
  }

  const { mimetype, buffer, size } = file;

  const filename = uuid();

  // NCP Object Storage 업로드
  AWS.config.update(awsConfig);
  const result = await new AWS.S3()
    .putObject({
      Bucket: bucketName,
      Key: filename,
      Body: buffer,
      ACL: 'public-read',
    })
    .promise();
  Logger.log('uploadFile result:', result);

  const updatedImage = await this.imageRepository.save({
    mimetype,
    filename,
    size,
  });

  return updatedImage;
}

업로드 파일 로직은 uuid로 생성된 식별자를 파일이름으로 Object Storage에 업로드하고,
나머지 파일에 대한 정보는 관계형 DB 이미지 테이블에 저장한다.

다운로드 로직 추가

yarn workspace server add form-data

폼데이터로 응답을 해야하기 때문에 form-data 모듈을 설치해줬다.

async downloadFile(filename: string): Promise<Buffer> {
  // NCP Object Storage 다운로드
  AWS.config.update(awsConfig);
  const result = await new AWS.S3()
    .getObject({
      Bucket: bucketName,
      Key: filename,
    })
    .promise();
  Logger.log(`downloadFile result: ${result.ETag}`);

  return result.Body as Buffer;
}
@Get(':id')
@UseGuards(CookieAuthGuard)
async findBoardById(
  @Param('id', ParseIntPipe) id: number,
  @Res() res,
): Promise<void> {
  const found = await this.boardService.findBoardById(id);
  // AES 복호화
  if (found.content) {
    found.content = decryptAes(found.content); // AES 복호화하여 반환
  }

  // 폼 데이터 만들어 반환
  const formData = new FormData();
  formData.append('id', found.id.toString());
  formData.append('title', found.title);
  formData.append('content', found.content);
  formData.append('author', found.user.nickname);
  formData.append('created_at', found.created_at.toString());
  formData.append('updated_at', found.updated_at.toString());
  formData.append('like_cnt', found.like_cnt.toString());

  // NCP Object Storage 다운로드
  const files = [];
  for (let image of found.images) {
    const file: Buffer = await this.boardService.downloadFile(image.filename);
    console.log(file);
    formData.append('file', file, {
      filename: image.filename,
      contentType: image.mimetype,
    });
  }

  res.set({
    'Content-Type': 'multipart/form-data',
  });
  formData.pipe(res);
  // return found;
}

다운로드는 컨트롤러에서 개별 파일 다운로드하는 서비스 메소드인 downloadFile을 호출해서 순서대로 폼데이터에 넣고,
@Res() 데코레이터로 가져온 Response 객체에 전달한다.

결과 화면

  • 업로드
스크린샷 2023-11-23 오후 2 08 43
  • 다운로드
스크린샷 2023-11-23 오후 2 08 35

학습메모

  1. 잘못된 ncloud 공식문서 (ncloud financial)
  2. 올바른 endpoint 주소(일반 ncloud)
profile
해커 출신 개발자

0개의 댓글