From 634b3614ae0ee1395e1c6066b37d7537d1c9e69d Mon Sep 17 00:00:00 2001 From: minjiwon Date: Fri, 10 Jan 2025 00:36:47 +0900 Subject: [PATCH 01/11] =?UTF-8?q?feat:=20ProductResponse=EC=97=90=20?= =?UTF-8?q?=EC=B6=94=EC=B2=9C=EC=83=81=ED=92=88=EC=9D=B8=EC=A7=80=20?= =?UTF-8?q?=ED=8C=90=EB=8B=A8=ED=95=98=EB=8A=94=20=ED=95=84=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20-=20ProductResponse/ImageResponse=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=20=EC=9D=B4=EB=8F=99=20-=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EC=9D=B4=EB=8F=99=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?ProductResponse/ImageResponse=20=EA=B2=BD=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../response/product/ProductFindResponse.java | 50 ------------------- .../service/auction/AuctionService.java | 21 ++++---- .../HighestBidSseNotificationService.java | 29 +++++------ .../moduleapi/service/file/FileService.java | 9 ++-- .../service/file/LocalFileService.java | 47 ++++++++--------- .../moduledomain}/response/ImageResponse.java | 3 +- .../response/ProductFindResponse.java | 20 ++++++-- .../controller/response/ImageResponse.java | 24 --------- 8 files changed, 74 insertions(+), 129 deletions(-) delete mode 100644 module-api/src/main/java/com/example/moduleapi/controller/response/product/ProductFindResponse.java rename {module-api/src/main/java/com/example/moduleapi/controller => module-domain/src/main/java/com/example/moduledomain}/response/ImageResponse.java (92%) rename {module-recommendation/src/main/java/com/example/modulerecommendation/controller => module-domain/src/main/java/com/example/moduledomain}/response/ProductFindResponse.java (61%) delete mode 100644 module-recommendation/src/main/java/com/example/modulerecommendation/controller/response/ImageResponse.java diff --git a/module-api/src/main/java/com/example/moduleapi/controller/response/product/ProductFindResponse.java b/module-api/src/main/java/com/example/moduleapi/controller/response/product/ProductFindResponse.java deleted file mode 100644 index d7428af..0000000 --- a/module-api/src/main/java/com/example/moduleapi/controller/response/product/ProductFindResponse.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.example.moduleapi.controller.response.product; - -import com.example.moduleapi.controller.response.ImageResponse; -import com.example.moduledomain.domain.product.Category; -import com.example.moduledomain.domain.product.Product; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import java.time.LocalDateTime; -import java.util.List; - -@Getter -@NoArgsConstructor -public class ProductFindResponse { - private String userId; - private List imageResponses; - private String productName; - private String description; - private Category category; - private LocalDateTime startDate; - private LocalDateTime closeDate; - private int startPrice; - - @Builder - public ProductFindResponse(String userId, List imageResponses, String productName, String description, Category category, LocalDateTime startDate, LocalDateTime closeDate, int startPrice) { - this.userId = userId; - this.imageResponses = imageResponses; - this.productName = productName; - this.description = description; - this.category = category; - this.startDate = startDate; - this.closeDate = closeDate; - this.startPrice = startPrice; - } - - public static ProductFindResponse from(Product product, List imageResponses) { - return ProductFindResponse.builder() - .userId(product.getUserId()) - .imageResponses(imageResponses) - .productName(product.getProductName()) - .description(product.getDescription()) - .category(product.getCategory()) - .startDate(product.getStartDate()) - .closeDate(product.getCloseDate()) - .startPrice(product.getStartPrice()) - .build(); - - } -} diff --git a/module-api/src/main/java/com/example/moduleapi/service/auction/AuctionService.java b/module-api/src/main/java/com/example/moduleapi/service/auction/AuctionService.java index 09463c3..48bfe57 100644 --- a/module-api/src/main/java/com/example/moduleapi/service/auction/AuctionService.java +++ b/module-api/src/main/java/com/example/moduleapi/service/auction/AuctionService.java @@ -14,7 +14,6 @@ import com.example.moduleapi.controller.request.auction.BidRequest; import com.example.moduleapi.controller.request.point.PointAmount; import com.example.moduleapi.controller.response.auction.BidResponse; -import com.example.moduleapi.controller.response.product.ProductFindResponse; import com.example.moduleapi.exception.auction.BiddingFailException; import com.example.moduleapi.exception.auction.RedisLockAcquisitionException; import com.example.moduleapi.exception.auction.RedisLockInterruptedException; @@ -24,6 +23,7 @@ import com.example.moduledomain.domain.user.CustomUserDetails; import com.example.moduledomain.domain.user.Gender; import com.example.moduledomain.domain.user.User; +import com.example.moduledomain.response.ProductFindResponse; @Service public class AuctionService { @@ -35,9 +35,12 @@ public class AuctionService { private final BidLoggingService bidLoggingService; private final PointService pointService; - public AuctionService(ProductFacade productFacade, HighestBidSseNotificationService bidSseNotificationService, - RedissonClient redissonClient, KafkaProducerService kafkaProducerService, BidLoggingService bidLoggingService, - PointService pointService) { + public AuctionService(ProductFacade productFacade, + HighestBidSseNotificationService bidSseNotificationService, + RedissonClient redissonClient, + KafkaProducerService kafkaProducerService, + BidLoggingService bidLoggingService, + PointService pointService) { this.productFacade = productFacade; this.bidSseNotificationService = bidSseNotificationService; this.redissonClient = redissonClient; @@ -60,7 +63,7 @@ public BidResponse biddingPrice(CustomUserDetails user, BidRequest bidRequest, L currentHighestPrice = processBid(user, bidRequest, productId); kafkaProducerService.publishAuctionPriceChangeNotification(productId, currentHighestPrice); bidSseNotificationService.sendToAllUsers(productId, "최고가가 " + currentHighestPrice + "원으로 올랐습니다.", - "최고가 수정 알림"); + "최고가 수정 알림"); } catch (InterruptedException e) { throw new RedisLockInterruptedException(productId, e); } finally { @@ -70,7 +73,7 @@ public BidResponse biddingPrice(CustomUserDetails user, BidRequest bidRequest, L } return BidResponse.from(productId, - calculateIncreaseRate(productId, currentHighestPrice, bidRequest.getBiddingPrice())); + calculateIncreaseRate(productId, currentHighestPrice, bidRequest.getBiddingPrice())); } @Transactional @@ -90,7 +93,7 @@ private Long processBid(CustomUserDetails customUserDetails, BidRequest bidReque boolean isAuctionSuccessful = isAuctionSuccessful(userIdAndCurrentPrice, bidRequest); BidLogging bidLogging = createBidLogging(user.getId(), productId, user.getGender(), - bidRequest.getBiddingPrice(), user.getAge(), isAuctionSuccessful); + bidRequest.getBiddingPrice(), user.getAge(), isAuctionSuccessful); bidLoggingService.logging(bidLogging); if (userIdAndCurrentPrice == null) { // 최초 입찰 @@ -117,7 +120,7 @@ private void isBiddingAvailable(CustomUserDetails user, BidRequest bidRequest, L } private Long updateRedisBidData(CustomUserDetails user, RMap> bidMap, BidRequest bidRequest, - Long productId) { + Long productId) { Pair newPair = Pair.of(user.getUser().getId(), Long.valueOf(bidRequest.getBiddingPrice())); bidMap.put(productId, newPair); // productId에 대한 최고가 정보 업데이트 return Long.valueOf(bidRequest.getBiddingPrice()); @@ -136,7 +139,7 @@ private double increaseRate(Long previousPrice, int nextPrice) { } private BidLogging createBidLogging(Long userId, Long productId, Gender gender, int age, int price, - boolean isAuctionSuccessful) { + boolean isAuctionSuccessful) { ProductFindResponse product = productFacade.findById(productId); return BidLogging.builder() .userId(userId) diff --git a/module-api/src/main/java/com/example/moduleapi/service/auction/HighestBidSseNotificationService.java b/module-api/src/main/java/com/example/moduleapi/service/auction/HighestBidSseNotificationService.java index 8e8b8b5..e86fcce 100644 --- a/module-api/src/main/java/com/example/moduleapi/service/auction/HighestBidSseNotificationService.java +++ b/module-api/src/main/java/com/example/moduleapi/service/auction/HighestBidSseNotificationService.java @@ -1,19 +1,20 @@ package com.example.moduleapi.service.auction; -import com.example.moduleapi.controller.response.product.ProductFindResponse; -import com.example.moduleapi.service.product.ProductFacade; -import com.example.moduledomain.domain.user.CustomUserDetails; -import com.example.moduledomain.domain.user.User; -import com.example.moduledomain.repository.auction.EmitterRepository; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; - import java.io.IOException; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.Map; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import com.example.moduleapi.service.product.ProductFacade; +import com.example.moduledomain.domain.user.CustomUserDetails; +import com.example.moduledomain.domain.user.User; +import com.example.moduledomain.repository.auction.EmitterRepository; +import com.example.moduledomain.response.ProductFindResponse; + @Service public class HighestBidSseNotificationService { private final EmitterRepository emitterRepository; @@ -34,7 +35,7 @@ public SseEmitter subscribe(CustomUserDetails user, Long productId) { // 503 에러 방지를 위한 데이터 전송 // Emitter를 생성하고 나서 만료 시간까지 아무런 데이터도 보내지 않으면 재연결 요청시 503 Service Unavailable 에러가 발생할 수 있음. send(emitter, user.getUser(), productId, "SSE Emitter Created. [userId=" + user.getUser().getUserId() + "]", - "SSE CONNECT."); + "SSE CONNECT."); return emitter; } @@ -59,10 +60,10 @@ public void sendToAllUsers(Long productId, Object data, String comment) { private void send(SseEmitter sseEmitter, User user, Long productId, Object data, String comment) { try { sseEmitter.send(SseEmitter.event() // SSE 이벤트를 생성하고 해당 Emitter로 전송합. - .id(productId.toString()) // 이벤트의 고유 ID (문자열 형태로 변환) - .name("THE HIGHEST PRICE UPDATE") // 이벤트의 이름을 "THE HIGHEST PRICE UPDATE"로 지정 - .data(data) // 전송할 데이터 - .comment(comment)); // 이벤트에 대한 코멘트 + .id(productId.toString()) // 이벤트의 고유 ID (문자열 형태로 변환) + .name("THE HIGHEST PRICE UPDATE") // 이벤트의 이름을 "THE HIGHEST PRICE UPDATE"로 지정 + .data(data) // 전송할 데이터 + .comment(comment)); // 이벤트에 대한 코멘트 } catch (IOException e) { emitterRepository.deleteEmitter(user, productId); sseEmitter.completeWithError(e); diff --git a/module-api/src/main/java/com/example/moduleapi/service/file/FileService.java b/module-api/src/main/java/com/example/moduleapi/service/file/FileService.java index 0506032..695ca56 100644 --- a/module-api/src/main/java/com/example/moduleapi/service/file/FileService.java +++ b/module-api/src/main/java/com/example/moduleapi/service/file/FileService.java @@ -1,12 +1,13 @@ package com.example.moduleapi.service.file; -import com.example.moduleapi.controller.response.ImageResponse; +import java.util.List; + +import org.springframework.web.multipart.MultipartFile; + import com.example.moduledomain.domain.product.Product; import com.example.moduledomain.domain.product.ProductImage; import com.example.moduledomain.domain.user.User; -import org.springframework.web.multipart.MultipartFile; - -import java.util.List; +import com.example.moduledomain.response.ImageResponse; public interface FileService { List uploadImages(User user, Product product, List images); diff --git a/module-api/src/main/java/com/example/moduleapi/service/file/LocalFileService.java b/module-api/src/main/java/com/example/moduleapi/service/file/LocalFileService.java index c9c78bb..d0c28b7 100644 --- a/module-api/src/main/java/com/example/moduleapi/service/file/LocalFileService.java +++ b/module-api/src/main/java/com/example/moduleapi/service/file/LocalFileService.java @@ -1,18 +1,5 @@ package com.example.moduleapi.service.file; -import com.example.moduleapi.controller.response.ImageResponse; -import com.example.moduleapi.exception.file.CreateDirectoryFailException; -import com.example.moduleapi.exception.file.DeleteImageFailException; -import com.example.moduleapi.exception.file.ImageFileUploadFailException; -import com.example.moduledomain.domain.product.Product; -import com.example.moduledomain.domain.product.ProductImage; -import com.example.moduledomain.domain.user.User; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; -import org.springframework.web.multipart.MultipartFile; - import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -23,6 +10,20 @@ import java.util.UUID; import java.util.stream.Collectors; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.springframework.web.multipart.MultipartFile; + +import com.example.moduleapi.exception.file.CreateDirectoryFailException; +import com.example.moduleapi.exception.file.DeleteImageFailException; +import com.example.moduleapi.exception.file.ImageFileUploadFailException; +import com.example.moduledomain.domain.product.Product; +import com.example.moduledomain.domain.product.ProductImage; +import com.example.moduledomain.domain.user.User; +import com.example.moduledomain.response.ImageResponse; + @Service @Profile("dev") public class LocalFileService implements FileService { @@ -51,8 +52,8 @@ public List uploadImages(User user, Product product, List loadImages(List productImages) { return productImages.stream() - .map(ImageResponse::from) - .collect(Collectors.toList()); + .map(ImageResponse::from) + .collect(Collectors.toList()); } @Override @@ -98,19 +99,19 @@ private String createNewImageFileName(String originalImageFile) { String uuid = UUID.randomUUID().toString(); String extension = StringUtils.getFilenameExtension(originalImageFile); StringBuilder newName = new StringBuilder() - .append(uuid) - .append(".") - .append(extension); + .append(uuid) + .append(".") + .append(extension); return String.valueOf(newName); } private String createSavedImageFullPath(String userId, String savedImageFileName) { StringBuilder fullDirectoryPath = new StringBuilder() - .append(baseUrl) - .append(File.separator) - .append(userId) - .append(File.separator) - .append(savedImageFileName); + .append(baseUrl) + .append(File.separator) + .append(userId) + .append(File.separator) + .append(savedImageFileName); return String.valueOf(fullDirectoryPath); } diff --git a/module-api/src/main/java/com/example/moduleapi/controller/response/ImageResponse.java b/module-domain/src/main/java/com/example/moduledomain/response/ImageResponse.java similarity index 92% rename from module-api/src/main/java/com/example/moduleapi/controller/response/ImageResponse.java rename to module-domain/src/main/java/com/example/moduledomain/response/ImageResponse.java index 5cbb392..5659092 100644 --- a/module-api/src/main/java/com/example/moduleapi/controller/response/ImageResponse.java +++ b/module-domain/src/main/java/com/example/moduledomain/response/ImageResponse.java @@ -1,6 +1,7 @@ -package com.example.moduleapi.controller.response; +package com.example.moduledomain.response; import com.example.moduledomain.domain.product.ProductImage; + import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/module-recommendation/src/main/java/com/example/modulerecommendation/controller/response/ProductFindResponse.java b/module-domain/src/main/java/com/example/moduledomain/response/ProductFindResponse.java similarity index 61% rename from module-recommendation/src/main/java/com/example/modulerecommendation/controller/response/ProductFindResponse.java rename to module-domain/src/main/java/com/example/moduledomain/response/ProductFindResponse.java index 3863aab..80af6de 100644 --- a/module-recommendation/src/main/java/com/example/modulerecommendation/controller/response/ProductFindResponse.java +++ b/module-domain/src/main/java/com/example/moduledomain/response/ProductFindResponse.java @@ -1,4 +1,4 @@ -package com.example.modulerecommendation.controller.response; +package com.example.moduledomain.response; import java.time.LocalDateTime; import java.util.List; @@ -21,10 +21,18 @@ public class ProductFindResponse { private LocalDateTime startDate; private LocalDateTime closeDate; private int startPrice; + private boolean recommended; @Builder - public ProductFindResponse(String userId, List imageResponses, String productName, - String description, Category category, LocalDateTime startDate, LocalDateTime closeDate, int startPrice) { + public ProductFindResponse(String userId, + List imageResponses, + String productName, + String description, + Category category, + LocalDateTime startDate, + LocalDateTime closeDate, + int startPrice, + boolean recommended) { this.userId = userId; this.imageResponses = imageResponses; this.productName = productName; @@ -33,9 +41,12 @@ public ProductFindResponse(String userId, List imageResponses, St this.startDate = startDate; this.closeDate = closeDate; this.startPrice = startPrice; + this.recommended = recommended; } - public static ProductFindResponse from(Product product, List imageResponses) { + public static ProductFindResponse from(Product product, + List imageResponses, + boolean recommended) { return ProductFindResponse.builder() .userId(product.getUserId()) .imageResponses(imageResponses) @@ -45,6 +56,7 @@ public static ProductFindResponse from(Product product, List imag .startDate(product.getStartDate()) .closeDate(product.getCloseDate()) .startPrice(product.getStartPrice()) + .recommended(recommended) .build(); } diff --git a/module-recommendation/src/main/java/com/example/modulerecommendation/controller/response/ImageResponse.java b/module-recommendation/src/main/java/com/example/modulerecommendation/controller/response/ImageResponse.java deleted file mode 100644 index 2de18ba..0000000 --- a/module-recommendation/src/main/java/com/example/modulerecommendation/controller/response/ImageResponse.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.example.modulerecommendation.controller.response; - -import com.example.moduledomain.domain.product.ProductImage; - -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@NoArgsConstructor -public class ImageResponse { - private String originalName; - private String imagePath; - - @Builder - public ImageResponse(String originalName, String imagePath) { - this.originalName = originalName; - this.imagePath = imagePath; - } - - public static ImageResponse from(ProductImage productImage) { - return new ImageResponse(productImage.getOriginalName(), productImage.getImageFullPath()); - } -} From 8355dbd3a5cda228dc69aa5a8c663ab0e4e57f58 Mon Sep 17 00:00:00 2001 From: minjiwon Date: Fri, 10 Jan 2025 01:30:33 +0900 Subject: [PATCH 02/11] =?UTF-8?q?feat:=20ProductFilter/ProductFilterReques?= =?UTF-8?q?t=20=EC=9A=94=EC=B2=AD=20dto=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../moduledomain/request/ProductFilter.java | 26 +++++++++++++++++++ .../request/ProductFilterRequest.java | 24 +++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 module-domain/src/main/java/com/example/moduledomain/request/ProductFilter.java create mode 100644 module-domain/src/main/java/com/example/moduledomain/request/ProductFilterRequest.java diff --git a/module-domain/src/main/java/com/example/moduledomain/request/ProductFilter.java b/module-domain/src/main/java/com/example/moduledomain/request/ProductFilter.java new file mode 100644 index 0000000..c20aaaa --- /dev/null +++ b/module-domain/src/main/java/com/example/moduledomain/request/ProductFilter.java @@ -0,0 +1,26 @@ +package com.example.moduledomain.request; + +import java.util.ArrayList; +import java.util.List; + +import com.example.moduledomain.domain.product.Category; +import com.example.moduledomain.domain.product.ProductCondition; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class ProductFilter { + private String keyword; + private List productCondition = new ArrayList<>(); + private List category = new ArrayList<>(); + + @Builder + public ProductFilter(String keyword, List productCondition, List category) { + this.keyword = keyword; + this.productCondition = productCondition; + this.category = category; + } +} diff --git a/module-domain/src/main/java/com/example/moduledomain/request/ProductFilterRequest.java b/module-domain/src/main/java/com/example/moduledomain/request/ProductFilterRequest.java new file mode 100644 index 0000000..5b2a3e4 --- /dev/null +++ b/module-domain/src/main/java/com/example/moduledomain/request/ProductFilterRequest.java @@ -0,0 +1,24 @@ +package com.example.moduledomain.request; + +import com.example.moduledomain.domain.product.OrderBy; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class ProductFilterRequest { + private OrderBy orderBy = OrderBy.START_DATE; + private ProductFilter productFilter; + private int pageNo = 0; + private int pageSize = 9; + + @Builder + public ProductFilterRequest(OrderBy orderBy, ProductFilter productFilter, int pageNo, int pageSize) { + this.orderBy = orderBy; + this.productFilter = productFilter; + this.pageNo = pageNo; + this.pageSize = pageSize; + } +} From 257b0a2c7b194c4651e5c830a9607043e74bf74f Mon Sep 17 00:00:00 2001 From: minjiwon Date: Fri, 10 Jan 2025 01:46:32 +0900 Subject: [PATCH 03/11] =?UTF-8?q?refactor:=20=EC=83=81=ED=92=88=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20API=20GET=20->=20POST=20=EB=B0=A9=EC=8B=9D?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20=ED=8C=8C=EB=9D=BC=EB=AF=B8?= =?UTF-8?q?=ED=84=B0=20=EC=B2=98=EB=A6=AC=20=EB=B0=A9=EC=8B=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20GET=20=EB=B0=A9=EC=8B=9D=EC=97=90=EC=84=9C=20PO?= =?UTF-8?q?ST=20=EB=B0=A9=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=20-=20requestParam=EC=9D=84=20requestBody=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=ED=95=98=EC=97=AC=20JSON=20=ED=98=95=ED=83=9C?= =?UTF-8?q?=EB=A1=9C=20=EB=B0=9B=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/ProductApiController.java | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/module-api/src/main/java/com/example/moduleapi/controller/product/ProductApiController.java b/module-api/src/main/java/com/example/moduleapi/controller/product/ProductApiController.java index a91aea1..4c72e23 100644 --- a/module-api/src/main/java/com/example/moduleapi/controller/product/ProductApiController.java +++ b/module-api/src/main/java/com/example/moduleapi/controller/product/ProductApiController.java @@ -8,8 +8,8 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; 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; @@ -17,13 +17,12 @@ import com.example.moduleapi.controller.request.product.ProductSaveRequest; import com.example.moduleapi.controller.request.product.ProductUpdateRequest; import com.example.moduleapi.controller.response.PagingResponse; -import com.example.moduleapi.controller.response.product.ProductFindResponse; import com.example.moduleapi.controller.response.product.ProductLikeResponse; import com.example.moduleapi.controller.response.product.ProductResponse; import com.example.moduleapi.service.product.ProductFacade; -import com.example.moduledomain.domain.product.OrderBy; -import com.example.moduledomain.domain.product.ProductCondition; import com.example.moduledomain.domain.user.CustomUserDetails; +import com.example.moduledomain.request.ProductFilterRequest; +import com.example.moduledomain.response.ProductFindResponse; @RestController @RequestMapping("/api/v1/products") @@ -48,16 +47,11 @@ public ProductFindResponse findById(@PathVariable Long id) { return productFacade.findById(id); } - @GetMapping + @PostMapping("/_search") public PagingResponse findProductsByCriteriaWithRecommendations( @AuthenticationPrincipal CustomUserDetails customUserDetails, - @RequestParam(value = "keyword", required = false) String keyword, - @RequestParam(value = "productCondition", required = false) ProductCondition productCondition, - @RequestParam(value = "pageNo", defaultValue = "0", required = false) int pageNo, - @RequestParam(value = "pageSize", defaultValue = DEFAULT_SIZE, required = false) int pageSize, - @RequestParam(value = "orderBy", required = false) OrderBy order) { - return productFacade.findProductsByCriteriaWithRecommendations(customUserDetails, keyword, productCondition, - pageNo, pageSize, order); + @RequestBody ProductFilterRequest productFilterRequest) { + return productFacade.findProductsByCriteriaWithRecommendations(customUserDetails, productFilterRequest); } @PutMapping("/{id}") @@ -83,7 +77,7 @@ public ProductLikeResponse productLike(@AuthenticationPrincipal CustomUserDetail @DeleteMapping("/{id}/likes") public ProductLikeResponse productLikeDelete(@AuthenticationPrincipal CustomUserDetails user, - @PathVariable Long id) { + @PathVariable Long id) { return productFacade.productLikeDelete(user.getUser(), id); } From f67ce3c08c7dd9ca8ba8177fe5a5dbbea4fe18d7 Mon Sep 17 00:00:00 2001 From: minjiwon Date: Fri, 10 Jan 2025 02:25:21 +0900 Subject: [PATCH 04/11] =?UTF-8?q?refactor:=EC=83=81=ED=92=88=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=EC=8B=9C=20ProductFacade?= =?UTF-8?q?=EB=A1=9C=20User=EA=B0=92=EC=9D=84=20=EA=BA=BC=EB=82=B4?= =?UTF-8?q?=EC=84=9C=20=EB=B0=98=ED=99=98=ED=95=98=EB=8F=84=EB=A1=9D=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 --- .../product/ProductApiController.java | 4 +- .../service/product/ProductFacade.java | 77 ++++++++----------- .../service/product/ProductService.java | 36 ++++----- 3 files changed, 52 insertions(+), 65 deletions(-) diff --git a/module-api/src/main/java/com/example/moduleapi/controller/product/ProductApiController.java b/module-api/src/main/java/com/example/moduleapi/controller/product/ProductApiController.java index 4c72e23..1bbd256 100644 --- a/module-api/src/main/java/com/example/moduleapi/controller/product/ProductApiController.java +++ b/module-api/src/main/java/com/example/moduleapi/controller/product/ProductApiController.java @@ -49,9 +49,9 @@ public ProductFindResponse findById(@PathVariable Long id) { @PostMapping("/_search") public PagingResponse findProductsByCriteriaWithRecommendations( - @AuthenticationPrincipal CustomUserDetails customUserDetails, + @AuthenticationPrincipal CustomUserDetails user, @RequestBody ProductFilterRequest productFilterRequest) { - return productFacade.findProductsByCriteriaWithRecommendations(customUserDetails, productFilterRequest); + return productFacade.findProductsByCriteriaWithRecommendations(user.getUser(), productFilterRequest); } @PutMapping("/{id}") diff --git a/module-api/src/main/java/com/example/moduleapi/service/product/ProductFacade.java b/module-api/src/main/java/com/example/moduleapi/service/product/ProductFacade.java index 3859fa2..1d78731 100644 --- a/module-api/src/main/java/com/example/moduleapi/service/product/ProductFacade.java +++ b/module-api/src/main/java/com/example/moduleapi/service/product/ProductFacade.java @@ -12,21 +12,20 @@ import com.example.moduleapi.controller.request.product.ProductSaveRequest; import com.example.moduleapi.controller.request.product.ProductUpdateRequest; -import com.example.moduleapi.controller.response.ImageResponse; import com.example.moduleapi.controller.response.PagingResponse; -import com.example.moduleapi.controller.response.product.ProductFindResponse; import com.example.moduleapi.controller.response.product.ProductLikeResponse; import com.example.moduleapi.controller.response.product.ProductResponse; import com.example.moduleapi.exception.product.UnauthorizedEnrollException; import com.example.moduleapi.service.file.FileService; import com.example.moduleapi.service.httpClient.RestHttpClient; -import com.example.moduledomain.domain.product.Category; -import com.example.moduledomain.domain.product.OrderBy; import com.example.moduledomain.domain.product.Product; -import com.example.moduledomain.domain.product.ProductCondition; import com.example.moduledomain.domain.product.ProductImage; import com.example.moduledomain.domain.user.CustomUserDetails; import com.example.moduledomain.domain.user.User; +import com.example.moduledomain.request.ProductFilter; +import com.example.moduledomain.request.ProductFilterRequest; +import com.example.moduledomain.response.ImageResponse; +import com.example.moduledomain.response.ProductFindResponse; import com.google.common.base.Preconditions; @Service @@ -63,44 +62,36 @@ public ProductFindResponse findById(Long productId) { Product product = productService.findById(productId); List productImages = productImageService.getImage(productId); List imageResponses = fileService.loadImages(productImages); - return ProductFindResponse.from(product, imageResponses); + return ProductFindResponse.from(product, imageResponses, false); } @Transactional(readOnly = true) public PagingResponse findProductsByCriteriaWithRecommendations(CustomUserDetails customUserDetails, - String keyword, - ProductCondition productCondition, - int pageNo, - int pageSize, - OrderBy order) { - List products = productService.findProductWithCriteria(keyword, productCondition, pageNo, pageSize, - order); - List productFindResponses = products.stream() + ProductFilterRequest productFilterRequest) { + // 1. 일반 상품 조회 + List products = productService.findProductWithCriteria(productFilterRequest); + List defaultProductFindResponses = products + .stream() .map(this::convertToProductFindResponse) .collect(Collectors.toList()); - /* - * 이 부분이 정렬/필터링하면서 함께 수정해야하는 부분 - * - if (keyword == null && productCondition == null && order == null) { - List recommendationProducts = findRecommendationProducts(customUserDetails.getUser(), - null, null, null); - List result = combineAndGetProducts(productFindResponses, - recommendationProducts); - return PagingResponse.from(result, pageNo); - } - */ - return PagingResponse.from(productFindResponses, pageNo); + // 2. 추천 상품 조회 + ProductFilter productFilter = productFilterRequest.getProductFilter(); + List recommendationProducts = findRecommendationProducts(customUserDetails.getUser(), productFilter); + + // 3. 일반 상품 + 추천 상품 combine + List resultProductResponse = combineAndGetProducts(defaultProductFindResponses, recommendationProducts); + + return PagingResponse.from(resultProductResponse, productFilterRequest.getPageNo()); } - private List findRecommendationProducts(User user, String keyword, - List categories, - List productConditions) { - return restHttpClient.findRecommendationProducts(user, keyword, categories, productConditions); + private List findRecommendationProducts(User user, ProductFilter productFilter) { + return restHttpClient.findRecommendationProducts(user, productFilter); } private List combineAndGetProducts(List original, List recommendations) { + // 여기 추천 상품 개수 조절 해야할듯? original.addAll(recommendations); Collections.shuffle(original); @@ -112,7 +103,7 @@ private List combineAndGetProducts(List productImages = productImageService.getImage(product.getId()); List imageResponses = fileService.loadImages(productImages); - return ProductFindResponse.from(product, imageResponses); + return ProductFindResponse.from(product, imageResponses, false); } @Transactional @@ -158,32 +149,32 @@ private void validateUser(User currentUser, ProductSaveRequest productSaveReques private void validateSaveRequest(ProductSaveRequest productSaveRequest, List images) { Preconditions.checkArgument(!productSaveRequest.getUserId().isBlank(), - "등록자 아이디는 필수 값입니다."); + "등록자 아이디는 필수 값입니다."); Preconditions.checkArgument(!productSaveRequest.getProductName().isBlank(), - "상품명은 필수 값입니다."); + "상품명은 필수 값입니다."); Preconditions.checkArgument(!productSaveRequest.getDescription().isBlank(), - "상품 설명은 필수 값입니다."); + "상품 설명은 필수 값입니다."); Preconditions.checkArgument(!ObjectUtils.isEmpty(productSaveRequest.getCategory()), "카테고리를 설정해주세요."); Preconditions.checkArgument(!productSaveRequest.getStartDate().isBefore(LocalDateTime.now()), - "경매 시작일이 현재 시각보다 이전일 수 없습니다."); + "경매 시작일이 현재 시각보다 이전일 수 없습니다."); Preconditions.checkArgument(!productSaveRequest.getCloseDate().isBefore(productSaveRequest.getStartDate()), - "경매 종료일이 경매 시작일보다 이전일 수 없습니다."); + "경매 종료일이 경매 시작일보다 이전일 수 없습니다."); Preconditions.checkArgument(productSaveRequest.getStartPrice() >= 1000, - "시작 가격은 최소 1000원입니다."); + "시작 가격은 최소 1000원입니다."); Preconditions.checkArgument(images != null && !images.isEmpty(), - "경매 상품에 대한 이미지는 필수 값입니다."); + "경매 상품에 대한 이미지는 필수 값입니다."); } private void validUpdateRequest(ProductUpdateRequest productUpdateRequest) { Preconditions.checkArgument(!productUpdateRequest.getProductName().isEmpty(), - "상품명은 필수 값입니다."); + "상품명은 필수 값입니다."); Preconditions.checkArgument(!productUpdateRequest.getDescription().isEmpty(), - "상품 설명은 필수 값입니다."); + "상품 설명은 필수 값입니다."); Preconditions.checkArgument(!productUpdateRequest.getStartDate().isBefore(LocalDateTime.now()), - "경매 시작일이 현재 시각보다 이전일 수 없습니다."); + "경매 시작일이 현재 시각보다 이전일 수 없습니다."); Preconditions.checkArgument(!productUpdateRequest.getCloseDate().isBefore(productUpdateRequest.getStartDate()), - "경매 종료일이 경매 시작일보다 이전일 수 없습니다."); + "경매 종료일이 경매 시작일보다 이전일 수 없습니다."); Preconditions.checkArgument(productUpdateRequest.getStartPrice() >= 1000, - "시작 가격은 최소 1000원입니다."); + "시작 가격은 최소 1000원입니다."); } } diff --git a/module-api/src/main/java/com/example/moduleapi/service/product/ProductService.java b/module-api/src/main/java/com/example/moduleapi/service/product/ProductService.java index 4ce2fda..8ff5128 100644 --- a/module-api/src/main/java/com/example/moduleapi/service/product/ProductService.java +++ b/module-api/src/main/java/com/example/moduleapi/service/product/ProductService.java @@ -1,19 +1,20 @@ package com.example.moduleapi.service.product; +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + import com.example.moduleapi.controller.request.product.ProductSaveRequest; import com.example.moduleapi.controller.request.product.ProductUpdateRequest; import com.example.moduleapi.exception.product.NotFoundProductException; import com.example.moduleapi.exception.product.ProductNotPendingException; import com.example.moduleapi.exception.product.UnauthorizedProductAccessException; -import com.example.moduledomain.domain.product.OrderBy; import com.example.moduledomain.domain.product.Product; import com.example.moduledomain.domain.product.ProductCondition; import com.example.moduledomain.domain.user.User; import com.example.moduledomain.repository.product.ProductRepository; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; +import com.example.moduledomain.request.ProductFilterRequest; @Service public class ProductService { @@ -34,7 +35,7 @@ public Product enroll(ProductSaveRequest productSaveRequest) { @Transactional(readOnly = true) public Product findById(Long id) { Product product = productRepository.findById(id) - .orElseThrow(() -> new NotFoundProductException(id)); + .orElseThrow(() -> new NotFoundProductException(id)); return product; } @@ -44,28 +45,23 @@ public List findByIdIn(List productIds) { } @Transactional(readOnly = true) - public List findProductWithCriteria(String keyword, ProductCondition productCondition, int pageNo, - int pageSize, - OrderBy order) { - if (order == null) - order = OrderBy.LATEST; - List products = productRepository.findProductsWithCriteria(keyword, productCondition, pageNo, pageSize, - order); + public List findProductWithCriteria(ProductFilterRequest productFilterRequest) { + List products = productRepository.findProductsWithCriteria(productFilterRequest); return products; } @Transactional public Product update(User user, Long productId, ProductUpdateRequest productUpdateRequest) { Product product = productRepository.findById(productId) - .orElseThrow(() -> new NotFoundProductException(productId)); + .orElseThrow(() -> new NotFoundProductException(productId)); checkProductAccessPermission(product, user.getUserId()); product.updateProductInfo( - productUpdateRequest.getProductName(), - productUpdateRequest.getDescription(), - productUpdateRequest.getStartDate(), - productUpdateRequest.getCloseDate(), - productUpdateRequest.getStartPrice() + productUpdateRequest.getProductName(), + productUpdateRequest.getDescription(), + productUpdateRequest.getStartDate(), + productUpdateRequest.getCloseDate(), + productUpdateRequest.getStartPrice() ); return product; @@ -74,7 +70,7 @@ public Product update(User user, Long productId, ProductUpdateRequest productUpd @Transactional public Long delete(User user, Long productId) { Product product = productRepository.findById(productId) - .orElseThrow(() -> new NotFoundProductException(productId)); + .orElseThrow(() -> new NotFoundProductException(productId)); checkProductAccessPermission(product, user.getUserId()); productRepository.deleteById(product.getId()); From 346edeeec9629bcc958a897d471fe81e86f01fe6 Mon Sep 17 00:00:00 2001 From: minjiwon Date: Fri, 10 Jan 2025 02:26:35 +0900 Subject: [PATCH 05/11] =?UTF-8?q?refactor:=20=EC=83=81=ED=92=88=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20API=EB=A1=9C=EB=B6=80?= =?UTF-8?q?=ED=84=B0=20=EB=B0=9B=EB=8A=94=20User=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/moduleapi/service/product/ProductFacade.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/module-api/src/main/java/com/example/moduleapi/service/product/ProductFacade.java b/module-api/src/main/java/com/example/moduleapi/service/product/ProductFacade.java index 1d78731..63fd2f1 100644 --- a/module-api/src/main/java/com/example/moduleapi/service/product/ProductFacade.java +++ b/module-api/src/main/java/com/example/moduleapi/service/product/ProductFacade.java @@ -20,7 +20,6 @@ import com.example.moduleapi.service.httpClient.RestHttpClient; import com.example.moduledomain.domain.product.Product; import com.example.moduledomain.domain.product.ProductImage; -import com.example.moduledomain.domain.user.CustomUserDetails; import com.example.moduledomain.domain.user.User; import com.example.moduledomain.request.ProductFilter; import com.example.moduledomain.request.ProductFilterRequest; @@ -66,7 +65,7 @@ public ProductFindResponse findById(Long productId) { } @Transactional(readOnly = true) - public PagingResponse findProductsByCriteriaWithRecommendations(CustomUserDetails customUserDetails, + public PagingResponse findProductsByCriteriaWithRecommendations(User user, ProductFilterRequest productFilterRequest) { // 1. 일반 상품 조회 List products = productService.findProductWithCriteria(productFilterRequest); @@ -77,7 +76,7 @@ public PagingResponse findProductsByCriteriaWithRecommendat // 2. 추천 상품 조회 ProductFilter productFilter = productFilterRequest.getProductFilter(); - List recommendationProducts = findRecommendationProducts(customUserDetails.getUser(), productFilter); + List recommendationProducts = findRecommendationProducts(user, productFilter); // 3. 일반 상품 + 추천 상품 combine List resultProductResponse = combineAndGetProducts(defaultProductFindResponses, recommendationProducts); From 86d3451beb604d9c359dde085c4b4650489cb136 Mon Sep 17 00:00:00 2001 From: minjiwon Date: Fri, 10 Jan 2025 02:31:03 +0900 Subject: [PATCH 06/11] =?UTF-8?q?refactor:=20ProductFacade,=20ProductServi?= =?UTF-8?q?ce=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=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 --- .../fixture/product/ProductFixtures.groovy | 6 ++- .../service/product/ProductFacadeTest.groovy | 40 ++++++++++++------- .../service/product/ProductServiceTest.groovy | 25 ++++++++---- 3 files changed, 46 insertions(+), 25 deletions(-) diff --git a/module-api/src/test/groovy/com/example/moduleapi/fixture/product/ProductFixtures.groovy b/module-api/src/test/groovy/com/example/moduleapi/fixture/product/ProductFixtures.groovy index b09550e..61b0301 100644 --- a/module-api/src/test/groovy/com/example/moduleapi/fixture/product/ProductFixtures.groovy +++ b/module-api/src/test/groovy/com/example/moduleapi/fixture/product/ProductFixtures.groovy @@ -1,11 +1,12 @@ package com.example.moduleapi.fixture.product -import com.example.moduleapi.controller.response.ImageResponse -import com.example.moduleapi.controller.response.product.ProductFindResponse + import com.example.moduledomain.domain.product.Category import com.example.moduledomain.domain.product.Product import com.example.moduledomain.domain.product.ProductCondition import com.example.moduledomain.domain.product.ProductImage +import com.example.moduledomain.response.ImageResponse +import com.example.moduledomain.response.ProductFindResponse import org.springframework.mock.web.MockMultipartFile import java.time.LocalDateTime @@ -44,6 +45,7 @@ class ProductFixtures { .startDate(map.getOrDefault("startDate", 시작일) as LocalDateTime) .closeDate(map.getOrDefault("closeDate", 종료일) as LocalDateTime) .startPrice(map.getOrDefault("startPrice", 1000) as int) + .recommended(map.getOrDefault("recommended", false) as boolean) .imageResponses([ProductFixtures.createImageResponse()]) .build() } diff --git a/module-api/src/test/groovy/com/example/moduleapi/service/product/ProductFacadeTest.groovy b/module-api/src/test/groovy/com/example/moduleapi/service/product/ProductFacadeTest.groovy index 5ac5cc6..1f522b8 100644 --- a/module-api/src/test/groovy/com/example/moduleapi/service/product/ProductFacadeTest.groovy +++ b/module-api/src/test/groovy/com/example/moduleapi/service/product/ProductFacadeTest.groovy @@ -2,16 +2,18 @@ package com.example.moduleapi.service.product import com.example.moduleapi.controller.request.product.ProductSaveRequest import com.example.moduleapi.controller.request.product.ProductUpdateRequest -import com.example.moduleapi.controller.response.ImageResponse import com.example.moduleapi.exception.product.NotFoundProductException import com.example.moduleapi.fixture.product.ProductFixtures import com.example.moduleapi.fixture.user.UserFixtures import com.example.moduleapi.service.file.FileService +import com.example.moduleapi.service.httpClient.RestHttpClient import com.example.moduledomain.domain.product.Category -import com.example.moduledomain.domain.product.OrderBy import com.example.moduledomain.domain.product.Product import com.example.moduledomain.domain.product.ProductImage import com.example.moduledomain.domain.user.User +import com.example.moduledomain.request.ProductFilter +import com.example.moduledomain.request.ProductFilterRequest +import com.example.moduledomain.response.ImageResponse import org.springframework.mock.web.MockMultipartFile import org.springframework.web.multipart.MultipartFile import spock.lang.Specification @@ -21,7 +23,8 @@ class ProductFacadeTest extends Specification { ProductImageService productImageService = Mock() ProductService productService = Mock() ProductLikeService productLikeService = Mock() - def productFacade = new ProductFacade(fileService, productImageService, productService, productLikeService) + RestHttpClient restHttpClient = Mock() + def productFacade = new ProductFacade(fileService, productImageService, productService, productLikeService, restHttpClient) def "상품 등록 성공"() { given: @@ -129,10 +132,21 @@ class ProductFacadeTest extends Specification { def "상품 목록 조회"() { given: - String keyword = "test" - int pageNo = 1 - int pageSize = 10 - OrderBy orderBy = OrderBy.LATEST + ProductFilter productFilter = new ProductFilter( + keyword: null, + productCondition: [], + category: [] + ) + + ProductFilterRequest productFilterRequest = new ProductFilterRequest( + orderBy: null, + productFilter: productFilter, + pageNo: 0, + pageSize: 9 + ) + + User user = UserFixtures.createUser() + List products = [ ProductFixtures.createProduct(["productName": "productName1"]), ProductFixtures.createProduct(["productName": "productName2"]), @@ -140,17 +154,13 @@ class ProductFacadeTest extends Specification { ] when: - def response = productFacade.findAll(keyword, ProductFixtures.READY, pageNo, pageSize, orderBy) + def response = productFacade.findProductsByCriteriaWithRecommendations(user, productFilterRequest) then: - 1 * productService.findProductWithCriteria(keyword, ProductFixtures.READY, pageNo, pageSize, orderBy) >> products - response.pageNo == pageNo + 1 * restHttpClient.findRecommendationProducts(_, _) >> [] + 1 * productService.findProductWithCriteria(productFilterRequest) >> products + response.pageNo == productFilterRequest.pageNo response.items.size() == 3 - - response.items[0].productName == "productName1" - response.items[1].productName == "productName2" - response.items[2].productName == "productName3" - } def "상품 수정 성공"() { diff --git a/module-api/src/test/groovy/com/example/moduleapi/service/product/ProductServiceTest.groovy b/module-api/src/test/groovy/com/example/moduleapi/service/product/ProductServiceTest.groovy index 2981972..c99d4ad 100644 --- a/module-api/src/test/groovy/com/example/moduleapi/service/product/ProductServiceTest.groovy +++ b/module-api/src/test/groovy/com/example/moduleapi/service/product/ProductServiceTest.groovy @@ -4,13 +4,14 @@ import com.example.moduleapi.controller.request.product.ProductSaveRequest import com.example.moduleapi.controller.request.product.ProductUpdateRequest import com.example.moduleapi.exception.product.ProductNotPendingException import com.example.moduleapi.fixture.product.ProductFixtures -import com.example.moduledomain.domain.product.Category import com.example.moduleapi.fixture.user.UserFixtures -import com.example.moduledomain.domain.product.OrderBy +import com.example.moduledomain.domain.product.Category import com.example.moduledomain.domain.product.Product import com.example.moduledomain.domain.product.ProductCondition import com.example.moduledomain.domain.user.User import com.example.moduledomain.repository.product.ProductRepository +import com.example.moduledomain.request.ProductFilter +import com.example.moduledomain.request.ProductFilterRequest import spock.lang.Specification class ProductServiceTest extends Specification { @@ -66,10 +67,18 @@ class ProductServiceTest extends Specification { def "상품 목록 조회"() { given: - String keyword = "" - int pageNo = 1 - int pageSize = 3 - OrderBy orderBy = OrderBy.LATEST + ProductFilter productFilter = new ProductFilter( + keyword: null, + productCondition: [], + category: [] + ) + + ProductFilterRequest productFilterRequest = new ProductFilterRequest( + orderBy: null, + productFilter: productFilter, + pageNo: 0, + pageSize: 9 + ) List mockProducts = [ ProductFixtures.createProduct([productCondition: ProductCondition.ACTIVE]), @@ -77,10 +86,10 @@ class ProductServiceTest extends Specification { ProductFixtures.createProduct([productCondition: ProductCondition.ACTIVE]) ] - productRepository.findProductsWithCriteria(keyword, ProductFixtures.ACTIVE, pageNo, pageSize, orderBy) >> mockProducts + productRepository.findProductsWithCriteria(productFilterRequest) >> mockProducts when: - List result = productService.findProductWithCriteria(keyword, ProductFixtures.ACTIVE, pageNo, pageSize, orderBy) + List result = productService.findProductWithCriteria(productFilterRequest) then: result.size() == 3 From 1185f6c37e65eded6546c86ec56e8dc25b6d7832 Mon Sep 17 00:00:00 2001 From: minjiwon Date: Fri, 10 Jan 2025 02:41:28 +0900 Subject: [PATCH 07/11] =?UTF-8?q?refactor:=20=EC=B6=94=EC=B2=9C=20?= =?UTF-8?q?=EC=84=9C=EB=B2=84=20API=EB=8F=84=20GET->POST=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20=ED=95=84=ED=84=B0=EB=A7=81=20?= =?UTF-8?q?=EC=A1=B0=EA=B1=B4=EB=93=A4=EC=9D=84=20requestbody=EB=A1=9C=20?= =?UTF-8?q?=EB=B0=9B=EC=9D=84=20=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?POST=20=EB=B0=A9=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20=EC=B6=94=EC=B2=9C=20=EC=84=9C=EB=B2=84?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EB=B0=98=ED=99=98=ED=95=B4=EC=A3=BC?= =?UTF-8?q?=EB=8A=94=20=EC=83=81=ED=92=88=EB=93=A4=EC=9D=80=20ProductFindR?= =?UTF-8?q?esponse=EC=9D=98=20recommended=20=ED=95=84=EB=93=9C=EB=A5=BC=20?= =?UTF-8?q?true=EB=A1=9C=20=ED=95=A0=EB=8B=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProductRecommendationController.java | 17 ++++++------- .../service/ProductListingService.java | 6 ++--- .../service/ProductRecommendationService.java | 24 +++++++++---------- 3 files changed, 22 insertions(+), 25 deletions(-) diff --git a/module-recommendation/src/main/java/com/example/modulerecommendation/controller/ProductRecommendationController.java b/module-recommendation/src/main/java/com/example/modulerecommendation/controller/ProductRecommendationController.java index ad96165..5c78bb2 100644 --- a/module-recommendation/src/main/java/com/example/modulerecommendation/controller/ProductRecommendationController.java +++ b/module-recommendation/src/main/java/com/example/modulerecommendation/controller/ProductRecommendationController.java @@ -2,15 +2,15 @@ import java.util.List; -import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import com.example.moduledomain.domain.product.Category; -import com.example.moduledomain.domain.product.ProductCondition; import com.example.moduledomain.domain.user.Gender; -import com.example.modulerecommendation.controller.response.ProductFindResponse; +import com.example.moduledomain.request.ProductFilter; +import com.example.moduledomain.response.ProductFindResponse; import com.example.modulerecommendation.service.ProductRecommendationService; @RestController @@ -22,14 +22,11 @@ public ProductRecommendationController(ProductRecommendationService productRecom this.productRecommendationService = productRecommendationService; } - @GetMapping("/recommendations") + @PostMapping("/_recommendations") public List findRecommendationProducts( @RequestParam Gender gender, @RequestParam int age, - @RequestParam String keyword, - @RequestParam List categories, - @RequestParam List productConditions) { - return productRecommendationService.getRecommendationProducts(gender, age, keyword, categories, - productConditions); + @RequestBody ProductFilter productFilter) { + return productRecommendationService.getRecommendationProducts(gender, age, productFilter); } } diff --git a/module-recommendation/src/main/java/com/example/modulerecommendation/service/ProductListingService.java b/module-recommendation/src/main/java/com/example/modulerecommendation/service/ProductListingService.java index a350ba8..9934ee7 100644 --- a/module-recommendation/src/main/java/com/example/modulerecommendation/service/ProductListingService.java +++ b/module-recommendation/src/main/java/com/example/modulerecommendation/service/ProductListingService.java @@ -10,8 +10,8 @@ import com.example.moduledomain.domain.product.ProductImage; import com.example.moduledomain.repository.product.ProductImageRepository; import com.example.moduledomain.repository.product.ProductRepository; -import com.example.modulerecommendation.controller.response.ImageResponse; -import com.example.modulerecommendation.controller.response.ProductFindResponse; +import com.example.moduledomain.response.ImageResponse; +import com.example.moduledomain.response.ProductFindResponse; @Service public class ProductListingService { @@ -58,6 +58,6 @@ private ProductFindResponse convertToProductFindResponse(Product product) { List imageResponses = productImages.stream() .map(ImageResponse::from) .collect(Collectors.toList()); - return ProductFindResponse.from(product, imageResponses); + return ProductFindResponse.from(product, imageResponses, true); } } diff --git a/module-recommendation/src/main/java/com/example/modulerecommendation/service/ProductRecommendationService.java b/module-recommendation/src/main/java/com/example/modulerecommendation/service/ProductRecommendationService.java index 0cdef88..ea73ab8 100644 --- a/module-recommendation/src/main/java/com/example/modulerecommendation/service/ProductRecommendationService.java +++ b/module-recommendation/src/main/java/com/example/modulerecommendation/service/ProductRecommendationService.java @@ -17,7 +17,8 @@ import com.example.moduledomain.domain.user.Gender; import com.example.moduledomain.repository.bidLogging.BidLoggingRepository; import com.example.moduledomain.repository.product.ProductRepository; -import com.example.modulerecommendation.controller.response.ProductFindResponse; +import com.example.moduledomain.request.ProductFilter; +import com.example.moduledomain.response.ProductFindResponse; @Service public class ProductRecommendationService { @@ -39,9 +40,7 @@ public ProductRecommendationService(ProductListingService productListingService, public List getRecommendationProducts(Gender gender, int age, - String keyword, - List categories, - List productConditions) { + ProductFilter productFilter) { /* * 1. 카테고리별 필터 * 1.1 필터링한 카테고리 상품이 추천 상품 카테고리에 없으면 빈 리스트 반환 @@ -50,6 +49,7 @@ public List getRecommendationProducts(Gender gender, * 3. keyword 필터링은 ProductListingService에서 진행 * 3. 최종적인 ProductIds 조회 후 반환. * */ + updateRecommendationProductStore(); String ageGroup = AgeGroup.fromAge(age); List recommendationProductIds = new ArrayList<>(); // 최종 추천 상품 ID 리스트 @@ -63,7 +63,7 @@ public List getRecommendationProducts(Gender gender, } // 카테고리와 상품 상태 모두 필터링이 없다면 모든 추천 상품을 반환 - if (categories.isEmpty() && productConditions.isEmpty()) { + if (productFilter.getCategory().isEmpty() && productFilter.getProductCondition().isEmpty()) { for (Map> conditionMap : categoryMap.values()) { for (List productIds : conditionMap.values()) { recommendationProductIds.addAll(productIds); @@ -71,8 +71,8 @@ public List getRecommendationProducts(Gender gender, } } else { // 카테고리만 필터링된 경우 - if (!categories.isEmpty()) { - for (Category category : categories) { + if (!productFilter.getCategory().isEmpty()) { + for (Category category : productFilter.getCategory()) { Map> productConditionListMap = categoryMap.getOrDefault(category, Collections.emptyMap()); if (productConditionListMap.isEmpty()) { @@ -80,24 +80,24 @@ public List getRecommendationProducts(Gender gender, } // 상품 상태만 필터링된 경우 - if (productConditions.isEmpty()) { + if (productFilter.getProductCondition().isEmpty()) { // 상품 상태 필터링이 없으면, 해당 카테고리의 모든 상품을 추가 for (List productIds : productConditionListMap.values()) { recommendationProductIds.addAll(productIds); } } else { // 카테고리와 상품 상태 둘 다 필터링된 경우 - for (ProductCondition productCondition : productConditions) { + for (ProductCondition productCondition : productFilter.getProductCondition()) { List productIds = productConditionListMap.getOrDefault(productCondition, Collections.emptyList()); recommendationProductIds.addAll(productIds); } } } } // 카테고리가 비어있고 상품 상태만 필터링하려면 여기서 처리 - else if (!productConditions.isEmpty()) { + else if (!productFilter.getProductCondition().isEmpty()) { // 카테고리 필터링이 없고, 경매 상품 상태만 필터링하는 경우 for (Map> productConditionListMap : categoryMap.values()) { - for (ProductCondition productCondition : productConditions) { + for (ProductCondition productCondition : productFilter.getProductCondition()) { List productIds = productConditionListMap.getOrDefault(productCondition, Collections.emptyList()); recommendationProductIds.addAll(productIds); } @@ -109,7 +109,7 @@ else if (!productConditions.isEmpty()) { if (recommendationProductIds.isEmpty()) { return Collections.emptyList(); } - return productListingService.findRecommendationProducts(recommendationProductIds, keyword); + return productListingService.findRecommendationProducts(recommendationProductIds, productFilter.getKeyword()); } @Scheduled(cron = "0 0 3 * * THU") From 9bd9c1012242d80c6ff7893614cbc6896bc6274d Mon Sep 17 00:00:00 2001 From: minjiwon Date: Fri, 10 Jan 2025 02:44:13 +0900 Subject: [PATCH 08/11] =?UTF-8?q?refactor:=20=EC=88=98=EC=A0=95=EB=90=9C?= =?UTF-8?q?=20=EC=B6=94=EC=B2=9C=20=EC=84=9C=EB=B2=84=20API=EC=97=90=20?= =?UTF-8?q?=EB=A7=9E=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProductRecommendationServerClient.java | 14 ++++++-------- .../service/httpClient/RestHttpClient.java | 15 +++++---------- .../RecommendationFallBackMethod.java | 9 +++------ 3 files changed, 14 insertions(+), 24 deletions(-) diff --git a/module-api/src/main/java/com/example/moduleapi/service/httpClient/ProductRecommendationServerClient.java b/module-api/src/main/java/com/example/moduleapi/service/httpClient/ProductRecommendationServerClient.java index 33e510f..dcfa006 100644 --- a/module-api/src/main/java/com/example/moduleapi/service/httpClient/ProductRecommendationServerClient.java +++ b/module-api/src/main/java/com/example/moduleapi/service/httpClient/ProductRecommendationServerClient.java @@ -3,15 +3,15 @@ import java.util.List; import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestParam; -import com.example.moduleapi.controller.response.product.ProductFindResponse; import com.example.moduleapi.service.httpClient.circuitBreaker.RecommendationFallBackMethod; -import com.example.moduledomain.domain.product.Category; -import com.example.moduledomain.domain.product.ProductCondition; import com.example.moduledomain.domain.user.Gender; +import com.example.moduledomain.request.ProductFilter; +import com.example.moduledomain.response.ProductFindResponse; import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker; @@ -19,13 +19,11 @@ @CircuitBreaker(name = "recommendationServer") public interface ProductRecommendationServerClient { - @GetMapping("/recommendations") + @PostMapping("/_recommendations") List getRecommendationProduct( @RequestHeader("Authorization") String authorizationHeader, @RequestParam("gender") Gender gender, @RequestParam("age") int age, - @RequestParam String keyword, - @RequestParam List categories, - @RequestParam List productConditions + @RequestBody ProductFilter productFilter ); } diff --git a/module-api/src/main/java/com/example/moduleapi/service/httpClient/RestHttpClient.java b/module-api/src/main/java/com/example/moduleapi/service/httpClient/RestHttpClient.java index 13dc54a..ac0e150 100644 --- a/module-api/src/main/java/com/example/moduleapi/service/httpClient/RestHttpClient.java +++ b/module-api/src/main/java/com/example/moduleapi/service/httpClient/RestHttpClient.java @@ -5,10 +5,9 @@ import org.springframework.stereotype.Service; -import com.example.moduleapi.controller.response.product.ProductFindResponse; -import com.example.moduledomain.domain.product.Category; -import com.example.moduledomain.domain.product.ProductCondition; import com.example.moduledomain.domain.user.User; +import com.example.moduledomain.request.ProductFilter; +import com.example.moduledomain.response.ProductFindResponse; @Service public class RestHttpClient { @@ -19,18 +18,14 @@ public RestHttpClient(ProductRecommendationServerClient productRecommendationCli } // 추천 상품 을 조회하는 메서드 - public List findRecommendationProducts(User user, - String keyword, - List categories, - List productConditions) { + public List findRecommendationProducts(User user, ProductFilter productFilter) { String authorizationHeader = generateAuthorizationHeader(user); + return productRecommendationClient.getRecommendationProduct( authorizationHeader, user.getGender(), user.getAge(), - keyword, - categories, - productConditions + productFilter ); } diff --git a/module-api/src/main/java/com/example/moduleapi/service/httpClient/circuitBreaker/RecommendationFallBackMethod.java b/module-api/src/main/java/com/example/moduleapi/service/httpClient/circuitBreaker/RecommendationFallBackMethod.java index 1ad592a..913557c 100644 --- a/module-api/src/main/java/com/example/moduleapi/service/httpClient/circuitBreaker/RecommendationFallBackMethod.java +++ b/module-api/src/main/java/com/example/moduleapi/service/httpClient/circuitBreaker/RecommendationFallBackMethod.java @@ -5,11 +5,10 @@ import org.springframework.stereotype.Component; -import com.example.moduleapi.controller.response.product.ProductFindResponse; import com.example.moduleapi.service.httpClient.ProductRecommendationServerClient; -import com.example.moduledomain.domain.product.Category; -import com.example.moduledomain.domain.product.ProductCondition; import com.example.moduledomain.domain.user.Gender; +import com.example.moduledomain.request.ProductFilter; +import com.example.moduledomain.response.ProductFindResponse; @Component public class RecommendationFallBackMethod implements ProductRecommendationServerClient { @@ -20,9 +19,7 @@ public class RecommendationFallBackMethod implements ProductRecommendationServer public List getRecommendationProduct(String authorizationHeader, Gender gender, int age, - String keyword, - List categories, - List productConditions) { + ProductFilter productFilter) { return new ArrayList<>(); } } From 5843cd550afdc777f19a915db9a9496162fcd1b7 Mon Sep 17 00:00:00 2001 From: minjiwon Date: Fri, 10 Jan 2025 02:46:47 +0900 Subject: [PATCH 09/11] =?UTF-8?q?feat:=20ifNotEmpty=20=EC=9C=A0=ED=8B=B8?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=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 --- .../moduledomain/repository/utils/QueryHelperUtils.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/module-domain/src/main/java/com/example/moduledomain/repository/utils/QueryHelperUtils.java b/module-domain/src/main/java/com/example/moduledomain/repository/utils/QueryHelperUtils.java index 89c42c4..9fb04a9 100644 --- a/module-domain/src/main/java/com/example/moduledomain/repository/utils/QueryHelperUtils.java +++ b/module-domain/src/main/java/com/example/moduledomain/repository/utils/QueryHelperUtils.java @@ -1,5 +1,6 @@ package com.example.moduledomain.repository.utils; +import java.util.List; import java.util.function.Supplier; import com.querydsl.core.types.dsl.BooleanExpression; @@ -9,4 +10,8 @@ public class QueryHelperUtils { public static BooleanExpression ifNotNull(T value, Supplier expressionSupplier) { return value != null ? expressionSupplier.get() : null; } + + public static BooleanExpression ifNotEmpty(List value, Supplier expressionSupplier) { + return !value.isEmpty() ? expressionSupplier.get() : null; + } } From 2888e2e729897fc925b843e30899b082a624ecbb Mon Sep 17 00:00:00 2001 From: minjiwon Date: Fri, 10 Jan 2025 02:49:44 +0900 Subject: [PATCH 10/11] =?UTF-8?q?refactor:=20repository=EA=B3=84=EC=B8=B5?= =?UTF-8?q?=EC=97=90=EC=84=9C=20ProductFilterRequest=EB=A5=BC=20=EB=B0=9B?= =?UTF-8?q?=EC=95=84=EC=84=9C=20=EC=B2=98=EB=A6=AC=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/ProductRepositoryCustom.java | 6 +-- .../product/ProductRepositoryImpl.java | 44 +++++++++++++------ 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/module-domain/src/main/java/com/example/moduledomain/repository/product/ProductRepositoryCustom.java b/module-domain/src/main/java/com/example/moduledomain/repository/product/ProductRepositoryCustom.java index 18ce1ab..f9dd2a0 100644 --- a/module-domain/src/main/java/com/example/moduledomain/repository/product/ProductRepositoryCustom.java +++ b/module-domain/src/main/java/com/example/moduledomain/repository/product/ProductRepositoryCustom.java @@ -2,12 +2,10 @@ import java.util.List; -import com.example.moduledomain.domain.product.OrderBy; import com.example.moduledomain.domain.product.Product; -import com.example.moduledomain.domain.product.ProductCondition; +import com.example.moduledomain.request.ProductFilterRequest; public interface ProductRepositoryCustom { - List findProductsWithCriteria(String keyword, ProductCondition productCondition, int pageNo, int pageSize, - OrderBy order); + List findProductsWithCriteria(ProductFilterRequest productFilterRequest); } diff --git a/module-domain/src/main/java/com/example/moduledomain/repository/product/ProductRepositoryImpl.java b/module-domain/src/main/java/com/example/moduledomain/repository/product/ProductRepositoryImpl.java index 1606331..54ec387 100644 --- a/module-domain/src/main/java/com/example/moduledomain/repository/product/ProductRepositoryImpl.java +++ b/module-domain/src/main/java/com/example/moduledomain/repository/product/ProductRepositoryImpl.java @@ -9,10 +9,12 @@ import org.springframework.stereotype.Repository; +import com.example.moduledomain.domain.product.Category; import com.example.moduledomain.domain.product.OrderBy; import com.example.moduledomain.domain.product.Product; import com.example.moduledomain.domain.product.ProductCondition; import com.example.moduledomain.repository.utils.QueryHelperUtils; +import com.example.moduledomain.request.ProductFilterRequest; import com.querydsl.core.Tuple; import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.dsl.BooleanExpression; @@ -30,11 +32,17 @@ public ProductRepositoryImpl(JPAQueryFactory jpaQueryFactory) { } @Override - public List findProductsWithCriteria(String keyword, ProductCondition productCondition, int pageNo, - int pageSize, - OrderBy order) { - // 정렬 조건 설정 + public List findProductsWithCriteria(ProductFilterRequest productFilterRequest) { + + String keyword = productFilterRequest.getProductFilter().getKeyword(); + List categories = productFilterRequest.getProductFilter().getCategory(); + List productConditions = productFilterRequest.getProductFilter().getProductCondition(); + OrderBy order = productFilterRequest.getOrderBy(); + int pageNo = productFilterRequest.getPageNo(); + int pageSize = productFilterRequest.getPageSize(); + OrderSpecifier orderSpecifier = order.toOrderSpecifier(); + if (order == OrderBy.LIKE) { NumberPath like = Expressions.numberPath(Long.class, "like"); List result = jpaQueryFactory @@ -42,7 +50,8 @@ public List findProductsWithCriteria(String keyword, ProductCondition p .from(product) .leftJoin(productLike).on(product.id.eq(productLike.productId)) .where(containsKeyword(keyword), - filterProductCondition(productCondition)) + filterProductCondition(productConditions), + filterCategories(categories)) .groupBy(product.id) .orderBy(like.desc()) .offset(pageNo * pageSize) @@ -57,7 +66,8 @@ public List findProductsWithCriteria(String keyword, ProductCondition p return jpaQueryFactory .selectFrom(product) .where(containsKeyword(keyword), - filterProductCondition(productCondition)) + filterProductCondition(productConditions), + filterCategories(categories)) .orderBy(orderSpecifier) .offset(pageNo * pageSize) .limit(pageSize) @@ -66,17 +76,23 @@ public List findProductsWithCriteria(String keyword, ProductCondition p public BooleanExpression containsKeyword(String keyword) { return QueryHelperUtils.ifNotNull(keyword, - () -> product.productName.containsIgnoreCase(keyword) - .or(product.description.containsIgnoreCase(keyword))); + () -> product.productName.containsIgnoreCase(keyword) + .or(product.description.containsIgnoreCase(keyword))); } - public BooleanExpression filterProductCondition(ProductCondition productCondition) { - LocalDateTime requestTime = LocalDateTime.now(); - if (productCondition != null) { - return productCondition.checkCondition(requestTime); + public BooleanExpression filterProductCondition(List productConditions) { + LocalDateTime currentTime = LocalDateTime.now(); + product.startDate.after(currentTime).or(product.startDate.loe(currentTime) + .and(product.closeDate.after(currentTime))); + if (!productConditions.isEmpty()) { + return product.productCondition.in(productConditions); } //상태가 null인 경우 기본적으로 READY와 ACTIVE 상태 필터링 - return product.startDate.after(requestTime).or(product.startDate.loe(requestTime) - .and(product.closeDate.after(requestTime))); + return product.startDate.after(currentTime).or(product.startDate.loe(currentTime) + .and(product.closeDate.after(currentTime))); + } + + public BooleanExpression filterCategories(List categories) { + return QueryHelperUtils.ifNotEmpty(categories, () -> product.category.in(categories)); } } From a44d36e877f6d1a567a305f56f595007ab700dfc Mon Sep 17 00:00:00 2001 From: minjiwon Date: Fri, 10 Jan 2025 19:19:43 +0900 Subject: [PATCH 11/11] =?UTF-8?q?fix:=20=EC=B5=9C=EC=A2=85=20=EC=83=81?= =?UTF-8?q?=ED=92=88=20=EB=B0=98=ED=99=98=20=EB=A6=AC=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EB=A5=BC=20shuffle=ED=95=B4=EC=84=9C=20=EC=A0=95=EB=A0=AC?= =?UTF-8?q?=EC=A1=B0=EA=B1=B4=20=EB=AC=B4=EC=8B=9C=EB=90=98=EB=8A=94=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0=20-=20=EC=9D=BC?= =?UTF-8?q?=EB=B0=98=20=EC=83=81=ED=92=88=EA=B3=BC=20=EC=B6=94=EC=B2=9C=20?= =?UTF-8?q?=EC=83=81=ED=92=88=EC=9D=84=20=ED=95=A9=EC=B9=9C=20=ED=9B=84,?= =?UTF-8?q?=20shuffle=EC=9D=B4=20=EB=90=98=EC=96=B4=20=EC=A0=95=EB=A0=AC?= =?UTF-8?q?=20=EC=A1=B0=EA=B1=B4=EC=9D=B4=20=EB=AC=B4=EC=8B=9C=EB=90=98?= =?UTF-8?q?=EB=8D=98=20=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95=20-?= =?UTF-8?q?=EC=B6=94=EC=B2=9C=20=EC=84=9C=EB=B2=84=EC=97=90=EC=84=9C=20=20?= =?UTF-8?q?=EC=B6=94=EC=B2=9C=20=EC=83=81=ED=92=88=EB=A7=8C=20shuffle?= =?UTF-8?q?=ED=95=9C=20=ED=9B=84=20=EB=B0=98=ED=99=98=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95,=20=EC=9D=B4=ED=9B=84=20?= =?UTF-8?q?=EC=9D=BC=EB=B0=98=20=EC=83=81=ED=92=88=20=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=99=80=20=EB=B3=91=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../moduleapi/service/product/ProductFacade.java | 16 ++-------------- .../service/ProductListingService.java | 5 ++++- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/module-api/src/main/java/com/example/moduleapi/service/product/ProductFacade.java b/module-api/src/main/java/com/example/moduleapi/service/product/ProductFacade.java index 63fd2f1..b5c668d 100644 --- a/module-api/src/main/java/com/example/moduleapi/service/product/ProductFacade.java +++ b/module-api/src/main/java/com/example/moduleapi/service/product/ProductFacade.java @@ -1,7 +1,6 @@ package com.example.moduleapi.service.product; import java.time.LocalDateTime; -import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -79,26 +78,15 @@ public PagingResponse findProductsByCriteriaWithRecommendat List recommendationProducts = findRecommendationProducts(user, productFilter); // 3. 일반 상품 + 추천 상품 combine - List resultProductResponse = combineAndGetProducts(defaultProductFindResponses, recommendationProducts); + defaultProductFindResponses.addAll(recommendationProducts); - return PagingResponse.from(resultProductResponse, productFilterRequest.getPageNo()); + return PagingResponse.from(defaultProductFindResponses, productFilterRequest.getPageNo()); } private List findRecommendationProducts(User user, ProductFilter productFilter) { return restHttpClient.findRecommendationProducts(user, productFilter); } - private List combineAndGetProducts(List original, - List recommendations) { - // 여기 추천 상품 개수 조절 해야할듯? - original.addAll(recommendations); - Collections.shuffle(original); - - return original.stream() - .limit(6) - .collect(Collectors.toList()); - } - private ProductFindResponse convertToProductFindResponse(Product product) { List productImages = productImageService.getImage(product.getId()); List imageResponses = fileService.loadImages(productImages); diff --git a/module-recommendation/src/main/java/com/example/modulerecommendation/service/ProductListingService.java b/module-recommendation/src/main/java/com/example/modulerecommendation/service/ProductListingService.java index 9934ee7..5b1b36a 100644 --- a/module-recommendation/src/main/java/com/example/modulerecommendation/service/ProductListingService.java +++ b/module-recommendation/src/main/java/com/example/modulerecommendation/service/ProductListingService.java @@ -1,5 +1,6 @@ package com.example.modulerecommendation.service; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -39,7 +40,9 @@ public List findRecommendationProducts(List productId List productFindResponses = products.stream() .map(this::convertToProductFindResponse) .collect(Collectors.toList()); - return productFindResponses; + + Collections.shuffle(productFindResponses); + return productFindResponses.subList(0, Math.min(productFindResponses.size(), 3)); } @Transactional(readOnly = true)