배경
취업지원 플랫폼에서 구직자와 회사의 이미지를 등록해야하는 상황이었습니다. S3라는 걸로 이미지 등록을 한다길래 개인적으로 공부가 필요했습니다.
S3 (Simple Storage Service)
AWS S3는 업계 최고의 확장성과 데이터 가용성 및 보안과 성능을 제공하는 온라인 오브젝트(객체) 스토리지 서비스입니다.
S3는 저장하는 데이터 양에 대한 비용도 저렴하고, 저장할 수 있는 데이터 양이 무한에 가깝습니다.
또한 'Elastic'한 성질때문에 별도의 스토리지 확장, 축소에 신경쓰지 않아도 된다고합니다.
[S3를 사용하는 이유]
- S3는 저장 용량이 무한대이고 파일 저장에 최적화되어 있다. 용량을 추가하거나 성능을 높이는 작업이 필요없다.
- 비용은 EC2와 EBS로 구축하는 것보다 훨씬 저렴하다
- S3 자체가 수천 대 이상의 매우 성능이 좋은 웹 서버로 구성되어 있어서 EC2와 EBS로 구축했을 때 처럼 Auto Scaling이나 Load Balancing에 신경쓰지 않아도 된다.
- 웹하드 서비스와 비슷하지만, 별도의 클라이언트 설치나 ActiveX를 통하지 않고, HTTP 프로토콜(restful)로 파일 업로드/다운로드 처리가 가능하다.
- S3 자체로 정적 웹서비스 가능 (html 파일을 스토리지에 저장하고, html 파일에 접근하면 그게 홈페이지)
- 동적 웹페이지와 정적 웹페이지가 섞여있을 때 동적 웹페이지만 EC2에서 서비스하고 정적 웹페이지는 S3를 이용하면 성능도 높이고 비용도 절감할 수 있다.
[S3사용 예]
- 클라우드 저장소 (개인 파일 보관, 구글 드라이브처럼 사용 가능)
- 서비스의 대용량 파일 저장소 - 이미지, 동영상, 빅데이터 (ex: 넷플릭스)
- 서비스 로그 저장 및 분석
- AWS 아데나를 이용한 빅데이터 업로드 및 분석
- 서비스 사용자의 데이터 업로드 서버 (이미지 서버, 동영상 서버)
- 중요한 파일은 EC2의 SSD (EBS: Elastic Block Store)에 저장하지 말고 S3에 저장
- glacier와의 연동으로 비용 절감 및 규정 준수 가능 (빙하라는 뜻으로 자주 쓰지 않는 데이터를 S3에서 자동으로 변환)
Multer-S3
Front-End에서 유저가 로컬에서 업로드한 이미지를 저장하는 방법은 여러가지가 있습니다.
서버에 이미지를 저장하는 폴더를 만들어 클라이언트로부터 요청받은 파일을 저장하는 방법이 있고,
이미지를 따로 저장하는 서버를 만들어 저장할 수도 있다고 합니다. 또한, DB에 Blob타입으로 저장할 수도 있다고 합니다.
위의 경우, 사용하는 서버와 DB의 상태 또는 이를 구동하는 PC의 성능에 따라 퍼포먼스가 달라질 수 있습니다.
그래서, AWS S3 버킷에 이미지 파일을 저장하고, DB엔 그 버킷의 이미지 파일 경로(이미지 주소)를 저장하고, 서버는 이 경로를 클라이언트로 응답하는 방법으로 사용하고자 합니다.
이렇게 하게 되면 PC의 성능을 고려하지 않아도 되며, 사용한 만큼 비용을 지불하는 것으로 보다 쾌적하게 백엔드를 구성할 수 있습니다.
이를 Multer-S3 와, AWS-SDK 모듈을 사용하여 구현할 수 있습니다.
AWS S3 설정하기
우선 S3를 이용하기 위해서는 설정이 필요했습니다.
Amazon S3 > 버킷 > 버킷 만들기 영역에서 내용을 설정하고 다른 내용은 설정할 필요가 없는데 퍼블릭 엑세스 차단만 설정했습니다.
그리고 버킷정책을 설정했습니다.
{
"Version": "2012-10-17",
"Id": "Policy1649421058532",
"Statement": [
{
"Sid": "Stmt1649420985040",
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::<버킷이름>/*"
}
]
}
그 다음으로는 ACL(액세스 제어 목록)에 들어가서 모든 사람(퍼블릭 엑세스) 읽기 허용
그 다음 IAM설정으로 들어가서 사용자 추가 권한 설정에 S3FullAccess를 선택
그 다음 엑세스키 설정
키파일을 다운 받으면 끝!
코드 작성
install할때 주의할게 있는데 multer-s3랑 aws-sdk 모두 같은 버전으로 install해야합니다.
그렇지 않으면 this.client.send is not a function 에러가 생긴다고합니다.
npm install --save multer-s3 npm install @aws-sdk/client-s3
multer.options.factory.ts
import { S3Client } from '@aws-sdk/client-s3';
import { ConfigService } from '@nestjs/config';
import { MulterOptions } from '@nestjs/platform-express/multer/interfaces/multer-options.interface';
import { basename, extname } from 'path';
import * as multerS3 from 'multer-s3';
// 파일 업로드를 위한 Multer설정 생성하는 함수
export const multerOptionsFactory = (
configService: ConfigService,
): MulterOptions => {
return {
// Multer옵션 설정
storage: multerS3({
s3: new S3Client({
region: configService.get('AWS_S3_REGION'),
credentials: {
accessKeyId: configService.get('AWS_ACCESS_KEY_ID'),
secretAccessKey: configService.get('AWS_SECRET_ACCESS_KEY'),
},
}),
bucket: configService.get('AWS_S3_BUCKET'),
acl: 'public-read',
contentType: multerS3.AUTO_CONTENT_TYPE,
// 파일키 설정
key(_req, file, callback) {
const fileType = file.mimetype.split('/')[0];
const ext = extname(file.originalname); // 확장자
const baseName = basename(file.originalname, ext); // 확장자 제외
const fileName =
fileType === 'video'
? `videos/${baseName}-${Date.now()}${ext}`
: `images/${baseName}-${Date.now()}${ext}`;
callback(null, fileName);
},
}),
// 파일 크기 제한 설정
limits: {
fileSize: 5 * 1024 * 1024, // 5mb
},
};
};
upload.controller.ts
import {
Controller,
FileTypeValidator,
MaxFileSizeValidator,
ParseFilePipe,
Post,
UploadedFile,
UseGuards,
UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { UploadService } from './upload.service';
import { UserGuard } from 'src/auth/jwt/jwt.user.guard';
@Controller('/api/upload')
export class UploadController {
constructor(private readonly uploadService: UploadService) {}
@Post()
@UseInterceptors(FileInterceptor('file'))
async uploadFile(
@UploadedFile()
file: Express.MulterS3.File,
) {
const photo = await this.uploadService.upload(file);
return photo;
}
}
upload.service.ts
import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class UploadService {
private readonly s3Client = new S3Client({
region: this.configService.getOrThrow('AWS_S3_REGION'),
});
constructor(private readonly configService: ConfigService) {}
async upload(file: Express.MulterS3.File) {
return { url: file.location };
}
}
위와같이 코드를 작성하니 이미지 등록을 하면 바로 s3에 저장되는 형태로 진행이 되었습니다.
실제로 이미지 등록을 할때도
아래와 같이 코드를 작성하였습니다.
[회사 이미지 등록]
// 이미지 수정하기
async function getCompanyImage() {
const companyImage = document.getElementById('image'); // 보이는 곳
const imageUploadEl = document.getElementById('company-image'); // 회사 이미지
const imageDeleteEl = document.getElementById('image-delete'); // 기본 프로필 적용
const saveBtnEl = document.getElementById('save-btn');
let imageUrl = companyImage.src; // 기존 이미지 URL 가져오기
// 기본 프로필 적용하기
imageDeleteEl.addEventListener('click', () => {
imageUrl = '/img/company.jpg'; // 이미지 삭제 시 URL 업데이트
companyImage.src = imageUrl; // 이미지 보여주기
});
imageUploadEl.addEventListener('change', async (e) => {
const selectedFile = e.target.files[0];
console.log(selectedFile);
// 파일 유효성 검사
if (selectedFile) {
if (selectedFile.size > 1 * 1024 * 1024) {
alert('파일 용량은 최대 1MB입니다.');
return;
}
if (
!selectedFile.type.includes('jpeg') &&
!selectedFile.type.includes('png')
) {
alert('jpeg 또는 png 파일만 업로드 가능합니다!');
return;
}
const formData = new FormData();
formData.append('file', selectedFile);
try {
const response = await fetch('/api/upload', {
method: 'POST',
body: formData,
});
if (response.ok) {
const data = await response.json();
imageUrl = data.url; // 업로드된 이미지 URL 업데이트
companyImage.src = imageUrl; // 이미지 보여주기
console.log(data);
} else {
throw new Error('이미지 업로드에 실패했습니다.');
}
} catch (error) {
console.error(error);
alert('이미지 업로드에 실패했습니다.');
}
}
});
saveBtnEl.addEventListener('click', async () => {
if (imageUrl) {
try {
await saveCompanyImage(imageUrl);
alert('이미지가 저장되었습니다.');
window.location.reload();
} catch (error) {
console.error('이미지 저장 오류:', error);
alert('이미지 저장에 실패했습니다.');
}
} else {
alert('이미지를 먼저 업로드하세요.');
}
});
async function saveCompanyImage(imageUrl) {
try {
const response = await fetch('/api/companies/image', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ image: imageUrl }),
});
if (!response.ok) {
throw new Error('이미지 저장 실패');
}
} catch (error) {
console.error('이미지 저장 오류:', error);
throw error;
}
}
}
getCompanyImage();
아래 코드처럼 POST로 보내게되면 저절로 s3에 이미지파일주소를 저장하고 클라이언트로 요청하는 방식으로 이미지 등록을 구현하였습니다.
try {
const response = await fetch('/api/upload', {
method: 'POST',
body: formData,
});
참고
https://jakekwak.gitbook.io/nestjs/techniques/file-upload
File upload - nestjs
파일 배열을 업로드하기 위해 FilesInterceptor()를 사용합니다. 이 인터셉터는 세 가지 인자를 취합니다. fieldName (동일하게 유지됨), maxCount는 동시에 업로드할 수 있는 최대 파일 수이며 선택적인 Mul
jakekwak.gitbook.io
NestJS에서 S3 다중 파일 업로드하기
안녕하세요, 503입니다. 최근 NestJS로 숙소 예약 서비스를 개발하는 팀프로젝트를 진행했었습니다만 당연하게도 이미지 업로드하는 기능이 필요 했었습니다. 단일파일 업로드부터 다중 파일 업
velog.io
'혼자 고민해보기_ 개발' 카테고리의 다른 글
EC2 인스턴스 CPU 과부화 이슈 (0) | 2023.09.26 |
---|---|
GitAction CI/CD 배포 (0) | 2023.09.26 |
ChatGPT Prompt 최적화 (0) | 2023.09.25 |
크롤링 처리 속도 개선 (0) | 2023.09.23 |
Trello project- KPT 회고 (0) | 2023.08.14 |