-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #44 from DSM-Repo/file-upload
pr :: image upload 관련 api 추가
- Loading branch information
Showing
12 changed files
with
292 additions
and
1 deletion.
There are no files selected for viewing
29 changes: 29 additions & 0 deletions
29
src/main/java/com/example/whopper/domain/file/api/FileController.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,29 @@ | ||
package com.example.whopper.domain.file.api; | ||
|
||
import com.example.whopper.domain.file.application.usecase.SaveImageUseCase; | ||
import com.example.whopper.domain.file.dto.response.ImagePathResponse; | ||
import com.example.whopper.domain.file.type.ImageType; | ||
import lombok.RequiredArgsConstructor; | ||
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.RequestParam; | ||
import org.springframework.web.bind.annotation.RequestPart; | ||
import org.springframework.web.bind.annotation.RestController; | ||
import org.springframework.web.multipart.MultipartFile; | ||
|
||
@RestController | ||
@RequiredArgsConstructor | ||
@RequestMapping("/file") | ||
public class FileController { | ||
|
||
private final SaveImageUseCase saveImageUseCase; | ||
|
||
@PostMapping(value = "/image", consumes = { | ||
MediaType.MULTIPART_FORM_DATA_VALUE | ||
}) | ||
public ImagePathResponse uploadImage(@RequestPart("file") MultipartFile filePart, @RequestParam("type") ImageType imageType) { | ||
String path = saveImageUseCase.saveImage(filePart, imageType); | ||
return new ImagePathResponse(path, saveImageUseCase.getFileBaseUrl()); | ||
} | ||
} |
78 changes: 78 additions & 0 deletions
78
src/main/java/com/example/whopper/domain/file/application/impl/SaveImageService.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,78 @@ | ||
package com.example.whopper.domain.file.application.impl; | ||
|
||
import com.example.whopper.domain.file.application.usecase.SaveImageUseCase; | ||
import com.example.whopper.infra.s3.AwsS3Properties; | ||
import com.example.whopper.infra.s3.AwsS3FileType; | ||
import com.example.whopper.domain.file.type.ImageType; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.web.multipart.MultipartFile; | ||
import software.amazon.awssdk.core.async.AsyncRequestBody; | ||
import software.amazon.awssdk.services.s3.model.PutObjectRequest; | ||
import software.amazon.awssdk.transfer.s3.S3TransferManager; | ||
import software.amazon.awssdk.transfer.s3.model.UploadRequest; | ||
|
||
import java.util.*; | ||
import java.util.concurrent.Executors; | ||
|
||
@Component | ||
@RequiredArgsConstructor | ||
public class SaveImageService implements SaveImageUseCase { | ||
private static final Set<String> VALID_EXTENSIONS = Set.of(".jpg", ".jpeg", ".png", ".heic", ".svg", ".webp", ".gif"); | ||
|
||
private final S3TransferManager s3TransferManager; | ||
private final AwsS3Properties awsS3Properties; | ||
|
||
public String saveImage(MultipartFile multipartFile, ImageType imageType) { | ||
String originalFileName = multipartFile.getOriginalFilename(); | ||
if (originalFileName == null || !isValidExtension(getExtension(originalFileName))) { | ||
throw new RuntimeException("Invalid file extension."); | ||
} | ||
|
||
// Generate a unique key for the file | ||
String key = generateFileKey(originalFileName, imageType); | ||
|
||
// Upload file and return the key | ||
uploadFile(multipartFile, key); | ||
|
||
return key; | ||
} | ||
|
||
public String getFileBaseUrl() { | ||
return awsS3Properties.url(); | ||
} | ||
|
||
private String getExtension(String filename) { | ||
return filename.substring(filename.lastIndexOf(".")).toLowerCase(); | ||
} | ||
|
||
private String generateFileKey(String originalFileName, ImageType imageType) { | ||
String folder = imageType == ImageType.PROFILE ? awsS3Properties.profileFolder() : awsS3Properties.documentFolder(); | ||
return folder + "/" + UUID.randomUUID() + originalFileName; | ||
} | ||
|
||
private void uploadFile(MultipartFile multipartFile, String key) { | ||
try { | ||
// InputStream을 사용하여 파일을 업로드 | ||
PutObjectRequest putObjectRequest = PutObjectRequest.builder() | ||
.bucket(awsS3Properties.bucket()) | ||
.key(key) | ||
.contentType(multipartFile.getContentType()) | ||
.acl(AwsS3FileType.IMAGE.getCannedAcl()) | ||
.build(); | ||
|
||
// S3에 파일 업로드 | ||
s3TransferManager.upload(UploadRequest.builder() | ||
.putObjectRequest(putObjectRequest) | ||
.requestBody(AsyncRequestBody.fromInputStream(multipartFile.getInputStream(), multipartFile.getSize(), Executors.newFixedThreadPool(10))) // ExecutorService 추가 | ||
.build()).completionFuture().join(); // 동기적으로 대기 | ||
} catch (Exception e) { | ||
throw new RuntimeException("파일 업로드 중 오류 발생", e); | ||
} | ||
} | ||
|
||
private boolean isValidExtension(String extension) { | ||
return VALID_EXTENSIONS.contains(extension); | ||
} | ||
} |
57 changes: 57 additions & 0 deletions
57
src/main/java/com/example/whopper/domain/file/application/impl/SavePdfService.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.example.whopper.domain.file.application.impl; | ||
|
||
import com.example.whopper.domain.file.application.usecase.SavePdfUseCase; | ||
import com.example.whopper.infra.s3.AwsS3FileType; | ||
import com.example.whopper.infra.s3.AwsS3Properties; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.web.multipart.MultipartFile; | ||
import software.amazon.awssdk.core.async.AsyncRequestBody; | ||
import software.amazon.awssdk.services.s3.model.PutObjectRequest; | ||
import software.amazon.awssdk.transfer.s3.S3TransferManager; | ||
import software.amazon.awssdk.transfer.s3.model.UploadRequest; | ||
|
||
import java.io.InputStream; | ||
import java.time.LocalDate; | ||
import java.time.format.DateTimeFormatter; | ||
import java.util.UUID; | ||
import java.util.concurrent.ExecutorService; | ||
import java.util.concurrent.Executors; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class SavePdfService implements SavePdfUseCase { | ||
|
||
private final AwsS3Properties awsS3Properties; | ||
private final S3TransferManager s3TransferManager; | ||
private final ExecutorService executorService = Executors.newFixedThreadPool(10); | ||
|
||
public String savePdf(MultipartFile multipartFile) { | ||
String folder = awsS3Properties.pdfFolder(); | ||
String date = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE); | ||
String key = String.format("%s/%s-%s.pdf", folder, date, UUID.randomUUID()); | ||
|
||
uploadFile(multipartFile, key); | ||
|
||
return key; | ||
} | ||
|
||
private void uploadFile(MultipartFile multipartFile, String key) { | ||
PutObjectRequest putObjectRequest = PutObjectRequest.builder() | ||
.bucket(awsS3Properties.bucket()) | ||
.key(key) | ||
.acl(AwsS3FileType.PDF.getCannedAcl()) | ||
.build(); | ||
|
||
// MultipartFile의 InputStream을 사용하여 파일을 S3에 업로드 | ||
try (InputStream inputStream = multipartFile.getInputStream()) { | ||
// S3 업로드 수행 | ||
s3TransferManager.upload(UploadRequest.builder() | ||
.putObjectRequest(putObjectRequest) | ||
.requestBody(AsyncRequestBody.fromInputStream(inputStream, multipartFile.getSize(), executorService)) // 수정된 부분 | ||
.build()).completionFuture().join(); // 동기적으로 대기 | ||
} catch (Exception e) { | ||
throw new RuntimeException("파일 업로드 중 오류 발생", e); | ||
} | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
src/main/java/com/example/whopper/domain/file/application/usecase/SaveImageUseCase.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,9 @@ | ||
package com.example.whopper.domain.file.application.usecase; | ||
|
||
import com.example.whopper.domain.file.type.ImageType; | ||
import org.springframework.web.multipart.MultipartFile; | ||
|
||
public interface SaveImageUseCase { | ||
String saveImage(MultipartFile multipartFile, ImageType imageType); | ||
String getFileBaseUrl(); | ||
} |
7 changes: 7 additions & 0 deletions
7
src/main/java/com/example/whopper/domain/file/application/usecase/SavePdfUseCase.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 com.example.whopper.domain.file.application.usecase; | ||
|
||
import org.springframework.web.multipart.MultipartFile; | ||
|
||
public interface SavePdfUseCase { | ||
String savePdf(MultipartFile multipartFile); | ||
} |
7 changes: 7 additions & 0 deletions
7
src/main/java/com/example/whopper/domain/file/dto/response/ImagePathResponse.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 com.example.whopper.domain.file.dto.response; | ||
|
||
public record ImagePathResponse( | ||
String imagePath, | ||
String baseUrl | ||
) { | ||
} |
6 changes: 6 additions & 0 deletions
6
src/main/java/com/example/whopper/domain/file/type/ImageType.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,6 @@ | ||
package com.example.whopper.domain.file.type; | ||
|
||
public enum ImageType { | ||
PROFILE, | ||
DOCUMENT | ||
} |
15 changes: 15 additions & 0 deletions
15
src/main/java/com/example/whopper/infra/config/AwsProperties.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,15 @@ | ||
package com.example.whopper.infra.config; | ||
|
||
import org.springframework.boot.context.properties.ConfigurationProperties; | ||
|
||
@ConfigurationProperties(prefix = "aws") | ||
public record AwsProperties( | ||
String region, | ||
CredentialsProperties credentials | ||
) { | ||
public record CredentialsProperties( | ||
String accessKey, | ||
String secretKey | ||
) { | ||
} | ||
} |
45 changes: 45 additions & 0 deletions
45
src/main/java/com/example/whopper/infra/s3/AwsS3Config.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,45 @@ | ||
package com.example.whopper.infra.s3; | ||
|
||
import com.example.whopper.infra.config.AwsProperties; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.stereotype.Component; | ||
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; | ||
import software.amazon.awssdk.regions.Region; | ||
import software.amazon.awssdk.services.s3.S3AsyncClient; | ||
import software.amazon.awssdk.services.s3.presigner.S3Presigner; | ||
import software.amazon.awssdk.transfer.s3.S3TransferManager; | ||
|
||
@Component | ||
public class AwsS3Config { | ||
private final AwsProperties awsProperties; | ||
|
||
@Autowired | ||
public AwsS3Config(AwsProperties awsProperties) { | ||
this.awsProperties = awsProperties; | ||
} | ||
|
||
// https://github.com/aws/aws-sdk-java-v2/issues/1750 | ||
@Bean | ||
public S3TransferManager s3TransferManager() { | ||
S3AsyncClient s3AsyncClient = S3AsyncClient | ||
.crtBuilder() | ||
.credentialsProvider(DefaultCredentialsProvider.create()) | ||
.region(Region.of(awsProperties.region())) | ||
.targetThroughputInGbps(20.0) | ||
.minimumPartSizeInBytes(8L * 1024 * 1024) // 8 MB in bytes | ||
.build(); | ||
|
||
return S3TransferManager.builder() | ||
.s3Client(s3AsyncClient) | ||
.build(); | ||
} | ||
|
||
@Bean | ||
public S3Presigner s3Presigner() { | ||
return S3Presigner.builder() | ||
.credentialsProvider(DefaultCredentialsProvider.create()) | ||
.region(Region.of(awsProperties.region())) | ||
.build(); | ||
} | ||
} |
14 changes: 14 additions & 0 deletions
14
src/main/java/com/example/whopper/infra/s3/AwsS3FileType.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.example.whopper.infra.s3; | ||
|
||
import lombok.Getter; | ||
import lombok.RequiredArgsConstructor; | ||
import software.amazon.awssdk.services.s3.model.ObjectCannedACL; | ||
|
||
@RequiredArgsConstructor | ||
@Getter | ||
public enum AwsS3FileType { | ||
PDF(ObjectCannedACL.AUTHENTICATED_READ), | ||
IMAGE(ObjectCannedACL.PUBLIC_READ); | ||
|
||
private final ObjectCannedACL cannedAcl; | ||
} |
13 changes: 13 additions & 0 deletions
13
src/main/java/com/example/whopper/infra/s3/AwsS3Properties.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 com.example.whopper.infra.s3; | ||
|
||
import org.springframework.boot.context.properties.ConfigurationProperties; | ||
|
||
@ConfigurationProperties(prefix = "aws.s3") | ||
public record AwsS3Properties( | ||
String url, | ||
String bucket, | ||
String profileFolder, | ||
String documentFolder, | ||
String pdfFolder | ||
) { | ||
} |
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