From 26c8f76892079eb7e8638cccab2de37072fadd69 Mon Sep 17 00:00:00 2001 From: taeyeongKims Date: Mon, 18 Nov 2024 14:25:28 +0900 Subject: [PATCH 01/14] =?UTF-8?q?#20=20EMPLOYEE,=20EMPLOYER=20=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EB=B8=94=20=EC=82=AD=EC=A0=9C=20=EB=B0=8F=20ACCOUNT?= =?UTF-8?q?=20=ED=85=8C=EC=9D=B4=EB=B8=94=EB=A1=9C=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/example/api/domain/Account.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/main/java/com/example/api/domain/Account.java b/src/main/java/com/example/api/domain/Account.java index 276424df..c04e0051 100644 --- a/src/main/java/com/example/api/domain/Account.java +++ b/src/main/java/com/example/api/domain/Account.java @@ -6,17 +6,10 @@ import com.example.api.board.dto.update.UpdateOpenStatusRequest; import com.example.api.board.dto.update.UpdateUserInfoRequest; import jakarta.persistence.*; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.EqualsAndHashCode; import lombok.Getter; -import lombok.Setter; import java.util.Collection; -import static jakarta.persistence.FetchType.*; - - @Entity @Getter @AttributeOverride(name = "createdDate", column = @Column(name = "ACCOUNT_REGISTERED_DATETIME")) From 831ad894440a4591acf8bdba2e13073b770bb6d4 Mon Sep 17 00:00:00 2001 From: taeyeongkims Date: Thu, 9 Jan 2025 11:26:14 +0900 Subject: [PATCH 02/14] =?UTF-8?q?#66=20(S3Controller)=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=EC=97=85=EB=A1=9C=EB=93=9C=20=EC=BB=A8=ED=8A=B8?= =?UTF-8?q?=EB=A1=A4=EB=9F=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/aws/controller/S3Controller.java | 23 +++++ .../example/api/aws/dto/OldKeyRequest.java | 4 + .../example/api/aws/dto/S3UploadRequest.java | 4 + .../api/aws/dto/UploadProfileRequest.java | 4 + .../api/aws/dto/UploadProfileResponse.java | 4 + .../example/api/aws/service/S3Service.java | 84 ++++++++++++++++++ .../api/global/config/AmazonConfig.java | 4 + .../example/api/global/config/WebConfig.java | 4 + .../api/aws/service/S3ServiceTest.java | 4 + src/test/resources/test-files/test-image.png | Bin 0 -> 3411 bytes 10 files changed, 135 insertions(+) create mode 100644 src/main/java/com/example/api/aws/controller/S3Controller.java create mode 100644 src/main/java/com/example/api/aws/dto/OldKeyRequest.java create mode 100644 src/main/java/com/example/api/aws/dto/S3UploadRequest.java create mode 100644 src/main/java/com/example/api/aws/dto/UploadProfileRequest.java create mode 100644 src/main/java/com/example/api/aws/dto/UploadProfileResponse.java create mode 100644 src/main/java/com/example/api/aws/service/S3Service.java create mode 100644 src/main/java/com/example/api/global/config/AmazonConfig.java create mode 100644 src/main/java/com/example/api/global/config/WebConfig.java create mode 100644 src/test/java/com/example/api/aws/service/S3ServiceTest.java create mode 100644 src/test/resources/test-files/test-image.png diff --git a/src/main/java/com/example/api/aws/controller/S3Controller.java b/src/main/java/com/example/api/aws/controller/S3Controller.java new file mode 100644 index 00000000..c3a1667d --- /dev/null +++ b/src/main/java/com/example/api/aws/controller/S3Controller.java @@ -0,0 +1,23 @@ +package com.example.api.aws.controller; + +import com.example.api.aws.dto.UploadProfileRequest; +import com.example.api.aws.service.S3Service; +import jakarta.validation.Valid; +import lombok.AllArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +@AllArgsConstructor +@RestController +@RequestMapping("/api/v1") +public class S3Controller { + private final S3Service s3Service; + + @PostMapping(value = "/upload/profile", consumes = "multipart/form-data") + public ResponseEntity upload(@RequestParam("file") MultipartFile file) { + UploadProfileRequest request = new UploadProfileRequest(1L, file); + return new ResponseEntity<>(s3Service.upload(request).path(), HttpStatus.OK); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/api/aws/dto/OldKeyRequest.java b/src/main/java/com/example/api/aws/dto/OldKeyRequest.java new file mode 100644 index 00000000..4842efe3 --- /dev/null +++ b/src/main/java/com/example/api/aws/dto/OldKeyRequest.java @@ -0,0 +1,4 @@ +package com.example.api.aws.dto; + +public record OldKeyRequest() { +} diff --git a/src/main/java/com/example/api/aws/dto/S3UploadRequest.java b/src/main/java/com/example/api/aws/dto/S3UploadRequest.java new file mode 100644 index 00000000..5539f7bf --- /dev/null +++ b/src/main/java/com/example/api/aws/dto/S3UploadRequest.java @@ -0,0 +1,4 @@ +package com.example.api.aws.dto; + +public record AwsS3(String bucket, String String key, String path) { +} diff --git a/src/main/java/com/example/api/aws/dto/UploadProfileRequest.java b/src/main/java/com/example/api/aws/dto/UploadProfileRequest.java new file mode 100644 index 00000000..6fe5e381 --- /dev/null +++ b/src/main/java/com/example/api/aws/dto/UploadProfileRequest.java @@ -0,0 +1,4 @@ +package com.example.api.aws.dto; + +public record UploadProfileRequest() { +} diff --git a/src/main/java/com/example/api/aws/dto/UploadProfileResponse.java b/src/main/java/com/example/api/aws/dto/UploadProfileResponse.java new file mode 100644 index 00000000..3e7a71e3 --- /dev/null +++ b/src/main/java/com/example/api/aws/dto/UploadProfileResponse.java @@ -0,0 +1,4 @@ +package com.example.api.aws.dto; + +public record UploadProfileResponse() { +} diff --git a/src/main/java/com/example/api/aws/service/S3Service.java b/src/main/java/com/example/api/aws/service/S3Service.java new file mode 100644 index 00000000..e952c628 --- /dev/null +++ b/src/main/java/com/example/api/aws/service/S3Service.java @@ -0,0 +1,84 @@ +package com.example.api.aws.entity; + +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.AmazonS3Exception; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.PutObjectRequest; +import com.example.api.account.repository.AccountRepository; +import com.example.api.aws.dto.AwsS3; +import com.example.api.aws.dto.UploadProfileRequest; +import com.example.api.exception.BusinessException; +import com.example.api.exception.ErrorCode; +import com.example.api.global.config.AmazonConfig; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class AwsUtils { + private final AmazonS3 amazonS3; + private final AmazonConfig amazonConfig; + private final AccountRepository accountRepository; + + @Transactional + public AwsS3 upload(@Validated UploadProfileRequest request) { + if (request.multipartFile() == null || request.multipartFile().isEmpty()) { + throw new BusinessException(ErrorCode.FILE_UPLOAD_FAILED); + } + + Optional userProfile = accountRepository.findProfileImageByAccountId(request.userId()); + userProfile.ifPresent(oldKey -> remove(new AwsS3(oldKey, null))); + + String key = generateFileName(request.userId(), request.multipartFile()); + String path = uploadToS3(request.multipartFile(), key); + accountRepository.updateProfileImageByAccountId(key, request.userId()); + + return new AwsS3(key, path); + } + + private String generateFileName(Long userId, MultipartFile file) { + String contentType = file.getContentType(); + String fileExtension = contentType != null && contentType.contains("/") + ? "." + contentType.split("/")[1] + : ".png"; + return String.format("user-profile/%d/profile%s", userId, fileExtension); + } + + private String uploadToS3(MultipartFile uploadFile, String fileName) { + try { + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(uploadFile.getSize()); + metadata.setContentType(uploadFile.getContentType()); + + amazonS3.putObject( + new PutObjectRequest( + amazonConfig.getBucket(), + fileName, + uploadFile.getInputStream(), + metadata + ) + ); + return getS3Url(amazonConfig.getBucket(), fileName); + + } catch (IOException e) { + throw new BusinessException(ErrorCode.FILE_UPLOAD_FAILED); + } + } + + private String getS3Url(String bucket, String fileName) { + return amazonS3.getUrl(bucket, fileName).toString(); + } + + public void remove(AwsS3 awsS3) { + if (!amazonS3.doesObjectExist(amazonConfig.getBucket(), awsS3.key())) { + throw new AmazonS3Exception("Object " + awsS3.key() + " does not exist!"); + } + amazonS3.deleteObject(amazonConfig.getBucket(), awsS3.key()); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/api/global/config/AmazonConfig.java b/src/main/java/com/example/api/global/config/AmazonConfig.java new file mode 100644 index 00000000..844de32d --- /dev/null +++ b/src/main/java/com/example/api/global/config/AmazonConfig.java @@ -0,0 +1,4 @@ +package com.example.api.global.config; + +public class AmazonConfig { +} diff --git a/src/main/java/com/example/api/global/config/WebConfig.java b/src/main/java/com/example/api/global/config/WebConfig.java new file mode 100644 index 00000000..49d5d06d --- /dev/null +++ b/src/main/java/com/example/api/global/config/WebConfig.java @@ -0,0 +1,4 @@ +package com.example.api.global.config; + +public class WebConfig { +} diff --git a/src/test/java/com/example/api/aws/service/S3ServiceTest.java b/src/test/java/com/example/api/aws/service/S3ServiceTest.java new file mode 100644 index 00000000..762cb006 --- /dev/null +++ b/src/test/java/com/example/api/aws/service/S3ServiceTest.java @@ -0,0 +1,4 @@ +import static org.junit.jupiter.api.Assertions.*; +class S3ServiceTest { + +} \ No newline at end of file diff --git a/src/test/resources/test-files/test-image.png b/src/test/resources/test-files/test-image.png new file mode 100644 index 0000000000000000000000000000000000000000..a22f0faad69c110397ac9a6570fab94991787c6b GIT binary patch literal 3411 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 literal 0 HcmV?d00001 From 69d7bc21f2ef563293bb8e637895a19cc92d560c Mon Sep 17 00:00:00 2001 From: taeyeongkims Date: Thu, 9 Jan 2025 11:26:57 +0900 Subject: [PATCH 03/14] =?UTF-8?q?#66=20(dto)=20=ED=95=84=EC=9A=94=20dto=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/example/api/aws/dto/OldKeyRequest.java | 4 +++- .../java/com/example/api/aws/dto/S3UploadRequest.java | 6 +++++- .../com/example/api/aws/dto/UploadProfileRequest.java | 9 +++++++-- .../com/example/api/aws/dto/UploadProfileResponse.java | 4 +++- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/example/api/aws/dto/OldKeyRequest.java b/src/main/java/com/example/api/aws/dto/OldKeyRequest.java index 4842efe3..2f5ae4e6 100644 --- a/src/main/java/com/example/api/aws/dto/OldKeyRequest.java +++ b/src/main/java/com/example/api/aws/dto/OldKeyRequest.java @@ -1,4 +1,6 @@ package com.example.api.aws.dto; -public record OldKeyRequest() { +import jakarta.validation.constraints.NotBlank; + +public record OldKeyRequest(@NotBlank String oldKey) { } diff --git a/src/main/java/com/example/api/aws/dto/S3UploadRequest.java b/src/main/java/com/example/api/aws/dto/S3UploadRequest.java index 5539f7bf..63083695 100644 --- a/src/main/java/com/example/api/aws/dto/S3UploadRequest.java +++ b/src/main/java/com/example/api/aws/dto/S3UploadRequest.java @@ -1,4 +1,8 @@ package com.example.api.aws.dto; -public record AwsS3(String bucket, String String key, String path) { +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import org.springframework.web.multipart.MultipartFile; + +public record S3UploadRequest(@NotNull MultipartFile multipartFile, @NotBlank String key) { } diff --git a/src/main/java/com/example/api/aws/dto/UploadProfileRequest.java b/src/main/java/com/example/api/aws/dto/UploadProfileRequest.java index 6fe5e381..8458a66a 100644 --- a/src/main/java/com/example/api/aws/dto/UploadProfileRequest.java +++ b/src/main/java/com/example/api/aws/dto/UploadProfileRequest.java @@ -1,4 +1,9 @@ package com.example.api.aws.dto; -public record UploadProfileRequest() { -} +import jakarta.validation.constraints.NotNull; +import org.springframework.web.multipart.MultipartFile; + +public record UploadProfileRequest( + @NotNull Long userId, + @NotNull MultipartFile multipartFile) { +} \ No newline at end of file diff --git a/src/main/java/com/example/api/aws/dto/UploadProfileResponse.java b/src/main/java/com/example/api/aws/dto/UploadProfileResponse.java index 3e7a71e3..fe100682 100644 --- a/src/main/java/com/example/api/aws/dto/UploadProfileResponse.java +++ b/src/main/java/com/example/api/aws/dto/UploadProfileResponse.java @@ -1,4 +1,6 @@ package com.example.api.aws.dto; -public record UploadProfileResponse() { +import jakarta.validation.constraints.NotNull; + +public record UploadProfileResponse(@NotNull String path) { } From 0fe3f4af2296fe73f6d6b120e8133755438d784a Mon Sep 17 00:00:00 2001 From: taeyeongkims Date: Thu, 9 Jan 2025 11:27:48 +0900 Subject: [PATCH 04/14] =?UTF-8?q?#66=20(service)=20s3=20service=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/api/aws/service/S3Service.java | 70 +++++++++++-------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/src/main/java/com/example/api/aws/service/S3Service.java b/src/main/java/com/example/api/aws/service/S3Service.java index e952c628..daa583cc 100644 --- a/src/main/java/com/example/api/aws/service/S3Service.java +++ b/src/main/java/com/example/api/aws/service/S3Service.java @@ -1,84 +1,94 @@ -package com.example.api.aws.entity; +package com.example.api.aws.service; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.model.AmazonS3Exception; +import com.amazonaws.services.s3.model.CannedAccessControlList; import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.PutObjectRequest; import com.example.api.account.repository.AccountRepository; -import com.example.api.aws.dto.AwsS3; +import com.example.api.aws.dto.S3UploadRequest; +import com.example.api.aws.dto.OldKeyRequest; import com.example.api.aws.dto.UploadProfileRequest; +import com.example.api.aws.dto.UploadProfileResponse; import com.example.api.exception.BusinessException; import com.example.api.exception.ErrorCode; import com.example.api.global.config.AmazonConfig; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; -import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.util.Optional; @Service @RequiredArgsConstructor -public class AwsUtils { +@Slf4j +public class S3Service { private final AmazonS3 amazonS3; private final AmazonConfig amazonConfig; private final AccountRepository accountRepository; @Transactional - public AwsS3 upload(@Validated UploadProfileRequest request) { - if (request.multipartFile() == null || request.multipartFile().isEmpty()) { - throw new BusinessException(ErrorCode.FILE_UPLOAD_FAILED); - } + public UploadProfileResponse upload(@Validated final UploadProfileRequest request) { + // 업로드 파일이 null 이라면 기본 프로필로 초기화 + if (initDefaultIfFileIsNull(request)) return new UploadProfileResponse(null); Optional userProfile = accountRepository.findProfileImageByAccountId(request.userId()); - userProfile.ifPresent(oldKey -> remove(new AwsS3(oldKey, null))); + userProfile.ifPresent(oldKey -> remove(new OldKeyRequest(oldKey))); - String key = generateFileName(request.userId(), request.multipartFile()); - String path = uploadToS3(request.multipartFile(), key); - accountRepository.updateProfileImageByAccountId(key, request.userId()); + String key = generateFileName(request); + String path = uploadToS3(new S3UploadRequest(request.multipartFile(), key)); + accountRepository.updateProfileImageByAccountId(key, request.userId()); // S3 업로드 이후 사용자 테이블 프로필 값 업데이트 + + return new UploadProfileResponse(path); + } - return new AwsS3(key, path); + private boolean initDefaultIfFileIsNull(final UploadProfileRequest request) { + if (request.multipartFile() == null || request.multipartFile().isEmpty()) { + accountRepository.updateProfileImageByAccountId(null, request.userId()); + String oldKey = "user-uploads/" + request.userId() + "/profile.png"; + remove(new OldKeyRequest(oldKey)); + return true; + } + return false; } - private String generateFileName(Long userId, MultipartFile file) { - String contentType = file.getContentType(); + private String generateFileName(final UploadProfileRequest request) { + String contentType = request.multipartFile().getContentType(); String fileExtension = contentType != null && contentType.contains("/") ? "." + contentType.split("/")[1] : ".png"; - return String.format("user-profile/%d/profile%s", userId, fileExtension); + return String.format("user-uploads/%d/profile%s", request.userId(), fileExtension); } - private String uploadToS3(MultipartFile uploadFile, String fileName) { + private String uploadToS3(final S3UploadRequest s3UploadRequest) { try { ObjectMetadata metadata = new ObjectMetadata(); - metadata.setContentLength(uploadFile.getSize()); - metadata.setContentType(uploadFile.getContentType()); + metadata.setContentLength(s3UploadRequest.multipartFile().getSize()); + metadata.setContentType(s3UploadRequest.multipartFile().getContentType()); amazonS3.putObject( new PutObjectRequest( amazonConfig.getBucket(), - fileName, - uploadFile.getInputStream(), + s3UploadRequest.key(), + s3UploadRequest.multipartFile().getInputStream(), metadata ) ); - return getS3Url(amazonConfig.getBucket(), fileName); + + return amazonS3.getUrl(amazonConfig.getBucket(), s3UploadRequest.key()).toString(); } catch (IOException e) { throw new BusinessException(ErrorCode.FILE_UPLOAD_FAILED); } } - private String getS3Url(String bucket, String fileName) { - return amazonS3.getUrl(bucket, fileName).toString(); - } - - public void remove(AwsS3 awsS3) { - if (!amazonS3.doesObjectExist(amazonConfig.getBucket(), awsS3.key())) { - throw new AmazonS3Exception("Object " + awsS3.key() + " does not exist!"); + public void remove(final OldKeyRequest request) { + if (!amazonS3.doesObjectExist(amazonConfig.getBucket(), request.oldKey())) { + throw new AmazonS3Exception("Object " + request.oldKey() + " does not exist!"); } - amazonS3.deleteObject(amazonConfig.getBucket(), awsS3.key()); + amazonS3.deleteObject(amazonConfig.getBucket(), request.oldKey()); } } \ No newline at end of file From 01f57f2acce6b34b6a1585d9d00c15877a519b55 Mon Sep 17 00:00:00 2001 From: taeyeongkims Date: Thu, 9 Jan 2025 11:32:17 +0900 Subject: [PATCH 05/14] =?UTF-8?q?#66=20(config)=20aws=20s3=20=EA=B6=8C?= =?UTF-8?q?=ED=95=9C=20=EB=B0=8F=20=EC=86=8D=EC=84=B1=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?,=20multipartResolver=20WebConfig=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/global/config/AmazonConfig.java | 44 ++++++++++++++++++- .../example/api/global/config/WebConfig.java | 13 +++++- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/example/api/global/config/AmazonConfig.java b/src/main/java/com/example/api/global/config/AmazonConfig.java index 844de32d..4d5a5a4c 100644 --- a/src/main/java/com/example/api/global/config/AmazonConfig.java +++ b/src/main/java/com/example/api/global/config/AmazonConfig.java @@ -1,4 +1,46 @@ package com.example.api.global.config; +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import jakarta.annotation.PostConstruct; +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@Getter public class AmazonConfig { -} + private AWSCredentials awsCredentials; + @Value("${cloud.aws.s3.bucket}") + private String bucket; + @Value("${cloud.aws.credentials.accessKey}") + private String accessKey; + @Value("${cloud.aws.credentials.secretKey}") + private String secretKey; + @Value("${cloud.aws.region.static}") + private String region; + + @PostConstruct + public void init() { + this.awsCredentials = new BasicAWSCredentials(accessKey, secretKey); + } + + @Bean + public AmazonS3 amazonS3() { + AWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey); + return AmazonS3ClientBuilder.standard() + .withRegion(region) + .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) + .build(); + } + + @Bean + public AWSCredentialsProvider awsCredentialsProvider() { + return new AWSStaticCredentialsProvider(awsCredentials); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/api/global/config/WebConfig.java b/src/main/java/com/example/api/global/config/WebConfig.java index 49d5d06d..4964abed 100644 --- a/src/main/java/com/example/api/global/config/WebConfig.java +++ b/src/main/java/com/example/api/global/config/WebConfig.java @@ -1,4 +1,15 @@ package com.example.api.global.config; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.multipart.MultipartResolver; +import org.springframework.web.multipart.support.StandardServletMultipartResolver; + +@Configuration public class WebConfig { -} + + @Bean + public MultipartResolver multipartResolver() { + return new StandardServletMultipartResolver(); + } +} \ No newline at end of file From 0af1c4b1eebd4f0649c2dd91ef6915767ec0cf4d Mon Sep 17 00:00:00 2001 From: taeyeongkims Date: Thu, 9 Jan 2025 11:34:02 +0900 Subject: [PATCH 06/14] =?UTF-8?q?#66=20(application.properties)=20s3?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EB=B0=8F=20multipartResolver=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.properties | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 5c44b70a..65810772 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -89,4 +89,14 @@ app.oauth2.authorized-redirect-uris=* # vendor vendor.api.base-url=https://api.odcloud.kr/api/nts-businessman/v1/validate -vendor.api.service-key=${VENDOR_API_SERVICE-KEY} \ No newline at end of file +vendor.api.service-key=${VENDOR_API_SERVICE-KEY} + +cloud.aws.s3.bucket=danpat +cloud.aws.region.static=ap-northeast-2 +cloud.aws.stack.auto=false +cloud.aws.credentials.accessKey=${AWS_CREDENTIALS_ACCESSKEY} +cloud.aws.credentials.secretKey=${AWS_CREDENTIALS_SECRETKEY} + +spring.servlet.multipart.enabled=true +spring.servlet.multipart.max-file-size=10MB +spring.servlet.multipart.max-request-size=10MB From 313dbc83282ca7a1f89f3aba0a877139d8c0a3a0 Mon Sep 17 00:00:00 2001 From: taeyeongkims Date: Thu, 9 Jan 2025 11:34:39 +0900 Subject: [PATCH 07/14] =?UTF-8?q?#66=20(s3ServiceTest)=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=EC=97=85=EB=A1=9C=EB=93=9C=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/aws/service/S3ServiceTest.java | 86 ++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/example/api/aws/service/S3ServiceTest.java b/src/test/java/com/example/api/aws/service/S3ServiceTest.java index 762cb006..18ebe55d 100644 --- a/src/test/java/com/example/api/aws/service/S3ServiceTest.java +++ b/src/test/java/com/example/api/aws/service/S3ServiceTest.java @@ -1,4 +1,88 @@ +package com.example.api.aws.service; + +import com.example.api.account.entity.Nationality; +import com.example.api.account.entity.UserRole; +import com.example.api.account.repository.AccountRepository; +import com.example.api.aws.dto.UploadProfileRequest; +import com.example.api.aws.dto.UploadProfileResponse; +import com.example.api.domain.Account; +import jakarta.annotation.PostConstruct; +import org.junit.jupiter.api.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.core.io.ClassPathResource; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.List; + import static org.junit.jupiter.api.Assertions.*; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@SpringBootTest class S3ServiceTest { - + @Autowired + private S3Service s3Service; + @Autowired + private AccountRepository accountRepository; + + @PostConstruct + void setUp() { + accountRepository.deleteAll(); + Account account = new Account( + 25, // age + false, // deleted + "alice@example.com", // email + "user01", // loginId + "Alice", // name + Nationality.KOREAN, // nationality + "nickname1", // nickname + true, // openStatus + "pass01", // password + "010-1234-5678", // phoneNumber + "user-uploads/1/profile.png", // profileImage + List.of(UserRole.EMPLOYEE), // roles + "F", // sex + 4.5f, // starPoint + 10 // workCount + ); + accountRepository.save(account); + } + + @Test + @Order(1) + @DisplayName("업로드 파일이 null일 경우 기본 프로필로 초기화") + void uploadProfileImage_NullFile_ShouldInitializeToDefaultImage() { + MultipartFile file = null; + + UploadProfileRequest request = new UploadProfileRequest(1L, file); + + UploadProfileResponse response = s3Service.upload(request); + String newFile = accountRepository.findProfileImageByAccountId(1L).orElse(null); + assertNull(response.path()); + assertNull(newFile); // null로 업데이트 되었는지 확인 + } + + @Test + @Order(2) + @DisplayName("정상 업로드 성공") + void upload_ShouldUploadFileSuccessfully() throws IOException { + ClassPathResource resource = new ClassPathResource("test-files/test-image.png"); + MultipartFile file = new MockMultipartFile( + "file", + resource.getFilename(), + "image/png", + resource.getInputStream() + ); + + UploadProfileRequest request = new UploadProfileRequest(1L, file); + + UploadProfileResponse response = s3Service.upload(request); + String newFile = accountRepository.findProfileImageByAccountId(1L).orElse(null); + + assertNotNull(response); + assertEquals("https://danpat.s3.ap-northeast-2.amazonaws.com/user-uploads/1/profile.png", response.path()); + assertNotEquals("oldProfile", newFile); // 새로운 파일 이름으로 업데이트 되었는지 확인 + } } \ No newline at end of file From e7b66c7f3dce5a452551b72ea9111ef30d25525e Mon Sep 17 00:00:00 2001 From: taeyeongkims Date: Thu, 9 Jan 2025 11:35:26 +0900 Subject: [PATCH 08/14] =?UTF-8?q?#66=20(test-resource)=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20resource=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/resources/application-test.properties | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/test/resources/application-test.properties b/src/test/resources/application-test.properties index 7b320cb3..33d21693 100644 --- a/src/test/resources/application-test.properties +++ b/src/test/resources/application-test.properties @@ -32,4 +32,10 @@ spring.security.oauth2.client.provider.naver.user-info-uri=https://openapi.naver spring.security.oauth2.client.provider.naver.user-name-attribute=response # redirect allow path -app.oauth2.authorized-redirect-uris=* \ No newline at end of file +app.oauth2.authorized-redirect-uris=* + +cloud.aws.s3.bucket=danpat +cloud.aws.region.static=ap-northeast-2 +cloud.aws.stack.auto=false +cloud.aws.credentials.accessKey=${AWS_CREDENTIALS_ACCESSKEY} +cloud.aws.credentials.secretKey=${AWS_CREDENTIALS_SECRETKEY} \ No newline at end of file From 47704521ad8368f1151c87251b0b818cd44918d5 Mon Sep 17 00:00:00 2001 From: taeyeongkims Date: Thu, 9 Jan 2025 11:39:24 +0900 Subject: [PATCH 09/14] =?UTF-8?q?#66=20(build.gradle)=20s3=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 98b67c8f..8efcf853 100644 --- a/build.gradle +++ b/build.gradle @@ -96,6 +96,9 @@ dependencies { //WIREMOCK (외부 의존성 테스트용) implementation 'org.wiremock.integrations:wiremock-spring-boot:3.3.0' + + // S3 + implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' } tasks.named('test') { @@ -122,4 +125,4 @@ sourceSets { srcDirs = ['src/test/java'] } } -} \ No newline at end of file +} From 2f06e582caffa492ca476686670c0ab421b0e414 Mon Sep 17 00:00:00 2001 From: taeyeongkims Date: Thu, 9 Jan 2025 11:42:30 +0900 Subject: [PATCH 10/14] =?UTF-8?q?#66=20(Account)=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=EC=9E=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/example/api/domain/Account.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/main/java/com/example/api/domain/Account.java b/src/main/java/com/example/api/domain/Account.java index c04e0051..f8040442 100644 --- a/src/main/java/com/example/api/domain/Account.java +++ b/src/main/java/com/example/api/domain/Account.java @@ -88,6 +88,24 @@ public Account(String loginId, String password, String email, String phoneNumber this.roles = roles; } + public Account(int age, boolean deleted, String email, String loginId, String name, Nationality nationality, String nickname, boolean openStatus, String password, String phoneNumber, String profileImage, Collection roles, String sex, float starPoint, int workCount) { + this.age = age; + this.deleted = deleted; + this.email = email; + this.loginId = loginId; + this.name = name; + this.nationality = nationality; + this.nickname = nickname; + this.openStatus = openStatus; + this.password = password; + this.phoneNumber = phoneNumber; + this.profileImage = profileImage; + this.roles = roles; + this.sex = sex; + this.starPoint = starPoint; + this.workCount = workCount; + } + public LoginUserRequest getLoginUser(){ return new LoginUserRequest(accountId); } From 00576f4ac9294c754e894911d95ba4fbb85fb92b Mon Sep 17 00:00:00 2001 From: taeyeongkims Date: Thu, 9 Jan 2025 11:43:16 +0900 Subject: [PATCH 11/14] =?UTF-8?q?#66=20(ErrorCode)=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/example/api/exception/ErrorCode.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/example/api/exception/ErrorCode.java b/src/main/java/com/example/api/exception/ErrorCode.java index 494a7034..68878d72 100644 --- a/src/main/java/com/example/api/exception/ErrorCode.java +++ b/src/main/java/com/example/api/exception/ErrorCode.java @@ -38,7 +38,9 @@ public enum ErrorCode { BUSINESS_DOMAIN_EXCEPTION(HttpStatus.BAD_REQUEST, "-700", "비즈니스 도메인 에러"), - CONTRACT_EXCEPTION(HttpStatus.BAD_REQUEST, "-800", "계약 도메인 에러"); + CONTRACT_EXCEPTION(HttpStatus.BAD_REQUEST, "-800", "계약 도메인 에러"), + + FILE_UPLOAD_FAILED(HttpStatus.INTERNAL_SERVER_ERROR,"F500", "이미지 업로드 실패"); private final HttpStatus httpStatus; private final String errorCode; @@ -49,5 +51,4 @@ public enum ErrorCode { this.errorCode = errorCodeResponse; this.errorDescription = errorDescription; } -} - +} \ No newline at end of file From 33badfcb9919095ffce2c8ecf44a565d3020fa04 Mon Sep 17 00:00:00 2001 From: taeyeongkims Date: Thu, 9 Jan 2025 11:44:12 +0900 Subject: [PATCH 12/14] =?UTF-8?q?#66=20(AccountRepository)=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=BF=BC=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/account/repository/AccountRepository.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/com/example/api/account/repository/AccountRepository.java b/src/main/java/com/example/api/account/repository/AccountRepository.java index 37cbb752..cef6eae2 100644 --- a/src/main/java/com/example/api/account/repository/AccountRepository.java +++ b/src/main/java/com/example/api/account/repository/AccountRepository.java @@ -4,6 +4,8 @@ import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.repository.query.Param; import java.util.Optional; @@ -16,4 +18,11 @@ public interface AccountRepository extends JpaRepository { Optional findByEmail(String email); Optional findUserByLoginId(String loginId); + + @Query("SELECT a.profileImage FROM Account a WHERE a.accountId = :accountId") + Optional findProfileImageByAccountId(@Param("accountId") Long accountId); + + @Query("update Account a set a.profileImage = :profileImage where a.accountId = :accountId") + @Modifying + void updateProfileImageByAccountId(@Param("profileImage") String profileImage, @Param("accountId") Long accountId); } \ No newline at end of file From edd6bfb090aafd2bcbfbabc6285a51cf095c390d Mon Sep 17 00:00:00 2001 From: taeyeongkims Date: Thu, 9 Jan 2025 11:44:39 +0900 Subject: [PATCH 13/14] =?UTF-8?q?#66=20=EC=BB=A8=EB=B2=A4=EC=85=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/example/api/aws/controller/S3Controller.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/example/api/aws/controller/S3Controller.java b/src/main/java/com/example/api/aws/controller/S3Controller.java index c3a1667d..4bd0f652 100644 --- a/src/main/java/com/example/api/aws/controller/S3Controller.java +++ b/src/main/java/com/example/api/aws/controller/S3Controller.java @@ -16,7 +16,7 @@ public class S3Controller { private final S3Service s3Service; @PostMapping(value = "/upload/profile", consumes = "multipart/form-data") - public ResponseEntity upload(@RequestParam("file") MultipartFile file) { + public ResponseEntity upload(@RequestParam("file") final MultipartFile file) { UploadProfileRequest request = new UploadProfileRequest(1L, file); return new ResponseEntity<>(s3Service.upload(request).path(), HttpStatus.OK); } From f38ecfd1a47101db0a690f16b94f1494e2910ec0 Mon Sep 17 00:00:00 2001 From: taeyeongKims Date: Thu, 16 Jan 2025 18:42:11 +0900 Subject: [PATCH 14/14] . --- build.gradle | 15 +-------------- .../com/example/api/aws/service/S3Service.java | 1 - src/main/resources/application.properties | 2 +- 3 files changed, 2 insertions(+), 16 deletions(-) diff --git a/build.gradle b/build.gradle index 8efcf853..f1c2bf90 100644 --- a/build.gradle +++ b/build.gradle @@ -82,20 +82,7 @@ dependencies { //객체 간 매핑 처리 implementation 'org.modelmapper:modelmapper:3.1.0' - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.4' - //객체 간 매핑 처리 - implementation 'org.modelmapper:modelmapper:3.1.0' - - - // OAUTH2 - implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' - - // MOCKITO - testImplementation "org.mockito:mockito-core:3.+" - - //WIREMOCK (외부 의존성 테스트용) - implementation 'org.wiremock.integrations:wiremock-spring-boot:3.3.0' // S3 implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' @@ -125,4 +112,4 @@ sourceSets { srcDirs = ['src/test/java'] } } -} +} \ No newline at end of file diff --git a/src/main/java/com/example/api/aws/service/S3Service.java b/src/main/java/com/example/api/aws/service/S3Service.java index daa583cc..2e0037f6 100644 --- a/src/main/java/com/example/api/aws/service/S3Service.java +++ b/src/main/java/com/example/api/aws/service/S3Service.java @@ -2,7 +2,6 @@ import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.model.AmazonS3Exception; -import com.amazonaws.services.s3.model.CannedAccessControlList; import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.PutObjectRequest; import com.example.api.account.repository.AccountRepository; diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 65810772..a4bd2071 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -99,4 +99,4 @@ cloud.aws.credentials.secretKey=${AWS_CREDENTIALS_SECRETKEY} spring.servlet.multipart.enabled=true spring.servlet.multipart.max-file-size=10MB -spring.servlet.multipart.max-request-size=10MB +spring.servlet.multipart.max-request-size=10MB \ No newline at end of file