From 302c85b488a172565a15ae3d0f8c27ecb60617b0 Mon Sep 17 00:00:00 2001 From: bokyeong Date: Fri, 11 Oct 2024 23:28:16 +0900 Subject: [PATCH 01/14] =?UTF-8?q?chore:=20[#30]=20S3=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=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 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.gradle b/build.gradle index 1ed3bd7..4b65aaf 100644 --- a/build.gradle +++ b/build.gradle @@ -62,6 +62,9 @@ dependencies { // Monitoring implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'io.micrometer:micrometer-registry-prometheus' + + //S3 + implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' } tasks.named('test') { From 6de5081e542a9f2b238262345794d1fc1c2b62d8 Mon Sep 17 00:00:00 2001 From: bokyeong Date: Fri, 11 Oct 2024 23:28:58 +0900 Subject: [PATCH 02/14] =?UTF-8?q?feat:=20[#30]=20S3Config=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../helpmeCookies/global/config/S3Config.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/main/java/com/helpmeCookies/global/config/S3Config.java diff --git a/src/main/java/com/helpmeCookies/global/config/S3Config.java b/src/main/java/com/helpmeCookies/global/config/S3Config.java new file mode 100644 index 0000000..e4df773 --- /dev/null +++ b/src/main/java/com/helpmeCookies/global/config/S3Config.java @@ -0,0 +1,29 @@ +package com.helpmeCookies.global.config; + +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class S3Config { + @Value("${cloud.aws.credentials.access-key}") + private String accessKey; + @Value("${cloud.aws.credentials.secret-key}") + private String secretKey; + @Value("${cloud.aws.region.static}") + private String region; + + public AmazonS3 amazonS3() { + AWSCredentials credentials = new BasicAWSCredentials(accessKey,secretKey); + + return AmazonS3ClientBuilder + .standard() + .withCredentials(new AWSStaticCredentialsProvider(credentials)) + .withRegion(region) + .build(); + } +} From fd0032a72a7913a8c3f1e02077caf9b7b7c339a5 Mon Sep 17 00:00:00 2001 From: bokyeong Date: Sat, 12 Oct 2024 02:19:28 +0900 Subject: [PATCH 03/14] =?UTF-8?q?fix:=20[#30]=20S3Config=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20Bean=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EC=85=98=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/helpmeCookies/global/config/S3Config.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/helpmeCookies/global/config/S3Config.java b/src/main/java/com/helpmeCookies/global/config/S3Config.java index e4df773..bdeb16f 100644 --- a/src/main/java/com/helpmeCookies/global/config/S3Config.java +++ b/src/main/java/com/helpmeCookies/global/config/S3Config.java @@ -6,6 +6,7 @@ import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3ClientBuilder; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @@ -17,6 +18,7 @@ public class S3Config { @Value("${cloud.aws.region.static}") private String region; + @Bean public AmazonS3 amazonS3() { AWSCredentials credentials = new BasicAWSCredentials(accessKey,secretKey); From 7012a460774291108032a51a88e184316acac46b Mon Sep 17 00:00:00 2001 From: bokyeong Date: Sat, 12 Oct 2024 02:20:09 +0900 Subject: [PATCH 04/14] =?UTF-8?q?fix:=20[#30]=20S3=20=EB=8B=A4=EC=A4=91=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=97=85=EB=A1=9C=EB=93=9C=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/utils/AwsS3FileUtils.java | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 src/main/java/com/helpmeCookies/global/utils/AwsS3FileUtils.java diff --git a/src/main/java/com/helpmeCookies/global/utils/AwsS3FileUtils.java b/src/main/java/com/helpmeCookies/global/utils/AwsS3FileUtils.java new file mode 100644 index 0000000..9186d72 --- /dev/null +++ b/src/main/java/com/helpmeCookies/global/utils/AwsS3FileUtils.java @@ -0,0 +1,71 @@ +package com.helpmeCookies.global.utils; + +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.CannedAccessControlList; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.PutObjectRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.server.ResponseStatusException; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +@Component +@RequiredArgsConstructor +public class AwsS3FileUtils { + private final AmazonS3 amazonS3; + + @Value("${cloud.aws.s3.bucket}") + private String bucket; + + //다중파일 업로드후 url 반환 + public List uploadMultiImages(List multipartFiles) { + List fileList = new ArrayList<>(); + + multipartFiles.forEach(file -> { + String fileName = createFileName(file.getOriginalFilename()); //파일 이름 난수화 + ObjectMetadata objectMetadata = new ObjectMetadata(); + objectMetadata.setContentLength(file.getSize()); + objectMetadata.setContentType(file.getContentType()); + + try (InputStream inputStream = file.getInputStream()) { + amazonS3.putObject(new PutObjectRequest(bucket, fileName, inputStream, objectMetadata) + .withCannedAcl(CannedAccessControlList.PublicRead)); + } catch (IOException e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "파일 업로드 실패" + fileName); + } + + fileList.add(fileName); + }); + + return fileList; + } + + public String createFileName(String fileName) { + return UUID.randomUUID().toString().concat(getFileExtension(fileName)); + } + + //TODO error handler 필요 + public String getFileExtension(String fileName) { + try { + String extension = fileName.substring(fileName.lastIndexOf(".")).toLowerCase(); + //이미지 파일 확장자 목록 + List allowedExtensions = Arrays.asList(".jpg", ".jpeg", ".png"); + + if (!allowedExtensions.contains(extension)) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "이미지 파일만 업로드가 가능합니다. 지원되지 않는 형식의 파일" + fileName); + } + return extension; + } catch (StringIndexOutOfBoundsException e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST,"잘못된 형식의 파일" + fileName + "입니다."); + } + } +} From d1983d02df5774105a6934607fd59a5595847b2f Mon Sep 17 00:00:00 2001 From: bokyeong Date: Sat, 12 Oct 2024 02:21:13 +0900 Subject: [PATCH 05/14] =?UTF-8?q?refactor:=20[#30]=20productImage=20?= =?UTF-8?q?=EC=97=B0=EA=B4=80=EA=B4=80=EA=B3=84=20=EB=A7=A4=ED=95=91=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/entity/ProductImage.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/helpmeCookies/product/entity/ProductImage.java b/src/main/java/com/helpmeCookies/product/entity/ProductImage.java index 699f53e..c967222 100644 --- a/src/main/java/com/helpmeCookies/product/entity/ProductImage.java +++ b/src/main/java/com/helpmeCookies/product/entity/ProductImage.java @@ -4,8 +4,7 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; +import lombok.Builder; @Entity public class ProductImage { @@ -15,7 +14,13 @@ public class ProductImage { private String photoUrl; - @ManyToOne - @JoinColumn(name = "product_id") - private Product product; + private Long productId; + + public ProductImage() {} + + @Builder + public ProductImage(String photoUrl, Long productId) { + this.photoUrl = photoUrl; + this.productId = productId; + } } From 87a5d53b220fd9e00db5226fdee1ae2b1738f3c9 Mon Sep 17 00:00:00 2001 From: bokyeong Date: Mon, 14 Oct 2024 00:58:16 +0900 Subject: [PATCH 06/14] =?UTF-8?q?feat:=20[#30]=20=EC=83=81=ED=92=88=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=85=EB=A1=9C=EB=93=9C=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=97=AC=EB=9F=AC=20=EC=82=AC=EC=A7=84=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=97=85=EB=A1=9C=EB=93=9C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/controller/ProductController.java | 21 ++++++++++---- .../product/dto/ProductImageResponse.java | 8 +++++ .../repository/ProductImageRepository.java | 9 ++++++ .../product/service/ProductImageService.java | 29 +++++++++++++++++++ 4 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/helpmeCookies/product/dto/ProductImageResponse.java create mode 100644 src/main/java/com/helpmeCookies/product/repository/ProductImageRepository.java create mode 100644 src/main/java/com/helpmeCookies/product/service/ProductImageService.java diff --git a/src/main/java/com/helpmeCookies/product/controller/ProductController.java b/src/main/java/com/helpmeCookies/product/controller/ProductController.java index c87732e..be6ccb7 100644 --- a/src/main/java/com/helpmeCookies/product/controller/ProductController.java +++ b/src/main/java/com/helpmeCookies/product/controller/ProductController.java @@ -1,28 +1,39 @@ package com.helpmeCookies.product.controller; +import com.helpmeCookies.product.dto.ProductImageResponse; import com.helpmeCookies.product.dto.ProductRequest; import com.helpmeCookies.product.dto.ProductResponse; import com.helpmeCookies.product.entity.Product; +import com.helpmeCookies.product.service.ProductImageService; import com.helpmeCookies.product.service.ProductService; +import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.List; @RestController @RequestMapping("/api/v1/products") +@RequiredArgsConstructor public class ProductController { private final ProductService productService; - - public ProductController(ProductService productService) { - this.productService = productService; - } + private final ProductImageService productImageService; @PostMapping public ResponseEntity saveProduct(@RequestBody ProductRequest productRequest) { - Product product = productService.save(productRequest); + productService.save(productRequest); return ResponseEntity.ok().build(); } + @PostMapping("/{productId}/images") + public ResponseEntity uploadImages(@PathVariable("productId") Long productId, List files) throws IOException { + List urls = productImageService.uploadMultiFiles(productId,files); + return ResponseEntity.ok(new ProductImageResponse(urls)); + } + @GetMapping("/{productId}") public ResponseEntity getProductInfo(@PathVariable("productId") Long productId) { Product product = productService.find(productId); diff --git a/src/main/java/com/helpmeCookies/product/dto/ProductImageResponse.java b/src/main/java/com/helpmeCookies/product/dto/ProductImageResponse.java new file mode 100644 index 0000000..b090167 --- /dev/null +++ b/src/main/java/com/helpmeCookies/product/dto/ProductImageResponse.java @@ -0,0 +1,8 @@ +package com.helpmeCookies.product.dto; + +import java.util.List; + +public record ProductImageResponse( + List urls +) { +} diff --git a/src/main/java/com/helpmeCookies/product/repository/ProductImageRepository.java b/src/main/java/com/helpmeCookies/product/repository/ProductImageRepository.java new file mode 100644 index 0000000..4795e2b --- /dev/null +++ b/src/main/java/com/helpmeCookies/product/repository/ProductImageRepository.java @@ -0,0 +1,9 @@ +package com.helpmeCookies.product.repository; + +import com.helpmeCookies.product.entity.ProductImage; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ProductImageRepository extends JpaRepository { +} diff --git a/src/main/java/com/helpmeCookies/product/service/ProductImageService.java b/src/main/java/com/helpmeCookies/product/service/ProductImageService.java new file mode 100644 index 0000000..87cc592 --- /dev/null +++ b/src/main/java/com/helpmeCookies/product/service/ProductImageService.java @@ -0,0 +1,29 @@ +package com.helpmeCookies.product.service; + +import com.helpmeCookies.global.utils.AwsS3FileUtils; +import com.helpmeCookies.product.entity.ProductImage; +import com.helpmeCookies.product.repository.ProductImageRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class ProductImageService { + private final AwsS3FileUtils awsS3FileUtils; + private final ProductImageRepository productImageRepository; + + @Transactional + public List uploadMultiFiles(Long productId, List files) throws IOException { + List urlList = awsS3FileUtils.uploadMultiImages(files); + urlList.forEach(fileUrl -> { + ProductImage productImage = new ProductImage(fileUrl,productId); + productImageRepository.save(productImage); + }); + return urlList; + } +} From de95b070250592e26c0530793c2981efab520b73 Mon Sep 17 00:00:00 2001 From: bokyeong Date: Mon, 14 Oct 2024 01:01:08 +0900 Subject: [PATCH 07/14] =?UTF-8?q?test:=20[#30]=20S3=20=EC=97=85=EB=A1=9C?= =?UTF-8?q?=EB=93=9C=20Mock=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ProductImageServiceTest.java | 65 +++++++++++++++++++ src/test/resources/application.yaml | 15 ++++- 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/helpmeCookies/product/service/ProductImageServiceTest.java diff --git a/src/test/java/com/helpmeCookies/product/service/ProductImageServiceTest.java b/src/test/java/com/helpmeCookies/product/service/ProductImageServiceTest.java new file mode 100644 index 0000000..c722d09 --- /dev/null +++ b/src/test/java/com/helpmeCookies/product/service/ProductImageServiceTest.java @@ -0,0 +1,65 @@ +package com.helpmeCookies.product.service; + +import com.helpmeCookies.global.utils.AwsS3FileUtils; +import com.helpmeCookies.product.entity.Category; +import com.helpmeCookies.product.entity.Product; +import com.helpmeCookies.product.repository.ProductImageRepository; +import com.helpmeCookies.product.repository.ProductRepository; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.when; + +@SpringBootTest +@ActiveProfiles("test") +class ProductImageServiceTest { + @MockBean + private AwsS3FileUtils awsS3FileUtils; + + private ProductImageService productImageService; + @MockBean + private ProductRepository productRepository; + @MockBean + private ProductImageRepository productImageRepository; + + @BeforeEach + void setUp() { + productImageService = new ProductImageService(awsS3FileUtils, productImageRepository); + } + + @AfterEach + void tearDown() { + } + + @Test + @DisplayName("상품 이미지 S3 서버에 업로드") + void uploadMultiFiles() throws IOException { + given(productRepository.findById(any())) + .willReturn(Optional.of(new Product("더미",Category.CERAMIC,"100",10000L,"테스트항목","테스트 주소", + null,null))); + MockMultipartFile file1 = new MockMultipartFile("test1","img1.jpg","image/jpeg","image content".getBytes()); + MockMultipartFile file2 = new MockMultipartFile("test2","img2.jpg","image/jpeg","image content".getBytes()); + List files = Arrays.asList(file1,file2); + + List expected = Arrays.asList("url1","url2"); + when(awsS3FileUtils.uploadMultiImages(files)).thenReturn(expected); + + List actual = productImageService.uploadMultiFiles(1L,files); + assertEquals(2,actual.size(), "배열의 크기는 2여야함"); + } +} diff --git a/src/test/resources/application.yaml b/src/test/resources/application.yaml index bf26694..eea2d27 100644 --- a/src/test/resources/application.yaml +++ b/src/test/resources/application.yaml @@ -21,4 +21,17 @@ logging.level: jwt: secret: testtesttesttesttesttesttesttesttest access-token-expire-time: 1800000 # 30 minutes - refresh-token-expire-time: 2592000000 # 30 days \ No newline at end of file + refresh-token-expire-time: 2592000000 # 30 days + +cloud: + aws: + s3: + bucket: testtest + credentials: + access-key: test-access-key + secret-key: test-secret-key + region: + static: ap-northeast-2 + auto: false + stack: + auto: false From a96a0e5743c6c581c15064fbd49354f02571d68a Mon Sep 17 00:00:00 2001 From: bokyeong Date: Mon, 14 Oct 2024 01:08:09 +0900 Subject: [PATCH 08/14] =?UTF-8?q?fix:=20[#40]=20Category=20=EC=BD=98?= =?UTF-8?q?=EC=86=94=20=EC=B6=9C=EB=A0=A5=20=EC=BD=94=EB=93=9C=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/helpmeCookies/product/entity/Category.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/helpmeCookies/product/entity/Category.java b/src/main/java/com/helpmeCookies/product/entity/Category.java index a58a7c0..0ab8e3a 100644 --- a/src/main/java/com/helpmeCookies/product/entity/Category.java +++ b/src/main/java/com/helpmeCookies/product/entity/Category.java @@ -33,7 +33,6 @@ public String getName() { } public static Category fromString(String name) { - System.out.println(name); Category category = nameToCategoryMap.get(name); if (category == null) { throw new IllegalArgumentException(name + "에 해당하는 카테고리가 없습니다."); From 5c465f5ea28022ccf7d19144d41e0575888d27ed Mon Sep 17 00:00:00 2001 From: bokyeong Date: Mon, 14 Oct 2024 23:22:22 +0900 Subject: [PATCH 09/14] =?UTF-8?q?fix:=20[#30]=20=EC=82=AC=EC=A7=84=20url?= =?UTF-8?q?=20=EC=A0=80=EC=9E=A5=20=EB=B0=8F=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/helpmeCookies/global/utils/AwsS3FileUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/helpmeCookies/global/utils/AwsS3FileUtils.java b/src/main/java/com/helpmeCookies/global/utils/AwsS3FileUtils.java index 9186d72..098f7a9 100644 --- a/src/main/java/com/helpmeCookies/global/utils/AwsS3FileUtils.java +++ b/src/main/java/com/helpmeCookies/global/utils/AwsS3FileUtils.java @@ -43,7 +43,7 @@ public List uploadMultiImages(List multipartFiles) { throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "파일 업로드 실패" + fileName); } - fileList.add(fileName); + fileList.add(amazonS3.getUrl(bucket,fileName).toString()); }); return fileList; From 49de4fb00299b3b1a0a6250d7563a1baebdfecd7 Mon Sep 17 00:00:00 2001 From: bokyeong Date: Tue, 15 Oct 2024 00:05:51 +0900 Subject: [PATCH 10/14] =?UTF-8?q?feat:=20[#30]=20=EC=83=81=ED=92=88=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=EC=8B=9C=20=EA=B4=80=EB=A0=A8=20=EC=82=AC?= =?UTF-8?q?=EC=A7=84=EC=97=94=ED=8B=B0=ED=8B=B0=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/repository/ProductImageRepository.java | 4 ++++ .../helpmeCookies/product/service/ProductService.java | 9 +++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/helpmeCookies/product/repository/ProductImageRepository.java b/src/main/java/com/helpmeCookies/product/repository/ProductImageRepository.java index 4795e2b..718b605 100644 --- a/src/main/java/com/helpmeCookies/product/repository/ProductImageRepository.java +++ b/src/main/java/com/helpmeCookies/product/repository/ProductImageRepository.java @@ -4,6 +4,10 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.List; + @Repository public interface ProductImageRepository extends JpaRepository { + List findAllByProductId(Long productId); + void deleteAllByProductId(Long productId); } diff --git a/src/main/java/com/helpmeCookies/product/service/ProductService.java b/src/main/java/com/helpmeCookies/product/service/ProductService.java index 1a16699..0e3e5f7 100644 --- a/src/main/java/com/helpmeCookies/product/service/ProductService.java +++ b/src/main/java/com/helpmeCookies/product/service/ProductService.java @@ -3,17 +3,17 @@ import com.helpmeCookies.product.dto.ProductRequest; import com.helpmeCookies.product.entity.Category; import com.helpmeCookies.product.entity.Product; +import com.helpmeCookies.product.repository.ProductImageRepository; import com.helpmeCookies.product.repository.ProductRepository; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service +@RequiredArgsConstructor public class ProductService { private final ProductRepository productRepository; - - public ProductService(ProductRepository productRepository) { - this.productRepository = productRepository; - } + private final ProductImageRepository productImageRepository; public Product save(ProductRequest productSaveRequest) { //TODO ArtistInfo 코드 병합시 수정 예정 @@ -44,5 +44,6 @@ public void edit(Long productId, ProductRequest productRequest) { public void delete(Long productId) { Product product = productRepository.findById(productId).orElseThrow(() -> new IllegalArgumentException("유효하지 않은 id입니다")); productRepository.deleteById(productId); + productImageRepository.deleteAllByProductId(productId); } } From 1b6390f4b7382511c4970f60714d1b19771de760 Mon Sep 17 00:00:00 2001 From: bokyeong Date: Tue, 15 Oct 2024 00:19:09 +0900 Subject: [PATCH 11/14] =?UTF-8?q?refactor:=20[#30]=20S3=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=EC=97=85=EB=A1=9C=EB=93=9C=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/utils/AwsS3FileUtils.java | 7 ++++--- .../product/dto/FileUploadResponse.java | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/helpmeCookies/product/dto/FileUploadResponse.java diff --git a/src/main/java/com/helpmeCookies/global/utils/AwsS3FileUtils.java b/src/main/java/com/helpmeCookies/global/utils/AwsS3FileUtils.java index 098f7a9..afc9043 100644 --- a/src/main/java/com/helpmeCookies/global/utils/AwsS3FileUtils.java +++ b/src/main/java/com/helpmeCookies/global/utils/AwsS3FileUtils.java @@ -4,6 +4,7 @@ import com.amazonaws.services.s3.model.CannedAccessControlList; import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.PutObjectRequest; +import com.helpmeCookies.product.dto.FileUploadResponse; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; @@ -27,8 +28,8 @@ public class AwsS3FileUtils { private String bucket; //다중파일 업로드후 url 반환 - public List uploadMultiImages(List multipartFiles) { - List fileList = new ArrayList<>(); + public List uploadMultiImages(List multipartFiles) { + List fileList = new ArrayList<>(); multipartFiles.forEach(file -> { String fileName = createFileName(file.getOriginalFilename()); //파일 이름 난수화 @@ -43,7 +44,7 @@ public List uploadMultiImages(List multipartFiles) { throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "파일 업로드 실패" + fileName); } - fileList.add(amazonS3.getUrl(bucket,fileName).toString()); + fileList.add(new FileUploadResponse(amazonS3.getUrl(bucket,fileName).toString(),fileName)); }); return fileList; diff --git a/src/main/java/com/helpmeCookies/product/dto/FileUploadResponse.java b/src/main/java/com/helpmeCookies/product/dto/FileUploadResponse.java new file mode 100644 index 0000000..ad03aff --- /dev/null +++ b/src/main/java/com/helpmeCookies/product/dto/FileUploadResponse.java @@ -0,0 +1,16 @@ +package com.helpmeCookies.product.dto; + +import com.helpmeCookies.product.entity.ProductImage; + +public record FileUploadResponse( + String photoUrl, + String uuid +) { + public ProductImage toEntity(Long productId) { + return ProductImage.builder() + .productId(productId) + .photoUrl(photoUrl) + .uuid(uuid) + .build(); + } +} From 94666714b1ac4fbccf31ccaa47985c36fdc7809d Mon Sep 17 00:00:00 2001 From: bokyeong Date: Tue, 15 Oct 2024 00:20:23 +0900 Subject: [PATCH 12/14] =?UTF-8?q?refactor:=20[#30]=20=EC=83=81=ED=92=88=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=88=98=EC=A0=95=20uuid=EC=99=80=20url?= =?UTF-8?q?=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/helpmeCookies/product/entity/ProductImage.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/helpmeCookies/product/entity/ProductImage.java b/src/main/java/com/helpmeCookies/product/entity/ProductImage.java index c967222..0a23f4f 100644 --- a/src/main/java/com/helpmeCookies/product/entity/ProductImage.java +++ b/src/main/java/com/helpmeCookies/product/entity/ProductImage.java @@ -15,12 +15,14 @@ public class ProductImage { private String photoUrl; private Long productId; + private String uuid; public ProductImage() {} @Builder - public ProductImage(String photoUrl, Long productId) { + public ProductImage(String photoUrl, Long productId, String uuid) { this.photoUrl = photoUrl; this.productId = productId; + this.uuid = uuid; } } From 53656ffb0923a91ff18539b9cd4dc5105368c79c Mon Sep 17 00:00:00 2001 From: bokyeong Date: Tue, 15 Oct 2024 00:21:31 +0900 Subject: [PATCH 13/14] =?UTF-8?q?feat:=20[#30]=20=EC=83=81=ED=92=88=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/controller/ProductController.java | 11 ++++++++-- .../product/service/ProductImageService.java | 22 ++++++++++++------- .../product/service/ProductService.java | 2 +- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/helpmeCookies/product/controller/ProductController.java b/src/main/java/com/helpmeCookies/product/controller/ProductController.java index be6ccb7..c655ee7 100644 --- a/src/main/java/com/helpmeCookies/product/controller/ProductController.java +++ b/src/main/java/com/helpmeCookies/product/controller/ProductController.java @@ -1,5 +1,6 @@ package com.helpmeCookies.product.controller; +import com.helpmeCookies.product.dto.FileUploadResponse; import com.helpmeCookies.product.dto.ProductImageResponse; import com.helpmeCookies.product.dto.ProductRequest; import com.helpmeCookies.product.dto.ProductResponse; @@ -30,8 +31,8 @@ public ResponseEntity saveProduct(@RequestBody ProductRequest productReque @PostMapping("/{productId}/images") public ResponseEntity uploadImages(@PathVariable("productId") Long productId, List files) throws IOException { - List urls = productImageService.uploadMultiFiles(productId,files); - return ResponseEntity.ok(new ProductImageResponse(urls)); + List responses = productImageService.uploadMultiFiles(productId,files); + return ResponseEntity.ok(new ProductImageResponse(responses.stream().map(FileUploadResponse::photoUrl).toList())); } @GetMapping("/{productId}") @@ -47,6 +48,12 @@ public ResponseEntity editProductInfo(@PathVariable("productId") Long prod return ResponseEntity.ok().build(); } + @PutMapping("/{productId}/images") + public ResponseEntity editImages(@PathVariable("productId") Long productId, List files) throws IOException { + productImageService.editImages(productId, files); + return ResponseEntity.ok().build(); + } + @DeleteMapping("/{productId}") public ResponseEntity deleteProduct(@PathVariable("productId") Long productId) { productService.delete(productId); diff --git a/src/main/java/com/helpmeCookies/product/service/ProductImageService.java b/src/main/java/com/helpmeCookies/product/service/ProductImageService.java index 87cc592..25215c5 100644 --- a/src/main/java/com/helpmeCookies/product/service/ProductImageService.java +++ b/src/main/java/com/helpmeCookies/product/service/ProductImageService.java @@ -1,7 +1,7 @@ package com.helpmeCookies.product.service; import com.helpmeCookies.global.utils.AwsS3FileUtils; -import com.helpmeCookies.product.entity.ProductImage; +import com.helpmeCookies.product.dto.FileUploadResponse; import com.helpmeCookies.product.repository.ProductImageRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -18,12 +18,18 @@ public class ProductImageService { private final ProductImageRepository productImageRepository; @Transactional - public List uploadMultiFiles(Long productId, List files) throws IOException { - List urlList = awsS3FileUtils.uploadMultiImages(files); - urlList.forEach(fileUrl -> { - ProductImage productImage = new ProductImage(fileUrl,productId); - productImageRepository.save(productImage); - }); - return urlList; + public List uploadMultiFiles(Long productId, List files) throws IOException { + List uploadResponses = awsS3FileUtils.uploadMultiImages(files); + uploadResponses.forEach(response -> + productImageRepository.save(response.toEntity(productId))); + return uploadResponses; + } + + @Transactional + public void editImages(Long productId, List files) throws IOException { + //우선은 전부 삭제하고 다시 업로드 + //추후에 개선 예정 + productImageRepository.deleteAllByProductId(productId); + uploadMultiFiles(productId, files); } } diff --git a/src/main/java/com/helpmeCookies/product/service/ProductService.java b/src/main/java/com/helpmeCookies/product/service/ProductService.java index 0e3e5f7..c9b22d4 100644 --- a/src/main/java/com/helpmeCookies/product/service/ProductService.java +++ b/src/main/java/com/helpmeCookies/product/service/ProductService.java @@ -43,7 +43,7 @@ public void edit(Long productId, ProductRequest productRequest) { public void delete(Long productId) { Product product = productRepository.findById(productId).orElseThrow(() -> new IllegalArgumentException("유효하지 않은 id입니다")); - productRepository.deleteById(productId); + productRepository.delete(product); productImageRepository.deleteAllByProductId(productId); } } From 9ab22aa690648de5d2fb2e9277e3f86b8856d6dc Mon Sep 17 00:00:00 2001 From: bokyeong Date: Tue, 15 Oct 2024 00:21:51 +0900 Subject: [PATCH 14/14] =?UTF-8?q?test:=20[#30]=20=EC=83=81=ED=92=88=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/service/ProductImageServiceTest.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/helpmeCookies/product/service/ProductImageServiceTest.java b/src/test/java/com/helpmeCookies/product/service/ProductImageServiceTest.java index c722d09..b0b3653 100644 --- a/src/test/java/com/helpmeCookies/product/service/ProductImageServiceTest.java +++ b/src/test/java/com/helpmeCookies/product/service/ProductImageServiceTest.java @@ -1,6 +1,7 @@ package com.helpmeCookies.product.service; import com.helpmeCookies.global.utils.AwsS3FileUtils; +import com.helpmeCookies.product.dto.FileUploadResponse; import com.helpmeCookies.product.entity.Category; import com.helpmeCookies.product.entity.Product; import com.helpmeCookies.product.repository.ProductImageRepository; @@ -16,6 +17,7 @@ import org.springframework.web.multipart.MultipartFile; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -56,10 +58,12 @@ void uploadMultiFiles() throws IOException { MockMultipartFile file2 = new MockMultipartFile("test2","img2.jpg","image/jpeg","image content".getBytes()); List files = Arrays.asList(file1,file2); - List expected = Arrays.asList("url1","url2"); + List expected = new ArrayList<>(); + expected.add(new FileUploadResponse("url1","1111")); + expected.add(new FileUploadResponse("url2","2222")); when(awsS3FileUtils.uploadMultiImages(files)).thenReturn(expected); - List actual = productImageService.uploadMultiFiles(1L,files); + List actual = productImageService.uploadMultiFiles(1L,files); assertEquals(2,actual.size(), "배열의 크기는 2여야함"); } }