Skip to content

Commit 27743e6

Browse files
authored
Feature/288 presigned 이미지 업로드 방식 구현 (#326)
* feat: presigned url 이미지 업로드 방식 추가 - presigned 이미지 업로드 방식 구현 - 테스트및 검증완료 * feat: presigned url 이미지 업로드 방식 추가 - presigned 이미지 업로드 방식 구현 - 테스트및 검증완료 * feat: presigned url 이미지 업로드 방식 추가 - presigned 이미지 업로드 방식 구현 - 테스트및 검증완료 * feat: presigned url 이미지 업로드 방식 추가 - presigned 이미지 업로드 방식 구현 - 테스트및 검증완료
1 parent 6c0ae04 commit 27743e6

File tree

7 files changed

+135
-9
lines changed

7 files changed

+135
-9
lines changed

src/main/java/com/somemore/global/imageupload/service/ImageUploadService.java

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,18 @@
55
import com.somemore.global.imageupload.usecase.ImageUploadUseCase;
66
import com.somemore.global.imageupload.util.ImageUploadUtils;
77
import com.somemore.global.imageupload.validator.ImageUploadValidator;
8-
import jakarta.annotation.PostConstruct;
98
import lombok.RequiredArgsConstructor;
109
import org.springframework.beans.factory.annotation.Value;
1110
import org.springframework.stereotype.Service;
1211
import org.springframework.web.multipart.MultipartFile;
1312
import software.amazon.awssdk.core.sync.RequestBody;
1413
import software.amazon.awssdk.services.s3.S3Client;
1514
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
15+
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
16+
import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;
1617

1718
import java.io.IOException;
19+
import java.time.Duration;
1820

1921
import static com.somemore.global.exception.ExceptionMessage.UPLOAD_FAILED;
2022

@@ -23,6 +25,7 @@
2325
public class ImageUploadService implements ImageUploadUseCase {
2426

2527
private final S3Client s3Client;
28+
private final S3Presigner s3Presigner;
2629
private final ImageUploadValidator imageUploadValidator;
2730

2831
@Value("${cloud.aws.s3.bucket}")
@@ -34,11 +37,28 @@ public class ImageUploadService implements ImageUploadUseCase {
3437
@Value("${default.image.url}")
3538
private String defaultImageUrl;
3639

37-
public static String DEFAULT_IMAGE_URL;
40+
public static final String DEFAULT_IMAGE_URL = "";
41+
private static final Duration GET_URL_EXPIRATION_DURATION = Duration.ofMinutes(3);
3842

39-
@PostConstruct
40-
private void init() {
41-
DEFAULT_IMAGE_URL = defaultImageUrl;
43+
44+
@Override
45+
public String getPresignedUrl(String filename) {
46+
if(imageUploadValidator.isEmptyFileName(filename)) {
47+
return null;
48+
}
49+
50+
String uniqueFilename = ImageUploadUtils.generateUniqueFileName(filename);
51+
52+
GetObjectPresignRequest getObjectPresignRequest = GetObjectPresignRequest.builder()
53+
.signatureDuration(GET_URL_EXPIRATION_DURATION)
54+
.getObjectRequest(builder -> builder
55+
.bucket(bucket)
56+
.key(uniqueFilename))
57+
.build();
58+
59+
return s3Presigner.presignGetObject(getObjectPresignRequest)
60+
.url()
61+
.toString();
4262
}
4363

4464
@Override

src/main/java/com/somemore/global/imageupload/usecase/ImageUploadUseCase.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@
44

55
public interface ImageUploadUseCase {
66
String uploadImage(ImageUploadRequestDto requestDto);
7+
String getPresignedUrl(String filename);
78
}

src/main/java/com/somemore/global/imageupload/util/ImageUploadUtils.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,14 @@ private ImageUploadUtils() {
1111
}
1212

1313
public static String generateUniqueFileName(String originalFileName) {
14+
1415
String uuid = UUID.randomUUID().toString();
16+
1517
String fileExtension = extractFileExtension(originalFileName);
16-
return uuid + fileExtension;
18+
19+
String fileNameWithoutExtension = originalFileName.substring(0, originalFileName.lastIndexOf("."));
20+
21+
return uuid + "_" + fileNameWithoutExtension + fileExtension;
1722
}
1823

1924
private static String extractFileExtension(String fileName) {

src/main/java/com/somemore/global/imageupload/validator/DefaultImageUploadValidator.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ public void validateFileType(MultipartFile file) {
2525
}
2626
}
2727

28+
@Override
29+
public boolean isEmptyFileName(String fileName) {
30+
return fileName == null || fileName.isEmpty();
31+
}
32+
2833
@Override
2934
public boolean isEmptyFile(MultipartFile file) {
3035
return file == null || file.isEmpty();

src/main/java/com/somemore/global/imageupload/validator/ImageUploadValidator.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,6 @@ public interface ImageUploadValidator {
99
void validateFileType(MultipartFile file);
1010

1111
boolean isEmptyFile(MultipartFile file);
12+
13+
boolean isEmptyFileName(String fileName);
1214
}

src/test/java/com/somemore/global/imageupload/service/ImageUploadServiceTest.java

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
package com.somemore.global.imageupload.service;
22

33
import static org.assertj.core.api.Assertions.assertThat;
4-
import static org.junit.jupiter.api.Assertions.assertNotNull;
5-
import static org.junit.jupiter.api.Assertions.assertThrows;
6-
import static org.junit.jupiter.api.Assertions.assertTrue;
4+
import static org.junit.jupiter.api.Assertions.*;
75
import static org.mockito.BDDMockito.given;
86
import static org.mockito.Mockito.any;
97
import static org.mockito.Mockito.mock;
@@ -17,6 +15,8 @@
1715
import com.somemore.support.IntegrationTestSupport;
1816
import java.io.IOException;
1917
import java.io.InputStream;
18+
import java.net.URL;
19+
2020
import org.junit.jupiter.api.BeforeEach;
2121
import org.junit.jupiter.api.DisplayName;
2222
import org.junit.jupiter.api.Test;
@@ -28,6 +28,9 @@
2828
import software.amazon.awssdk.core.sync.RequestBody;
2929
import software.amazon.awssdk.services.s3.S3Client;
3030
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
31+
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
32+
import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;
33+
import software.amazon.awssdk.services.s3.presigner.model.PresignedGetObjectRequest;
3134

3235
class ImageUploadServiceTest extends IntegrationTestSupport {
3336

@@ -76,6 +79,7 @@ void testUploadImage_success() {
7679
@DisplayName("이미지 형식이 올바르지 않다면 업로드 할 수 없다.")
7780
@Test
7881
void testUploadImage_failure() throws IOException {
82+
7983
// given
8084
when(multipartFile.getInputStream()).thenThrow(new IOException());
8185

@@ -88,6 +92,7 @@ void testUploadImage_failure() throws IOException {
8892
@DisplayName("이미지 파일이 없다면 기본 이미지 링크를 반환한다.")
8993
@Test
9094
void uploadImageWithEmptyFile() {
95+
9196
// given
9297
MultipartFile emptyFile = new MockMultipartFile("file", new byte[0]);
9398
given(imageUploadValidator.isEmptyFile(emptyFile)).willReturn(true);
@@ -99,4 +104,53 @@ void uploadImageWithEmptyFile() {
99104
// then
100105
assertThat(imgUrl).isEqualTo(ImageUploadService.DEFAULT_IMAGE_URL);
101106
}
107+
108+
@DisplayName("유효한 파일명으로 사전 서명된 URL을 생성할 수 있다.")
109+
@Test
110+
void getPresignedUrl_success() {
111+
112+
// given
113+
String filename = "testImage.jpg";
114+
115+
when(imageUploadValidator.isEmptyFileName(filename)).thenReturn(false);
116+
117+
S3Presigner mockPresigner = mock(S3Presigner.class);
118+
ReflectionTestUtils.setField(imageUploadService, "s3Presigner", mockPresigner);
119+
120+
PresignedGetObjectRequest mockPresignedRequest = mock(PresignedGetObjectRequest.class);
121+
URL mockUrl = mock(URL.class);
122+
123+
when(mockUrl.toString()).thenReturn("https://test-bucket.s3.amazonaws.com/unique-test-image.jpg");
124+
when(mockPresignedRequest.url()).thenReturn(mockUrl);
125+
when(mockPresigner.presignGetObject(any(GetObjectPresignRequest.class))).thenReturn(mockPresignedRequest);
126+
127+
// when
128+
String presignedUrl = imageUploadService.getPresignedUrl(filename);
129+
130+
// then
131+
assertNotNull(presignedUrl);
132+
assertTrue(presignedUrl.startsWith("https://test-bucket.s3.amazonaws.com/"));
133+
assertTrue(presignedUrl.endsWith(".jpg"));
134+
135+
verify(imageUploadValidator, times(1)).isEmptyFileName(filename);
136+
verify(mockPresigner, times(1)).presignGetObject(any(GetObjectPresignRequest.class));
137+
}
138+
139+
@DisplayName("파일명 검증에 실패하면 null을 반환한다.")
140+
@Test
141+
void getPresignedUrl_invalidFileName() {
142+
143+
// given
144+
String filename = "";
145+
146+
when(imageUploadValidator.isEmptyFileName(filename)).thenReturn(true);
147+
148+
// when
149+
String presignedUrl = imageUploadService.getPresignedUrl(filename);
150+
151+
// then
152+
assertNull(presignedUrl);
153+
154+
verify(imageUploadValidator, times(1)).isEmptyFileName(filename);
155+
}
102156
}

src/test/java/com/somemore/global/imageupload/validator/DefaultImageUploadValidatorTest.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,43 @@ void shouldThrowExceptionWhenFileTypeIsNull() {
9393
// then
9494
assertEquals(ImageUploadException.class, exception.getClass());
9595
}
96+
97+
@Test
98+
@DisplayName("파일 이름이 비어있는지 확인할 수 있다.")
99+
void shouldReturnTrueWhenFileNameIsEmpty() {
100+
// given
101+
String emptyFileName = "";
102+
103+
// when
104+
boolean isEmptyFileName = imageUploadValidator.isEmptyFileName(emptyFileName);
105+
106+
// then
107+
assertThat(isEmptyFileName).isTrue();
108+
}
109+
110+
@Test
111+
@DisplayName("파일 이름이 null이면 true를 반환한다.")
112+
void shouldReturnTrueWhenFileNameIsNull() {
113+
// given
114+
String nullFileName = null;
115+
116+
// when
117+
boolean isEmptyFileName = imageUploadValidator.isEmptyFileName(nullFileName);
118+
119+
// then
120+
assertThat(isEmptyFileName).isTrue();
121+
}
122+
123+
@Test
124+
@DisplayName("파일 이름이 비어있지 않으면 false를 반환한다.")
125+
void shouldReturnFalseWhenFileNameIsNotEmpty() {
126+
// given
127+
String validFileName = "testImage.jpg";
128+
129+
// when
130+
boolean isEmptyFileName = imageUploadValidator.isEmptyFileName(validFileName);
131+
132+
// then
133+
assertThat(isEmptyFileName).isFalse();
134+
}
96135
}

0 commit comments

Comments
 (0)