-
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와 cloudfront 연동해 파일 업로드 기능 추가
- Loading branch information
Showing
19 changed files
with
229 additions
and
85 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
28 changes: 28 additions & 0 deletions
28
perfume-api/src/main/java/io/perfume/api/common/config/S3Configuration.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 io.perfume.api.common.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 S3Configuration { | ||
@Value("${aws.s3.access-key}") | ||
private String accessKey; | ||
|
||
@Value("${aws.s3.private-key}") | ||
private String privateKey; | ||
|
||
@Bean | ||
public S3Client amazonS3() { | ||
AwsBasicCredentials credentials = AwsBasicCredentials.create(accessKey, privateKey); | ||
|
||
return S3Client.builder() | ||
.region(Region.AP_NORTHEAST_2) | ||
.credentialsProvider(StaticCredentialsProvider.create(credentials)) | ||
.build(); | ||
} | ||
} |
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
8 changes: 7 additions & 1 deletion
8
perfume-api/src/main/java/io/perfume/api/file/adapter/in/http/dto/UpdateFileResponseDto.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 |
---|---|---|
@@ -1,3 +1,9 @@ | ||
package io.perfume.api.file.adapter.in.http.dto; | ||
|
||
public record UpdateFileResponseDto(long id, String url) {} | ||
import io.perfume.api.file.application.port.in.dto.FileResult; | ||
|
||
public record UpdateFileResponseDto(long id, String url) { | ||
public static UpdateFileResponseDto from(FileResult result) { | ||
return new UpdateFileResponseDto(result.id(), result.url()); | ||
} | ||
} |
56 changes: 56 additions & 0 deletions
56
...pi/src/main/java/io/perfume/api/file/adapter/out/persistence/s3/S3PersistenceAdapter.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,56 @@ | ||
package io.perfume.api.file.adapter.out.persistence.s3; | ||
|
||
import io.perfume.api.base.PersistenceAdapter; | ||
import io.perfume.api.file.application.exception.SaveFileNotFoundException; | ||
import io.perfume.api.file.application.port.out.S3Repository; | ||
import java.io.IOException; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.beans.factory.annotation.Value; | ||
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.PutObjectRequest; | ||
import software.amazon.awssdk.services.s3.model.PutObjectResponse; | ||
|
||
@PersistenceAdapter | ||
@RequiredArgsConstructor | ||
public class S3PersistenceAdapter implements S3Repository { | ||
@Value("${aws.s3.bucket}") | ||
private String bucketName; | ||
|
||
@Value("${aws.s3.cloudFrontPath}") | ||
private String cloudFrontPath; | ||
|
||
private final S3Client s3Client; | ||
|
||
@Override | ||
public String uploadFile(MultipartFile file) { | ||
String key = file.getOriginalFilename(); | ||
PutObjectRequest putObjectRequest = | ||
PutObjectRequest.builder() | ||
.bucket(bucketName) | ||
.key(key) | ||
.contentType(file.getContentType()) | ||
.contentLength(file.getSize()) | ||
.build(); | ||
|
||
byte[] fileContent = getFileContent(file); | ||
|
||
PutObjectResponse response = | ||
s3Client.putObject(putObjectRequest, RequestBody.fromBytes(fileContent)); | ||
|
||
if (!response.sdkHttpResponse().statusText().orElse("FAIL").equals("OK")) { | ||
throw new IllegalStateException("Failed to upload file to S3."); | ||
} | ||
|
||
return cloudFrontPath + file.getOriginalFilename(); | ||
} | ||
|
||
private byte[] getFileContent(MultipartFile file) { | ||
try { | ||
return file.getBytes(); | ||
} catch (IOException e) { | ||
throw new SaveFileNotFoundException(e.getMessage()); | ||
} | ||
} | ||
} |
15 changes: 14 additions & 1 deletion
15
...pi/src/main/java/io/perfume/api/file/application/exception/SaveFileNotFoundException.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 |
---|---|---|
@@ -1,3 +1,16 @@ | ||
package io.perfume.api.file.application.exception; | ||
|
||
public class SaveFileNotFoundException extends RuntimeException {} | ||
import io.perfume.api.base.CustomHttpException; | ||
import io.perfume.api.base.LogLevel; | ||
import org.springframework.http.HttpStatus; | ||
|
||
public class SaveFileNotFoundException extends CustomHttpException { | ||
|
||
public SaveFileNotFoundException(String message) { | ||
super( | ||
HttpStatus.BAD_REQUEST, | ||
"Failed to save file because " + message, | ||
"failed to save empty file.", | ||
LogLevel.INFO); | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
perfume-api/src/main/java/io/perfume/api/file/application/port/in/UploadFileUseCase.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,13 @@ | ||
package io.perfume.api.file.application.port.in; | ||
|
||
import io.perfume.api.file.application.port.in.dto.FileResult; | ||
import java.time.LocalDateTime; | ||
import java.util.List; | ||
import org.springframework.web.multipart.MultipartFile; | ||
|
||
public interface UploadFileUseCase { | ||
FileResult uploadFile(MultipartFile file, final long userId, final LocalDateTime now); | ||
|
||
List<FileResult> uploadFiles( | ||
final List<MultipartFile> files, final long userId, final LocalDateTime now); | ||
} |
7 changes: 7 additions & 0 deletions
7
perfume-api/src/main/java/io/perfume/api/file/application/port/out/S3Repository.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,7 @@ | ||
package io.perfume.api.file.application.port.out; | ||
|
||
import org.springframework.web.multipart.MultipartFile; | ||
|
||
public interface S3Repository { | ||
String uploadFile(MultipartFile file); | ||
} |
38 changes: 0 additions & 38 deletions
38
perfume-api/src/main/java/io/perfume/api/file/application/service/FileService.java
This file was deleted.
Oops, something went wrong.
41 changes: 41 additions & 0 deletions
41
perfume-api/src/main/java/io/perfume/api/file/application/service/UploadFileService.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,41 @@ | ||
package io.perfume.api.file.application.service; | ||
|
||
import io.perfume.api.file.application.port.in.UploadFileUseCase; | ||
import io.perfume.api.file.application.port.in.dto.FileResult; | ||
import io.perfume.api.file.application.port.out.FileRepository; | ||
import io.perfume.api.file.application.port.out.S3Repository; | ||
import io.perfume.api.file.domain.File; | ||
import jakarta.transaction.Transactional; | ||
import java.time.LocalDateTime; | ||
import java.util.List; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.web.multipart.MultipartFile; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class UploadFileService implements UploadFileUseCase { | ||
|
||
private final FileRepository fileRepository; | ||
private final S3Repository s3Repository; | ||
|
||
@Transactional | ||
public FileResult uploadFile(MultipartFile file, long userId, LocalDateTime now) { | ||
String url = s3Repository.uploadFile(file); | ||
|
||
final File uploadedFile = fileRepository.save(File.createFile(url, userId, now)); | ||
|
||
return new FileResult(uploadedFile.getId(), uploadedFile.getUrl()); | ||
} | ||
|
||
public List<FileResult> uploadFiles(List<MultipartFile> files, long userId, LocalDateTime now) { | ||
return files.parallelStream() | ||
.map( | ||
file -> { | ||
String url = s3Repository.uploadFile(file); | ||
final File uploadedFile = fileRepository.save(File.createFile(url, userId, now)); | ||
return new FileResult(uploadedFile.getId(), uploadedFile.getUrl()); | ||
}) | ||
.toList(); | ||
} | ||
} |
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
Oops, something went wrong.