From e4f274940c5dc04eed13091d49a59596deede283 Mon Sep 17 00:00:00 2001 From: KWAK-JINHO Date: Wed, 9 Apr 2025 01:34:09 +0900 Subject: [PATCH 01/25] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20refactor:=20?= =?UTF-8?q?=EC=98=B5=EC=85=98=20=EB=B0=98=ED=99=98=ED=83=80=EC=9E=85=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 - Map의 반환타입을 SizeOption DTO로 수정 🔗 Resolves: #294 --- .../dto/FashionClothesOptionResponse.java | 10 ++++----- .../option/dto/GeneralOptionResponse.java | 11 +++++----- .../product/service/ProductServiceImpl.java | 14 +++++------- .../repository/SaleProductRepository.java | 22 ++++++++++--------- 4 files changed, 28 insertions(+), 29 deletions(-) diff --git a/backend/JiShop/src/main/java/com/jishop/option/dto/FashionClothesOptionResponse.java b/backend/JiShop/src/main/java/com/jishop/option/dto/FashionClothesOptionResponse.java index 84dd9a81..752afea9 100644 --- a/backend/JiShop/src/main/java/com/jishop/option/dto/FashionClothesOptionResponse.java +++ b/backend/JiShop/src/main/java/com/jishop/option/dto/FashionClothesOptionResponse.java @@ -8,15 +8,15 @@ public record FashionClothesOptionResponse( List option ) { - public static FashionClothesOptionResponse from(List> productOptions) { + public static FashionClothesOptionResponse from(List productOptions) { if (productOptions == null || productOptions.isEmpty()) { return new FashionClothesOptionResponse(List.of()); } Map> fashionClothesOptions = new HashMap<>(); - for (Map option : productOptions) { - String optionValue = (String) option.get("optionValue"); + for (SizeOption option : productOptions) { + String optionValue = option.optionValue(); String[] colorAndSize = optionValue.split("/"); if (colorAndSize.length == 2) { @@ -27,9 +27,9 @@ public static FashionClothesOptionResponse from(List> produc fashionClothesOptions.put(color, new ArrayList<>()); } SizeOption sizeOption = new SizeOption( - (Long) option.get("saleProductId"), + option.saleProductId(), size, - (int) option.get("optionExtra") + option.optionExtra() ); fashionClothesOptions.get(color).add(sizeOption); } diff --git a/backend/JiShop/src/main/java/com/jishop/option/dto/GeneralOptionResponse.java b/backend/JiShop/src/main/java/com/jishop/option/dto/GeneralOptionResponse.java index 979f9f50..82c99a55 100644 --- a/backend/JiShop/src/main/java/com/jishop/option/dto/GeneralOptionResponse.java +++ b/backend/JiShop/src/main/java/com/jishop/option/dto/GeneralOptionResponse.java @@ -2,19 +2,18 @@ import java.util.ArrayList; import java.util.List; -import java.util.Map; public record GeneralOptionResponse( List options ) { - public static GeneralOptionResponse from(final List> productOptions) { + public static GeneralOptionResponse from(final List productOptions) { final List generalOptions = new ArrayList<>(); - for (final Map option : productOptions) { + for (final SizeOption option : productOptions) { ProductOption productOption = new ProductOption( - (Long) option.get("saleProductId"), - (String) option.get("optionValue"), - (int) option.get("optionExtra") + option.saleProductId(), + option.optionValue(), + option.optionExtra() ); generalOptions.add(productOption); } diff --git a/backend/JiShop/src/main/java/com/jishop/product/service/ProductServiceImpl.java b/backend/JiShop/src/main/java/com/jishop/product/service/ProductServiceImpl.java index 412e12b0..2102b7e5 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/service/ProductServiceImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/product/service/ProductServiceImpl.java @@ -6,6 +6,7 @@ import com.jishop.member.domain.User; import com.jishop.option.dto.FashionClothesOptionResponse; import com.jishop.option.dto.GeneralOptionResponse; +import com.jishop.option.dto.SizeOption; import com.jishop.order.repository.OrderDetailRepository; import com.jishop.product.domain.DiscountStatus; import com.jishop.product.domain.Product; @@ -25,7 +26,6 @@ import org.springframework.transaction.annotation.Transactional; import java.util.List; -import java.util.Map; @Service @RequiredArgsConstructor @@ -59,26 +59,24 @@ public ProductDetailResponse getProduct(final User user, final Long productId) { final Product product = productRepository.findById(productId) .orElseThrow(() -> new DomainException(ErrorType.PRODUCT_NOT_FOUND)); - // 찜 상태 final boolean isWished = (user != null) && productWishListRepository .isProductWishedByUser(user.getId(), productId); - // 상품 옵션 final Long categoryType = product.getLCatId(); + final Object productsOptions; if (categoryType == 50000000L) { - final List> fashionClothesOptions = saleProductRepository + final List fashionClothesOptions = saleProductRepository .findFashionClothesOptionsByProductId(productId); - productsOptions = fashionClothesOptions.isEmpty() ? + productsOptions = fashionClothesOptions.isEmpty() ? List.of() : FashionClothesOptionResponse.from(fashionClothesOptions); } else { - final List> generalOptions = saleProductRepository + final List generalOptions = saleProductRepository .findGeneralOptionsByProductId(productId); - productsOptions = GeneralOptionResponse.from(generalOptions); + productsOptions = GeneralOptionResponse.from(generalOptions); } - // 상품 리뷰 final List productsReviews = reviewProductRepository.findAllByProduct(product); final int reviewCount = productsReviews.isEmpty() ? 0 : productsReviews.get(0).getReviewCount(); final double reviewRate = productsReviews.isEmpty() ? 0.0 : productsReviews.get(0).getAverageRating(); diff --git a/backend/JiShop/src/main/java/com/jishop/saleproduct/repository/SaleProductRepository.java b/backend/JiShop/src/main/java/com/jishop/saleproduct/repository/SaleProductRepository.java index bc266dd7..f5419782 100644 --- a/backend/JiShop/src/main/java/com/jishop/saleproduct/repository/SaleProductRepository.java +++ b/backend/JiShop/src/main/java/com/jishop/saleproduct/repository/SaleProductRepository.java @@ -1,5 +1,6 @@ package com.jishop.saleproduct.repository; +import com.jishop.option.dto.SizeOption; import com.jishop.saleproduct.domain.SaleProduct; import jakarta.persistence.LockModeType; import org.springframework.data.jpa.repository.JpaRepository; @@ -9,7 +10,6 @@ import org.springframework.stereotype.Repository; import java.util.List; -import java.util.Map; @Repository public interface SaleProductRepository extends JpaRepository { @@ -25,15 +25,17 @@ public interface SaleProductRepository extends JpaRepository "WHERE sp.id IN :ids") List findAllByIdsForOrder(@Param("ids") List ids); - @Query("SELECT sp.id as saleProductId, o.optionValue as optionValue, o.optionExtra as optionExtra FROM SaleProduct sp " + - "LEFT JOIN sp.option o ON o.categoryType = 'FASHION_CLOTHES' " + - "WHERE sp.product.id = :productId AND o.id IS NOT NULL") - List> findFashionClothesOptionsByProductId(@Param("productId") Long productId); - - @Query("SELECT sp.id as saleProductId, o.optionValue as optionValue, o.optionExtra as optionExtra FROM SaleProduct sp " + - "LEFT JOIN sp.option o ON o.categoryType <> 'FASHION_CLOTHES' " + - "WHERE sp.product.id = :productId AND o.id IS NOT NULL") - List> findGeneralOptionsByProductId(@Param("productId") Long productId); + @Query("SELECT new com.jishop.option.dto.SizeOption(sp.id, o.optionValue, o.optionExtra) " + + "FROM SaleProduct sp " + + "LEFT JOIN sp.option o ON o.categoryType = 'FASHION_CLOTHES' " + + "WHERE sp.product.id = :productId AND o.id IS NOT NULL") + List findFashionClothesOptionsByProductId(@Param("productId") Long productId); + + @Query("SELECT new com.jishop.option.dto.SizeOption(sp.id, o.optionValue, o.optionExtra) " + + "FROM SaleProduct sp " + + "LEFT JOIN sp.option o ON o.categoryType <> 'FASHION_CLOTHES' " + + "WHERE sp.product.id = :productId AND o.id IS NOT NULL") + List findGeneralOptionsByProductId(@Param("productId") Long productId); @Query("SELECT sp FROM SaleProduct sp " + From 48fe4fcc50ed02cce43e997f5795200d85aed23b Mon Sep 17 00:00:00 2001 From: KWAK-JINHO Date: Thu, 10 Apr 2025 23:58:34 +0900 Subject: [PATCH 02/25] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20refactor:=20fetch?= =?UTF-8?q?Count()=20deprecated=EB=A1=9C=20=EC=9D=B8=ED=95=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 🔗 Resolves: #294 --- .../product/repository/ProductRepositoryQueryDslImpl.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java b/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java index 23ae18ab..a9a524dc 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java @@ -42,9 +42,10 @@ public long countProductsByCondition(final ProductRequest productRequest) { final BooleanBuilder filterBuilder = productQueryHelper .findProductsByCondition(productRequest, product, reviewProduct); - return queryFactory.selectFrom(product) + return queryFactory.select(product.count()) + .from(product) .where(filterBuilder) - .fetchCount(); + .fetchOne(); } private OrderSpecifier addSorting(final String sort, final QProduct product) { From 9822ddb8452b1bae8ad0d8a35d8cf24912408112 Mon Sep 17 00:00:00 2001 From: KWAK-JINHO Date: Fri, 11 Apr 2025 00:08:46 +0900 Subject: [PATCH 03/25] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20refactor:=20fetch?= =?UTF-8?q?One=20optional=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - null일경우 0반환하게 수정 🔗 Resolves: #294 --- .../repository/ProductRepositoryQueryDslImpl.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java b/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java index a9a524dc..c563e139 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java @@ -12,6 +12,7 @@ import org.springframework.stereotype.Repository; import java.util.List; +import java.util.Optional; @Repository @RequiredArgsConstructor @@ -42,10 +43,12 @@ public long countProductsByCondition(final ProductRequest productRequest) { final BooleanBuilder filterBuilder = productQueryHelper .findProductsByCondition(productRequest, product, reviewProduct); - return queryFactory.select(product.count()) - .from(product) - .where(filterBuilder) - .fetchOne(); + return Optional.ofNullable( + queryFactory.select(product.count()) + .from(product) + .where(filterBuilder) + .fetchOne() + ).orElse(0L); } private OrderSpecifier addSorting(final String sort, final QProduct product) { From 2bf090fb4530505ef63947e4ca1a66db2ea90d3b Mon Sep 17 00:00:00 2001 From: KWAK-JINHO Date: Fri, 11 Apr 2025 03:46:54 +0900 Subject: [PATCH 04/25] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20refactor:=20produ?= =?UTF-8?q?ct=20=EC=97=94=ED=8B=B0=ED=8B=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - embedded로 분리를 통해 가독성 향상 🔗 Resolves: #294 --- backend/JiShop/gradlew | 0 .../jishop/cart/dto/CartDetailResponse.java | 16 +-- .../cart/service/Impl/CartServiceImpl.java | 15 +-- .../logshop/dto/RecentProductResponse.java | 16 +-- .../com/jishop/order/domain/OrderDetail.java | 4 +- .../order/dto/OrderProductResponse.java | 4 +- .../service/impl/OrderGetServiceImpl.java | 8 +- .../PopularCalculationServiceImpl.java | 28 ++--- .../com/jishop/product/domain/Product.java | 113 ++++-------------- .../product/domain/embed/CategoryInfo.java | 26 ++++ .../jishop/product/domain/embed/ImageUrl.java | 36 ++++++ .../product/domain/embed/ProductInfo.java | 46 +++++++ .../jishop/product/domain/embed/Status.java | 43 +++++++ .../dto/response/ProductDetailResponse.java | 13 +- .../product/dto/response/ProductResponse.java | 14 +-- .../ProductQueryHelperImpl.java | 10 +- .../product/repository/ProductRepository.java | 6 +- .../ProductRepositoryQueryDslImpl.java | 6 +- .../product/service/ProductServiceImpl.java | 2 +- .../dto/ProductWishProductResponse.java | 2 +- .../repository/ProductWishListRepository.java | 8 +- .../com/jishop/review/dto/ProductSummary.java | 14 +-- .../review/repository/ReviewRepository.java | 6 +- 23 files changed, 262 insertions(+), 174 deletions(-) mode change 100644 => 100755 backend/JiShop/gradlew create mode 100644 backend/JiShop/src/main/java/com/jishop/product/domain/embed/CategoryInfo.java create mode 100644 backend/JiShop/src/main/java/com/jishop/product/domain/embed/ImageUrl.java create mode 100644 backend/JiShop/src/main/java/com/jishop/product/domain/embed/ProductInfo.java create mode 100644 backend/JiShop/src/main/java/com/jishop/product/domain/embed/Status.java diff --git a/backend/JiShop/gradlew b/backend/JiShop/gradlew old mode 100644 new mode 100755 diff --git a/backend/JiShop/src/main/java/com/jishop/cart/dto/CartDetailResponse.java b/backend/JiShop/src/main/java/com/jishop/cart/dto/CartDetailResponse.java index b1baef45..4f13e47e 100644 --- a/backend/JiShop/src/main/java/com/jishop/cart/dto/CartDetailResponse.java +++ b/backend/JiShop/src/main/java/com/jishop/cart/dto/CartDetailResponse.java @@ -29,37 +29,37 @@ public static CartDetailResponse from( return new CartDetailResponse( cartId, product.getId(), - product.getProduct().getName(), + product.getProduct().getProductInfo().getName(), product.getOption() != null ? product.getOption().getOptionValue() : "기본옵션", paymentPrice, orderPrice, discountPrice, quantity, paymentPrice * quantity, - product.getProduct().getMainImage(), - product.getProduct().getBrand(), + product.getProduct().getImage().getMainImage(), + product.getProduct().getProductInfo().getBrand(), isExisted ); } public static CartDetailResponse of(Cart cart, boolean isExisted) { SaleProduct saleProduct = cart.getSaleProduct(); - int paymentPrice = saleProduct.getProduct().getDiscountPrice(); - int orderPrice = saleProduct.getProduct().getOriginPrice(); + int paymentPrice = saleProduct.getProduct().getProductInfo().getDiscountPrice(); + int orderPrice = saleProduct.getProduct().getProductInfo().getOriginPrice(); int discountPrice = orderPrice - paymentPrice; return new CartDetailResponse( cart.getId(), saleProduct.getId(), - saleProduct.getProduct().getName(), + saleProduct.getProduct().getProductInfo().getName(), saleProduct.getOption() != null ? saleProduct.getOption().getOptionValue() : "기본옵션", paymentPrice, orderPrice, discountPrice, cart.getQuantity(), paymentPrice * cart.getQuantity(), - saleProduct.getProduct().getMainImage(), - saleProduct.getProduct().getBrand(), + saleProduct.getProduct().getImage().getMainImage(), + saleProduct.getProduct().getProductInfo().getBrand(), isExisted ); } diff --git a/backend/JiShop/src/main/java/com/jishop/cart/service/Impl/CartServiceImpl.java b/backend/JiShop/src/main/java/com/jishop/cart/service/Impl/CartServiceImpl.java index aba860c0..632b6ad5 100644 --- a/backend/JiShop/src/main/java/com/jishop/cart/service/Impl/CartServiceImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/cart/service/Impl/CartServiceImpl.java @@ -133,15 +133,16 @@ public CartResponse getGuestCart(List guestCartRequests) { return new CartDetailResponse( null, //장바구니 ID는 null (비회원이니까) saleProduct.getId(), - saleProduct.getProduct().getName(), + saleProduct.getProduct().getProductInfo().getName(), saleProduct.getOption() != null ? saleProduct.getOption().getOptionValue() : "기본옵션", - saleProduct.getProduct().getDiscountPrice(), - saleProduct.getProduct().getOriginPrice(), - saleProduct.getProduct().getOriginPrice() - saleProduct.getProduct().getDiscountPrice(), + saleProduct.getProduct().getProductInfo().getDiscountPrice(), + saleProduct.getProduct().getProductInfo().getOriginPrice(), + saleProduct.getProduct().getProductInfo().getOriginPrice() - + saleProduct.getProduct().getProductInfo().getDiscountPrice(), quantity, - saleProduct.getProduct().getDiscountPrice() * quantity, - saleProduct.getProduct().getMainImage(), - saleProduct.getProduct().getBrand(), + saleProduct.getProduct().getProductInfo().getDiscountPrice() * quantity, + saleProduct.getProduct().getImage().getMainImage(), + saleProduct.getProduct().getProductInfo().getBrand(), false ); }) diff --git a/backend/JiShop/src/main/java/com/jishop/logshop/dto/RecentProductResponse.java b/backend/JiShop/src/main/java/com/jishop/logshop/dto/RecentProductResponse.java index 60837f15..e6888105 100644 --- a/backend/JiShop/src/main/java/com/jishop/logshop/dto/RecentProductResponse.java +++ b/backend/JiShop/src/main/java/com/jishop/logshop/dto/RecentProductResponse.java @@ -39,14 +39,14 @@ public static class ProductResponse { public static ProductResponse from(Product product) { return new ProductResponse( product.getId(), - product.getName(), - product.getMallSeq(), - product.getBrand(), - product.getDescription(), - product.getOriginPrice(), - product.getDiscountPrice(), - product.getDiscountRate(), - product.getMainImage() + product.getProductInfo().getName(), + product.getProductInfo().getMallSeq(), + product.getProductInfo().getBrand(), + product.getProductInfo().getDescription(), + product.getProductInfo().getOriginPrice(), + product.getProductInfo().getDiscountPrice(), + product.getProductInfo().getDiscountRate(), + product.getImage().getMainImage() ); } } diff --git a/backend/JiShop/src/main/java/com/jishop/order/domain/OrderDetail.java b/backend/JiShop/src/main/java/com/jishop/order/domain/OrderDetail.java index bb403281..3079b09a 100644 --- a/backend/JiShop/src/main/java/com/jishop/order/domain/OrderDetail.java +++ b/backend/JiShop/src/main/java/com/jishop/order/domain/OrderDetail.java @@ -53,8 +53,8 @@ public OrderDetail(Order order, String orderNumber, SaleProduct saleProduct, int } public static OrderDetail from(Order order, SaleProduct saleProduct, int quantity){ - int orderPrice = saleProduct.getProduct().getOriginPrice(); - int paymentPrice = saleProduct.getProduct().getDiscountPrice(); + int orderPrice = saleProduct.getProduct().getProductInfo().getOriginPrice(); + int paymentPrice = saleProduct.getProduct().getProductInfo().getDiscountPrice(); int discountPrice = orderPrice - paymentPrice; if(saleProduct.getOption() != null){ diff --git a/backend/JiShop/src/main/java/com/jishop/order/dto/OrderProductResponse.java b/backend/JiShop/src/main/java/com/jishop/order/dto/OrderProductResponse.java index d636f1a4..4527db5e 100644 --- a/backend/JiShop/src/main/java/com/jishop/order/dto/OrderProductResponse.java +++ b/backend/JiShop/src/main/java/com/jishop/order/dto/OrderProductResponse.java @@ -20,7 +20,7 @@ public static OrderProductResponse from(OrderDetail detail, boolean canReview) { return new OrderProductResponse( detail.getId(), detail.getSaleProduct().getId(), - detail.getSaleProduct().getProduct().getMainImage(), + detail.getSaleProduct().getProduct().getImage().getMainImage(), detail.getSaleProduct().getName(), detail.getSaleProduct().getOption() != null ? detail.getSaleProduct().getOption().getOptionValue() : null, detail.getPaymentPrice(), @@ -29,7 +29,7 @@ public static OrderProductResponse from(OrderDetail detail, boolean canReview) { detail.getQuantity(), detail.getPaymentPrice() * detail.getQuantity(), canReview, - detail.getSaleProduct().getProduct().getBrand() + detail.getSaleProduct().getProduct().getProductInfo().getBrand() ); } } diff --git a/backend/JiShop/src/main/java/com/jishop/order/service/impl/OrderGetServiceImpl.java b/backend/JiShop/src/main/java/com/jishop/order/service/impl/OrderGetServiceImpl.java index 991bb772..c21ebad0 100644 --- a/backend/JiShop/src/main/java/com/jishop/order/service/impl/OrderGetServiceImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/order/service/impl/OrderGetServiceImpl.java @@ -104,8 +104,8 @@ public CartResponse getCheckOut(User user, List orderDetailR .map(OrderDetailRequest::quantity) .orElseThrow(() -> new DomainException(ErrorType.INVALID_QUANTITY)); // 기본값은 1로 설정 - int paymentPrice = product.getProduct().getDiscountPrice(); - int orderPrice = product.getProduct().getOriginPrice(); + int paymentPrice = product.getProduct().getProductInfo().getDiscountPrice(); + int orderPrice = product.getProduct().getProductInfo().getOriginPrice(); int discountPrice = orderPrice - paymentPrice; return CartDetailResponse.from( @@ -131,8 +131,8 @@ public CartResponse getCheckoutInstant(User user, Long saleProductId, int quant SaleProduct saleProduct = saleProductRepository.findById(saleProductId) .orElseThrow(() -> new DomainException(ErrorType.PRODUCT_NOT_FOUND)); - int paymentPrice = saleProduct.getProduct().getDiscountPrice(); - int orderPrice = saleProduct.getProduct().getOriginPrice(); + int paymentPrice = saleProduct.getProduct().getProductInfo().getDiscountPrice(); + int orderPrice = saleProduct.getProduct().getProductInfo().getOriginPrice(); int discountPrice = orderPrice - paymentPrice; CartDetailResponse cartDetailResponse = CartDetailResponse.from( diff --git a/backend/JiShop/src/main/java/com/jishop/popular/service/PopularCalculationServiceImpl.java b/backend/JiShop/src/main/java/com/jishop/popular/service/PopularCalculationServiceImpl.java index 9fc04f43..97810f10 100644 --- a/backend/JiShop/src/main/java/com/jishop/popular/service/PopularCalculationServiceImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/popular/service/PopularCalculationServiceImpl.java @@ -96,8 +96,8 @@ public List findPopularProductsByKeyword(String keyword, List products; // 브랜드 이름과 정확히 일치하는 경우 - if(productRepository.existsByBrand(keyword)){ - products = productRepository.findAllByBrand(keyword); + if(productRepository.existsByProductInfo_Brand(keyword)){ + products = productRepository.findAllByProductInfo_Brand(keyword); } // 그 외에는 Fulltext 검색으로 통합 처리 else { @@ -140,12 +140,12 @@ public PopularProductResponse convertToPopularProductResponse(ProductScore produ Product product = productScore.getProduct(); return new PopularProductResponse( product.getId(), - product.getMainImage(), - product.getBrand(), - product.getName(), - product.getOriginPrice(), - product.getDiscountPrice(), - productScore.getProduct().getDiscountRate(), + product.getImage().getMainImage(), + product.getProductInfo().getBrand(), + product.getProductInfo().getName(), + product.getProductInfo().getOriginPrice(), + product.getProductInfo().getDiscountPrice(), + productScore.getProduct().getProductInfo().getDiscountRate(), productScore.getTotalOrderCount(), productScore.getReviewRating().doubleValue() ); @@ -160,12 +160,12 @@ public PopularProductResponse convertToPopularProductResponse(ProductScore produ public PopularProductResponse convertToPopularProductResponseFromProduct(Product product){ return new PopularProductResponse( product.getId(), - product.getMainImage(), - product.getBrand(), - product.getName(), - product.getOriginPrice(), - product.getDiscountPrice(), - product.getDiscountRate(), + product.getImage().getMainImage(), + product.getProductInfo().getBrand(), + product.getProductInfo().getName(), + product.getProductInfo().getOriginPrice(), + product.getProductInfo().getDiscountPrice(), + product.getProductInfo().getDiscountRate(), 0, 0.0 ); diff --git a/backend/JiShop/src/main/java/com/jishop/product/domain/Product.java b/backend/JiShop/src/main/java/com/jishop/product/domain/Product.java index 5a3b298c..ec313276 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/domain/Product.java +++ b/backend/JiShop/src/main/java/com/jishop/product/domain/Product.java @@ -2,6 +2,10 @@ import com.jishop.category.domain.Category; import com.jishop.common.util.BaseEntity; +import com.jishop.product.domain.embed.CategoryInfo; +import com.jishop.product.domain.embed.ImageUrl; +import com.jishop.product.domain.embed.ProductInfo; +import com.jishop.product.domain.embed.Status; import com.jishop.productscore.domain.ProductScore; import com.jishop.saleproduct.domain.SaleProduct; import jakarta.persistence.*; @@ -9,7 +13,6 @@ import lombok.Getter; import lombok.NoArgsConstructor; -import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -19,111 +22,40 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Product extends BaseEntity { - // 상품 정보 - @Column(name = "name", nullable = false) - private String name; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "category_id", nullable = false) - private Category category; - @Column(name = "mall_seq", nullable = false) - private String mallSeq; - @Column(name = "manufacture_date", nullable = false) - private LocalDateTime manufactureDate; - @Column(name = "brand", nullable = false) - private String brand; - @Column(name = "description", nullable = false) - private String description; - @Column(name = "origin_price", nullable = false) - private int originPrice; - @Column(name = "discount_price", nullable = false) - private int discountPrice; - @Column(name = "discount_rate", nullable = false) - private int discountRate; - - // 상품 상태 - @Column(name = "secret", nullable = false) - private Boolean secret; - @Enumerated(EnumType.STRING) - @Column(name = "sale_status", nullable = false) - private SaleStatus saleStatus; - @Enumerated(EnumType.STRING) - @Column(name = "labels", length = 50) - private Labels labels; - @Enumerated(EnumType.STRING) - @Column(name = "discount_status", nullable = false) - private DiscountStatus discountStatus; - - // "오늘의 특가" 구분 필드값 - @Column(name = "is_discount", nullable = false) - private Boolean isDiscount; + @Embedded + private ProductInfo productInfo; + @Embedded + private CategoryInfo categoryInfo; + @Embedded + private ImageUrl image; + @Embedded + private Status status; @Column(name = "wish_list_count", nullable = false, columnDefinition = "int default 0") private int wishListCount; @Column(name = "product_view_count", nullable = false, columnDefinition = "int default 0") private int productViewCount; - @Column(name = "main_image", nullable = false) - private String mainImage; - @Column(name = "image1") - private String image1; - @Column(name = "image2") - private String image2; - @Column(name = "image3") - private String image3; - @Column(name = "image4") - private String image4; - @Column(name = "detail_image") - private String detailImage; - - // 카테고리 분류 - @Column(name = "l_cat_id", nullable = false) - private Long lCatId; - @Column(name = "m_cat_id") - private Long mCatId; - @Column(name = "s_cat_id") - private Long sCatId; - + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "category_id", nullable = false) + private Category category; @OneToMany(mappedBy = "product", cascade = CascadeType.ALL) - private List saleProducts = new ArrayList<>(); - - // 상품 점수(ProductScore)와 연관관게 추가 + private final List saleProducts = new ArrayList<>(); @OneToOne(mappedBy = "product", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true) private ProductScore productScore; - public Product(Category category, Long lCatId, Long mCatId, Long sCatId, - String mallSeq, String name, String description, int originPrice, int discountPrice, - int discountRate, LocalDateTime manufactureDate, Boolean secret, SaleStatus saleStatus, - DiscountStatus discountStatus, Boolean isDiscount, String brand, int wishListCount, Labels labels, - String mainImage, String image1, String image2, String image3, String image4, String detailImage, - int productViewCount + public Product(ProductInfo productInfo, CategoryInfo categoryInfo, Status status, ImageUrl image, + Category category, int wishListCount, int productViewCount ) { + this.productInfo = productInfo; + this.categoryInfo = categoryInfo; + this.status = status; + this.image = image; this.category = category; - this.lCatId = lCatId; - this.mCatId = mCatId; - this.sCatId = sCatId; - this.mallSeq = mallSeq; - this.name = name; - this.description = description; - this.originPrice = originPrice; - this.discountPrice = discountPrice; - this.discountRate = discountRate; - this.manufactureDate = manufactureDate; - this.secret = secret; - this.saleStatus = saleStatus; - this.discountStatus = discountStatus; - this.isDiscount = isDiscount; - this.brand = brand; this.wishListCount = wishListCount; - this.labels = labels; - this.mainImage = mainImage; - this.image1 = image1; - this.image2 = image2; - this.image3 = image3; - this.image4 = image4; - this.detailImage = detailImage; this.productViewCount = productViewCount; } @@ -135,7 +67,6 @@ public void decrementWishCount() { if (this.wishListCount > 0) { this.wishListCount--;} } - // 상품 점수(ProductScore)와 연관관계 편의 메서드 public void setProductScore(ProductScore productScore) { this.productScore = productScore; if(productScore != null && productScore.getProduct() != this){ diff --git a/backend/JiShop/src/main/java/com/jishop/product/domain/embed/CategoryInfo.java b/backend/JiShop/src/main/java/com/jishop/product/domain/embed/CategoryInfo.java new file mode 100644 index 00000000..cbd38e02 --- /dev/null +++ b/backend/JiShop/src/main/java/com/jishop/product/domain/embed/CategoryInfo.java @@ -0,0 +1,26 @@ +package com.jishop.product.domain.embed; + +import com.jishop.category.domain.Category; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Embeddable +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class CategoryInfo { + + @Column(name = "l_cat_id", nullable = false) + private Long lCatId; + @Column(name = "m_cat_id") + private Long mCatId; + @Column(name = "s_cat_id") + private Long sCatId; + + public CategoryInfo(Category category, Long lCatId, Long mCatId, Long sCatId) { + this.lCatId = lCatId; + this.mCatId = mCatId; + this.sCatId = sCatId; + } +} \ No newline at end of file diff --git a/backend/JiShop/src/main/java/com/jishop/product/domain/embed/ImageUrl.java b/backend/JiShop/src/main/java/com/jishop/product/domain/embed/ImageUrl.java new file mode 100644 index 00000000..c8947d37 --- /dev/null +++ b/backend/JiShop/src/main/java/com/jishop/product/domain/embed/ImageUrl.java @@ -0,0 +1,36 @@ +package com.jishop.product.domain.embed; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Embeddable +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ImageUrl { + + @Column(name = "main_image", nullable = false) + private String mainImage; + @Column(name = "image1") + private String image1; + @Column(name = "image2") + private String image2; + @Column(name = "image3") + private String image3; + @Column(name = "image4") + private String image4; + @Column(name = "detail_image") + private String detailImage; + + public ImageUrl(String mainImage, String image1, String image2, String image3, + String image4, String detailImage) { + this.mainImage = mainImage; + this.image1 = image1; + this.image2 = image2; + this.image3 = image3; + this.image4 = image4; + this.detailImage = detailImage; + } +} diff --git a/backend/JiShop/src/main/java/com/jishop/product/domain/embed/ProductInfo.java b/backend/JiShop/src/main/java/com/jishop/product/domain/embed/ProductInfo.java new file mode 100644 index 00000000..58c1dd35 --- /dev/null +++ b/backend/JiShop/src/main/java/com/jishop/product/domain/embed/ProductInfo.java @@ -0,0 +1,46 @@ +package com.jishop.product.domain.embed; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Embeddable +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ProductInfo { + + @Column(name = "name", nullable = false) + private String name; + @Column(name = "mall_seq", nullable = false) + private String mallSeq; + @Column(name = "manufacture_date", nullable = false) + private LocalDateTime manufactureDate; + @Column(name = "brand", nullable = false) + private String brand; + @Column(name = "description", nullable = false) + private String description; + + @Column(name = "origin_price", nullable = false) + private int originPrice; + @Column(name = "discount_price", nullable = false) + private int discountPrice; + @Column(name = "discount_rate", nullable = false) + private int discountRate; + + public ProductInfo(String name, String mallSeq, LocalDateTime manufactureDate, + String brand, String description, int originPrice, int discountPrice, int discountRate + ) { + this.name = name; + this.mallSeq = mallSeq; + this.manufactureDate = manufactureDate; + this.brand = brand; + this.description = description; + this.originPrice = originPrice; + this.discountPrice = discountPrice; + this.discountRate = discountRate; + } +} diff --git a/backend/JiShop/src/main/java/com/jishop/product/domain/embed/Status.java b/backend/JiShop/src/main/java/com/jishop/product/domain/embed/Status.java new file mode 100644 index 00000000..a0226349 --- /dev/null +++ b/backend/JiShop/src/main/java/com/jishop/product/domain/embed/Status.java @@ -0,0 +1,43 @@ +package com.jishop.product.domain.embed; + +import com.jishop.product.domain.DiscountStatus; +import com.jishop.product.domain.Labels; +import com.jishop.product.domain.SaleStatus; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Embeddable +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Status { + + @Column(name = "secret", nullable = false) + private Boolean secret; + @Enumerated(EnumType.STRING) + @Column(name = "sale_status", nullable = false) + private SaleStatus saleStatus; + @Enumerated(EnumType.STRING) + @Column(name = "labels", length = 50) + private Labels labels; + @Enumerated(EnumType.STRING) + @Column(name = "discount_status", nullable = false) + private DiscountStatus discountStatus; + + // "오늘의 특가" 구분 필드값 + @Column(name = "is_discount", nullable = false) + private Boolean isDiscount; + + public Status(Boolean secret, SaleStatus saleStatus, Labels labels, Boolean isDiscount, DiscountStatus discountStatus + ) { + this.secret = secret; + this.saleStatus = saleStatus; + this.labels = labels; + this.isDiscount = isDiscount; + this.discountStatus = discountStatus; + } +} diff --git a/backend/JiShop/src/main/java/com/jishop/product/dto/response/ProductDetailResponse.java b/backend/JiShop/src/main/java/com/jishop/product/dto/response/ProductDetailResponse.java index 5acde8d8..cb0e62e0 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/dto/response/ProductDetailResponse.java +++ b/backend/JiShop/src/main/java/com/jishop/product/dto/response/ProductDetailResponse.java @@ -23,13 +23,18 @@ public static ProductDetailResponse from(final ProductResponse productResponse, return new ProductDetailResponse( productResponse, - product.getDescription(), - product.getIsDiscount(), + product.getProductInfo().getDescription(), + product.getStatus().getIsDiscount(), option, reviewCount, reviewRate, - new String[] { product.getImage1(), product.getImage2(), product.getImage3(), product.getImage4() }, - product.getDetailImage(), + new String[] { + product.getImage().getImage1(), + product.getImage().getImage2(), + product.getImage().getImage3(), + product.getImage().getImage4() + }, + product.getImage().getDetailImage(), wishStatus ); } diff --git a/backend/JiShop/src/main/java/com/jishop/product/dto/response/ProductResponse.java b/backend/JiShop/src/main/java/com/jishop/product/dto/response/ProductResponse.java index 8ef6f723..c1d30f11 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/dto/response/ProductResponse.java +++ b/backend/JiShop/src/main/java/com/jishop/product/dto/response/ProductResponse.java @@ -16,13 +16,13 @@ public record ProductResponse( public static ProductResponse from(final Product product) { return new ProductResponse( product.getId(), - product.getName(), - product.getLabels(), - product.getOriginPrice(), - product.getDiscountPrice(), - product.getBrand(), - product.getDiscountRate(), - product.getMainImage() + product.getProductInfo().getName(), + product.getStatus().getLabels(), + product.getProductInfo().getOriginPrice(), + product.getProductInfo().getDiscountPrice(), + product.getProductInfo().getBrand(), + product.getProductInfo().getDiscountRate(), + product.getImage().getMainImage() ); } } diff --git a/backend/JiShop/src/main/java/com/jishop/product/implementation/ProductQueryHelperImpl.java b/backend/JiShop/src/main/java/com/jishop/product/implementation/ProductQueryHelperImpl.java index 3d948d52..f5e53a27 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/implementation/ProductQueryHelperImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/product/implementation/ProductQueryHelperImpl.java @@ -39,13 +39,13 @@ private static void addPriceRangesFiltering(final List priceRanges, for (final Integer range : priceRanges) { switch (range) { case 0 -> - priceBuilder.or(product.discountPrice.between(0, 25000)); + priceBuilder.or(product.productInfo.discountPrice.between(0, 25000)); case 25000 -> - priceBuilder.or(product.discountPrice.between(25001, 50000)); + priceBuilder.or(product.productInfo.discountPrice.between(25001, 50000)); case 50000 -> - priceBuilder.or(product.discountPrice.between(50001, 100000)); + priceBuilder.or(product.productInfo.discountPrice.between(50001, 100000)); case 100000 -> - priceBuilder.or(product.discountPrice.gt(100000)); + priceBuilder.or(product.productInfo.discountPrice.gt(100000)); default -> { } } } @@ -137,7 +137,7 @@ private void addCategory(final Long categoryId, final QProduct product, final Bo private static void addKeyword(final String keyword, final QProduct product, final BooleanBuilder builder) { builder.and( - product.name.containsIgnoreCase(keyword).or(product.description.containsIgnoreCase(keyword)) + product.productInfo.name.containsIgnoreCase(keyword).or(product.productInfo.description.containsIgnoreCase(keyword)) ); } } diff --git a/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepository.java b/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepository.java index 26d40ffd..68daa830 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepository.java +++ b/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepository.java @@ -12,10 +12,10 @@ public interface ProductRepository extends JpaRepository, ProductRepositoryQueryDsl { - @Query("SELECT p FROM Product p WHERE p.discountStatus = :status AND p.isDiscount = true") + @Query("SELECT p FROM Product p WHERE p.status.discountStatus = :status AND p.status.isDiscount = true") Page findDailDealProducts(@Param("status") DiscountStatus status, Pageable pageable); - boolean existsByBrand(String keyword); + boolean existsByProductInfo_Brand(String keyword); // "남성 셔츠" 가 검색어로 들어온 경우 +남성 +셔츠로 변환(BOOLEAN MODE) // "남성셔츠" 가 검색으로 들어온 경우 남성, 성셔, 셔츠로 변환(ngram parser) @@ -25,7 +25,7 @@ public interface ProductRepository extends JpaRepository, Product ) Long existsByFulltext(String keyword); - List findAllByBrand(String keyword); + List findAllByProductInfo_Brand(String keyword); @Query( diff --git a/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java b/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java index c563e139..0d512fcf 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java @@ -54,9 +54,9 @@ public long countProductsByCondition(final ProductRequest productRequest) { private OrderSpecifier addSorting(final String sort, final QProduct product) { return switch (sort) { case "latest" -> product.createdAt.desc(); - case "priceAsc" -> product.discountPrice.asc(); - case "priceDesc" -> product.discountPrice.desc(); - case "discount" -> product.discountRate.desc(); + case "priceAsc" -> product.productInfo.discountPrice.asc(); + case "priceDesc" -> product.productInfo.discountPrice.desc(); + case "discount" -> product.productInfo.discountRate.desc(); default -> product.wishListCount.desc(); }; } diff --git a/backend/JiShop/src/main/java/com/jishop/product/service/ProductServiceImpl.java b/backend/JiShop/src/main/java/com/jishop/product/service/ProductServiceImpl.java index 2102b7e5..00bfa7c5 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/service/ProductServiceImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/product/service/ProductServiceImpl.java @@ -62,7 +62,7 @@ public ProductDetailResponse getProduct(final User user, final Long productId) { final boolean isWished = (user != null) && productWishListRepository .isProductWishedByUser(user.getId(), productId); - final Long categoryType = product.getLCatId(); + final Long categoryType = product.getCategoryInfo().getLCatId(); final Object productsOptions; if (categoryType == 50000000L) { diff --git a/backend/JiShop/src/main/java/com/jishop/productwishlist/dto/ProductWishProductResponse.java b/backend/JiShop/src/main/java/com/jishop/productwishlist/dto/ProductWishProductResponse.java index 6397b21b..abf0280f 100644 --- a/backend/JiShop/src/main/java/com/jishop/productwishlist/dto/ProductWishProductResponse.java +++ b/backend/JiShop/src/main/java/com/jishop/productwishlist/dto/ProductWishProductResponse.java @@ -8,7 +8,7 @@ public record ProductWishProductResponse( String mainImage ) { public ProductWishProductResponse(Product product){ - this(product.getId(), product.getName(), product.getMainImage()); + this(product.getId(), product.getProductInfo().getName(), product.getImage().getMainImage()); } } diff --git a/backend/JiShop/src/main/java/com/jishop/productwishlist/repository/ProductWishListRepository.java b/backend/JiShop/src/main/java/com/jishop/productwishlist/repository/ProductWishListRepository.java index 62dc3f60..1ff55ad9 100644 --- a/backend/JiShop/src/main/java/com/jishop/productwishlist/repository/ProductWishListRepository.java +++ b/backend/JiShop/src/main/java/com/jishop/productwishlist/repository/ProductWishListRepository.java @@ -26,15 +26,15 @@ public interface ProductWishListRepository extends JpaRepository getPopularProductsByWishList(Pageable pageable); @Query("SELECT CASE WHEN COUNT(pw) > 0 THEN true ELSE false END FROM ProductWishList pw " + diff --git a/backend/JiShop/src/main/java/com/jishop/review/dto/ProductSummary.java b/backend/JiShop/src/main/java/com/jishop/review/dto/ProductSummary.java index 37bdc94c..91da4194 100644 --- a/backend/JiShop/src/main/java/com/jishop/review/dto/ProductSummary.java +++ b/backend/JiShop/src/main/java/com/jishop/review/dto/ProductSummary.java @@ -16,13 +16,13 @@ public record ProductSummary( public static ProductSummary from(Product product) { return new ProductSummary( product.getId(), - product.getName(), - product.getLabels(), - product.getOriginPrice(), - product.getDiscountPrice(), - product.getIsDiscount(), - product.getBrand(), - product.getMainImage() + product.getProductInfo().getName(), + product.getStatus().getLabels(), + product.getProductInfo().getOriginPrice(), + product.getProductInfo().getDiscountPrice(), + product.getStatus().getIsDiscount(), + product.getProductInfo().getBrand(), + product.getImage().getMainImage() ); } } diff --git a/backend/JiShop/src/main/java/com/jishop/review/repository/ReviewRepository.java b/backend/JiShop/src/main/java/com/jishop/review/repository/ReviewRepository.java index 38c39dd9..122a9178 100644 --- a/backend/JiShop/src/main/java/com/jishop/review/repository/ReviewRepository.java +++ b/backend/JiShop/src/main/java/com/jishop/review/repository/ReviewRepository.java @@ -62,9 +62,9 @@ public interface ReviewRepository extends JpaRepository { @Query(value = """ SELECT new com.jishop.review.dto.ReviewWriteResponse ( - p.id, od.id ,p.name, p.labels, p.originPrice, - p.isDiscount, p.discountRate, p.discountPrice, - p.brand, od.createdAt, p.mainImage, od.quantity, ot.optionValue) + p.id, od.id ,p.productInfo.name, p.status.labels, p.productInfo.originPrice, + p.status.isDiscount, p.productInfo.discountRate, p.productInfo.discountPrice, + p.productInfo.brand, od.createdAt, p.image.mainImage, od.quantity, ot.optionValue) FROM OrderDetail od JOIN od.order o LEFT JOIN Review r ON r.orderDetail = od JOIN od.saleProduct sp JOIN sp.product p JOIN sp.option ot WHERE o.userId = :userId AND o.status = 'PURCHASED_CONFIRMED' AND r.id IS NULL From ecb79680d03a950708dd33505b523d2528edeaff Mon Sep 17 00:00:00 2001 From: KWAK-JINHO Date: Fri, 11 Apr 2025 03:47:28 +0900 Subject: [PATCH 05/25] =?UTF-8?q?=F0=9F=94=A7=20chore:=20macOS=20=EA=B2=BD?= =?UTF-8?q?=EA=B3=A0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🔗 Resolves: #294 --- backend/JiShop/build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/JiShop/build.gradle b/backend/JiShop/build.gradle index fa537f3e..61fe6885 100644 --- a/backend/JiShop/build.gradle +++ b/backend/JiShop/build.gradle @@ -80,6 +80,9 @@ dependencies { implementation 'org.springframework.session:spring-session-data-redis' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' + // Apple Silicon native DNS resolver + implementation 'io.netty:netty-resolver-dns-native-macos:4.1.100.Final:osx-aarch_64' + // Logstash를 위한 logstash-logback-encoder // implementation 'net.logstash.logback:logstash-logback-encoder:7.2' From 9a190b1e6b000a9c64f034b76edac5f99b139a76 Mon Sep 17 00:00:00 2001 From: KWAK-JINHO Date: Sat, 12 Apr 2025 20:27:32 +0900 Subject: [PATCH 06/25] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20refactor:=20?= =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=ED=8A=B8=EB=9E=9C=EC=9E=AD=EC=85=98=20readOnly=EB=A1=9C=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 🔗 Resolves: #294 --- .../com/jishop/category/service/Impl/CategoryServiceImpl.java | 2 +- .../jishop/product/implementation/ProductQueryHelperImpl.java | 2 -- .../java/com/jishop/product/service/ProductServiceImpl.java | 3 --- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/backend/JiShop/src/main/java/com/jishop/category/service/Impl/CategoryServiceImpl.java b/backend/JiShop/src/main/java/com/jishop/category/service/Impl/CategoryServiceImpl.java index 5b9ea43c..499e81ea 100644 --- a/backend/JiShop/src/main/java/com/jishop/category/service/Impl/CategoryServiceImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/category/service/Impl/CategoryServiceImpl.java @@ -17,7 +17,7 @@ @Slf4j @Service -@Transactional +@Transactional(readOnly = true) @RequiredArgsConstructor public class CategoryServiceImpl implements CategoryService { diff --git a/backend/JiShop/src/main/java/com/jishop/product/implementation/ProductQueryHelperImpl.java b/backend/JiShop/src/main/java/com/jishop/product/implementation/ProductQueryHelperImpl.java index f5e53a27..e20d55d4 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/implementation/ProductQueryHelperImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/product/implementation/ProductQueryHelperImpl.java @@ -8,12 +8,10 @@ import com.querydsl.jpa.JPAExpressions; import com.querydsl.jpa.JPQLQuery; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.util.List; -@Slf4j @Component @RequiredArgsConstructor public class ProductQueryHelperImpl implements ProductQueryHelper { diff --git a/backend/JiShop/src/main/java/com/jishop/product/service/ProductServiceImpl.java b/backend/JiShop/src/main/java/com/jishop/product/service/ProductServiceImpl.java index 00bfa7c5..25b11211 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/service/ProductServiceImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/product/service/ProductServiceImpl.java @@ -1,6 +1,5 @@ package com.jishop.product.service; -import com.jishop.category.service.CategoryService; import com.jishop.common.exception.DomainException; import com.jishop.common.exception.ErrorType; import com.jishop.member.domain.User; @@ -32,8 +31,6 @@ @Transactional(readOnly = true) public class ProductServiceImpl implements ProductService { - private final CategoryService categoryService; - private final ProductRepository productRepository; private final ReviewProductRepository reviewProductRepository; private final ProductWishListRepository productWishListRepository; From 795438b03724823f06efd125cece82bba6f970fc Mon Sep 17 00:00:00 2001 From: KWAK-JINHO Date: Sat, 12 Apr 2025 21:21:23 +0900 Subject: [PATCH 07/25] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20refactor:=20?= =?UTF-8?q?=EC=83=81=ED=92=88=20=EC=A1=B0=ED=9A=8C=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?dto=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - List.of로 변경하여 불변하게 전달 🔗 Resolves: #294 --- .../java/com/jishop/product/dto/request/ProductRequest.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/backend/JiShop/src/main/java/com/jishop/product/dto/request/ProductRequest.java b/backend/JiShop/src/main/java/com/jishop/product/dto/request/ProductRequest.java index ddcf0b28..7b36b3a0 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/dto/request/ProductRequest.java +++ b/backend/JiShop/src/main/java/com/jishop/product/dto/request/ProductRequest.java @@ -3,7 +3,6 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Pattern; -import java.util.Arrays; import java.util.List; public record ProductRequest( @@ -22,8 +21,8 @@ public record ProductRequest( public ProductRequest { if (sort == null || sort.isEmpty()) {sort = "wish";} if (priceRanges == null || priceRanges.isEmpty()) { - priceRanges = Arrays.asList(0, 25000, 50000, 100000); + priceRanges = List.of(0, 25000, 50000, 100000); } - if (ratings == null || ratings.isEmpty()) {ratings = Arrays.asList(1, 2, 3, 4, 5);} + if (ratings == null || ratings.isEmpty()) {ratings = List.of(1, 2, 3, 4, 5);} } } From 1a98ac13a4611a23ad9e2f7368d206379390ea5d Mon Sep 17 00:00:00 2001 From: KWAK-JINHO Date: Sun, 13 Apr 2025 03:53:34 +0900 Subject: [PATCH 08/25] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20refactor:=20QClas?= =?UTF-8?q?s=20=EC=83=9D=EC=84=B1=20=EC=9C=84=EC=B9=98=20=EC=A7=80?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🔗 Resolves: #294 --- backend/JiShop/build.gradle | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/backend/JiShop/build.gradle b/backend/JiShop/build.gradle index 61fe6885..afc10d4e 100644 --- a/backend/JiShop/build.gradle +++ b/backend/JiShop/build.gradle @@ -46,7 +46,7 @@ dependencies { // Querydsl implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' - annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" + annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" annotationProcessor "jakarta.annotation:jakarta.annotation-api" annotationProcessor "jakarta.persistence:jakarta.persistence-api" @@ -100,3 +100,13 @@ dependencyManagement { tasks.named('test') { useJUnitPlatform() } + +def generated = 'build/generated/sources/annotationProcessor/java/main' + +tasks.withType(JavaCompile).configureEach { + options.getGeneratedSourceOutputDirectory().set(file(generated)) +} + +clean { + delete file(generated) +} \ No newline at end of file From af70da95bf0c5f7ab959788e2b43e234b48a336b Mon Sep 17 00:00:00 2001 From: KWAK-JINHO Date: Mon, 14 Apr 2025 12:50:27 +0900 Subject: [PATCH 09/25] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20refactor:=20?= =?UTF-8?q?=EC=83=81=ED=92=88=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EB=A6=AC=ED=8E=99=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 구현계층 삭제 - keyword 빈 값으로 받을 수 있게 변경 - 서비스 레이어 분리 🔗 Resolves: #294 --- .../controller/ProductControllerImpl.java | 4 +- .../product/dto/request/ProductRequest.java | 14 +- .../implementation/ProductQueryHelper.java | 12 -- .../ProductQueryHelperImpl.java | 141 ------------------ .../repository/ProductRepositoryQueryDsl.java | 4 +- .../ProductRepositoryQueryDslImpl.java | 133 +++++++++++++++-- .../product/service/ProductService.java | 3 - .../service/ProductWishlistService.java | 13 ++ .../{ => impl}/ProductServiceImpl.java | 44 ++++-- .../impl/ProductWishlistServiceImpl.java | 38 +++++ 10 files changed, 209 insertions(+), 197 deletions(-) delete mode 100644 backend/JiShop/src/main/java/com/jishop/product/implementation/ProductQueryHelper.java delete mode 100644 backend/JiShop/src/main/java/com/jishop/product/implementation/ProductQueryHelperImpl.java create mode 100644 backend/JiShop/src/main/java/com/jishop/product/service/ProductWishlistService.java rename backend/JiShop/src/main/java/com/jishop/product/service/{ => impl}/ProductServiceImpl.java (78%) create mode 100644 backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductWishlistServiceImpl.java diff --git a/backend/JiShop/src/main/java/com/jishop/product/controller/ProductControllerImpl.java b/backend/JiShop/src/main/java/com/jishop/product/controller/ProductControllerImpl.java index 57fd6703..fde2f043 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/controller/ProductControllerImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/product/controller/ProductControllerImpl.java @@ -7,6 +7,7 @@ import com.jishop.product.dto.response.ProductResponse; import com.jishop.product.dto.response.TodaySpecialListResponse; import com.jishop.product.service.ProductService; +import com.jishop.product.service.ProductWishlistService; import lombok.RequiredArgsConstructor; import org.springframework.data.web.PagedModel; import org.springframework.validation.annotation.Validated; @@ -20,6 +21,7 @@ public class ProductControllerImpl implements ProductController { private final ProductService productService; + private final ProductWishlistService productWishListService; @Override @GetMapping @@ -51,7 +53,7 @@ public List getProductByWishTopTen( if (page < 0 || page > 100) {page = 0;} if (size <= 0 || size > 100) {size = 8;} - return productService.getProductsByWishList(page, size); + return productWishListService.getProductsByWishList(page, size); } @Override diff --git a/backend/JiShop/src/main/java/com/jishop/product/dto/request/ProductRequest.java b/backend/JiShop/src/main/java/com/jishop/product/dto/request/ProductRequest.java index 7b36b3a0..7d0d3e4a 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/dto/request/ProductRequest.java +++ b/backend/JiShop/src/main/java/com/jishop/product/dto/request/ProductRequest.java @@ -1,16 +1,13 @@ package com.jishop.product.dto.request; -import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Pattern; import java.util.List; public record ProductRequest( - @NotBlank String keyword, - @Pattern(regexp = "^(wish|discount|latest|priceAsc|priceDesc)$", - message = "정렬 기준은 'wish', 'discount', 'latest', 'priceAsc', 'priceDesc' 중 하나여야 합니다") + message = "정렬 기준은 'wish', 'discount', 'latest', 'priceAsc', 'priceDesc' 중 하나여야 합니다") String sort, Long categoryId, // 가격 필터 (값 범위: 0~25000, 25000~50000, 50000~100000, 100000~) @@ -19,10 +16,9 @@ public record ProductRequest( List ratings ){ public ProductRequest { - if (sort == null || sort.isEmpty()) {sort = "wish";} - if (priceRanges == null || priceRanges.isEmpty()) { - priceRanges = List.of(0, 25000, 50000, 100000); - } - if (ratings == null || ratings.isEmpty()) {ratings = List.of(1, 2, 3, 4, 5);} + if (keyword == null) { keyword = ""; } + if (sort == null || sort.isEmpty()) { sort = "wish"; } + if (priceRanges == null || priceRanges.isEmpty()) { priceRanges = List.of(0, 25000, 50000, 100000); } + if (ratings == null || ratings.isEmpty()) { ratings = List.of(1, 2, 3, 4, 5); } } } diff --git a/backend/JiShop/src/main/java/com/jishop/product/implementation/ProductQueryHelper.java b/backend/JiShop/src/main/java/com/jishop/product/implementation/ProductQueryHelper.java deleted file mode 100644 index 11fdbb5a..00000000 --- a/backend/JiShop/src/main/java/com/jishop/product/implementation/ProductQueryHelper.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.jishop.product.implementation; - -import com.jishop.product.domain.QProduct; -import com.jishop.product.dto.request.ProductRequest; -import com.jishop.reviewproduct.domain.QReviewProduct; -import com.querydsl.core.BooleanBuilder; - -public interface ProductQueryHelper { - - BooleanBuilder findProductsByCondition(final ProductRequest request, - final QProduct product, final QReviewProduct reviewProduct); -} diff --git a/backend/JiShop/src/main/java/com/jishop/product/implementation/ProductQueryHelperImpl.java b/backend/JiShop/src/main/java/com/jishop/product/implementation/ProductQueryHelperImpl.java deleted file mode 100644 index e20d55d4..00000000 --- a/backend/JiShop/src/main/java/com/jishop/product/implementation/ProductQueryHelperImpl.java +++ /dev/null @@ -1,141 +0,0 @@ -package com.jishop.product.implementation; - -import com.jishop.category.repository.CategoryRepository; -import com.jishop.product.domain.QProduct; -import com.jishop.product.dto.request.ProductRequest; -import com.jishop.reviewproduct.domain.QReviewProduct; -import com.querydsl.core.BooleanBuilder; -import com.querydsl.jpa.JPAExpressions; -import com.querydsl.jpa.JPQLQuery; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; - -import java.util.List; - -@Component -@RequiredArgsConstructor -public class ProductQueryHelperImpl implements ProductQueryHelper { - - private final CategoryRepository categoryRepository; - - @Override - public BooleanBuilder findProductsByCondition( - final ProductRequest productRequest, final QProduct product, final QReviewProduct reviewProduct) { - final BooleanBuilder builder = new BooleanBuilder(); - - addPriceRangesFiltering(productRequest.priceRanges(), product, builder); - addRatingsFilter(productRequest.ratings(), reviewProduct, product, builder); - addCategory(productRequest.categoryId(), product, builder); - addKeyword(productRequest.keyword(), product, builder); - return builder; - } - - private static void addPriceRangesFiltering(final List priceRanges, - final QProduct product, final BooleanBuilder builder) { - final BooleanBuilder priceBuilder = new BooleanBuilder(); - - for (final Integer range : priceRanges) { - switch (range) { - case 0 -> - priceBuilder.or(product.productInfo.discountPrice.between(0, 25000)); - case 25000 -> - priceBuilder.or(product.productInfo.discountPrice.between(25001, 50000)); - case 50000 -> - priceBuilder.or(product.productInfo.discountPrice.between(50001, 100000)); - case 100000 -> - priceBuilder.or(product.productInfo.discountPrice.gt(100000)); - default -> { } - } - } - - if (priceBuilder.hasValue()) { - builder.and(priceBuilder); - } - } - - private static void addRatingsFilter(final List ratings, final QReviewProduct reviewProduct, - final QProduct product, final BooleanBuilder builder) { - if (ratings.size() != 5 || - !ratings.contains(1) || - !ratings.contains(2) || - !ratings.contains(3) || - !ratings.contains(4) || - !ratings.contains(5)) { - - final BooleanBuilder ratingBuilder = new BooleanBuilder(); - - // 나눗셈 오류 방지 - ratingBuilder.and(reviewProduct.reviewCount.gt(0)); - - // 평점 = 리뷰총점 / 리뷰개수 - for (final Integer rating : ratings) { - switch (rating) { - case 1 -> ratingBuilder.or( - reviewProduct.reviewScore.divide(reviewProduct.reviewCount) - .goe(1.0).and( - reviewProduct.reviewScore.divide(reviewProduct.reviewCount).lt(2.0) - ) - ); - case 2 -> ratingBuilder.or( - reviewProduct.reviewScore.divide(reviewProduct.reviewCount) - .goe(2.0).and( - reviewProduct.reviewScore.divide(reviewProduct.reviewCount).lt(3.0) - ) - ); - case 3 -> ratingBuilder.or( - reviewProduct.reviewScore.divide(reviewProduct.reviewCount) - .goe(3.0).and( - reviewProduct.reviewScore.divide(reviewProduct.reviewCount).lt(4.0) - ) - ); - case 4 -> ratingBuilder.or( - reviewProduct.reviewScore.divide(reviewProduct.reviewCount) - .goe(4.0).and( - reviewProduct.reviewScore.divide(reviewProduct.reviewCount).lt(5.0) - ) - ); - case 5 -> ratingBuilder.or( - reviewProduct.reviewScore.divide(reviewProduct.reviewCount).goe(5.0) - ); - default -> {} - } - } - - if (ratingBuilder.hasValue()) { - final JPQLQuery subQuery = JPAExpressions - .select(reviewProduct.product.id) - .from(reviewProduct) - .where(ratingBuilder); - builder.and(product.id.in(subQuery)); - - // 아래는 직접 JOIN 방식 (메인 쿼리에 JOIN 조건 추가 필요) - // queryFactory.selectFrom(product) - // .join(reviewProduct).on(product.id.eq(reviewProduct.productId)) - // .where(builder.and(ratingBuilder)) - } - } - } - - private void addCategory(final Long categoryId, final QProduct product, final BooleanBuilder builder) { - if (categoryId == null) return; - - final List categoryIds = categoryRepository.findById(categoryId) - .map(category -> { - final List subCategoryPKs = categoryRepository.findIdsByCurrentIds( - categoryRepository.findAllSubCategoryIds(categoryId) - ); - return subCategoryPKs.isEmpty() - ? List.of(category.getId()) - : subCategoryPKs; - }) - .orElse(List.of(-1L)); - - builder.and(product.category.id.in(categoryIds)); - } - - private static void addKeyword(final String keyword, final QProduct product, final BooleanBuilder builder) { - builder.and( - product.productInfo.name.containsIgnoreCase(keyword).or(product.productInfo.description.containsIgnoreCase(keyword)) - ); - } -} diff --git a/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDsl.java b/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDsl.java index 01544cc6..3f69c81d 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDsl.java +++ b/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDsl.java @@ -7,7 +7,7 @@ public interface ProductRepositoryQueryDsl { - List findProductsByCondition(final ProductRequest request, final int page, final int size); + List findProductsByCondition(final ProductRequest request, final int page, final int size, final List categoryIds); - long countProductsByCondition(final ProductRequest request); + long countProductsByCondition(final ProductRequest request, final List categoryIds); } diff --git a/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java b/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java index 0d512fcf..bcdf6282 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java @@ -3,10 +3,11 @@ import com.jishop.product.domain.Product; import com.jishop.product.domain.QProduct; import com.jishop.product.dto.request.ProductRequest; -import com.jishop.product.implementation.ProductQueryHelper; import com.jishop.reviewproduct.domain.QReviewProduct; import com.querydsl.core.BooleanBuilder; import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.jpa.JPAExpressions; +import com.querydsl.jpa.JPQLQuery; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @@ -14,24 +15,32 @@ import java.util.List; import java.util.Optional; +import static com.jishop.product.domain.embed.QProductInfo.productInfo; + @Repository @RequiredArgsConstructor public class ProductRepositoryQueryDslImpl implements ProductRepositoryQueryDsl { private final JPAQueryFactory queryFactory; - private final ProductQueryHelper productQueryHelper; - private final QProduct product = QProduct.product; - private final QReviewProduct reviewProduct = QReviewProduct.reviewProduct; + private static final QProduct product = QProduct.product; + private static final QReviewProduct reviewProduct = QReviewProduct.reviewProduct; @Override - public List findProductsByCondition(final ProductRequest productRequest, final int page, final int size) { - final BooleanBuilder filterBuilder = productQueryHelper - .findProductsByCondition(productRequest, product, reviewProduct); + public List findProductsByCondition(final ProductRequest productRequest, + final int page, + final int size, + final List categoryIds) { + final BooleanBuilder builder = new BooleanBuilder(); + + addPriceRangesFiltering(productRequest.priceRanges(), builder); + addRatingsFilter(productRequest.ratings(), builder); + addCategoryFilter(categoryIds, builder); + addKeywordFilter(productRequest.keyword(), builder); - final OrderSpecifier orderSpecifier = addSorting(productRequest.sort(), product); + final OrderSpecifier orderSpecifier = addSorting(productRequest.sort()); return queryFactory.selectFrom(product) - .where(filterBuilder) + .where(builder) .orderBy(orderSpecifier) .offset((long) page * size) .limit(size) @@ -39,19 +48,23 @@ public List findProductsByCondition(final ProductRequest productRequest } @Override - public long countProductsByCondition(final ProductRequest productRequest) { - final BooleanBuilder filterBuilder = productQueryHelper - .findProductsByCondition(productRequest, product, reviewProduct); + public long countProductsByCondition(final ProductRequest productRequest, final List categoryIds) { + final BooleanBuilder builder = new BooleanBuilder(); + + addPriceRangesFiltering(productRequest.priceRanges(), builder); + addRatingsFilter(productRequest.ratings(), builder); + addCategoryFilter(categoryIds, builder); + addKeywordFilter(productRequest.keyword(), builder); return Optional.ofNullable( queryFactory.select(product.count()) .from(product) - .where(filterBuilder) + .where(builder) .fetchOne() ).orElse(0L); } - private OrderSpecifier addSorting(final String sort, final QProduct product) { + private OrderSpecifier addSorting(final String sort) { return switch (sort) { case "latest" -> product.createdAt.desc(); case "priceAsc" -> product.productInfo.discountPrice.asc(); @@ -60,4 +73,96 @@ private OrderSpecifier addSorting(final String sort, final QProduct product) default -> product.wishListCount.desc(); }; } + + private void addPriceRangesFiltering(final List priceRanges, final BooleanBuilder builder) { + final BooleanBuilder priceBuilder = new BooleanBuilder(); + + for (final Integer range : priceRanges) { + switch (range) { + case 0 -> + priceBuilder.or(productInfo.discountPrice.between(0, 25000)); + case 25000 -> + priceBuilder.or(productInfo.discountPrice.between(25001, 50000)); + case 50000 -> + priceBuilder.or(productInfo.discountPrice.between(50001, 100000)); + case 100000 -> + priceBuilder.or(productInfo.discountPrice.gt(100000)); + default -> { } + } + } + + if (priceBuilder.hasValue()) { + builder.and(priceBuilder); + } + } + + private static void addRatingsFilter(final List ratings, final BooleanBuilder builder) { + if (ratings.size() != 5 || + !ratings.contains(1) || + !ratings.contains(2) || + !ratings.contains(3) || + !ratings.contains(4) || + !ratings.contains(5)) { + + final BooleanBuilder ratingBuilder = new BooleanBuilder(); + + // 나눗셈 오류 방지 + ratingBuilder.and(reviewProduct.reviewCount.gt(0)); + + // 평점 = 리뷰총점 / 리뷰개수 + for (final Integer rating : ratings) { + switch (rating) { + case 1 -> ratingBuilder.or( + reviewProduct.reviewScore.divide(reviewProduct.reviewCount) + .goe(1.0).and( + reviewProduct.reviewScore.divide(reviewProduct.reviewCount).lt(2.0) + ) + ); + case 2 -> ratingBuilder.or( + reviewProduct.reviewScore.divide(reviewProduct.reviewCount) + .goe(2.0).and( + reviewProduct.reviewScore.divide(reviewProduct.reviewCount).lt(3.0) + ) + ); + case 3 -> ratingBuilder.or( + reviewProduct.reviewScore.divide(reviewProduct.reviewCount) + .goe(3.0).and( + reviewProduct.reviewScore.divide(reviewProduct.reviewCount).lt(4.0) + ) + ); + case 4 -> ratingBuilder.or( + reviewProduct.reviewScore.divide(reviewProduct.reviewCount) + .goe(4.0).and( + reviewProduct.reviewScore.divide(reviewProduct.reviewCount).lt(5.0) + ) + ); + case 5 -> ratingBuilder.or( + reviewProduct.reviewScore.divide(reviewProduct.reviewCount).goe(5.0) + ); + default -> {} + } + } + + if (ratingBuilder.hasValue()) { + final JPQLQuery subQuery = JPAExpressions + .select(reviewProduct.product.id) + .from(reviewProduct) + .where(ratingBuilder); + builder.and(product.id.in(subQuery)); + } + } + } + + private void addCategoryFilter(final List categoryIds, final BooleanBuilder builder) { + if (categoryIds == null || categoryIds.isEmpty()) return; + + builder.and(product.category.id.in(categoryIds)); + } + + private static void addKeywordFilter(final String keyword, final BooleanBuilder builder) { + builder.and( + product.productInfo.name.containsIgnoreCase(keyword) + .or(product.productInfo.description.containsIgnoreCase(keyword)) + ); + } } \ No newline at end of file diff --git a/backend/JiShop/src/main/java/com/jishop/product/service/ProductService.java b/backend/JiShop/src/main/java/com/jishop/product/service/ProductService.java index 08906e73..a55920f4 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/service/ProductService.java +++ b/backend/JiShop/src/main/java/com/jishop/product/service/ProductService.java @@ -7,15 +7,12 @@ import com.jishop.product.dto.response.TodaySpecialListResponse; import org.springframework.data.web.PagedModel; -import java.util.List; - public interface ProductService { PagedModel getProductList(final ProductRequest productRequest, final int page, final int size); ProductDetailResponse getProduct(final User user, final Long productId); - List getProductsByWishList(final int page, final int size); PagedModel getProductsByTodaySpecial(final int page, final int size); } diff --git a/backend/JiShop/src/main/java/com/jishop/product/service/ProductWishlistService.java b/backend/JiShop/src/main/java/com/jishop/product/service/ProductWishlistService.java new file mode 100644 index 00000000..ea66264b --- /dev/null +++ b/backend/JiShop/src/main/java/com/jishop/product/service/ProductWishlistService.java @@ -0,0 +1,13 @@ +package com.jishop.product.service; + +import com.jishop.member.domain.User; +import com.jishop.product.dto.response.ProductResponse; + +import java.util.List; + +public interface ProductWishlistService { + + List getProductsByWishList(final int page, final int size); + + boolean isProductWishedByUser(final User user, final Long productId); +} diff --git a/backend/JiShop/src/main/java/com/jishop/product/service/ProductServiceImpl.java b/backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductServiceImpl.java similarity index 78% rename from backend/JiShop/src/main/java/com/jishop/product/service/ProductServiceImpl.java rename to backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductServiceImpl.java index 25b11211..ebea8129 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/service/ProductServiceImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductServiceImpl.java @@ -1,5 +1,6 @@ -package com.jishop.product.service; +package com.jishop.product.service.impl; +import com.jishop.category.repository.CategoryRepository; import com.jishop.common.exception.DomainException; import com.jishop.common.exception.ErrorType; import com.jishop.member.domain.User; @@ -14,7 +15,8 @@ import com.jishop.product.dto.response.ProductResponse; import com.jishop.product.dto.response.TodaySpecialListResponse; import com.jishop.product.repository.ProductRepository; -import com.jishop.productwishlist.repository.ProductWishListRepository; +import com.jishop.product.service.ProductService; +import com.jishop.product.service.ProductWishlistService; import com.jishop.reviewproduct.domain.ReviewProduct; import com.jishop.reviewproduct.repository.ReviewProductRepository; import com.jishop.saleproduct.repository.SaleProductRepository; @@ -33,18 +35,22 @@ public class ProductServiceImpl implements ProductService { private final ProductRepository productRepository; private final ReviewProductRepository reviewProductRepository; - private final ProductWishListRepository productWishListRepository; private final SaleProductRepository saleProductRepository; + private final CategoryRepository categoryRepository; private final OrderDetailRepository orderDetailRepository; + private final ProductWishlistService wishlistService; + @Override public PagedModel getProductList(final ProductRequest productRequest, final int page, final int size) { - final List selectedProducts = productRepository.findProductsByCondition(productRequest, page, size); + final List categoryIds = getCategoryIdsWithSubcategories(productRequest.categoryId()); + + final List selectedProducts = productRepository.findProductsByCondition(productRequest, page, size, categoryIds); final List productListResponse = selectedProducts.stream() .map(ProductResponse::from).toList(); - final long totalCount = productRepository.countProductsByCondition(productRequest); + final long totalCount = productRepository.countProductsByCondition(productRequest, categoryIds); final Pageable pageable = PageRequest.of(page, size); final Page pagedProductsResponse = new PageImpl<>(productListResponse, pageable, totalCount); @@ -56,8 +62,7 @@ public ProductDetailResponse getProduct(final User user, final Long productId) { final Product product = productRepository.findById(productId) .orElseThrow(() -> new DomainException(ErrorType.PRODUCT_NOT_FOUND)); - final boolean isWished = (user != null) && productWishListRepository - .isProductWishedByUser(user.getId(), productId); + final boolean isWished = wishlistService.isProductWishedByUser(user, productId); final Long categoryType = product.getCategoryInfo().getLCatId(); @@ -82,14 +87,6 @@ public ProductDetailResponse getProduct(final User user, final Long productId) { .from(ProductResponse.from(product), product, isWished, reviewCount, reviewRate, productsOptions); } - @Override - public List getProductsByWishList(final int page, final int size) { - final Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); - final Page productPage = productWishListRepository.getPopularProductsByWishList(pageable); - - return productPage.stream().map(ProductResponse::from).toList(); - } - @Override public PagedModel getProductsByTodaySpecial(final int page, final int size) { final Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); @@ -103,4 +100,21 @@ public PagedModel getProductsByTodaySpecial(final int return new PagedModel<>(responsePage); } + + + + private List getCategoryIdsWithSubcategories(Long categoryId) { + if (categoryId == null) return List.of(); + + return categoryRepository.findById(categoryId) + .map(category -> { + final List subCategoryPKs = categoryRepository.findIdsByCurrentIds( + categoryRepository.findAllSubCategoryIds(categoryId) + ); + return subCategoryPKs.isEmpty() + ? List.of(category.getId()) + : subCategoryPKs; + }) + .orElse(List.of()); + } } diff --git a/backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductWishlistServiceImpl.java b/backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductWishlistServiceImpl.java new file mode 100644 index 00000000..99700960 --- /dev/null +++ b/backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductWishlistServiceImpl.java @@ -0,0 +1,38 @@ +package com.jishop.product.service.impl; + +import com.jishop.member.domain.User; +import com.jishop.product.domain.Product; +import com.jishop.product.dto.response.ProductResponse; +import com.jishop.product.service.ProductWishlistService; +import com.jishop.productwishlist.repository.ProductWishListRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ProductWishlistServiceImpl implements ProductWishlistService { + + private final ProductWishListRepository productWishListRepository; + + @Override + public List getProductsByWishList(final int page, final int size) { + final Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); + final Page productPage = productWishListRepository.getPopularProductsByWishList(pageable); + + return productPage.stream().map(ProductResponse::from).toList(); + } + + @Override + public boolean isProductWishedByUser(final User user, final Long productId) { + + return (user != null) && productWishListRepository.isProductWishedByUser(user.getId(), productId); + } +} From bb0c689c1819321bb42774503e3fdea923d08775 Mon Sep 17 00:00:00 2001 From: KWAK-JINHO Date: Mon, 14 Apr 2025 13:02:51 +0900 Subject: [PATCH 10/25] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20refactor:=20Produ?= =?UTF-8?q?ctRepositoryQueryDslImpl=20=EB=A6=AC=ED=8E=99=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 필터 메서드 호출 전 null 체크 추가 🔗 Resolves: #294 --- .../ProductRepositoryQueryDslImpl.java | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java b/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java index bcdf6282..00a4d87b 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java @@ -32,10 +32,18 @@ public List findProductsByCondition(final ProductRequest productRequest final List categoryIds) { final BooleanBuilder builder = new BooleanBuilder(); - addPriceRangesFiltering(productRequest.priceRanges(), builder); - addRatingsFilter(productRequest.ratings(), builder); - addCategoryFilter(categoryIds, builder); - addKeywordFilter(productRequest.keyword(), builder); + if (productRequest.priceRanges() != null && !productRequest.priceRanges().isEmpty()) { + addPriceRangesFiltering(productRequest.priceRanges(), builder); + } + if (productRequest.ratings() != null && !productRequest.ratings().isEmpty()) { + addRatingsFilter(productRequest.ratings(), builder); + } + if (categoryIds != null && !categoryIds.isEmpty()) { + addCategoryFilter(categoryIds, builder); + } + if (productRequest.keyword() != null && !productRequest.keyword().trim().isEmpty()) { + addKeywordFilter(productRequest.keyword(), builder); + } final OrderSpecifier orderSpecifier = addSorting(productRequest.sort()); @@ -51,10 +59,18 @@ public List findProductsByCondition(final ProductRequest productRequest public long countProductsByCondition(final ProductRequest productRequest, final List categoryIds) { final BooleanBuilder builder = new BooleanBuilder(); - addPriceRangesFiltering(productRequest.priceRanges(), builder); - addRatingsFilter(productRequest.ratings(), builder); - addCategoryFilter(categoryIds, builder); - addKeywordFilter(productRequest.keyword(), builder); + if (productRequest.priceRanges() != null && !productRequest.priceRanges().isEmpty()) { + addPriceRangesFiltering(productRequest.priceRanges(), builder); + } + if (productRequest.ratings() != null && !productRequest.ratings().isEmpty()) { + addRatingsFilter(productRequest.ratings(), builder); + } + if (categoryIds != null && !categoryIds.isEmpty()) { + addCategoryFilter(categoryIds, builder); + } + if (productRequest.keyword() != null && !productRequest.keyword().trim().isEmpty()) { + addKeywordFilter(productRequest.keyword(), builder); + } return Optional.ofNullable( queryFactory.select(product.count()) @@ -154,7 +170,6 @@ private static void addRatingsFilter(final List ratings, final BooleanB } private void addCategoryFilter(final List categoryIds, final BooleanBuilder builder) { - if (categoryIds == null || categoryIds.isEmpty()) return; builder.and(product.category.id.in(categoryIds)); } From 554e1c33d63785c3bf14967a28e0720666ffb760 Mon Sep 17 00:00:00 2001 From: KWAK-JINHO Date: Mon, 14 Apr 2025 14:07:37 +0900 Subject: [PATCH 11/25] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20refactor:=20?= =?UTF-8?q?=EB=B3=84=EC=A0=90=ED=95=84=ED=84=B0=EB=A7=81=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - or 조건으로 카운트개수만 만족해도 필터에 추가 되던 현상 and 조건으로 수정하여 해결 🔗 Resolves: #294 --- .../ProductRepositoryQueryDslImpl.java | 62 ++++++++++++------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java b/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java index 00a4d87b..ad6b0946 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java @@ -6,6 +6,8 @@ import com.jishop.reviewproduct.domain.QReviewProduct; import com.querydsl.core.BooleanBuilder; import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.core.types.dsl.NumberExpression; import com.querydsl.jpa.JPAExpressions; import com.querydsl.jpa.JPQLQuery; import com.querydsl.jpa.impl.JPAQueryFactory; @@ -47,6 +49,16 @@ public List findProductsByCondition(final ProductRequest productRequest final OrderSpecifier orderSpecifier = addSorting(productRequest.sort()); +// BooleanBuilder testBuilder = new BooleanBuilder(); +// testBuilder.and(reviewProduct.reviewCount.gt(0)); +// +// JPQLQuery simpleSubQuery = JPAExpressions +// .select(reviewProduct.product.id) +// .from(reviewProduct) +// .where(testBuilder); +// +// builder.and(product.id.in(simpleSubQuery)); + return queryFactory.selectFrom(product) .where(builder) .orderBy(orderSpecifier) @@ -122,43 +134,46 @@ private static void addRatingsFilter(final List ratings, final BooleanB final BooleanBuilder ratingBuilder = new BooleanBuilder(); - // 나눗셈 오류 방지 + // review_count > 0 조건을 AND로 추가 (OR 대신) ratingBuilder.and(reviewProduct.reviewCount.gt(0)); + // 평점 필터링을 위한 내부 Builder + BooleanBuilder ratingRangeBuilder = new BooleanBuilder(); + + NumberExpression avgRating = Expressions.numberTemplate( + Double.class, + "1.0 * {0} / {1}", + reviewProduct.reviewScore, + reviewProduct.reviewCount + ); + // 평점 = 리뷰총점 / 리뷰개수 for (final Integer rating : ratings) { switch (rating) { - case 1 -> ratingBuilder.or( - reviewProduct.reviewScore.divide(reviewProduct.reviewCount) - .goe(1.0).and( - reviewProduct.reviewScore.divide(reviewProduct.reviewCount).lt(2.0) - ) + case 1 -> ratingRangeBuilder.or( + avgRating.goe(1.0).and(avgRating.lt(2.0)) ); - case 2 -> ratingBuilder.or( - reviewProduct.reviewScore.divide(reviewProduct.reviewCount) - .goe(2.0).and( - reviewProduct.reviewScore.divide(reviewProduct.reviewCount).lt(3.0) - ) + case 2 -> ratingRangeBuilder.or( + avgRating.goe(2.0).and(avgRating.lt(3.0)) ); - case 3 -> ratingBuilder.or( - reviewProduct.reviewScore.divide(reviewProduct.reviewCount) - .goe(3.0).and( - reviewProduct.reviewScore.divide(reviewProduct.reviewCount).lt(4.0) - ) + case 3 -> ratingRangeBuilder.or( + avgRating.goe(3.0).and(avgRating.lt(4.0)) ); - case 4 -> ratingBuilder.or( - reviewProduct.reviewScore.divide(reviewProduct.reviewCount) - .goe(4.0).and( - reviewProduct.reviewScore.divide(reviewProduct.reviewCount).lt(5.0) - ) + case 4 -> ratingRangeBuilder.or( + avgRating.goe(4.0).and(avgRating.lt(5.0)) ); - case 5 -> ratingBuilder.or( - reviewProduct.reviewScore.divide(reviewProduct.reviewCount).goe(5.0) + case 5 -> ratingRangeBuilder.or( + avgRating.goe(5.0) ); default -> {} } } + // 평점 범위 조건을 메인 rating builder에 AND로 추가 + if (ratingRangeBuilder.hasValue()) { + ratingBuilder.and(ratingRangeBuilder); + } + if (ratingBuilder.hasValue()) { final JPQLQuery subQuery = JPAExpressions .select(reviewProduct.product.id) @@ -170,7 +185,6 @@ private static void addRatingsFilter(final List ratings, final BooleanB } private void addCategoryFilter(final List categoryIds, final BooleanBuilder builder) { - builder.and(product.category.id.in(categoryIds)); } From 679cc0f36a6ffd72c48dde76a5368f51fba14d3b Mon Sep 17 00:00:00 2001 From: KWAK-JINHO Date: Mon, 14 Apr 2025 16:41:59 +0900 Subject: [PATCH 12/25] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20refactor:=20?= =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20=ED=97=A4=EB=8D=94=20?= =?UTF-8?q?=EC=83=81=ED=92=88=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EB=A6=AC=ED=8E=99=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - products 서비스로 위치변경 🔗 Resolves: #294 --- .../controller/CategoryController.java | 4 -- .../controller/CategoryControllerImpl.java | 13 ------- .../category/service/CategoryService.java | 4 +- .../product/controller/ProductController.java | 7 +++- .../controller/ProductControllerImpl.java | 18 +++++++-- .../repository/ProductRepositoryQueryDsl.java | 5 ++- .../service/ProductCategoryService.java | 9 +++++ .../impl/ProductCategoryServiceImpl.java | 38 +++++++++++++++++++ 8 files changed, 72 insertions(+), 26 deletions(-) create mode 100644 backend/JiShop/src/main/java/com/jishop/product/service/ProductCategoryService.java create mode 100644 backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductCategoryServiceImpl.java diff --git a/backend/JiShop/src/main/java/com/jishop/category/controller/CategoryController.java b/backend/JiShop/src/main/java/com/jishop/category/controller/CategoryController.java index f5f22bb0..4e1bec33 100644 --- a/backend/JiShop/src/main/java/com/jishop/category/controller/CategoryController.java +++ b/backend/JiShop/src/main/java/com/jishop/category/controller/CategoryController.java @@ -1,17 +1,13 @@ package com.jishop.category.controller; import com.jishop.category.dto.CategoryResponse; -import com.jishop.product.dto.response.ProductResponse; import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.data.web.PagedModel; import java.util.List; @Tag(name = "카테고리 API") public interface CategoryController { - PagedModel getProductListByCategory(Long CategoryId, int page); - List getCategoryFilterInfo(); List getSubcategoriesByParentId(Long categoryId); diff --git a/backend/JiShop/src/main/java/com/jishop/category/controller/CategoryControllerImpl.java b/backend/JiShop/src/main/java/com/jishop/category/controller/CategoryControllerImpl.java index 57c162e1..3f96c2a3 100644 --- a/backend/JiShop/src/main/java/com/jishop/category/controller/CategoryControllerImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/category/controller/CategoryControllerImpl.java @@ -2,9 +2,7 @@ import com.jishop.category.dto.CategoryResponse; import com.jishop.category.service.CategoryService; -import com.jishop.product.dto.response.ProductResponse; import lombok.RequiredArgsConstructor; -import org.springframework.data.web.PagedModel; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -19,17 +17,6 @@ public class CategoryControllerImpl implements CategoryController { private final CategoryService categoryService; - @Override - @GetMapping("/products") - public PagedModel getProductListByCategory( - @RequestParam(defaultValue = "50000000L") Long categoryId, - @RequestParam(defaultValue = "0") int page) { - if (page < 0 || page > 100) {page = 0;} - - return categoryService.getProductsByCategory(categoryId, page); - } - - // 진입 시점에 부르면 내려감 헤더용 @Override @GetMapping("/root") public List getCategoryFilterInfo() { diff --git a/backend/JiShop/src/main/java/com/jishop/category/service/CategoryService.java b/backend/JiShop/src/main/java/com/jishop/category/service/CategoryService.java index 18a82d89..40f2ac74 100644 --- a/backend/JiShop/src/main/java/com/jishop/category/service/CategoryService.java +++ b/backend/JiShop/src/main/java/com/jishop/category/service/CategoryService.java @@ -1,14 +1,12 @@ package com.jishop.category.service; import com.jishop.category.dto.CategoryResponse; -import com.jishop.product.dto.response.ProductResponse; -import org.springframework.data.web.PagedModel; import java.util.List; public interface CategoryService { - PagedModel getProductsByCategory(Long categoryId, int page); +// PagedModel getProductsByCategory(Long categoryId, int page); List getCategoryFilterInfo(); diff --git a/backend/JiShop/src/main/java/com/jishop/product/controller/ProductController.java b/backend/JiShop/src/main/java/com/jishop/product/controller/ProductController.java index 846ca021..2e2e4a53 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/controller/ProductController.java +++ b/backend/JiShop/src/main/java/com/jishop/product/controller/ProductController.java @@ -22,8 +22,11 @@ public interface ProductController { ProductDetailResponse getProduct(final User user, final Long productId); @Operation(summary = "인기순(찜순) 상품 조회") - List getProductByWishTopTen(final int page, final int size); + List getProductsByWishTopTen(final int page, final int size); @Operation(summary = "오늘의 특가 상품 조회") - PagedModel getProductByTodaySpecial(final int page, final int size); + PagedModel getProductsByTodaySpecial(final int page, final int size); + + @Operation(summary = "헤더 카테고리 상품 조회") + PagedModel getProductsByCategory(final Long CategoryId, final int page, final int size); } diff --git a/backend/JiShop/src/main/java/com/jishop/product/controller/ProductControllerImpl.java b/backend/JiShop/src/main/java/com/jishop/product/controller/ProductControllerImpl.java index fde2f043..d7dbbba1 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/controller/ProductControllerImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/product/controller/ProductControllerImpl.java @@ -6,6 +6,7 @@ import com.jishop.product.dto.response.ProductDetailResponse; import com.jishop.product.dto.response.ProductResponse; import com.jishop.product.dto.response.TodaySpecialListResponse; +import com.jishop.product.service.ProductCategoryService; import com.jishop.product.service.ProductService; import com.jishop.product.service.ProductWishlistService; import lombok.RequiredArgsConstructor; @@ -22,6 +23,7 @@ public class ProductControllerImpl implements ProductController { private final ProductService productService; private final ProductWishlistService productWishListService; + private final ProductCategoryService productCategoryService; @Override @GetMapping @@ -43,10 +45,9 @@ public ProductDetailResponse getProduct(@CurrentUser final User user, @PathVari return productService.getProduct(user, productId); } - // 상위 찜순 데이터 @Override @GetMapping("/popular") - public List getProductByWishTopTen( + public List getProductsByWishTopTen( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "8") int size ) { @@ -58,7 +59,7 @@ public List getProductByWishTopTen( @Override @GetMapping("/specialPrices") - public PagedModel getProductByTodaySpecial( + public PagedModel getProductsByTodaySpecial( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "8") int size ) { @@ -67,4 +68,15 @@ public PagedModel getProductByTodaySpecial( return productService.getProductsByTodaySpecial(page, size); } + + @Override + @GetMapping("/category/{categoryId}") + public PagedModel getProductsByCategory( + @PathVariable final Long categoryId, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "12") int size) { + if (page < 0 || page > 100) {page = 0;} + + return productCategoryService.getProductsByCategory(categoryId, page, size); + } } diff --git a/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDsl.java b/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDsl.java index 3f69c81d..40e4890e 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDsl.java +++ b/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDsl.java @@ -7,7 +7,10 @@ public interface ProductRepositoryQueryDsl { - List findProductsByCondition(final ProductRequest request, final int page, final int size, final List categoryIds); + List findProductsByCondition(final ProductRequest request, + final int page, + final int size, + final List categoryIds); long countProductsByCondition(final ProductRequest request, final List categoryIds); } diff --git a/backend/JiShop/src/main/java/com/jishop/product/service/ProductCategoryService.java b/backend/JiShop/src/main/java/com/jishop/product/service/ProductCategoryService.java new file mode 100644 index 00000000..99973b8e --- /dev/null +++ b/backend/JiShop/src/main/java/com/jishop/product/service/ProductCategoryService.java @@ -0,0 +1,9 @@ +package com.jishop.product.service; + +import com.jishop.product.dto.response.ProductResponse; +import org.springframework.data.web.PagedModel; + +public interface ProductCategoryService { + + PagedModel getProductsByCategory(final Long categoryId, final int page, final int size); +} diff --git a/backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductCategoryServiceImpl.java b/backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductCategoryServiceImpl.java new file mode 100644 index 00000000..be41e69b --- /dev/null +++ b/backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductCategoryServiceImpl.java @@ -0,0 +1,38 @@ +package com.jishop.product.service.impl; + +import com.jishop.category.repository.CategoryRepository; +import com.jishop.product.domain.Product; +import com.jishop.product.dto.response.ProductResponse; +import com.jishop.product.service.ProductCategoryService; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.*; +import org.springframework.data.web.PagedModel; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ProductCategoryServiceImpl implements ProductCategoryService { + + private final CategoryRepository categoryRepository; + + @Override + public PagedModel getProductsByCategory(final Long categoryId, final int page, final int size) { + + final Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "wishListCount")); + final Page productPage = categoryRepository.findProductsByCategoryWithAllDescendants(categoryId, pageable); + + final List productsResponse = productPage.getContent().stream() + .map(ProductResponse::from) + .toList(); + + return new PagedModel<>(new PageImpl<>( + productsResponse, + pageable, + productPage.getTotalElements() + )); + } +} From 0c30ca3965c63be0ba91dc9744b584eb6fa5b643 Mon Sep 17 00:00:00 2001 From: KWAK-JINHO Date: Mon, 14 Apr 2025 16:44:00 +0900 Subject: [PATCH 13/25] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20refactor:=20?= =?UTF-8?q?=EC=83=81=ED=92=88=EB=AA=A9=EB=A1=9D=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EB=8F=99=EC=A0=81=EC=BF=BC=EB=A6=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - BooleanBuilder -> BooleanExpression 으로 변경 🔗 Resolves: #294 --- .../product/dto/request/ProductRequest.java | 1 - .../ProductRepositoryQueryDslImpl.java | 185 ++++++------------ .../product/service/ProductService.java | 1 - .../service/impl/ProductServiceImpl.java | 2 - 4 files changed, 64 insertions(+), 125 deletions(-) diff --git a/backend/JiShop/src/main/java/com/jishop/product/dto/request/ProductRequest.java b/backend/JiShop/src/main/java/com/jishop/product/dto/request/ProductRequest.java index 7d0d3e4a..e42e6303 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/dto/request/ProductRequest.java +++ b/backend/JiShop/src/main/java/com/jishop/product/dto/request/ProductRequest.java @@ -17,7 +17,6 @@ public record ProductRequest( ){ public ProductRequest { if (keyword == null) { keyword = ""; } - if (sort == null || sort.isEmpty()) { sort = "wish"; } if (priceRanges == null || priceRanges.isEmpty()) { priceRanges = List.of(0, 25000, 50000, 100000); } if (ratings == null || ratings.isEmpty()) { ratings = List.of(1, 2, 3, 4, 5); } } diff --git a/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java b/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java index ad6b0946..8e388317 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java @@ -6,10 +6,10 @@ import com.jishop.reviewproduct.domain.QReviewProduct; import com.querydsl.core.BooleanBuilder; import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.Expressions; import com.querydsl.core.types.dsl.NumberExpression; import com.querydsl.jpa.JPAExpressions; -import com.querydsl.jpa.JPQLQuery; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @@ -32,36 +32,15 @@ public List findProductsByCondition(final ProductRequest productRequest final int page, final int size, final List categoryIds) { - final BooleanBuilder builder = new BooleanBuilder(); - - if (productRequest.priceRanges() != null && !productRequest.priceRanges().isEmpty()) { - addPriceRangesFiltering(productRequest.priceRanges(), builder); - } - if (productRequest.ratings() != null && !productRequest.ratings().isEmpty()) { - addRatingsFilter(productRequest.ratings(), builder); - } - if (categoryIds != null && !categoryIds.isEmpty()) { - addCategoryFilter(categoryIds, builder); - } - if (productRequest.keyword() != null && !productRequest.keyword().trim().isEmpty()) { - addKeywordFilter(productRequest.keyword(), builder); - } - - final OrderSpecifier orderSpecifier = addSorting(productRequest.sort()); - -// BooleanBuilder testBuilder = new BooleanBuilder(); -// testBuilder.and(reviewProduct.reviewCount.gt(0)); -// -// JPQLQuery simpleSubQuery = JPAExpressions -// .select(reviewProduct.product.id) -// .from(reviewProduct) -// .where(testBuilder); -// -// builder.and(product.id.in(simpleSubQuery)); return queryFactory.selectFrom(product) - .where(builder) - .orderBy(orderSpecifier) + .where( + priceRangesFilter(productRequest.priceRanges()), + ratingsFilter(productRequest.ratings()), + categoryFilter(categoryIds), + keywordFilter(productRequest.keyword()) + ) + .orderBy(getSortOrderSpecifier(productRequest.sort())) .offset((long) page * size) .limit(size) .fetch(); @@ -69,30 +48,21 @@ public List findProductsByCondition(final ProductRequest productRequest @Override public long countProductsByCondition(final ProductRequest productRequest, final List categoryIds) { - final BooleanBuilder builder = new BooleanBuilder(); - - if (productRequest.priceRanges() != null && !productRequest.priceRanges().isEmpty()) { - addPriceRangesFiltering(productRequest.priceRanges(), builder); - } - if (productRequest.ratings() != null && !productRequest.ratings().isEmpty()) { - addRatingsFilter(productRequest.ratings(), builder); - } - if (categoryIds != null && !categoryIds.isEmpty()) { - addCategoryFilter(categoryIds, builder); - } - if (productRequest.keyword() != null && !productRequest.keyword().trim().isEmpty()) { - addKeywordFilter(productRequest.keyword(), builder); - } return Optional.ofNullable( queryFactory.select(product.count()) .from(product) - .where(builder) + .where( + priceRangesFilter(productRequest.priceRanges()), + ratingsFilter(productRequest.ratings()), + categoryFilter(categoryIds), + keywordFilter(productRequest.keyword()) + ) .fetchOne() ).orElse(0L); } - private OrderSpecifier addSorting(final String sort) { + private OrderSpecifier getSortOrderSpecifier(final String sort) { return switch (sort) { case "latest" -> product.createdAt.desc(); case "priceAsc" -> product.productInfo.discountPrice.asc(); @@ -102,96 +72,69 @@ private OrderSpecifier addSorting(final String sort) { }; } - private void addPriceRangesFiltering(final List priceRanges, final BooleanBuilder builder) { - final BooleanBuilder priceBuilder = new BooleanBuilder(); + private BooleanExpression priceRangesFilter(final List priceRanges) { + BooleanBuilder priceBuilder = new BooleanBuilder(); for (final Integer range : priceRanges) { switch (range) { - case 0 -> - priceBuilder.or(productInfo.discountPrice.between(0, 25000)); - case 25000 -> - priceBuilder.or(productInfo.discountPrice.between(25001, 50000)); - case 50000 -> - priceBuilder.or(productInfo.discountPrice.between(50001, 100000)); - case 100000 -> - priceBuilder.or(productInfo.discountPrice.gt(100000)); - default -> { } + case 0 -> priceBuilder.or(productInfo.discountPrice.between(0, 25000)); + case 25000 -> priceBuilder.or(productInfo.discountPrice.between(25001, 50000)); + case 50000 -> priceBuilder.or(productInfo.discountPrice.between(50001, 100000)); + case 100000 -> priceBuilder.or(productInfo.discountPrice.gt(100000)); + default -> {} } } - if (priceBuilder.hasValue()) { - builder.and(priceBuilder); - } + return priceBuilder.hasValue() ? Expressions.asBoolean(priceBuilder.getValue()) : null; } - private static void addRatingsFilter(final List ratings, final BooleanBuilder builder) { - if (ratings.size() != 5 || - !ratings.contains(1) || - !ratings.contains(2) || - !ratings.contains(3) || - !ratings.contains(4) || - !ratings.contains(5)) { - - final BooleanBuilder ratingBuilder = new BooleanBuilder(); - - // review_count > 0 조건을 AND로 추가 (OR 대신) - ratingBuilder.and(reviewProduct.reviewCount.gt(0)); - - // 평점 필터링을 위한 내부 Builder - BooleanBuilder ratingRangeBuilder = new BooleanBuilder(); - - NumberExpression avgRating = Expressions.numberTemplate( - Double.class, - "1.0 * {0} / {1}", - reviewProduct.reviewScore, - reviewProduct.reviewCount - ); - - // 평점 = 리뷰총점 / 리뷰개수 - for (final Integer rating : ratings) { - switch (rating) { - case 1 -> ratingRangeBuilder.or( - avgRating.goe(1.0).and(avgRating.lt(2.0)) - ); - case 2 -> ratingRangeBuilder.or( - avgRating.goe(2.0).and(avgRating.lt(3.0)) - ); - case 3 -> ratingRangeBuilder.or( - avgRating.goe(3.0).and(avgRating.lt(4.0)) - ); - case 4 -> ratingRangeBuilder.or( - avgRating.goe(4.0).and(avgRating.lt(5.0)) - ); - case 5 -> ratingRangeBuilder.or( - avgRating.goe(5.0) - ); - default -> {} - } - } - - // 평점 범위 조건을 메인 rating builder에 AND로 추가 - if (ratingRangeBuilder.hasValue()) { - ratingBuilder.and(ratingRangeBuilder); - } + private BooleanExpression ratingsFilter(final List ratings) { + if (ratings.size() == 5) { + return null; + } + // 카운트 > 0 조건 builder + BooleanBuilder ratingBuilder = new BooleanBuilder(); + ratingBuilder.and(reviewProduct.reviewCount.gt(0)); + + NumberExpression avgRating = Expressions.numberTemplate( + Double.class, + "1.0 * {0} / {1}", + reviewProduct.reviewScore, + reviewProduct.reviewCount + ); - if (ratingBuilder.hasValue()) { - final JPQLQuery subQuery = JPAExpressions - .select(reviewProduct.product.id) - .from(reviewProduct) - .where(ratingBuilder); - builder.and(product.id.in(subQuery)); + // 평점 범위 조건 builder + BooleanBuilder ratingRangeBuilder = new BooleanBuilder(); + for (final Integer rating : ratings) { + switch (rating) { + case 1 -> ratingRangeBuilder.or(avgRating.goe(1.0).and(avgRating.lt(2.0))); + case 2 -> ratingRangeBuilder.or(avgRating.goe(2.0).and(avgRating.lt(3.0))); + case 3 -> ratingRangeBuilder.or(avgRating.goe(3.0).and(avgRating.lt(4.0))); + case 4 -> ratingRangeBuilder.or(avgRating.goe(4.0).and(avgRating.lt(5.0))); + case 5 -> ratingRangeBuilder.or(avgRating.goe(5.0)); + default -> { + } } } + if (ratingRangeBuilder.hasValue()) { + ratingBuilder.and(ratingRangeBuilder); + } + return ratingBuilder.hasValue() ? + product.id.in( + JPAExpressions.select(reviewProduct.product.id) + .from(reviewProduct) + .where(ratingBuilder) + ) : null; } - private void addCategoryFilter(final List categoryIds, final BooleanBuilder builder) { - builder.and(product.category.id.in(categoryIds)); + private BooleanExpression categoryFilter(final List categoryIds) { + + return product.category.id.in(categoryIds); } - private static void addKeywordFilter(final String keyword, final BooleanBuilder builder) { - builder.and( - product.productInfo.name.containsIgnoreCase(keyword) - .or(product.productInfo.description.containsIgnoreCase(keyword)) - ); + private BooleanExpression keywordFilter(final String keyword) { + + return product.productInfo.name.containsIgnoreCase(keyword) + .or(product.productInfo.description.containsIgnoreCase(keyword)); } } \ No newline at end of file diff --git a/backend/JiShop/src/main/java/com/jishop/product/service/ProductService.java b/backend/JiShop/src/main/java/com/jishop/product/service/ProductService.java index a55920f4..a696adcd 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/service/ProductService.java +++ b/backend/JiShop/src/main/java/com/jishop/product/service/ProductService.java @@ -13,6 +13,5 @@ public interface ProductService { ProductDetailResponse getProduct(final User user, final Long productId); - PagedModel getProductsByTodaySpecial(final int page, final int size); } diff --git a/backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductServiceImpl.java b/backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductServiceImpl.java index ebea8129..53083d28 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductServiceImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductServiceImpl.java @@ -101,8 +101,6 @@ public PagedModel getProductsByTodaySpecial(final int return new PagedModel<>(responsePage); } - - private List getCategoryIdsWithSubcategories(Long categoryId) { if (categoryId == null) return List.of(); From 0a5e1d3524a6e2f79965e326a39fd9ab16ea9514 Mon Sep 17 00:00:00 2001 From: KWAK-JINHO Date: Mon, 14 Apr 2025 16:53:43 +0900 Subject: [PATCH 14/25] =?UTF-8?q?=F0=9F=94=A7=20chore:=20=EA=B8=B0?= =?UTF-8?q?=ED=83=80=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🔗 Resolves: #42 --- .../controller/CategoryController.java | 3 +++ .../service/Impl/CategoryServiceImpl.java | 21 ------------------- 2 files changed, 3 insertions(+), 21 deletions(-) diff --git a/backend/JiShop/src/main/java/com/jishop/category/controller/CategoryController.java b/backend/JiShop/src/main/java/com/jishop/category/controller/CategoryController.java index 4e1bec33..f40101c0 100644 --- a/backend/JiShop/src/main/java/com/jishop/category/controller/CategoryController.java +++ b/backend/JiShop/src/main/java/com/jishop/category/controller/CategoryController.java @@ -1,6 +1,7 @@ package com.jishop.category.controller; import com.jishop.category.dto.CategoryResponse; +import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; @@ -8,7 +9,9 @@ @Tag(name = "카테고리 API") public interface CategoryController { + @Operation(summary = "최상위 카테고리의 ID와 이름 조회") List getCategoryFilterInfo(); + @Operation(summary = "해당 카테고리의 한단계 하위카테고리 ID 조회") List getSubcategoriesByParentId(Long categoryId); } diff --git a/backend/JiShop/src/main/java/com/jishop/category/service/Impl/CategoryServiceImpl.java b/backend/JiShop/src/main/java/com/jishop/category/service/Impl/CategoryServiceImpl.java index 499e81ea..ca35c870 100644 --- a/backend/JiShop/src/main/java/com/jishop/category/service/Impl/CategoryServiceImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/category/service/Impl/CategoryServiceImpl.java @@ -4,12 +4,8 @@ import com.jishop.category.dto.CategoryResponse; import com.jishop.category.repository.CategoryRepository; import com.jishop.category.service.CategoryService; -import com.jishop.product.domain.Product; -import com.jishop.product.dto.response.ProductResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.data.domain.*; -import org.springframework.data.web.PagedModel; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -24,23 +20,6 @@ public class CategoryServiceImpl implements CategoryService { private final CategoryRepository categoryRepository; // private final CategoryRedisService categoryRedisService; - @Override - public PagedModel getProductsByCategory(Long categoryId, int page) { - - Pageable pageable = PageRequest.of(page, 12, Sort.by(Sort.Direction.DESC, "wishListCount")); - Page productPage = categoryRepository.findProductsByCategoryWithAllDescendants(categoryId, pageable); - - List productsResponse = productPage.getContent().stream() - .map(ProductResponse::from) - .toList(); - - return new PagedModel<>(new PageImpl<>( - productsResponse, - pageable, - productPage.getTotalElements() - )); - } - @Override public List getCategoryFilterInfo() { List topLevelCategories = categoryRepository.findTopLevelCategories(); From f5d8b78bfa790e0f16cdb3b88040af90f7945c20 Mon Sep 17 00:00:00 2001 From: KWAK-JINHO Date: Tue, 15 Apr 2025 00:49:24 +0900 Subject: [PATCH 15/25] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20refactor:=20?= =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=ED=95=84=ED=84=B0=EB=A7=81?= =?UTF-8?q?=20=EC=8B=9C=20=EB=AA=BB=EB=B6=88=EB=9F=AC=EC=98=A4=EB=8D=98=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 카테고리id null로 들어올때 처리 🔗 Resolves: #294 --- .../ProductRepositoryQueryDslImpl.java | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java b/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java index 8e388317..5736e3de 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java @@ -34,12 +34,7 @@ public List findProductsByCondition(final ProductRequest productRequest final List categoryIds) { return queryFactory.selectFrom(product) - .where( - priceRangesFilter(productRequest.priceRanges()), - ratingsFilter(productRequest.ratings()), - categoryFilter(categoryIds), - keywordFilter(productRequest.keyword()) - ) + .where(buildProductConditions(productRequest, categoryIds)) .orderBy(getSortOrderSpecifier(productRequest.sort())) .offset((long) page * size) .limit(size) @@ -52,16 +47,20 @@ public long countProductsByCondition(final ProductRequest productRequest, final return Optional.ofNullable( queryFactory.select(product.count()) .from(product) - .where( - priceRangesFilter(productRequest.priceRanges()), - ratingsFilter(productRequest.ratings()), - categoryFilter(categoryIds), - keywordFilter(productRequest.keyword()) - ) + .where(buildProductConditions(productRequest, categoryIds)) .fetchOne() ).orElse(0L); } + private BooleanExpression buildProductConditions(final ProductRequest request, final List categoryIds) { + return Expressions.allOf( + priceRangesFilter(request.priceRanges()), + ratingsFilter(request.ratings()), + categoryFilter(categoryIds), + keywordFilter(request.keyword()) + ); + } + private OrderSpecifier getSortOrderSpecifier(final String sort) { return switch (sort) { case "latest" -> product.createdAt.desc(); @@ -128,7 +127,9 @@ private BooleanExpression ratingsFilter(final List ratings) { } private BooleanExpression categoryFilter(final List categoryIds) { - + if (categoryIds == null || categoryIds.isEmpty()) { + return null; + } return product.category.id.in(categoryIds); } From e483a0a61a7f3661ccce1a4c157022f8f6f831b9 Mon Sep 17 00:00:00 2001 From: KWAK-JINHO Date: Tue, 15 Apr 2025 01:19:34 +0900 Subject: [PATCH 16/25] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20refactor:=20Produ?= =?UTF-8?q?ctService=20=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EC=B1=85=EC=9E=84=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EA=B2=8C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🔗 Resolves: #294 --- .../service/ProductCategoryService.java | 4 ++++ .../impl/ProductCategoryServiceImpl.java | 16 ++++++++++++++ .../service/impl/ProductServiceImpl.java | 21 +++---------------- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/backend/JiShop/src/main/java/com/jishop/product/service/ProductCategoryService.java b/backend/JiShop/src/main/java/com/jishop/product/service/ProductCategoryService.java index 99973b8e..da1b8f65 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/service/ProductCategoryService.java +++ b/backend/JiShop/src/main/java/com/jishop/product/service/ProductCategoryService.java @@ -3,7 +3,11 @@ import com.jishop.product.dto.response.ProductResponse; import org.springframework.data.web.PagedModel; +import java.util.List; + public interface ProductCategoryService { PagedModel getProductsByCategory(final Long categoryId, final int page, final int size); + + List getCategoryIdsWithSubcategories(final Long categoryId); } diff --git a/backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductCategoryServiceImpl.java b/backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductCategoryServiceImpl.java index be41e69b..38f3bdc5 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductCategoryServiceImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductCategoryServiceImpl.java @@ -35,4 +35,20 @@ public PagedModel getProductsByCategory(final Long categoryId, productPage.getTotalElements() )); } + + @Override + public List getCategoryIdsWithSubcategories(final Long categoryId) { + if (categoryId == null) return List.of(); + + return categoryRepository.findById(categoryId) + .map(category -> { + final List subCategoryPKs = categoryRepository.findIdsByCurrentIds( + categoryRepository.findAllSubCategoryIds(categoryId) + ); + return subCategoryPKs.isEmpty() + ? List.of(category.getId()) + : subCategoryPKs; + }) + .orElse(List.of()); + } } diff --git a/backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductServiceImpl.java b/backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductServiceImpl.java index 53083d28..fada6ddc 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductServiceImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductServiceImpl.java @@ -1,6 +1,5 @@ package com.jishop.product.service.impl; -import com.jishop.category.repository.CategoryRepository; import com.jishop.common.exception.DomainException; import com.jishop.common.exception.ErrorType; import com.jishop.member.domain.User; @@ -15,6 +14,7 @@ import com.jishop.product.dto.response.ProductResponse; import com.jishop.product.dto.response.TodaySpecialListResponse; import com.jishop.product.repository.ProductRepository; +import com.jishop.product.service.ProductCategoryService; import com.jishop.product.service.ProductService; import com.jishop.product.service.ProductWishlistService; import com.jishop.reviewproduct.domain.ReviewProduct; @@ -36,15 +36,15 @@ public class ProductServiceImpl implements ProductService { private final ProductRepository productRepository; private final ReviewProductRepository reviewProductRepository; private final SaleProductRepository saleProductRepository; - private final CategoryRepository categoryRepository; private final OrderDetailRepository orderDetailRepository; private final ProductWishlistService wishlistService; + private final ProductCategoryService productCategoryService; @Override public PagedModel getProductList(final ProductRequest productRequest, final int page, final int size) { - final List categoryIds = getCategoryIdsWithSubcategories(productRequest.categoryId()); + final List categoryIds = productCategoryService.getCategoryIdsWithSubcategories(productRequest.categoryId()); final List selectedProducts = productRepository.findProductsByCondition(productRequest, page, size, categoryIds); final List productListResponse = selectedProducts.stream() @@ -100,19 +100,4 @@ public PagedModel getProductsByTodaySpecial(final int return new PagedModel<>(responsePage); } - - private List getCategoryIdsWithSubcategories(Long categoryId) { - if (categoryId == null) return List.of(); - - return categoryRepository.findById(categoryId) - .map(category -> { - final List subCategoryPKs = categoryRepository.findIdsByCurrentIds( - categoryRepository.findAllSubCategoryIds(categoryId) - ); - return subCategoryPKs.isEmpty() - ? List.of(category.getId()) - : subCategoryPKs; - }) - .orElse(List.of()); - } } From 9092b4f29af4b85ec83cbb4101eec206f166cefc Mon Sep 17 00:00:00 2001 From: KWAK-JINHO Date: Tue, 15 Apr 2025 01:44:28 +0900 Subject: [PATCH 17/25] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20refactor:=20?= =?UTF-8?q?=EC=83=81=ED=92=88=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?null=20=EC=B2=98=EB=A6=AC=20=EB=A0=88=ED=8F=AC=EC=A7=80?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=EC=97=90=EC=84=9C=20=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🔗 Resolves: #294 --- .../java/com/jishop/product/dto/request/ProductRequest.java | 2 -- .../product/repository/ProductRepositoryQueryDslImpl.java | 6 +++++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/backend/JiShop/src/main/java/com/jishop/product/dto/request/ProductRequest.java b/backend/JiShop/src/main/java/com/jishop/product/dto/request/ProductRequest.java index e42e6303..da51d7a6 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/dto/request/ProductRequest.java +++ b/backend/JiShop/src/main/java/com/jishop/product/dto/request/ProductRequest.java @@ -17,7 +17,5 @@ public record ProductRequest( ){ public ProductRequest { if (keyword == null) { keyword = ""; } - if (priceRanges == null || priceRanges.isEmpty()) { priceRanges = List.of(0, 25000, 50000, 100000); } - if (ratings == null || ratings.isEmpty()) { ratings = List.of(1, 2, 3, 4, 5); } } } diff --git a/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java b/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java index 5736e3de..30effbdb 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepositoryQueryDslImpl.java @@ -72,6 +72,10 @@ private OrderSpecifier getSortOrderSpecifier(final String sort) { } private BooleanExpression priceRangesFilter(final List priceRanges) { + if (priceRanges == null || priceRanges.isEmpty()) { + return null; + } + BooleanBuilder priceBuilder = new BooleanBuilder(); for (final Integer range : priceRanges) { @@ -88,7 +92,7 @@ private BooleanExpression priceRangesFilter(final List priceRanges) { } private BooleanExpression ratingsFilter(final List ratings) { - if (ratings.size() == 5) { + if (ratings == null || ratings.isEmpty() || ratings.size() == 5) { return null; } // 카운트 > 0 조건 builder From a262264c844c394806c2bc4a29d45efca55270c2 Mon Sep 17 00:00:00 2001 From: KWAK-JINHO Date: Tue, 15 Apr 2025 01:45:05 +0900 Subject: [PATCH 18/25] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20refactor:=20?= =?UTF-8?q?=ED=97=A4=EB=8D=94=20=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20?= =?UTF-8?q?=EC=83=81=ED=92=88=EC=A1=B0=ED=9A=8C=20size=EB=B2=94=EC=9C=84?= =?UTF-8?q?=20=EA=B3=A0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🔗 Resolves: #294 --- .../com/jishop/product/controller/ProductControllerImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/JiShop/src/main/java/com/jishop/product/controller/ProductControllerImpl.java b/backend/JiShop/src/main/java/com/jishop/product/controller/ProductControllerImpl.java index d7dbbba1..0f670efb 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/controller/ProductControllerImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/product/controller/ProductControllerImpl.java @@ -76,6 +76,7 @@ public PagedModel getProductsByCategory( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "12") int size) { if (page < 0 || page > 100) {page = 0;} + if (size <= 0 || size > 100) {size = 12;} return productCategoryService.getProductsByCategory(categoryId, page, size); } From b0461d2c251ffe186f79db6bd57a7bb053ce430d Mon Sep 17 00:00:00 2001 From: KWAK-JINHO Date: Tue, 15 Apr 2025 01:45:43 +0900 Subject: [PATCH 19/25] =?UTF-8?q?=F0=9F=94=A7=20chore:=20=EB=A7=A4?= =?UTF-8?q?=EA=B0=9C=EB=B3=80=EC=88=98=20=EB=AA=85=20=EC=98=A4=ED=83=80=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 🔗 Resolves: #294 --- .../category/repository/CategoryRepository.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/JiShop/src/main/java/com/jishop/category/repository/CategoryRepository.java b/backend/JiShop/src/main/java/com/jishop/category/repository/CategoryRepository.java index 6c904cd7..103da42b 100644 --- a/backend/JiShop/src/main/java/com/jishop/category/repository/CategoryRepository.java +++ b/backend/JiShop/src/main/java/com/jishop/category/repository/CategoryRepository.java @@ -17,21 +17,21 @@ public interface CategoryRepository extends JpaRepository { /** * 상위(1뎁스) 카테고리 ID로 그 하위(2,3뎁스) 카테고리 상품 목록 모두 조회 - * @param CategoryId 1뎁스 카테고리 ID + * @param categoryId 1뎁스 카테고리 ID * @return 해당 카테고리에 속한 상품 페이지 */ @Query(value = "SELECT p FROM Product p " + "LEFT JOIN p.category c1 " + "LEFT JOIN Category c2 ON c1.parent = c2 " + - "WHERE c1.parent.currentId = :CategoryId OR " + - "c2.parent.currentId = :CategoryId", + "WHERE c1.parent.currentId = :categoryId OR " + + "c2.parent.currentId = :categoryId", countQuery = "SELECT COUNT(p) FROM Product p " + "LEFT JOIN p.category c1 " + "LEFT JOIN Category c2 ON c1.parent = c2 " + - "WHERE c1.parent.currentId = :CategoryId OR " + - "c2.parent.currentId = :CategoryId") + "WHERE c1.parent.currentId = :categoryId OR " + + "c2.parent.currentId = :categoryId") Page findProductsByCategoryWithAllDescendants( - @Param("CategoryId") Long CategoryId, Pageable pageable); + @Param("categoryId") Long categoryId, Pageable pageable); /** * 특정 카테고리의 모든 하위 카테고리 ID를 재귀적으로 조회 From 09b1f700e75e569b1d3d9753747fe655a7f31777 Mon Sep 17 00:00:00 2001 From: KWAK-JINHO Date: Tue, 15 Apr 2025 02:36:58 +0900 Subject: [PATCH 20/25] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20refactor:=20?= =?UTF-8?q?=EC=98=A4=EB=8A=98=EC=9D=98=20=ED=8A=B9=EA=B0=80=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EB=A6=AC=ED=8E=99=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - @Transactional과 @Scheduled 분리하여 구조 개선 🔗 Resolves: #294 --- .../controller/ProductControllerImpl.java | 4 +- .../product/repository/ProductRepository.java | 26 ++++++-- .../service/DiscountSatusScheduler.java | 62 ------------------- .../service/DiscountStatusScheduler.java | 28 +++++++++ .../service/ProductDiscountService.java | 11 ++++ .../product/service/ProductService.java | 3 - .../impl/ProductDiscountServiceImpl.java | 50 +++++++++++++++ .../service/impl/ProductServiceImpl.java | 21 ++----- 8 files changed, 118 insertions(+), 87 deletions(-) delete mode 100644 backend/JiShop/src/main/java/com/jishop/product/service/DiscountSatusScheduler.java create mode 100644 backend/JiShop/src/main/java/com/jishop/product/service/DiscountStatusScheduler.java create mode 100644 backend/JiShop/src/main/java/com/jishop/product/service/ProductDiscountService.java create mode 100644 backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductDiscountServiceImpl.java diff --git a/backend/JiShop/src/main/java/com/jishop/product/controller/ProductControllerImpl.java b/backend/JiShop/src/main/java/com/jishop/product/controller/ProductControllerImpl.java index 0f670efb..be1324a4 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/controller/ProductControllerImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/product/controller/ProductControllerImpl.java @@ -7,6 +7,7 @@ import com.jishop.product.dto.response.ProductResponse; import com.jishop.product.dto.response.TodaySpecialListResponse; import com.jishop.product.service.ProductCategoryService; +import com.jishop.product.service.ProductDiscountService; import com.jishop.product.service.ProductService; import com.jishop.product.service.ProductWishlistService; import lombok.RequiredArgsConstructor; @@ -24,6 +25,7 @@ public class ProductControllerImpl implements ProductController { private final ProductService productService; private final ProductWishlistService productWishListService; private final ProductCategoryService productCategoryService; + private final ProductDiscountService productDiscountService; @Override @GetMapping @@ -66,7 +68,7 @@ public PagedModel getProductsByTodaySpecial( if (page < 0 || page > 100) {page = 0;} if (size <= 0 || size > 100) {size = 8;} - return productService.getProductsByTodaySpecial(page, size); + return productDiscountService.getProductsByDailyDeal(page, size); } @Override diff --git a/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepository.java b/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepository.java index 68daa830..39fb139b 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepository.java +++ b/backend/JiShop/src/main/java/com/jishop/product/repository/ProductRepository.java @@ -5,6 +5,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -13,10 +14,30 @@ public interface ProductRepository extends JpaRepository, ProductRepositoryQueryDsl { @Query("SELECT p FROM Product p WHERE p.status.discountStatus = :status AND p.status.isDiscount = true") - Page findDailDealProducts(@Param("status") DiscountStatus status, Pageable pageable); + Page findDailyDealProducts(@Param("status") DiscountStatus status, Pageable pageable); + + @Modifying + @Query(value = "UPDATE products SET discount_status = 'NONE', is_discount = false " + + "WHERE discount_status = 'DAILY_DEAL'", nativeQuery = true) + int resetPreviousDailyDeals(); + + @Modifying + @Query(value = "UPDATE products p " + + "JOIN ( SELECT id " + + "FROM products " + + "WHERE discount_price < origin_price " + + "AND sale_status = 'SELLING' " + + "AND secret = false " + + "ORDER BY RAND() " + + "LIMIT 100 ) " + + "AS selected_products ON p.id = selected_products.id " + + "SET p.discount_status = 'DAILY_DEAL', p.is_discount = true", nativeQuery = true) + int updateNewDailyDeals(); boolean existsByProductInfo_Brand(String keyword); + List findAllByProductInfo_Brand(String keyword); + // "남성 셔츠" 가 검색어로 들어온 경우 +남성 +셔츠로 변환(BOOLEAN MODE) // "남성셔츠" 가 검색으로 들어온 경우 남성, 성셔, 셔츠로 변환(ngram parser) @Query( @@ -25,9 +46,6 @@ public interface ProductRepository extends JpaRepository, Product ) Long existsByFulltext(String keyword); - List findAllByProductInfo_Brand(String keyword); - - @Query( value = "SELECT * FROM products WHERE MATCH(name, brand) AGAINST(:keyword IN BOOLEAN MODE)", nativeQuery = true diff --git a/backend/JiShop/src/main/java/com/jishop/product/service/DiscountSatusScheduler.java b/backend/JiShop/src/main/java/com/jishop/product/service/DiscountSatusScheduler.java deleted file mode 100644 index eab6d316..00000000 --- a/backend/JiShop/src/main/java/com/jishop/product/service/DiscountSatusScheduler.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.jishop.product.service; - -import com.jishop.product.repository.ProductRepository; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.context.annotation.Configuration; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.transaction.annotation.Transactional; - -@Slf4j -@Configuration -@EnableScheduling -@RequiredArgsConstructor -public class DiscountSatusScheduler { - - private final ProductRepository productRepository; - private final JdbcTemplate jdbcTemplate; - - @Transactional - @Scheduled(cron = "0 0 0 * * *") // 매일 자정에 실행 - public void randomPickDailyHotDeal() { - log.info("오늘의 특가 랜덤 Pick Pick 시작합니다!!! 두구두구두구..."); - - try { - // 1. 기존 일일 특가 상품 초기화 - resetPreviousDailyDeals(); - - // 2. 새로운 일일 특가 상품 설정 - updateNewDailyDeals(); - - log.info("오늘의 특가 뽑기가 잘 완료되었습니다. 박수!!"); - } catch (Exception e) { - log.error("오늘의 특가 뽑기 도중 에러가 발생했습니다.", e); - } - } - - private void resetPreviousDailyDeals() { - String resetQuery = "UPDATE products SET discount_status = 'NONE', is_discount = false " + - "WHERE discount_status = 'DAILY_DEAL'"; - - int updatedCount = jdbcTemplate.update(resetQuery); - log.info(" {} 개의 상품이 리셋되었습니다. ", updatedCount); - } - - private void updateNewDailyDeals() { - String updateQuery = " UPDATE products p " + - " JOIN ( SELECT id " + - " FROM products " + - " WHERE discount_price < origin_price " + - " AND sale_status = 'SELLING' " + - " AND secret = false " + - " ORDER BY RAND() " + - " LIMIT 100 )" + - " AS selected_products ON p.id = selected_products.id " + - " SET p.discount_status = 'DAILY_DEAL', p.is_discount = true"; - - int updatedCount = jdbcTemplate.update(updateQuery); - log.info(" {}개의 상품이 오늘의 특가로 뽑혔습니다.", updatedCount); - } -} diff --git a/backend/JiShop/src/main/java/com/jishop/product/service/DiscountStatusScheduler.java b/backend/JiShop/src/main/java/com/jishop/product/service/DiscountStatusScheduler.java new file mode 100644 index 00000000..b54f18ba --- /dev/null +++ b/backend/JiShop/src/main/java/com/jishop/product/service/DiscountStatusScheduler.java @@ -0,0 +1,28 @@ +package com.jishop.product.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; + +@Slf4j +@Configuration +@EnableScheduling +@RequiredArgsConstructor +public class DiscountStatusScheduler { + + private final ProductDiscountService productDiscountService; + + @Scheduled(cron = "0 0 0 * * *") // 매일 자정에 실행 + public void randomPickDailyHotDeal() { + log.info("오늘의 특가 랜덤 Pick Pick 시작합니다!!! 두구두구두구..."); + + try { + productDiscountService.updateDailyDeals(); + log.info("오늘의 특가 뽑기가 잘 완료되었습니다. 박수!!"); + } catch (Exception e) { + log.error("오늘의 특가 뽑기 도중 에러가 발생했습니다.", e); + } + } +} diff --git a/backend/JiShop/src/main/java/com/jishop/product/service/ProductDiscountService.java b/backend/JiShop/src/main/java/com/jishop/product/service/ProductDiscountService.java new file mode 100644 index 00000000..66b69e8c --- /dev/null +++ b/backend/JiShop/src/main/java/com/jishop/product/service/ProductDiscountService.java @@ -0,0 +1,11 @@ +package com.jishop.product.service; + +import com.jishop.product.dto.response.TodaySpecialListResponse; +import org.springframework.data.web.PagedModel; + +public interface ProductDiscountService { + + PagedModel getProductsByDailyDeal(final int page, final int size); + + void updateDailyDeals(); +} diff --git a/backend/JiShop/src/main/java/com/jishop/product/service/ProductService.java b/backend/JiShop/src/main/java/com/jishop/product/service/ProductService.java index a696adcd..65252033 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/service/ProductService.java +++ b/backend/JiShop/src/main/java/com/jishop/product/service/ProductService.java @@ -4,7 +4,6 @@ import com.jishop.product.dto.request.ProductRequest; import com.jishop.product.dto.response.ProductDetailResponse; import com.jishop.product.dto.response.ProductResponse; -import com.jishop.product.dto.response.TodaySpecialListResponse; import org.springframework.data.web.PagedModel; public interface ProductService { @@ -12,6 +11,4 @@ public interface ProductService { PagedModel getProductList(final ProductRequest productRequest, final int page, final int size); ProductDetailResponse getProduct(final User user, final Long productId); - - PagedModel getProductsByTodaySpecial(final int page, final int size); } diff --git a/backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductDiscountServiceImpl.java b/backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductDiscountServiceImpl.java new file mode 100644 index 00000000..71d1183e --- /dev/null +++ b/backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductDiscountServiceImpl.java @@ -0,0 +1,50 @@ +package com.jishop.product.service.impl; + +import com.jishop.order.repository.OrderDetailRepository; +import com.jishop.product.domain.DiscountStatus; +import com.jishop.product.domain.Product; +import com.jishop.product.dto.response.TodaySpecialListResponse; +import com.jishop.product.repository.ProductRepository; +import com.jishop.product.service.ProductDiscountService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.PagedModel; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ProductDiscountServiceImpl implements ProductDiscountService { + + private final ProductRepository productRepository; + private final OrderDetailRepository orderDetailRepository; + + @Override + @Transactional(readOnly = true) + public PagedModel getProductsByDailyDeal(final int page, final int size) { + final Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); + final Page productPage = productRepository.findDailyDealProducts(DiscountStatus.DAILY_DEAL, pageable); + + final Page responsePage = productPage.map(product -> { + final long totalSales = orderDetailRepository.countTotalOrdersByProductId(product.getId()); + + return TodaySpecialListResponse.from(product, totalSales); + }); + + return new PagedModel<>(responsePage); + } + + @Transactional + public void updateDailyDeals() { + int resetCount = productRepository.resetPreviousDailyDeals(); + log.info(" {} 개의 상품이 리셋되었습니다. ", resetCount); + + int updatedCount = productRepository.updateNewDailyDeals(); + log.info(" {}개의 상품이 오늘의 특가로 뽑혔습니다.", updatedCount); + } +} diff --git a/backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductServiceImpl.java b/backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductServiceImpl.java index fada6ddc..005e93e6 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductServiceImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductServiceImpl.java @@ -7,12 +7,10 @@ import com.jishop.option.dto.GeneralOptionResponse; import com.jishop.option.dto.SizeOption; import com.jishop.order.repository.OrderDetailRepository; -import com.jishop.product.domain.DiscountStatus; import com.jishop.product.domain.Product; import com.jishop.product.dto.request.ProductRequest; import com.jishop.product.dto.response.ProductDetailResponse; import com.jishop.product.dto.response.ProductResponse; -import com.jishop.product.dto.response.TodaySpecialListResponse; import com.jishop.product.repository.ProductRepository; import com.jishop.product.service.ProductCategoryService; import com.jishop.product.service.ProductService; @@ -21,7 +19,10 @@ import com.jishop.reviewproduct.repository.ReviewProductRepository; import com.jishop.saleproduct.repository.SaleProductRepository; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.*; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.data.web.PagedModel; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -86,18 +87,4 @@ public ProductDetailResponse getProduct(final User user, final Long productId) { return ProductDetailResponse .from(ProductResponse.from(product), product, isWished, reviewCount, reviewRate, productsOptions); } - - @Override - public PagedModel getProductsByTodaySpecial(final int page, final int size) { - final Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); - final Page productPage = productRepository.findDailDealProducts(DiscountStatus.DAILY_DEAL, pageable); - - final Page responsePage = productPage.map(product -> { - final long totalSales = orderDetailRepository.countTotalOrdersByProductId(product.getId()); - - return TodaySpecialListResponse.from(product, totalSales); - }); - - return new PagedModel<>(responsePage); - } } From 671b0fc7960a742b84cd7a7e7a3e6ab5e91fa37c Mon Sep 17 00:00:00 2001 From: KWAK-JINHO Date: Tue, 15 Apr 2025 20:41:57 +0900 Subject: [PATCH 21/25] =?UTF-8?q?=F0=9F=94=A7=20chore:=20=EC=BB=A8?= =?UTF-8?q?=EB=B2=A4=EC=85=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🔗 Resolves: #294 --- .../main/java/com/jishop/product/domain/embed/CategoryInfo.java | 2 +- .../src/main/java/com/jishop/product/domain/embed/ImageUrl.java | 2 +- .../main/java/com/jishop/product/domain/embed/ProductInfo.java | 2 +- .../src/main/java/com/jishop/product/domain/embed/Status.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/JiShop/src/main/java/com/jishop/product/domain/embed/CategoryInfo.java b/backend/JiShop/src/main/java/com/jishop/product/domain/embed/CategoryInfo.java index cbd38e02..7ff4473f 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/domain/embed/CategoryInfo.java +++ b/backend/JiShop/src/main/java/com/jishop/product/domain/embed/CategoryInfo.java @@ -6,8 +6,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; -@Embeddable @Getter +@Embeddable @NoArgsConstructor(access = AccessLevel.PROTECTED) public class CategoryInfo { diff --git a/backend/JiShop/src/main/java/com/jishop/product/domain/embed/ImageUrl.java b/backend/JiShop/src/main/java/com/jishop/product/domain/embed/ImageUrl.java index c8947d37..88218008 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/domain/embed/ImageUrl.java +++ b/backend/JiShop/src/main/java/com/jishop/product/domain/embed/ImageUrl.java @@ -6,8 +6,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; -@Embeddable @Getter +@Embeddable @NoArgsConstructor(access = AccessLevel.PROTECTED) public class ImageUrl { diff --git a/backend/JiShop/src/main/java/com/jishop/product/domain/embed/ProductInfo.java b/backend/JiShop/src/main/java/com/jishop/product/domain/embed/ProductInfo.java index 58c1dd35..dabb92fd 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/domain/embed/ProductInfo.java +++ b/backend/JiShop/src/main/java/com/jishop/product/domain/embed/ProductInfo.java @@ -8,8 +8,8 @@ import java.time.LocalDateTime; -@Embeddable @Getter +@Embeddable @NoArgsConstructor(access = AccessLevel.PROTECTED) public class ProductInfo { diff --git a/backend/JiShop/src/main/java/com/jishop/product/domain/embed/Status.java b/backend/JiShop/src/main/java/com/jishop/product/domain/embed/Status.java index a0226349..f6d9119a 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/domain/embed/Status.java +++ b/backend/JiShop/src/main/java/com/jishop/product/domain/embed/Status.java @@ -11,8 +11,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; -@Embeddable @Getter +@Embeddable @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Status { From 95d8454033cd12a87bd9dc96c9aa5abcf5464685 Mon Sep 17 00:00:00 2001 From: KWAK-JINHO Date: Wed, 16 Apr 2025 00:39:38 +0900 Subject: [PATCH 22/25] =?UTF-8?q?=F0=9F=94=A7=20chore:=20=EC=A3=BC?= =?UTF-8?q?=EC=84=9D=20=EB=B0=8F=20=EA=B3=B5=EB=B0=B1=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🔗 Resolves: #294 --- .../main/java/com/jishop/category/service/CategoryService.java | 2 -- .../com/jishop/category/service/Impl/CategoryServiceImpl.java | 1 - 2 files changed, 3 deletions(-) diff --git a/backend/JiShop/src/main/java/com/jishop/category/service/CategoryService.java b/backend/JiShop/src/main/java/com/jishop/category/service/CategoryService.java index 40f2ac74..cb315c59 100644 --- a/backend/JiShop/src/main/java/com/jishop/category/service/CategoryService.java +++ b/backend/JiShop/src/main/java/com/jishop/category/service/CategoryService.java @@ -6,8 +6,6 @@ public interface CategoryService { -// PagedModel getProductsByCategory(Long categoryId, int page); - List getCategoryFilterInfo(); List getSubcategoriesByParentId(Long categoryId); diff --git a/backend/JiShop/src/main/java/com/jishop/category/service/Impl/CategoryServiceImpl.java b/backend/JiShop/src/main/java/com/jishop/category/service/Impl/CategoryServiceImpl.java index ca35c870..978d516a 100644 --- a/backend/JiShop/src/main/java/com/jishop/category/service/Impl/CategoryServiceImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/category/service/Impl/CategoryServiceImpl.java @@ -18,7 +18,6 @@ public class CategoryServiceImpl implements CategoryService { private final CategoryRepository categoryRepository; -// private final CategoryRedisService categoryRedisService; @Override public List getCategoryFilterInfo() { From 21fcd084ba146b7526b48bbbd8ad9cba5762e778 Mon Sep 17 00:00:00 2001 From: KWAK-JINHO Date: Wed, 16 Apr 2025 00:41:23 +0900 Subject: [PATCH 23/25] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20refactor:=20optio?= =?UTF-8?q?n=20dto=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 패션의류옵션 DTO 더 명확한 책임 분리 🔗 Resolves: #294 --- .../dto/FashionClothesOptionResponse.java | 15 ++++--------- .../com/jishop/option/dto/SizeOption.java | 21 +++++++++++++++++++ 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/backend/JiShop/src/main/java/com/jishop/option/dto/FashionClothesOptionResponse.java b/backend/JiShop/src/main/java/com/jishop/option/dto/FashionClothesOptionResponse.java index 752afea9..11d75ecc 100644 --- a/backend/JiShop/src/main/java/com/jishop/option/dto/FashionClothesOptionResponse.java +++ b/backend/JiShop/src/main/java/com/jishop/option/dto/FashionClothesOptionResponse.java @@ -16,21 +16,14 @@ public static FashionClothesOptionResponse from(List productOptions) Map> fashionClothesOptions = new HashMap<>(); for (SizeOption option : productOptions) { - String optionValue = option.optionValue(); - String[] colorAndSize = optionValue.split("/"); - - if (colorAndSize.length == 2) { - String color = colorAndSize[0]; - String size = colorAndSize[1]; + if (option.isValidOption()) { + String color = option.extractColor(); if (!fashionClothesOptions.containsKey(color)) { fashionClothesOptions.put(color, new ArrayList<>()); } - SizeOption sizeOption = new SizeOption( - option.saleProductId(), - size, - option.optionExtra() - ); + + SizeOption sizeOption = option.withSize(); fashionClothesOptions.get(color).add(sizeOption); } } diff --git a/backend/JiShop/src/main/java/com/jishop/option/dto/SizeOption.java b/backend/JiShop/src/main/java/com/jishop/option/dto/SizeOption.java index b380b5fd..32c00746 100644 --- a/backend/JiShop/src/main/java/com/jishop/option/dto/SizeOption.java +++ b/backend/JiShop/src/main/java/com/jishop/option/dto/SizeOption.java @@ -5,4 +5,25 @@ public record SizeOption( String optionValue, int optionExtra ) { + public boolean isValidOption() { + return (optionValue != null) && optionValue.split("/").length == 2; + } + + public String extractColor() { + String[] colorAndSize = optionValue.split("/"); + return colorAndSize[0]; + } + + public String extractSize() { + String[] colorAndSize = optionValue.split("/"); + return colorAndSize[1]; + } + + public SizeOption withSize() { + return new SizeOption( + saleProductId(), + extractSize(), + optionExtra() + ); + } } From a808c50630f81e259425f690637eacf0bd17d4e8 Mon Sep 17 00:00:00 2001 From: KWAK-JINHO Date: Wed, 16 Apr 2025 02:10:51 +0900 Subject: [PATCH 24/25] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20refactor:=20embed?= =?UTF-8?q?=20=EB=B6=84=EB=A6=AC=EC=97=90=20=EB=94=B0=EB=A5=B8=20dto=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🔗 Resolves: #294 --- .../response/ProductCategoryInfoResponse.java | 17 +++++++++++++ .../dto/response/ProductImageUrlResponse.java | 23 ++++++++++++++++++ .../dto/response/ProductInfoResponse.java | 23 ++++++++++++++++++ .../dto/response/ProductStatusResponse.java | 24 +++++++++++++++++++ .../service/impl/ProductServiceImpl.java | 2 -- 5 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 backend/JiShop/src/main/java/com/jishop/product/dto/response/ProductCategoryInfoResponse.java create mode 100644 backend/JiShop/src/main/java/com/jishop/product/dto/response/ProductImageUrlResponse.java create mode 100644 backend/JiShop/src/main/java/com/jishop/product/dto/response/ProductInfoResponse.java create mode 100644 backend/JiShop/src/main/java/com/jishop/product/dto/response/ProductStatusResponse.java diff --git a/backend/JiShop/src/main/java/com/jishop/product/dto/response/ProductCategoryInfoResponse.java b/backend/JiShop/src/main/java/com/jishop/product/dto/response/ProductCategoryInfoResponse.java new file mode 100644 index 00000000..88556733 --- /dev/null +++ b/backend/JiShop/src/main/java/com/jishop/product/dto/response/ProductCategoryInfoResponse.java @@ -0,0 +1,17 @@ +package com.jishop.product.dto.response; + +import com.jishop.product.domain.embed.CategoryInfo; + +public record ProductCategoryInfoResponse( + Long lCatId, + Long mCatId, + Long sCatId +) { + public static ProductCategoryInfoResponse from(CategoryInfo categoryInfo) { + return new ProductCategoryInfoResponse( + categoryInfo.getLCatId(), + categoryInfo.getMCatId(), + categoryInfo.getSCatId() + ); + } +} diff --git a/backend/JiShop/src/main/java/com/jishop/product/dto/response/ProductImageUrlResponse.java b/backend/JiShop/src/main/java/com/jishop/product/dto/response/ProductImageUrlResponse.java new file mode 100644 index 00000000..7dc87394 --- /dev/null +++ b/backend/JiShop/src/main/java/com/jishop/product/dto/response/ProductImageUrlResponse.java @@ -0,0 +1,23 @@ +package com.jishop.product.dto.response; + +import com.jishop.product.domain.embed.ImageUrl; + +public record ProductImageUrlResponse( + String mainImage, + String image1, + String image2, + String image3, + String image4, + String detailImage +) { + public static ProductImageUrlResponse from(ImageUrl imageUrl) { + return new ProductImageUrlResponse( + imageUrl.getMainImage(), + imageUrl.getImage1(), + imageUrl.getImage2(), + imageUrl.getImage3(), + imageUrl.getImage4(), + imageUrl.getDetailImage() + ); + } +} diff --git a/backend/JiShop/src/main/java/com/jishop/product/dto/response/ProductInfoResponse.java b/backend/JiShop/src/main/java/com/jishop/product/dto/response/ProductInfoResponse.java new file mode 100644 index 00000000..b39bf393 --- /dev/null +++ b/backend/JiShop/src/main/java/com/jishop/product/dto/response/ProductInfoResponse.java @@ -0,0 +1,23 @@ +package com.jishop.product.dto.response; + +import com.jishop.product.domain.embed.ProductInfo; + +public record ProductInfoResponse( + String name, + String brand, + String description, + int originPrice, + int discountPrice, + int discountRate +) { + public static ProductInfoResponse from(ProductInfo productInfo) { + return new ProductInfoResponse( + productInfo.getName(), + productInfo.getBrand(), + productInfo.getDescription(), + productInfo.getOriginPrice(), + productInfo.getDiscountPrice(), + productInfo.getDiscountRate() + ); + } +} \ No newline at end of file diff --git a/backend/JiShop/src/main/java/com/jishop/product/dto/response/ProductStatusResponse.java b/backend/JiShop/src/main/java/com/jishop/product/dto/response/ProductStatusResponse.java new file mode 100644 index 00000000..4e898e25 --- /dev/null +++ b/backend/JiShop/src/main/java/com/jishop/product/dto/response/ProductStatusResponse.java @@ -0,0 +1,24 @@ +package com.jishop.product.dto.response; + +import com.jishop.product.domain.DiscountStatus; +import com.jishop.product.domain.Labels; +import com.jishop.product.domain.SaleStatus; +import com.jishop.product.domain.embed.Status; + +public record ProductStatusResponse( + Boolean secret, + SaleStatus saleStatus, + Labels labels, + DiscountStatus discountStatus, + Boolean isDiscount +) { + public static ProductStatusResponse from(Status status) { + return new ProductStatusResponse( + status.getSecret(), + status.getSaleStatus(), + status.getLabels(), + status.getDiscountStatus(), + status.getIsDiscount() + ); + } +} diff --git a/backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductServiceImpl.java b/backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductServiceImpl.java index 005e93e6..73ecc721 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductServiceImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/product/service/impl/ProductServiceImpl.java @@ -6,7 +6,6 @@ import com.jishop.option.dto.FashionClothesOptionResponse; import com.jishop.option.dto.GeneralOptionResponse; import com.jishop.option.dto.SizeOption; -import com.jishop.order.repository.OrderDetailRepository; import com.jishop.product.domain.Product; import com.jishop.product.dto.request.ProductRequest; import com.jishop.product.dto.response.ProductDetailResponse; @@ -37,7 +36,6 @@ public class ProductServiceImpl implements ProductService { private final ProductRepository productRepository; private final ReviewProductRepository reviewProductRepository; private final SaleProductRepository saleProductRepository; - private final OrderDetailRepository orderDetailRepository; private final ProductWishlistService wishlistService; private final ProductCategoryService productCategoryService; From c7fc814e018e975304614ef93c9f5a9f08cc4268 Mon Sep 17 00:00:00 2001 From: KWAK-JINHO Date: Wed, 16 Apr 2025 02:33:40 +0900 Subject: [PATCH 25/25] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20refactor:=20null?= =?UTF-8?q?=20=ED=95=84=ED=84=B0=EB=A7=81=20=EC=9C=84=ED=95=9C=20List=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🔗 Resolves: #294 --- .../dto/response/ProductDetailResponse.java | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/backend/JiShop/src/main/java/com/jishop/product/dto/response/ProductDetailResponse.java b/backend/JiShop/src/main/java/com/jishop/product/dto/response/ProductDetailResponse.java index cb0e62e0..72b85a64 100644 --- a/backend/JiShop/src/main/java/com/jishop/product/dto/response/ProductDetailResponse.java +++ b/backend/JiShop/src/main/java/com/jishop/product/dto/response/ProductDetailResponse.java @@ -3,6 +3,9 @@ import com.fasterxml.jackson.annotation.JsonUnwrapped; import com.jishop.product.domain.Product; +import java.util.List; +import java.util.stream.Stream; + public record ProductDetailResponse( @JsonUnwrapped ProductResponse productResponse, @@ -12,7 +15,7 @@ public record ProductDetailResponse( Object option, int reviewCount, double reviewRate, - String[] images, + List images, String detailImage, Object isWished ) { @@ -21,6 +24,17 @@ public static ProductDetailResponse from(final ProductResponse productResponse, final Object option) { final Object wishStatus = isWished != null && isWished; + ProductImageUrlResponse imageUrlResponse = ProductImageUrlResponse.from(product.getImage()); + + List imageList = Stream.of( + imageUrlResponse.image1(), + imageUrlResponse.image2(), + imageUrlResponse.image3(), + imageUrlResponse.image4() + ) + .filter(img -> img != null && !img.isEmpty()) + .toList(); + return new ProductDetailResponse( productResponse, product.getProductInfo().getDescription(), @@ -28,13 +42,8 @@ public static ProductDetailResponse from(final ProductResponse productResponse, option, reviewCount, reviewRate, - new String[] { - product.getImage().getImage1(), - product.getImage().getImage2(), - product.getImage().getImage3(), - product.getImage().getImage4() - }, - product.getImage().getDetailImage(), + imageList, + imageUrlResponse.detailImage(), wishStatus ); }