프로젝트 구현 중 이미지를 DB에 바로 넣는 구현 보다는 원본은 S3에 넣고 그 객체 URL을 DB에 넣기로 하였습니다.
S3에 버킷은 이미 생성된 상황이었고 저는 로직을 수정하는 작업을 진행하였습니다.
이미지 파일로 업로드 하기 위해선 타입을 MultipartFile로 올려줘야 한다. 그럼 DTO와 요청을 나눠서 보내는 방법을 선택했습니다.
Controller를 두 개를 만드는 것이 아닌 @RequestPart를 사용하였습니다.
@PostMapping(value = "/diary", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<Long> create(
@RequestPart(value = "createRequest") CreateDiaryRequest createDiaryRequest,
@RequestPart(value = "image") List<MultipartFile> images) {
return ResponseEntity.status(HttpStatus.CREATED)
.body(diaryQueryService.create(createDiaryRequest, images));
}
하지만 이렇게 요청을 보낼 경우 Content-type을 명시하지 않으면 application/json이 아닌 application/octet_stream으로 요청이 전송이 되어 오류가 나올 수 있습니다.
그럼 우선 AWS 정보를 Config 클래스로 넣어줘야 합니다.
@Configuration
public class S3Config {
@Value("${cloud.aws.credentials.access-key}")
private String accessKey;
@Value("${cloud.aws.credentials.secret-key}")
private String secretKey;
@Value("${cloud.aws.region.static}")
private String region;
@Bean
public AmazonS3 amazonS3Client() {
BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey);
return AmazonS3ClientBuilder.standard()
.withRegion(region)
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
.build();
}
}
accessKey와 secretKey는 S3 버킷 생성 시 사용자 부여할 때 받은 키를 작성하시면 됩니다.
이제 이미지를 S3에 업로드하는 방법을 보도록 하겠습니다.
@Component
@RequiredArgsConstructor
public class S3StorageManagerService implements S3StorageManagerUseCase {
private final AmazonS3 amazonS3Client;
@Value("${cloud.aws.s3.bucket}")
private String bucket;
@Override
public List<String> uploadImages(List<MultipartFile> images) {
return images.stream()
.filter(this::isValidImage)
.map(this::uploadImageToS3)
.collect(Collectors.toList());
}
private boolean isValidImage(MultipartFile image) {
return image != null && !image.isEmpty() && image.getSize() > 0;
}
private String uploadImageToS3(MultipartFile image) {
String imageUrl = generateUniqueFileName(image.getOriginalFilename());
try {
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentType(image.getContentType());
metadata.setContentLength(image.getSize());
amazonS3Client.putObject(
new PutObjectRequest(bucket, imageUrl, image.getInputStream(), metadata));
} catch (IOException e) {
throw new AwsS3Exception();
}
return imageUrl;
}
private String generateUniqueFileName(String originalFilename) {
int baseLength = UUID.randomUUID().toString().length() + 1;
int exceedLength = baseLength + originalFilename.length() - 128;
if (exceedLength > 0) {
originalFilename = originalFilename.substring(exceedLength);
}
return UUID.randomUUID() + "-" + originalFilename;
}
}
1. uploadImages : 이미지를 S3에 업로드 하는 메소드
2. isValidImage : 이미지가 빈값인지 확인하는 validation
3. uploadToS3 : S3에 업로드 할 시 필요한 정보를 넣어주는 메소드
4. generateUniqueFilename : 이미지를 S3에 업로드시 이름을 고유하게 하기 위한 메소드
아직 S3에 이미지를 업로드하는 기능만 구현한 상황이고 추후에 Update와 Delete를 구현할 예정입니다.
Test를 할 때도 imageFile은 MultipartFile로 넣어줘야 해서 MockMultipartFile을 사용해서 구현했습니다.
private List<MultipartFile> getImageFile() {
return Arrays.asList(
new MockMultipartFile(
"image1", // 파일 파라미터 이름
"image1.jpg", // 파일명
"image/jpeg", // 컨텐츠 타입
"이미지_콘텐츠1".getBytes() // 파일 콘텐츠
),
new MockMultipartFile(
"image2", // 파일 파라미터 이름
"image2.jpg", // 파일명
"image/jpeg", // 컨텐츠 타입
"이미지_콘텐츠2".getBytes() // 파일 콘텐츠
)
);
}
테스트 할 때마다 S3에 이미지를 넣을 수는 없으 Fake용 s3Upload 클래스를 작성하였습니다.
public class FakeS3StorageManager implements S3StorageManagerUseCase {
@Override
public List<String> uploadImages(List<MultipartFile> images) {
return Arrays.asList("fakeImageUrl1", "fakeImageUrl2");
}
}
이렇게 이미지를 S3에 업로드 해보는 기능을 구현해 보았습니다. 이번에 새롭게 경험해보면서 더 좋은 경험을 할 수 있었습니다.
'백엔드 지식 저장소' 카테고리의 다른 글
Soft Delete로 Delete 구현 후 테스트 케이스 실패 (0) | 2024.05.31 |
---|---|
Spring Data MongoDB에 QueryDSL 적용하기 (0) | 2024.02.20 |
Git 자주 사용하는 명령어 정리 (0) | 2024.02.02 |
전략 패턴이란? (0) | 2024.01.23 |
java: Attempt to recreate a file for type study.querydsl.entity.Qclass 문제 해결 (0) | 2024.01.11 |