-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
#26 [feat] s3 모듈 구현
- Loading branch information
Showing
15 changed files
with
266 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
31 changes: 31 additions & 0 deletions
31
module-api/src/main/java/com/mile/controller/TestController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package com.mile.controller; | ||
|
||
import com.mile.aws.utils.PreSignedUrlResponse; | ||
import com.mile.aws.utils.S3BucketDirectory; | ||
import com.mile.aws.utils.S3Service; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.PostMapping; | ||
import org.springframework.web.bind.annotation.RequestBody; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RestController; | ||
import org.springframework.web.multipart.MultipartFile; | ||
|
||
import java.io.IOException; | ||
|
||
@RestController | ||
@RequestMapping("/api/test") | ||
@RequiredArgsConstructor | ||
public class TestController { | ||
private final S3Service s3Service; | ||
|
||
@PostMapping() | ||
public void testImage(@RequestBody MultipartFile image) throws IOException { | ||
s3Service.uploadImage(S3BucketDirectory.TEST_PREFIX, image); | ||
} | ||
|
||
@GetMapping | ||
public PreSignedUrlResponse getPresignedUrl() { | ||
return s3Service.getUploadPreSignedUrl(S3BucketDirectory.TEST_PREFIX); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
...er/serivce/dto/AccessTokenGetSuccess.java → ...er/service/dto/AccessTokenGetSuccess.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
...ser/serivce/dto/LoginSuccessResponse.java → ...ser/service/dto/LoginSuccessResponse.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
bootJar { enabled = false } | ||
jar { enabled = true } | ||
|
||
dependencies { | ||
implementation project(":module-common") | ||
implementation("software.amazon.awssdk:bom:2.21.0") | ||
implementation("software.amazon.awssdk:s3:2.21.0") | ||
} |
57 changes: 57 additions & 0 deletions
57
module-external/src/main/java/com/mile/aws/config/AwsConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package com.mile.aws.config; | ||
|
||
|
||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import software.amazon.awssdk.auth.credentials.SystemPropertyCredentialsProvider; | ||
import software.amazon.awssdk.regions.Region; | ||
import software.amazon.awssdk.services.s3.S3Client; | ||
import software.amazon.awssdk.services.s3.presigner.S3Presigner; | ||
|
||
@Configuration | ||
public class AwsConfig { | ||
|
||
private static String AWS_ACCESS_KEY_ID = "aws.accessKeyId"; | ||
private static String AWS_SECRET_ACCESS_KEY = "aws.secretAccessKey"; | ||
|
||
private final String accessKey; | ||
private final String secretKey; | ||
private final String regionString; | ||
|
||
public AwsConfig(@Value("${aws-property.access-key}") final String accessKey, | ||
@Value("${aws-property.secret-key}") final String secretKey, | ||
@Value("${aws-property.aws-region}") final String regionString) { | ||
this.accessKey = accessKey; | ||
this.secretKey = secretKey; | ||
this.regionString = regionString; | ||
} | ||
|
||
@Bean | ||
public SystemPropertyCredentialsProvider systemPropertyCredentialsProvider() { | ||
System.setProperty(AWS_ACCESS_KEY_ID, accessKey); | ||
System.setProperty(AWS_SECRET_ACCESS_KEY, secretKey); | ||
return SystemPropertyCredentialsProvider.create(); | ||
} | ||
|
||
@Bean | ||
public Region getRegion() { | ||
return Region.of(regionString); | ||
} | ||
|
||
@Bean | ||
public S3Client getS3Client() { | ||
return S3Client.builder() | ||
.region(getRegion()) | ||
.credentialsProvider(systemPropertyCredentialsProvider()) | ||
.build(); | ||
} | ||
|
||
@Bean | ||
public S3Presigner getS3PreSigner() { | ||
return S3Presigner.builder() | ||
.region(getRegion()) | ||
.credentialsProvider(systemPropertyCredentialsProvider()) | ||
.build(); | ||
} | ||
} |
14 changes: 14 additions & 0 deletions
14
module-external/src/main/java/com/mile/aws/utils/PreSignedUrlResponse.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package com.mile.aws.utils; | ||
|
||
public record PreSignedUrlResponse( | ||
String fileName, | ||
String url | ||
) { | ||
|
||
public static PreSignedUrlResponse of( | ||
final String fileName, | ||
final String url | ||
) { | ||
return new PreSignedUrlResponse(fileName, url); | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
module-external/src/main/java/com/mile/aws/utils/S3BucketDirectory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package com.mile.aws.utils; | ||
|
||
|
||
import lombok.AccessLevel; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE) | ||
public enum S3BucketDirectory { | ||
|
||
TEST_PREFIX("test/"), | ||
POST_PREFIX("post/"); | ||
|
||
private final String name; | ||
|
||
public String value() { | ||
return this.name; | ||
} | ||
} |
120 changes: 120 additions & 0 deletions
120
module-external/src/main/java/com/mile/aws/utils/S3Service.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
package com.mile.aws.utils; | ||
|
||
|
||
import com.mile.aws.config.AwsConfig; | ||
import com.mile.exception.message.ErrorMessage; | ||
import com.mile.exception.model.BadRequestException; | ||
import com.mile.exception.model.MileException; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.web.multipart.MultipartFile; | ||
import software.amazon.awssdk.core.sync.RequestBody; | ||
import software.amazon.awssdk.services.s3.S3Client; | ||
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; | ||
import software.amazon.awssdk.services.s3.model.GetUrlRequest; | ||
import software.amazon.awssdk.services.s3.model.PutObjectRequest; | ||
import software.amazon.awssdk.services.s3.presigner.S3Presigner; | ||
import software.amazon.awssdk.services.s3.presigner.model.PutObjectPresignRequest; | ||
|
||
import java.io.IOException; | ||
import java.time.Duration; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
import java.util.UUID; | ||
|
||
@Slf4j | ||
@Component | ||
public class S3Service { | ||
|
||
private static final List<String> IMAGE_EXTENSIONS = Arrays.asList("image/jpeg", "image/png", "image/jpg", "image/webp"); | ||
private static final Long MAX_FILE_SIZE = 5 * 1024 * 1024L; | ||
|
||
private static final Long PRE_SIGNED_URL_EXPIRE_MINUTE = 1L; // 만료시간 1분 | ||
|
||
private final String bucketName; | ||
private final AwsConfig awsConfig; | ||
|
||
public S3Service(@Value("${aws-property.s3-bucket-name}") final String bucketName, AwsConfig awsConfig) { | ||
this.bucketName = bucketName; | ||
this.awsConfig = awsConfig; | ||
} | ||
|
||
// Multipart 요청을 통한 이미지 업로드 | ||
public String uploadImage(final S3BucketDirectory directoryPath, | ||
final MultipartFile image) throws IOException { | ||
validateExtension(image); | ||
validateFileSize(image); | ||
|
||
String key = directoryPath.value() + generateImageFileName(); | ||
try { | ||
final S3Client s3Client = awsConfig.getS3Client(); | ||
PutObjectRequest request = PutObjectRequest.builder() | ||
.bucket(bucketName) | ||
.key(key) | ||
.contentType(image.getContentType()) | ||
.contentDisposition("inline").build(); | ||
|
||
RequestBody requestBody = RequestBody.fromBytes(image.getBytes()); | ||
s3Client.putObject(request, requestBody); | ||
key = s3Client.utilities().getUrl(GetUrlRequest.builder().bucket(bucketName).key(key).build()).toString(); | ||
return key; | ||
} catch (RuntimeException e) { | ||
throw new MileException(ErrorMessage.INVALID_BUCKET_PREFIX); | ||
} | ||
} | ||
|
||
|
||
// PreSigned Url을 통한 이미지 업로드 | ||
public PreSignedUrlResponse getUploadPreSignedUrl(final S3BucketDirectory prefix) { | ||
final String fileName = generateImageFileName(); // UUID 문자열 | ||
final String key = prefix.value() + fileName; | ||
|
||
try { | ||
final S3Presigner preSigner = awsConfig.getS3PreSigner(); | ||
|
||
PutObjectRequest request = PutObjectRequest.builder() | ||
.bucket(bucketName) | ||
.key(key).build(); | ||
|
||
PutObjectPresignRequest preSignedUrlRequest = PutObjectPresignRequest.builder() | ||
.signatureDuration(Duration.ofMinutes(PRE_SIGNED_URL_EXPIRE_MINUTE)) | ||
.putObjectRequest(request).build(); | ||
|
||
String url = preSigner.presignPutObject(preSignedUrlRequest).url().toString(); | ||
return PreSignedUrlResponse.of(fileName, url); | ||
} catch (RuntimeException e) { | ||
throw new MileException(ErrorMessage.IMAGE_UPLOAD_ERROR); | ||
} | ||
} | ||
|
||
// S3 버킷에 업로드된 이미지 삭제 | ||
public void deleteImage(final String key) throws IOException { | ||
try { | ||
final S3Client s3Client = awsConfig.getS3Client(); | ||
|
||
s3Client.deleteObject((DeleteObjectRequest.Builder builder) -> | ||
builder.bucket(bucketName) | ||
.key(key).build()); | ||
} catch (RuntimeException e) { | ||
throw new MileException(ErrorMessage.IMAGE_DELETE_ERROR); | ||
} | ||
} | ||
|
||
private String generateImageFileName() { | ||
return UUID.randomUUID().toString() + ".jpg"; | ||
} | ||
|
||
private void validateExtension(MultipartFile image) { | ||
String contentType = image.getContentType(); | ||
if (!IMAGE_EXTENSIONS.contains(contentType)) { | ||
throw new BadRequestException(ErrorMessage.IMAGE_EXTENSION_INVALID_ERROR); | ||
} | ||
} | ||
|
||
private void validateFileSize(MultipartFile image) { | ||
if (image.getSize() > MAX_FILE_SIZE) { | ||
throw new BadRequestException(ErrorMessage.IMAGE_SIZE_INVALID_ERROR); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters