-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
이미지 S3 업로드 기능 구현 #707
이미지 S3 업로드 기능 구현 #707
Changes from 4 commits
6de3df4
05f6984
d930f35
ac9ccc9
a831c1f
0b663d3
c6f1ad1
3ce395b
2c65770
c1e510f
eedf6e2
33963e2
fbe2eb6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package com.ddang.ddang.auction.configuration; | ||
|
||
import com.google.api.client.util.Value; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.context.annotation.Profile; | ||
import software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider; | ||
import software.amazon.awssdk.regions.Region; | ||
import software.amazon.awssdk.services.s3.S3Client; | ||
|
||
@Profile("!local && !test") | ||
@Configuration | ||
public class AwsConfiguration { | ||
|
||
@Value("${aws.s3.region}") | ||
private String s3Region; | ||
|
||
@Bean | ||
public S3Client s3Client() { | ||
return S3Client.builder() | ||
.region(Region.of(s3Region)) | ||
.credentialsProvider(InstanceProfileCredentialsProvider.create()) | ||
.build(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,21 +5,21 @@ | |
import com.ddang.ddang.image.infrastructure.local.exception.EmptyImageException; | ||
import com.ddang.ddang.image.infrastructure.local.exception.StoreImageFailureException; | ||
import com.ddang.ddang.image.infrastructure.local.exception.UnsupportedImageFileExtensionException; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.context.annotation.Profile; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.web.multipart.MultipartFile; | ||
|
||
import java.io.File; | ||
import java.io.IOException; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.UUID; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.web.multipart.MultipartFile; | ||
|
||
@Component | ||
@Profile("local || test") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
public class LocalStoreImageProcessor implements StoreImageProcessor { | ||
|
||
private static final List<String> WHITE_IMAGE_EXTENSION = List.of("jpg", "jpeg", "png"); | ||
private static final String EXTENSION_FILE_CHARACTER = "."; | ||
|
||
@Value("${image.store.dir}") | ||
private String imageStoreDir; | ||
|
||
|
@@ -38,7 +38,8 @@ public List<StoreImageDto> storeImageFiles(final List<MultipartFile> imageFiles) | |
return storeImageDtos; | ||
} | ||
|
||
public StoreImageDto storeImageFile(MultipartFile imageFile) { | ||
@Override | ||
public StoreImageDto storeImageFile(final MultipartFile imageFile) { | ||
try { | ||
final String originalImageFileName = imageFile.getOriginalFilename(); | ||
final String storeImageFileName = createStoreImageFileName(originalImageFileName); | ||
|
@@ -47,16 +48,16 @@ public StoreImageDto storeImageFile(MultipartFile imageFile) { | |
imageFile.transferTo(new File(fullPath)); | ||
|
||
return new StoreImageDto(originalImageFileName, storeImageFileName); | ||
} catch (IOException ex) { | ||
} catch (final IOException ex) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 칭찬final 붙이기 감사합니다~ |
||
throw new StoreImageFailureException("이미지 저장에 실패했습니다.", ex); | ||
} | ||
} | ||
|
||
private String findFullPath(String storeImageFileName) { | ||
private String findFullPath(final String storeImageFileName) { | ||
return imageStoreDir + storeImageFileName; | ||
} | ||
|
||
private String createStoreImageFileName(String originalFilename) { | ||
private String createStoreImageFileName(final String originalFilename) { | ||
final String extension = extractExtension(originalFilename); | ||
|
||
validateImageFileExtension(extension); | ||
|
@@ -66,7 +67,7 @@ private String createStoreImageFileName(String originalFilename) { | |
return uuid + EXTENSION_FILE_CHARACTER + extension; | ||
} | ||
|
||
private String extractExtension(String originalFilename) { | ||
private String extractExtension(final String originalFilename) { | ||
int position = originalFilename.lastIndexOf(EXTENSION_FILE_CHARACTER); | ||
|
||
return originalFilename.substring(position + 1); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
package com.ddang.ddang.image.infrastructure.s3; | ||
|
||
import com.ddang.ddang.image.domain.StoreImageProcessor; | ||
import com.ddang.ddang.image.domain.dto.StoreImageDto; | ||
import com.ddang.ddang.image.infrastructure.local.exception.EmptyImageException; | ||
import com.ddang.ddang.image.infrastructure.local.exception.StoreImageFailureException; | ||
import com.ddang.ddang.image.infrastructure.local.exception.UnsupportedImageFileExtensionException; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.context.annotation.Profile; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.web.multipart.MultipartFile; | ||
import software.amazon.awssdk.core.exception.SdkException; | ||
import software.amazon.awssdk.core.sync.RequestBody; | ||
import software.amazon.awssdk.services.s3.S3Client; | ||
import software.amazon.awssdk.services.s3.model.PutObjectRequest; | ||
|
||
import java.io.IOException; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.UUID; | ||
|
||
@Component | ||
@Profile("!local && !test") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 선택이 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 좋습니다! 이 생각은 못했네요 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 칭찬우와 지토 좋은 아이디어 감사합니다 |
||
@RequiredArgsConstructor | ||
public class S3StoreImageProcessor implements StoreImageProcessor { | ||
|
||
@Value("${aws.s3.bucket-name}") | ||
private String bucketName; | ||
|
||
@Value("${aws.s3.image-path}") | ||
private String path; | ||
|
||
private final S3Client s3Client; | ||
|
||
@Override | ||
public List<StoreImageDto> storeImageFiles(final List<MultipartFile> imageFiles) { | ||
final List<StoreImageDto> storeImageDtos = new ArrayList<>(); | ||
|
||
for (final MultipartFile imageFile : imageFiles) { | ||
if (imageFile.isEmpty()) { | ||
throw new EmptyImageException("이미지 파일의 데이터가 비어 있습니다."); | ||
} | ||
|
||
storeImageDtos.add(storeImageFile(imageFile)); | ||
} | ||
|
||
return storeImageDtos; | ||
} | ||
|
||
@Override | ||
public StoreImageDto storeImageFile(final MultipartFile imageFile) { | ||
try { | ||
final String originalImageFileName = imageFile.getOriginalFilename(); | ||
final String storeImageFileName = createStoreImageFileName(originalImageFileName); | ||
final String fullPath = findFullPath(storeImageFileName); | ||
final PutObjectRequest putObjectRequest = PutObjectRequest.builder() | ||
.key(fullPath) | ||
.bucket(bucketName) | ||
.contentType(imageFile.getContentType()) | ||
.build(); | ||
|
||
s3Client.putObject( | ||
putObjectRequest, | ||
RequestBody.fromInputStream(imageFile.getInputStream(), imageFile.getSize()) | ||
); | ||
|
||
return new StoreImageDto(originalImageFileName, storeImageFileName); | ||
} catch (final IOException ex) { | ||
throw new StoreImageFailureException("이미지 저장에 실패했습니다.", ex); | ||
} catch (final SdkException ex) { | ||
throw new StoreImageFailureException("AWS 이미지 저장에 실패했습니다.", ex); | ||
} | ||
Comment on lines
+73
to
+75
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 칭찬예외 처리 👍 |
||
} | ||
|
||
private String findFullPath(final String storeImageFileName) { | ||
return path + storeImageFileName; | ||
} | ||
|
||
private String createStoreImageFileName(final String originalFilename) { | ||
final String extension = extractExtension(originalFilename); | ||
|
||
validateImageFileExtension(extension); | ||
|
||
final String uuid = UUID.randomUUID().toString(); | ||
|
||
return uuid + EXTENSION_FILE_CHARACTER + extension; | ||
} | ||
|
||
private String extractExtension(final String originalFilename) { | ||
int position = originalFilename.lastIndexOf(EXTENSION_FILE_CHARACTER); | ||
|
||
return originalFilename.substring(position + 1); | ||
} | ||
|
||
private void validateImageFileExtension(final String extension) { | ||
if (!WHITE_IMAGE_EXTENSION.contains(extension)) { | ||
throw new UnsupportedImageFileExtensionException("지원하지 않는 확장자입니다. : " + extension); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
질문
전에 클라이언트에서 jpg, jpeg만 와서 png는 넣을 지 말지 얘기했던 것 같은데, png도 그냥 하는 걸로 결정 됐었나요?
사소하긴 하지만, 기억이 나지 않아 여쭤봅니다.