728x90
얼마 전 S3로 이미지를 저장하고 저장된 이미지의 URL을 반환하는 기능을 구현했다.
하지만 한 가지 문제가 있었는데, 고용량의 이미지가 그대로 올라간 것이다.
Thumbnail 이미지를 생성하는 이유
- 썸네일 이미지를 생성하면 이미지의 파일 크기를 크게 줄일 수 있다. 즉, 이미지가 더 빨리 전송되기때문에 웹 성능이 향상된다. 더 좋은 사용자 경험을 전달할 수 있다.
- 더 작은 용량의 이미지를 전달하기 때문에 서버의 부하를 줄이고 더 많은 요청을 동시에 처리할 수 있다.
Thumbnail 이미지를 생성해서 사용자 경험을 개선하고 리소스 사용량을 줄여보자
AmazonS3ResourceStorage
보통 원본 이미지와 썸네일 이미지를 같이 저장하지만 우리 서비스에서 이미지는 챌린지 참여 인증 사진용으로 일회용으로 사용된다. 그래서 서버 비용을 줄이기 위해 썸네일 이미지만 S3에 저장하기로 했다.
@Slf4j
@Component
@RequiredArgsConstructor
public class AmazonS3ResourceStorage {
public String store(String fullPath, MultipartFile multipartFile) {
File file = new File(MultipartUtil.getLocalHomeDirectory(), fullPath);
try {
multipartFile.transferTo(file);
createThumbnail(fullPath, file); // 썸네일 생성해주는 메서드
amazonS3Client.putObject(new PutObjectRequest(bucket, fullPath, file)
.withCannedAcl(CannedAccessControlList.PublicRead));
} catch (Exception e) {
log.error(e.getMessage());
throw new BusinessLogicException(ExceptionCode.FAILED_TO_UPLOAD_FILE);
} finally {
if (file.exists()) removeNewFile(file);
return amazonS3Client.getUrl(bucket, fullPath).toString();
}
private void createThumbnail(String fullPath, File file) {
double ratio = 2; // 이미지 축소 비율
int idx = fullPath.lastIndexOf(".");
String ext = fullPath.substring(idx + 1);// 파일 확장자
try {
BufferedImage oimage = ImageIO.read(file); // original image
int tWidth = (int) (oimage.getWidth() / ratio);
int tHeight = (int) (oimage.getHeight() / ratio);
BufferedImage thumbnail = new BufferedImage(tWidth, tHeight, BufferedImage.TYPE_3BYTE_BGR); // 썸네일
Graphics2D graphic = thumbnail.createGraphics();
Image image = oimage.getScaledInstance(tWidth, tHeight, Image.SCALE_SMOOTH);
graphic.drawImage(image, 0, 0, tWidth, tHeight, null);
graphic.dispose(); // 리소스를 모두 해제
ImageIO.write(thumbnail, ext, file);
} catch (IOException e) {
e.printStackTrace();
}
}
...
}
또 다른 문제 발생!
썸네일 이미지를 생성한 후 또 다른 문제가 생겼다.
이미 충분히 작고 적은 용량의 이미지까지 썸네일을 생성해버리니 이미지가 깨지는 것이다.
해결
가로 길이 또는 세로 길이가 1000 * 1000을 넘어가는 이미지에 대해서만 썸네일을 생성하도록 로직을 수정했다.
@Slf4j
@Component
@RequiredArgsConstructor
public class AmazonS3ResourceStorage {
public String store(String fullPath, MultipartFile multipartFile) {
File file = new File(MultipartUtil.getLocalHomeDirectory(), fullPath);
try {
multipartFile.transferTo(file);
BufferedImage oimage = ImageIO.read(file); // original image
int height = oimage.getHeight();
int width = oimage.getWidth();
if (width > 1000 && height > 1000) { // image 크기가 1000*1000이 넘으면 리사이징
createThumbnail(fullPath, file, oimage);
}
amazonS3Client.putObject(new PutObjectRequest(bucket, fullPath, file)
.withCannedAcl(CannedAccessControlList.PublicRead));
} catch (Exception e) {
log.error(e.getMessage());
throw new BusinessLogicException(ExceptionCode.FAILED_TO_UPLOAD_FILE);
} finally {
if (file.exists()) removeNewFile(file);
}
return amazonS3Client.getUrl(bucket, fullPath).toString().replaceFirst("s", "");
}
private void createThumbnail(String fullPath, File file, BufferedImage oimage) {
double ratio = 2;
int idx = fullPath.lastIndexOf(".");
String ext = fullPath.substring(idx + 1);
try {
int tWidth = (int) (oimage.getWidth() / ratio);
int tHeight = (int) (oimage.getHeight() / ratio);
BufferedImage thumbnail = new BufferedImage(tWidth, tHeight, BufferedImage.TYPE_3BYTE_BGR);
Graphics2D graphic = thumbnail.createGraphics();
Image image = oimage.getScaledInstance(tWidth, tHeight, Image.SCALE_SMOOTH);
graphic.drawImage(image, 0, 0, tWidth, tHeight, null);
graphic.dispose();
ImageIO.write(thumbnail, ext, file);
} catch (IOException e) {
e.printStackTrace();
}
}
...
}
파일 업로드 용량 설정
추가로 application.yml에 파일 업로드 용량을 설정할 수 있다.
이를 통해 업로드되는 파일의 용량을 제한 할 수 있다.
설정한 용량을 넘기는 파일이 업로드 되면 FileSizeLimitExceededException 또는 SizeLimitExceededException이 발생한다.
spring:
servlet:
multipart:
maxFileSize: 10MB # 파일 하나의 최대 크기
maxRequestSize: 30MB # 한 번에 최대 업로드 가능 용량
마무리
프로젝트 일정이 급해서 급하게 작업하다보니 Thumbnail 이미지를 통해 얼만큼의 웹 성능의 개선이 있었는지 확인하지 못한 것이 아쉽다.
다음 프로젝트에는 테스트를 통해 얼만큼의 개선이 있는지 확인해봐야겠다.
728x90
'Spring' 카테고리의 다른 글
Spring Security - Spring Security란? (0) | 2023.03.27 |
---|---|
Spring - Spring initializr로 프로젝트 생성하기 (0) | 2023.03.22 |
Spring - Jasypt를 사용해서 application.yml 프로퍼티 암호화하기 (0) | 2023.03.09 |
Spring - Spring으로 AWS S3에 이미지 업로드하기2: Spring에서 기능 구현 (0) | 2023.03.06 |
Spring Security - OAuth2와 JWT로 로그인 구현하기(Kakao, Google, Naver) (6) | 2023.03.05 |