diff --git a/service/brand/brand-api/src/main/java/radiata/service/brand/api/presentation/ProductController.java b/service/brand/brand-api/src/main/java/radiata/service/brand/api/presentation/ProductController.java index f79e79dc..e854d95f 100644 --- a/service/brand/brand-api/src/main/java/radiata/service/brand/api/presentation/ProductController.java +++ b/service/brand/brand-api/src/main/java/radiata/service/brand/api/presentation/ProductController.java @@ -2,7 +2,6 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.web.PageableDefault; @@ -62,7 +61,7 @@ public CommonResponse getProducts( ) { System.out.println("gender:" + condition.gender()); - Page response = productQueryService.getProducts(condition, pageable); + var response = productQueryService.getProducts(condition, pageable); return SuccessResponse.success(SuccessMessage.OK.getMessage(), response); } diff --git a/service/brand/brand-core/src/main/java/radiata/service/brand/core/implement/RestPage.java b/service/brand/brand-core/src/main/java/radiata/service/brand/core/implement/RestPage.java new file mode 100644 index 00000000..5d44ee70 --- /dev/null +++ b/service/brand/brand-core/src/main/java/radiata/service/brand/core/implement/RestPage.java @@ -0,0 +1,11 @@ +package radiata.service.brand.core.implement; + +import java.util.List; + +public record RestPage( + List content, + int size, + long total +) { + +} diff --git a/service/brand/brand-core/src/main/java/radiata/service/brand/core/service/FeignClient/TimeSaleClient.java b/service/brand/brand-core/src/main/java/radiata/service/brand/core/service/FeignClient/TimeSaleClient.java index f7e6f49e..85798448 100644 --- a/service/brand/brand-core/src/main/java/radiata/service/brand/core/service/FeignClient/TimeSaleClient.java +++ b/service/brand/brand-core/src/main/java/radiata/service/brand/core/service/FeignClient/TimeSaleClient.java @@ -1,8 +1,9 @@ package radiata.service.brand.core.service.FeignClient; +import java.util.List; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestParam; import radiata.common.domain.timesale.dto.response.TimeSaleProductResponseDto; import radiata.common.response.SuccessResponse; @@ -10,12 +11,9 @@ public interface TimeSaleClient { //상품 단건 타임세일 최처가 조회 - @GetMapping("/products/{productId}/timesale-products/max-discount") - SuccessResponse getMaxDiscountTimeSaleProduct( - @PathVariable String productId); + @GetMapping("/timesale-products/max-discount") + SuccessResponse> getMaxDiscountTimeSaleProducts( + @RequestParam List productIds); + - //상품 리스트 타임세일 최저가 조회 - /* @GetMapping("/products/itmesale-prducts/max-discount") - SuccessResponse> getMaxDiscountTimeSaleProducts(); -*/ } diff --git a/service/brand/brand-core/src/main/java/radiata/service/brand/core/service/Mapper/ProductMapper.java b/service/brand/brand-core/src/main/java/radiata/service/brand/core/service/Mapper/ProductMapper.java index 5e5d7286..d1b70a3d 100644 --- a/service/brand/brand-core/src/main/java/radiata/service/brand/core/service/Mapper/ProductMapper.java +++ b/service/brand/brand-core/src/main/java/radiata/service/brand/core/service/Mapper/ProductMapper.java @@ -1,9 +1,12 @@ package radiata.service.brand.core.service.Mapper; import static org.mapstruct.MappingConstants.ComponentModel.SPRING; +import java.util.List; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.springframework.data.domain.Page; import radiata.common.domain.brand.response.ProductGetResponseDto; +import radiata.service.brand.core.implement.RestPage; import radiata.service.brand.core.model.entity.Product; @Mapper(componentModel = SPRING) @@ -14,4 +17,16 @@ public interface ProductMapper { @Mapping(source = "product.id", target = "productId") @Mapping(source = "product.stock.stock", target = "stock") ProductGetResponseDto toProductGetResponseDto(Product product); + + @Mapping(source = "content", target = "content") + @Mapping(source = "size", target = "size") + @Mapping(source = "totalElements", target = "total") + RestPage toRestPage(Page page); + + @Mapping(source = "maxDiscountAmount", target = "discountAmount") + ProductGetResponseDto toMaxDiscountAmount(ProductGetResponseDto dto, int maxDiscountAmount); + + RestPage toRestPageFromDiscountMax(List content, int size, + long total); + } diff --git a/service/brand/brand-core/src/main/java/radiata/service/brand/core/service/ProductQueryService.java b/service/brand/brand-core/src/main/java/radiata/service/brand/core/service/ProductQueryService.java index d3c364a2..54cc203d 100644 --- a/service/brand/brand-core/src/main/java/radiata/service/brand/core/service/ProductQueryService.java +++ b/service/brand/brand-core/src/main/java/radiata/service/brand/core/service/ProductQueryService.java @@ -1,6 +1,8 @@ package radiata.service.brand.core.service; import feign.FeignException; +import java.util.List; +import java.util.Objects; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; @@ -12,6 +14,7 @@ import radiata.common.domain.timesale.dto.response.TimeSaleProductResponseDto; import radiata.service.brand.core.implement.ProductReader; import radiata.service.brand.core.implement.RedisService; +import radiata.service.brand.core.implement.RestPage; import radiata.service.brand.core.model.entity.Product; import radiata.service.brand.core.service.FeignClient.TimeSaleClient; import radiata.service.brand.core.service.Mapper.ProductMapper; @@ -78,26 +81,51 @@ public ProductGetResponseDto getProduct(String productId) { * 상품 목록 조회 */ @Transactional(readOnly = true) - public Page getProducts(ProductSearchCondition condition, Pageable pageable) { + public RestPage getProducts(ProductSearchCondition condition, Pageable pageable) { String productSearchPagingKey = generateSearchCacheKey(condition, pageable); - Page cachedProducts = redisService.getProductDto(productSearchPagingKey, Page.class); + //레디스 조회 + RestPage cachedProducts = redisService.getProductDto(productSearchPagingKey, + RestPage.class); if (cachedProducts != null) { return cachedProducts; } + //db 조회 Page productsSearchWithCondition = productReader.readWithCondition(condition, pageable) .map(productMapper::toProductGetResponseDto); - //todo : TimeSaleProduct api ,최저가 비교 + // 최저가 비교 - if (pageable.getPageSize() <= 5) { - redisService.setProductDtoWithExpire(productSearchPagingKey, productsSearchWithCondition, - PRODUCT_SEARCH_DURATION); + List RequestMaxProductsDiscountIds = productsSearchWithCondition.stream() + .map(ProductGetResponseDto::productId) + .toList(); + + List timeSaleMaxProducts = timeSaleClient.getMaxDiscountTimeSaleProducts( + RequestMaxProductsDiscountIds).data(); + + List list = productsSearchWithCondition.stream().toList(); + + //최저가 list + for (TimeSaleProductResponseDto dto : timeSaleMaxProducts) { + list.stream() + .filter(t -> Objects.equals(t.productId(), dto.productId())).findFirst() + .map(t -> + list.set(list.indexOf(t), + productMapper.toMaxDiscountAmount(t, Math.max(t.discountAmount(), dto.discountRate()))) + ); } - return productsSearchWithCondition; + RestPage response = productMapper.toRestPageFromDiscountMax(list, + productsSearchWithCondition.getSize(), + productsSearchWithCondition.getTotalElements()); + if (pageable.getPageNumber() <= 5) { + redisService.setProductDtoWithExpire(productSearchPagingKey, + response, + PRODUCT_SEARCH_DURATION); + } + return response; } @@ -105,7 +133,7 @@ private ProductGetResponseDto fetchAndCacheTimeSaleProduct(Product product, Stri try { TimeSaleProductResponseDto maxDisCountTimeSaleProduct = - timeSaleClient.getMaxDiscountTimeSaleProduct(product.getId()).data(); + timeSaleClient.getMaxDiscountTimeSaleProducts(product.getId().lines().toList()).data().getFirst(); if (maxDisCountTimeSaleProduct.discountRate() > product.getDiscountAmount()) { product.setMaxDiscountAmount(maxDisCountTimeSaleProduct.discountRate()); diff --git a/service/gateway/gateway-core/src/main/java/radiata/service/gateway/core/config/GatewayConfig.java b/service/gateway/gateway-core/src/main/java/radiata/service/gateway/core/config/GatewayConfig.java index c3e1bd6f..a9a342f4 100644 --- a/service/gateway/gateway-core/src/main/java/radiata/service/gateway/core/config/GatewayConfig.java +++ b/service/gateway/gateway-core/src/main/java/radiata/service/gateway/core/config/GatewayConfig.java @@ -20,11 +20,11 @@ public RouteLocator customRoutes(RouteLocatorBuilder builder) { .route("order-service", r -> r.path("/orders/**") .filters(f -> applyCommonFilters(f, "order-service")) .uri("lb://order-service")) - .route("brand-service", r -> r.path("/goods/**", "/brands/**", "/categories/**") + .route("brand-service", r -> r.path("/products/**", "/brands/**", "/categories/**") .filters(f -> applyCommonFilters(f, "brand-service")) .uri("lb://brand-service")) //todo : /products 경로 겹침 >>products 변경 부탁드려용 - .route("timesale-service", r -> r.path("/timesales/**", "/timesale-products/**", "/products/**") + .route("timesale-service", r -> r.path("/timesales/**", "/timesale-products/**") .filters(f -> applyCommonFilters(f, "timesale-service")) .uri("lb://timesale-service")) .route("payment-service", r -> r.path("/payments/**", "/payusers/**") diff --git a/service/timesale/timesale-api/src/main/java/radiata/service/timeslae/api/controller/TimeSaleProductController.java b/service/timesale/timesale-api/src/main/java/radiata/service/timeslae/api/controller/TimeSaleProductController.java index 99139a80..12804fc1 100644 --- a/service/timesale/timesale-api/src/main/java/radiata/service/timeslae/api/controller/TimeSaleProductController.java +++ b/service/timesale/timesale-api/src/main/java/radiata/service/timeslae/api/controller/TimeSaleProductController.java @@ -4,7 +4,6 @@ import static radiata.common.message.SuccessMessage.GET_MAX_DISCOUNT_TIME_SALE_PRODUCT; import static radiata.common.message.SuccessMessage.SALE_TIME_SALE_PRODUCT; import static radiata.common.response.SuccessResponse.success; - import jakarta.validation.Valid; import java.util.List; import lombok.RequiredArgsConstructor; @@ -29,49 +28,49 @@ public class TimeSaleProductController { @PostMapping("/timesale-products") public ResponseEntity createTimeSaleProduct( - @Valid @RequestBody - TimeSaleProductCreateRequestDto requestDto + @Valid @RequestBody + TimeSaleProductCreateRequestDto requestDto ) { return ResponseEntity.status(CREATE_TIME_SALE_PRODUCT.getHttpStatus()) - .body(success(CREATE_TIME_SALE_PRODUCT.getMessage(), - timeSaleProductApiService.createTimeSaleProduct(requestDto))); + .body(success(CREATE_TIME_SALE_PRODUCT.getMessage(), + timeSaleProductApiService.createTimeSaleProduct(requestDto))); } @PatchMapping("/timesale-products/{timeSaleProductId}") public ResponseEntity saleTimeSaleProduct( - @PathVariable - String timeSaleProductId, - @RequestBody - TimeSaleProductSaleRequestDto requestDto + @PathVariable + String timeSaleProductId, + @RequestBody + TimeSaleProductSaleRequestDto requestDto ) { return ResponseEntity.status(SALE_TIME_SALE_PRODUCT.getHttpStatus()) - .body(success(SALE_TIME_SALE_PRODUCT.getMessage(), - timeSaleProductApiService.saleTimeSaleProduct(timeSaleProductId, - requestDto))); + .body(success(SALE_TIME_SALE_PRODUCT.getMessage(), + timeSaleProductApiService.saleTimeSaleProduct(timeSaleProductId, + requestDto))); } - @GetMapping("/products/timesale-products/max-discount") + @GetMapping("/timesale-products/max-discount") public ResponseEntity getMaxDiscountTimeSaleProduct( - @RequestParam - List productIds + @RequestParam + List productIds ) { return ResponseEntity.status(GET_MAX_DISCOUNT_TIME_SALE_PRODUCT.getHttpStatus()) - .body(success(GET_MAX_DISCOUNT_TIME_SALE_PRODUCT.getMessage(), - timeSaleProductApiService.getMaxDiscountTimeSaleProduct(productIds))); + .body(success(GET_MAX_DISCOUNT_TIME_SALE_PRODUCT.getMessage(), + timeSaleProductApiService.getMaxDiscountTimeSaleProduct(productIds))); } - @GetMapping("/products/timesale-products/max-discount-has-stock") + @GetMapping("/timesale-products/max-discount-has-stock") public ResponseEntity getMaxDiscountTimeSaleProductHasStock( - @RequestParam - List productIds + @RequestParam + List productIds ) { return ResponseEntity.status(GET_MAX_DISCOUNT_TIME_SALE_PRODUCT.getHttpStatus()) - .body(success(GET_MAX_DISCOUNT_TIME_SALE_PRODUCT.getMessage(), - timeSaleProductApiService.getMaxDiscountTimeSaleProductHasStock( - productIds))); + .body(success(GET_MAX_DISCOUNT_TIME_SALE_PRODUCT.getMessage(), + timeSaleProductApiService.getMaxDiscountTimeSaleProductHasStock( + productIds))); } } diff --git a/service/timesale/timesale-core/src/main/java/radiata/service/timesale/core/implementation/TimeSaleReaderImpl.java b/service/timesale/timesale-core/src/main/java/radiata/service/timesale/core/implementation/TimeSaleReaderImpl.java index 8b1c20bf..bcb52ac3 100644 --- a/service/timesale/timesale-core/src/main/java/radiata/service/timesale/core/implementation/TimeSaleReaderImpl.java +++ b/service/timesale/timesale-core/src/main/java/radiata/service/timesale/core/implementation/TimeSaleReaderImpl.java @@ -1,7 +1,6 @@ package radiata.service.timesale.core.implementation; import static radiata.common.message.ExceptionMessage.NOT_FOUND; - import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; @@ -39,7 +38,7 @@ public Page readByCondition(TimeSaleSearchCondition condition, Pageabl public TimeSale readByProductId(String productId) { return timeSaleQueryRepository.findByProductId(productId).orElseThrow( - () -> new BusinessException(NOT_FOUND) + () -> new BusinessException(NOT_FOUND) ); } diff --git a/service/timesale/timesale-core/src/main/java/radiata/service/timesale/core/infrastructure/repository/TimeSaleQueryRepositoryImpl.java b/service/timesale/timesale-core/src/main/java/radiata/service/timesale/core/infrastructure/repository/TimeSaleQueryRepositoryImpl.java index 61802249..302a21bc 100644 --- a/service/timesale/timesale-core/src/main/java/radiata/service/timesale/core/infrastructure/repository/TimeSaleQueryRepositoryImpl.java +++ b/service/timesale/timesale-core/src/main/java/radiata/service/timesale/core/infrastructure/repository/TimeSaleQueryRepositoryImpl.java @@ -1,8 +1,7 @@ package radiata.service.timesale.core.infrastructure.repository; import static radiata.service.timesale.core.domain.QTimeSale.timeSale; -import static radiata.service.timesale.core.domain.QTimeSaleProduct.*; - +import static radiata.service.timesale.core.domain.QTimeSaleProduct.timeSaleProduct; import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.PathBuilder; @@ -34,18 +33,18 @@ public TimeSaleQueryRepositoryImpl(EntityManager em) { @Override public Page findTimeSalesByCondition(TimeSaleSearchCondition condition, - Pageable pageable) { + Pageable pageable) { List content = queryFactory.selectFrom(timeSale) - .where(titleEq(condition.title())) - .orderBy(getOrderSpecifiers(pageable.getSort()).toArray(OrderSpecifier[]::new)) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .fetch(); + .where(titleEq(condition.title())) + .orderBy(getOrderSpecifiers(pageable.getSort()).toArray(OrderSpecifier[]::new)) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); JPAQuery total = queryFactory.select(timeSale.count()) - .from(timeSale) - .where(titleEq(condition.title())); + .from(timeSale) + .where(titleEq(condition.title())); return PageableExecutionUtils.getPage(content, pageable, total::fetchOne); } @@ -56,14 +55,14 @@ public Optional findByProductId(String productId) { LocalDateTime now = LocalDateTime.now(); return Optional.ofNullable(queryFactory.select(timeSale) - .from(timeSale) - .leftJoin(timeSaleProduct).on(timeSaleProduct.timeSale.id.eq(timeSale.id)) - .where( - timeSaleProduct.productId.eq(productId), - timeSaleProduct.timeSaleStartTime.before(now), - timeSaleProduct.timeSaleEndTime.after(now) - ) - .fetchOne()); + .from(timeSale) + .leftJoin(timeSaleProduct).on(timeSaleProduct.timeSale.id.eq(timeSale.id)) + .where( + timeSaleProduct.productId.eq(productId), + timeSaleProduct.timeSaleStartTime.before(now), + timeSaleProduct.timeSaleEndTime.after(now) + ) + .fetchOne()); } @Override @@ -72,16 +71,17 @@ public List findByProductIds(List productIds) { LocalDateTime now = LocalDateTime.now(); return queryFactory.select(timeSale) - .from(timeSale) - .leftJoin(timeSaleProduct).on(timeSaleProduct.timeSale.id.eq(timeSale.id)) - .where( - timeSaleProduct.productId.in(productIds), - timeSaleProduct.timeSaleStartTime.before(now), - timeSaleProduct.timeSaleEndTime.after(now) - ) - .fetch(); + .from(timeSale) + .leftJoin(timeSaleProduct).on(timeSaleProduct.timeSale.id.eq(timeSale.id)) + .where( + timeSaleProduct.productId.in(productIds), + timeSaleProduct.timeSaleStartTime.before(now), + timeSaleProduct.timeSaleEndTime.after(now) + ) + .fetch(); } + private BooleanExpression titleEq(String title) { return title != null ? timeSale.title.contains(title) : null; @@ -91,8 +91,8 @@ private List getOrderSpecifiers(Sort sort) { List orders = sort.stream().map(o -> { com.querydsl.core.types.Order direction = - o.isAscending() ? com.querydsl.core.types.Order.ASC - : com.querydsl.core.types.Order.DESC; + o.isAscending() ? com.querydsl.core.types.Order.ASC + : com.querydsl.core.types.Order.DESC; String property = o.getProperty(); PathBuilder orderByExpression = new PathBuilder<>(TimeSale.class, "timeSale"); return new OrderSpecifier(direction, orderByExpression.get(property)); diff --git a/service/timesale/timesale-core/src/main/java/radiata/service/timesale/core/service/TimeSaleProductServiceImpl.java b/service/timesale/timesale-core/src/main/java/radiata/service/timesale/core/service/TimeSaleProductServiceImpl.java index 53109705..2ea88efd 100644 --- a/service/timesale/timesale-core/src/main/java/radiata/service/timesale/core/service/TimeSaleProductServiceImpl.java +++ b/service/timesale/timesale-core/src/main/java/radiata/service/timesale/core/service/TimeSaleProductServiceImpl.java @@ -74,19 +74,21 @@ public List getMaxDiscountTimeSaleProduct(List allTimeSaleProducts = timeSaleReader.readByProductIds(productIds).stream() - .flatMap(timeSale -> timeSale.getTimeSaleProducts().stream()) - .map(timeSaleProductMapper::toDto) - .toList(); - + .flatMap(timeSale -> timeSale.getTimeSaleProducts().stream()) + .map(timeSaleProductMapper::toDto) + .toList(); Map maxDiscountProducts = allTimeSaleProducts.stream() - .collect(Collectors.toMap( - TimeSaleProductResponseDto::productId, - Function.identity(), - (existing, replacement) -> replacement.discountRate() > existing.discountRate() ? replacement : existing - )); - - return new ArrayList<>(maxDiscountProducts.values()); + .collect(Collectors.toMap( + TimeSaleProductResponseDto::productId, + Function.identity(), + (existing, replacement) -> replacement.discountRate() > existing.discountRate() ? replacement : existing + )); + + return productIds.stream() + .filter(maxDiscountProducts::containsKey) + .map(maxDiscountProducts::get) + .toList(); } @Override @@ -97,22 +99,20 @@ public List getMaxDiscountTimeSaleProductHasStock(Li } List allTimeSaleProducts = timeSaleReader.readByProductIds(productIds).stream() - .flatMap(timeSale -> timeSale.getTimeSaleProducts().stream()) - .map(timeSaleProductMapper::toDto) - .toList(); - + .flatMap(timeSale -> timeSale.getTimeSaleProducts().stream()) + .map(timeSaleProductMapper::toDto) + .toList(); List availableStockProducts = allTimeSaleProducts.stream() - .filter(product -> product.saleQuantity() < product.totalQuantity()) - .toList(); - + .filter(product -> product.saleQuantity() < product.totalQuantity()) + .toList(); Map maxDiscountProducts = availableStockProducts.stream() - .collect(Collectors.toMap( - TimeSaleProductResponseDto::productId, - Function.identity(), - (existing, replacement) -> replacement.discountRate() > existing.discountRate() ? replacement : existing - )); + .collect(Collectors.toMap( + TimeSaleProductResponseDto::productId, + Function.identity(), + (existing, replacement) -> replacement.discountRate() > existing.discountRate() ? replacement : existing + )); return new ArrayList<>(maxDiscountProducts.values()); } diff --git a/service/user/user-core/src/main/java/radiata/service/user/core/service/UserCommandService.java b/service/user/user-core/src/main/java/radiata/service/user/core/service/UserCommandService.java index f22a938a..763b553e 100644 --- a/service/user/user-core/src/main/java/radiata/service/user/core/service/UserCommandService.java +++ b/service/user/user-core/src/main/java/radiata/service/user/core/service/UserCommandService.java @@ -1,6 +1,7 @@ package radiata.service.user.core.service; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.springframework.kafka.annotation.KafkaListener; import org.springframework.stereotype.Service; @@ -11,15 +12,18 @@ import radiata.common.message.ExceptionMessage; import radiata.service.user.core.domain.model.constant.PointType; import radiata.service.user.core.domain.model.entity.User; +import radiata.service.user.core.domain.repository.PointHistoryRepository; import radiata.service.user.core.domain.repository.UserRepository; import radiata.service.user.core.implement.PointHandler; @Service @RequiredArgsConstructor +@Slf4j public class UserCommandService { private final UserRepository userRepository; private final PointHandler pointHandler; + private final PointHistoryRepository pointHistoryRepository; /** * 회원 정보 수정