![[Spring] URL 이미지 리사이징 후, S3에 업로드](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbo2ABS%2FbtsIO2hfVoo%2Ff1IgCWhPSGGUvR0smvtdKk%2Fimg.png)
택시 합승 서비스 개발 중 프론트엔드 개발자로부터 다음과 같은 요청을 받았습니다.
이미지 최적화가 되지 않아 프로필 사진이 하얀색으로 표시되는 문제가 발생했습니다.
이 문제를 해결하기 위해 이미지 크기를 86 x 86으로 변경해야 했습니다.
하지만 카카오 소셜 로그인을 통해 얻은 프로필 이미지는 카카오에서 파일 형태로 제공되지 않고, 이미지 URL만 제공됩니다. 따라서 이 URL을 파일로 변환하고, 사이즈를 조정한 후 S3에 업로드해야 하는 상황입니다.
문제 접근
문제 해결을 위한 제가 생각한 접근 방법은 다음과 같습니다.
- 카카오로부터 받은 이미지 URL을 파일로 변환
- 이미지 크기 변경
- 리사이징한 이미지를 AWS S3에 저장
이미지 URL 리소스 로드
public class ImageService {
private final ResourceLoader resourceLoader;
@Transactional
public void getResizedImageUrl(String imageUrl) {
Resource imageResource = resourceLoader.getResource(imageUrl);
if (!imageResource.exists()) { //리소스가 존재하는 않는다면 에러 발생
throw new ApiException(ErrorCode.FILE_RESIZING_ERROR);
}
try (InputStream inputStream = imageResource.getInputStream()) {
BufferedImage originImage = ImageIO.read(inputStream);
} catch(IOException e) {
throw new ApiException(ErrorCode.FILE_RESIZING_ERROR);
}
}
...
ResourceLoader는 다양한 리소스를 읽어오는 기능을 제공하는 인터페이스입니다.
아래와 같이 다양한 리소스 소스에서 리소스를 로드할 수 있습니다.
- 파일 시스템에서 읽어오기
- classpath에서 읽어오기
- URL로 읽어오기
- 상대/절대 경로로 읽어오기
getResource(imageURL) 메서드를 통해 imageUrl에 해당하는 리소스를 가져오며, 그 결과를 Resource 타입으로 저장합니다.
이미지 크기 변경
이미지 리사이징을 위해 Marvin 라이브러리를 사용하였습니다.
Marvin 라이브러리를 사용하기 위해 build.gradle에 아래 내용을 추가하였습니다.
// Marvin(이미지 리사이징)
implementation 'com.github.downgoon:marvin:1.5.5'
implementation 'com.github.downgoon:MarvinPlugins:1.5.5'
implementation 'org.springframework:spring-test'
Marvin은 멀티 스레드 및 병렬 처리 방식으로 이미지를 리사이징합니다.
따라서 여러 스레드가 동시에 이미지 리사이징 작업을 수행할 경우 리소스 경합이 발생할 수 있습니다.
이를 해결하기 위해 트랜잭션을 사용하여 스레드가 공유하는 리소스에 대한 동시 접근을 제어해야 합니다.
저는 클래스 전체에 @Transactional을 선언하였습니다.
MultipartFile resizeImage(String fileName, String fileFormatName, BufferedImage originalImage, int targetSize) {
try {
MarvinImage marvinImage = new MarvinImage(originalImage);
Scale scale = new Scale();
scale.load();
//이미지 크기 설정
scale.setAttribute("newWidth", targetSize);
scale.setAttribute("newHeight", targetSize);
//이미지 리사이징 수행
scale.process(marvinImage.clone(), marvinImage, null, null, false);
//리사이징된 이미지를 BufferedImage 형태로 가져오기
BufferedImage bufferedImage = marvinImage.getBufferedImageNoAlpha();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, fileFormatName, baos);
baos.flush();
return new MockMultipartFile(fileName, baos.toByteArray());
} catch (IOException e) {
throw new ApiException(ErrorCode.FILE_RESIZING_ERROR);
}
}
ImageIo.write()를 통해 리사이징된 이미지를 JPEG 형식으로 ByteArrayOutputStream에 기록하였습니다.
이 과정에서 이미지가 바이트 배열로 변환됩니다.
이후 이 바이트 배열을 MockMultipartFile로 변환하여 S3 파일 업로드를 진행하였습니다.
resizeImage 메서드에 파라미터로 MultiPartFile이 아닌 BufferedImage 타입으로 받은 이유는, Marvin 라이브러리의 생성자 요구사항이 BufferedImage 형태이기 때문입니다.
리사이징한 이미지 S3에 저장
마지막으로, 리사이징한 이미지를 S3에 저장하는 코드를 추가하였습니다.
위의 getResizedImage 메서드에 추가적으로 작성하였습니다.
@Transactional
public String getResizedImageUrl(String imageUrl) {
Resource imageResource = resourceLoader.getResource(imageUrl);
if (!imageResource.exists()) { //리소스가 존재하는 않는다면 에러 발생
throw new ApiException(ErrorCode.FILE_RESIZING_ERROR);
}
try (InputStream inputStream = imageResource.getInputStream()) {
BufferedImage originImage = ImageIO.read(inputStream);
//파일 이름 및 URL 생성
String fileName = Instant.now().toEpochMilli() + "_" + sanitizeFileName(Objects.requireNonNull(imageUrl));
String fileUrl = "https://" + bucket + ".s3." + region + ".amazonaws.com/" + fileName;
String fileFormatName = "JPEG";
MultipartFile resizedFile = resizeImage(fileName, fileFormatName, originImage, 86);
//S3에 업로드할 메타데이터 설정
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentType(resizedFile.getContentType());
metadata.setContentLength(resizedFile.getSize());
//S3에 업로드
amazonS3Client.putObject(bucket, fileName, resizedFile.getInputStream(), metadata);
return fileUrl;
} catch(IOException e) {
throw new ApiException(ErrorCode.FILE_RESIZING_ERROR);
}
}
문제 해결
소셜 로그인을 진행함과 동시에 카카오로부터 받은 이미지 URL을 리사이징하고, S3에 업로드한 후 DB에 이미지 URL을 저장하였습니다.
이미지 URL에 접근하여 크기를 확인해보니 크기가 86 x 86으로 변경된 것을 확인할 수 있었습니다.
참고자료
[개발] 이미지 리사이징한 썸네일 생성
기존에는 프론트 측에서 주는 이미지 파일을 S3에 바로 업로드 하고, 원본 이미지를 프론트에서 바로 사용하도록 하였습니다. 이렇게 진행하다 보니 게시글의 리스트를 조회하는 부분에서 썸네
velog.io
'Spring' 카테고리의 다른 글
[Spring] 놀멍 서비스 개발 일지 - 지도 화면 개발하기1 (0) | 2025.01.05 |
---|---|
[Spring] Redis 테스트 환경 구축하기(Embedded Redis) (2) | 2024.12.31 |
[Spring] Google STT(Speech-to-Text) 서비스 사용하기 (2) | 2024.06.30 |
[Spring] Spring Data JPA 페이징 처리 알아보기 (2) | 2023.10.03 |
[Spring] Spring Security와 OAuth 2.0으로 구글 소셜 로그인 구현(1) (0) | 2023.08.11 |
느리더라도 단단하게 성장하고자 합니다!
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!