-
Notifications
You must be signed in to change notification settings - Fork 0
object storage에 이미지 업로드하기
지난 주에 object storage 없이 일단 서버에 multer를 이용해서 이미지와 동영상을 업로드할 수 있도록 구현했었다. 이를 object storage에 업로드하도록 수정하게 됐다.
- Node 환경에서 파일 업로드를 위해 사용되는 미들웨어
- multipart/form-data 형태의 데이터를 처리한다.
- Nest에서는 File upload와 관련된 기본적인 내장 모듈 제공
- 따로 지정하지 않을 경우 파일은 MemoryStorage에 저장되며 Buffer 객체로 확인 가능
지난주에 multerOption
을 사용해 storage
와 file limit
등을 지정해주었기에 object storage에 업로드할 때에는 multerOption
의 storage
를 S3storage
로 변경해주면 될 거라고 생각했다.
import multerS3 from 'multer-s3';
import { S3Client } from '@aws-sdk/client-s3';
import { MulterOptions } from '@nestjs/platform-express/multer/interfaces/multer-options.interface';
import { extname } from 'path';
import { BadRequestException } from '@nestjs/common';
import 'dotenv/config';
const imageMimeType = ['image/jpg', 'image/jpeg', 'image/png'];
const videoMimeType = ['video/mp4'];
const MAX_FILE_SIZE = 30 * 1024 * 1024;
export const s3 = new S3Client({
region: process.env.AWS_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY ?? '',
secretAccessKey: process.env.AWS_SECRET_KEY ?? '',
},
endpoint: process.env.S3_ENDPOINT,
});
export const s3_bucket = process.env.S3_BUCKET ?? 'meetmeet';
export const multerOptions = (
folder: string,
containVideo: boolean,
): MulterOptions => {
return {
storage: multerS3({
s3,
bucket: s3_bucket,
contentType: multerS3.AUTO_CONTENT_TYPE,
key(req, file, callback) {
callback(null, `${folder}/${Date.now()}${extname(file.originalname)}`);
},
acl: 'public-read',
}),
fileFilter(req, file, callback) {
const types = containVideo
? [...imageMimeType, ...videoMimeType]
: imageMimeType;
const mimeType = types.find((type) => file.mimetype === type);
if (!mimeType) {
callback(
new BadRequestException(`${types.join(', ')}만 저장할 수 있습니다.`),
false,
);
}
return callback(null, true);
},
limits: { fileSize: MAX_FILE_SIZE },
};
};
@UseGuards(JwtAuthGuard)
@UseInterceptors(FilesInterceptor('contents', 10, multerOptions))
@Post()
@ApiOperation({
summary: '피드 생성 API',
description: '일정 멤버만 피드를 생성할 수 있습니다.',
})
@ApiBearerAuth()
@ApiConsumes('multipart/form-data')
createFeed(
@GetUser() user: User,
@UploadedFiles() contents: Array<Express.Multer.File>,
@Body() createFeedDto: CreateFeedDto,
) {
return this.feedService.createFeed(user, contents, createFeedDto);
}
그렇게 위와 같이 코드를 작성하고 나니 object storage에 잘 업로드 되는 것을 확인할 수 있었다.
그러나 여기서 발생했던 문제는 요청이 실패했을 경우에도 object storage에 파일이 업로드된다는 것이었다.
Multer는 요청과 컨트롤러 사이에서 파일을 업로드하고 @UploadFile()
데코레이터로 처리된 파일을 매개변수로 넘겨준다. 따라서 컨트롤러에서 에러가 발생해 요청이 실패하는 경우에도 Multer에서 처리한 파일은 object storage에 업로드되는 것이었다.
⇒ Multer에서 memory storage에 업로드해두었다가 비즈니스 로직 이후에 object storage에 업로드하도록 변경
async createContent(file: Express.Multer.File, dir: string) {
file.path = this.generateFilePath(dir, file.originalname);
const content = this.createEntity(file);
await this.objectStorage.upload(file).catch(() => {
throw new ObjectStorageUploadException();
});
return await this.contentRepository.save(content);
}
@Injectable()
export class ObjectStorage {
private readonly s3: S3;
private readonly s3Bucket: string;
private readonly locationPrefix: string;
constructor(configService: ConfigService) {
this.s3 = new S3({
region: configService.get<string>('AWS_REGION'),
credentials: {
accessKeyId: configService.get('AWS_ACCESS_KEY'),
secretAccessKey: configService.get('AWS_SECRET_KEY'),
},
endpoint: configService.get('S3_ENDPOINT'),
});
this.s3Bucket = configService.get('S3_BUCKET', 'meetmeet');
}
async upload(file: Express.Multer.File) {
await this.s3
.upload({
Bucket: this.s3Bucket,
Key: file.path,
ACL: 'public-read',
Body: file.buffer,
})
.promise();
}
async delete(filePath: string) {
await this.s3
.deleteObject({ Bucket: this.s3Bucket, Key: filePath })
.promise();
}
async deleteBulk(files: string[]) {
await this.s3
.deleteObjects({
Bucket: this.s3Bucket,
Delete: {
Objects: files.map((file) => {
return { Key: file };
}),
},
})
.promise();
}
}
AWS sdk를 이용해 object storage에 업로드하기 위한 클래스를 따로 만들어주고, 비즈니스 로직 이후에 마지막으로 content 테이블에 이미지에 대한 정보를 저장하기 전 object storage에 업로드하도록 변경했다.
결과적으로 요청이 성공적으로 이루어진 경우에만 object storage에 파일이 업로드되고 해당 정보가 content 테이블에 저장되는 것을 확인할 수 있었다.
- Week1 - Day01
- Week1 - Day02
- Week1 - Day03
- Week1 - Day04
- Week2 - Day01
- Week2 - Day02
- Week2 - Day03
- Week2 - Day04
- Week3 - Day01
- Week3 - Day02
- Week3 - Day03
- Week3 - Day04
- Week4 - Day01
- Week4 - Day02
- Week4 - Day03
- Week4 - Day04
- Week4 - Day05
- Week5 - Day01
- Week5 - Day02
- Week5 - Day03
- Week5 - Day04
- Week6 - Day01
- Week6 - Day02