-
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.
[feat] S3 설정 추가
- Loading branch information
Showing
15 changed files
with
449 additions
and
4 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
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
34 changes: 34 additions & 0 deletions
34
src/main/java/org/recordy/server/external/config/S3Config.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,34 @@ | ||
package org.recordy.server.external.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.AwsBasicCredentials; | ||
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; | ||
import software.amazon.awssdk.regions.Region; | ||
import software.amazon.awssdk.services.s3.S3Client; | ||
|
||
@Configuration | ||
public class S3Config { | ||
|
||
private final String accessKey; | ||
private final String secretKey; | ||
private final String regionString; | ||
|
||
public S3Config(@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 S3Client getS3Client() { | ||
AwsBasicCredentials awsCreds = AwsBasicCredentials.create(accessKey, secretKey); | ||
return S3Client.builder() | ||
.region(Region.of(regionString)) | ||
.credentialsProvider(StaticCredentialsProvider.create(awsCreds)) | ||
.build(); | ||
} | ||
} |
30 changes: 30 additions & 0 deletions
30
src/main/java/org/recordy/server/external/controller/S3TestApi.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,30 @@ | ||
package org.recordy.server.external.controller; | ||
|
||
import io.swagger.v3.oas.annotations.Operation; | ||
import io.swagger.v3.oas.annotations.media.Content; | ||
import io.swagger.v3.oas.annotations.responses.ApiResponse; | ||
import io.swagger.v3.oas.annotations.tags.Tag; | ||
import org.springframework.http.MediaType; | ||
import org.springframework.web.bind.annotation.RequestPart; | ||
import org.springframework.web.multipart.MultipartFile; | ||
|
||
import java.io.IOException; | ||
|
||
@Tag(name = "S3 테스트 관련 API") | ||
public interface S3TestApi { | ||
|
||
@Operation( | ||
summary = "이미지 업로드 테스트 API", | ||
description = "S3에 이미지가 업로드 되는지 테스트하는 API입니다. 이미지 사이즈는 5MB를 넘을 수 없습니다. 이미지 확장자는 jpg, png, webp만 가능합니다.", | ||
responses = { | ||
@ApiResponse( | ||
responseCode = "200", | ||
description = "성공", | ||
content = @Content( | ||
mediaType = MediaType.MULTIPART_FORM_DATA_VALUE | ||
) | ||
) | ||
} | ||
) | ||
public String uploadTest(@RequestPart MultipartFile file) throws IOException; | ||
} |
28 changes: 28 additions & 0 deletions
28
src/main/java/org/recordy/server/external/controller/S3TestController.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,28 @@ | ||
package org.recordy.server.external.controller; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import org.recordy.server.external.service.impl.S3ServiceImpl; | ||
import org.springframework.http.MediaType; | ||
import org.springframework.web.bind.annotation.PostMapping; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RequestPart; | ||
import org.springframework.web.bind.annotation.RestController; | ||
import org.springframework.web.multipart.MultipartFile; | ||
|
||
import java.io.IOException; | ||
|
||
|
||
@RequiredArgsConstructor | ||
@RestController | ||
@RequestMapping("/api/v1") | ||
public class S3TestController implements S3TestApi { | ||
|
||
private final S3ServiceImpl s3ServiceImpl; | ||
|
||
@Override | ||
@PostMapping(path = "/uploadTest", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) | ||
public String uploadTest(@RequestPart MultipartFile file) throws IOException { | ||
return this.s3ServiceImpl.uploadFile(file); | ||
} | ||
|
||
} |
11 changes: 11 additions & 0 deletions
11
src/main/java/org/recordy/server/external/exception/ExternalException.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,11 @@ | ||
package org.recordy.server.external.exception; | ||
|
||
import org.recordy.server.common.exception.RecordyException; | ||
import org.recordy.server.common.message.ErrorMessage; | ||
|
||
public class ExternalException extends RecordyException { | ||
|
||
public ExternalException(ErrorMessage errorMessage) { | ||
super(errorMessage); | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
src/main/java/org/recordy/server/external/service/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,16 @@ | ||
package org.recordy.server.external.service; | ||
|
||
import org.springframework.web.multipart.MultipartFile; | ||
|
||
import java.io.IOException; | ||
|
||
public interface S3Service { | ||
|
||
// command | ||
public String uploadFile(MultipartFile file) throws IOException; | ||
void deleteFile(String key) throws IOException; | ||
|
||
// query | ||
|
||
|
||
} |
95 changes: 95 additions & 0 deletions
95
src/main/java/org/recordy/server/external/service/impl/S3ServiceImpl.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,95 @@ | ||
package org.recordy.server.external.service.impl; | ||
|
||
import org.recordy.server.common.message.ErrorMessage; | ||
import org.recordy.server.external.config.S3Config; | ||
import org.recordy.server.external.exception.ExternalException; | ||
import org.recordy.server.external.service.S3Service; | ||
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.PutObjectRequest; | ||
|
||
import java.io.IOException; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
import java.util.Objects; | ||
import java.util.UUID; | ||
|
||
@Component | ||
public class S3ServiceImpl implements S3Service { | ||
|
||
private String bucketName; | ||
private S3Config s3Config; | ||
private S3Client s3Client; | ||
private static final List<String> FILE_EXTENSIONS = Arrays.asList("image/jpeg", "image/png", "image/jpg", "image/webp", "video/mp4", "video/mov", "video/quicktime"); | ||
private static final Long MAX_SIZE = 100 * 1024 * 1024L; // 100MB | ||
|
||
public S3ServiceImpl(@Value("${aws-property.s3-bucket-name}") final String bucketName, S3Config s3Config) { | ||
this.bucketName = bucketName; | ||
this.s3Config = s3Config; | ||
this.s3Client = s3Config.getS3Client(); | ||
} | ||
|
||
@Override | ||
public String uploadFile(MultipartFile file) throws IOException { | ||
validateFileExtension(file); | ||
validateFileSize(file); | ||
final String url = getFileExtension(file); | ||
PutObjectRequest request = PutObjectRequest.builder() | ||
.bucket(bucketName) | ||
.key(url) | ||
.contentType(file.getContentType()) | ||
.contentDisposition("inline") | ||
.build(); | ||
RequestBody requestBody = RequestBody.fromBytes(file.getBytes()); | ||
s3Client.putObject(request, requestBody); | ||
return url; | ||
} | ||
|
||
public String getFileExtension(MultipartFile file) { | ||
return UUID.randomUUID() + switch (Objects.requireNonNull(file.getContentType())) { | ||
case "image/jpeg", "image/jpg" -> ".jpg"; | ||
case "image/png" -> ".png"; | ||
case "image/webp" -> ".webp"; | ||
case "video/mp4" -> ".mp4"; | ||
case "video/mov", "video/quicktime" -> ".mov"; | ||
default -> throw new ExternalException(ErrorMessage.INVALID_FILE_TYPE); | ||
}; | ||
} | ||
|
||
@Override | ||
public void deleteFile(String key) throws IOException { | ||
s3Client.deleteObject(DeleteObjectRequest.builder() | ||
.bucket(bucketName) | ||
.key(key) | ||
.build()); | ||
} | ||
|
||
public void setS3Client(S3Client s3Client) { | ||
this.s3Client = s3Client; | ||
} | ||
|
||
private String generateImageFileName() { | ||
return UUID.randomUUID() + ".jpg"; | ||
} | ||
|
||
private String generateVideoFileName() { | ||
return UUID.randomUUID() + ".mp4"; | ||
} | ||
|
||
public void validateFileExtension(MultipartFile file) { | ||
String contentType = file.getContentType(); | ||
if (!FILE_EXTENSIONS.contains(contentType)) { | ||
throw new ExternalException(ErrorMessage.INVALID_FILE_TYPE); | ||
} | ||
} | ||
|
||
public void validateFileSize(MultipartFile file) { | ||
if (file.getSize() > MAX_SIZE) { | ||
throw new ExternalException(ErrorMessage.INVALID_FILE_SIZE); | ||
} | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
src/main/java/org/recordy/server/record/exception/RecordException.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,12 @@ | ||
package org.recordy.server.record.exception; | ||
|
||
import org.recordy.server.common.exception.RecordyException; | ||
import org.recordy.server.common.message.ErrorMessage; | ||
|
||
public class RecordException extends RecordyException { | ||
|
||
public RecordException(ErrorMessage errorMessage) { | ||
super(errorMessage); | ||
} | ||
|
||
} |
17 changes: 14 additions & 3 deletions
17
src/main/java/org/recordy/server/record/service/impl/FileServiceImpl.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
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
52 changes: 52 additions & 0 deletions
52
src/test/java/org/recordy/server/external/controller/S3TestControllerTest.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,52 @@ | ||
package org.recordy.server.external.controller; | ||
|
||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.Test; | ||
import org.recordy.server.external.service.impl.S3ServiceImpl; | ||
import org.recordy.server.mock.FakeContainer; | ||
import org.springframework.mock.web.MockMultipartFile; | ||
|
||
import java.io.IOException; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
|
||
public class S3TestControllerTest { | ||
|
||
private S3TestController s3TestController; | ||
private S3ServiceImpl s3Service; | ||
|
||
@BeforeEach | ||
void init() { | ||
FakeContainer fakeContainer = new FakeContainer(); | ||
s3Service = (S3ServiceImpl) fakeContainer.s3Service; | ||
s3TestController = new S3TestController(s3Service); | ||
} | ||
|
||
@Test | ||
void uploadImageTest_이미지_업로드_성공() throws IOException { | ||
// given | ||
MockMultipartFile image = new MockMultipartFile("image", "test.jpg", "image/jpeg", new byte[1024]); | ||
|
||
// when | ||
String result = s3TestController.uploadTest(image); | ||
|
||
// then | ||
assertThat(result).isNotNull(); | ||
assertThat(result).contains("recordy/file/"); | ||
} | ||
|
||
@Test | ||
void uploadVideoTest_비디오_업로드_성공() throws IOException { | ||
// given | ||
MockMultipartFile video = new MockMultipartFile("video", "test.mp4", "video/mp4", new byte[1024 * 50]); | ||
|
||
// when | ||
String result = s3TestController.uploadTest(video); | ||
|
||
// then | ||
assertThat(result).isNotNull(); | ||
assertThat(result).contains("recordy/file/"); | ||
} | ||
} | ||
|
Oops, something went wrong.