Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[]feat(brand) : 실시간 상품 최저가 조회 쿼리 변경 #115

Merged
merged 8 commits into from
Oct 24, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -62,7 +61,7 @@ public CommonResponse getProducts(
) {
System.out.println("gender:" + condition.gender());

Page<ProductGetResponseDto> response = productQueryService.getProducts(condition, pageable);
var response = productQueryService.getProducts(condition, pageable);
return SuccessResponse.success(SuccessMessage.OK.getMessage(), response);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package radiata.service.brand.core.implement;

import java.util.List;

public record RestPage<T>(
List<T> content,
int size,
long total
) {

}
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
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;

@FeignClient(name = "timesale-service")
public interface TimeSaleClient {

//상품 단건 타임세일 최처가 조회
@GetMapping("/products/{productId}/timesale-products/max-discount")
SuccessResponse<TimeSaleProductResponseDto> getMaxDiscountTimeSaleProduct(
@PathVariable String productId);
@GetMapping("/timesale-products/max-discount")
SuccessResponse<List<TimeSaleProductResponseDto>> getMaxDiscountTimeSaleProducts(
@RequestParam List<String> productIds);


//상품 리스트 타임세일 최저가 조회
/* @GetMapping("/products/itmesale-prducts/max-discount")
SuccessResponse<List<TimeSaleProductResponseDto>> getMaxDiscountTimeSaleProducts();
*/
}
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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<ProductGetResponseDto> toRestPage(Page<ProductGetResponseDto> page);

@Mapping(source = "maxDiscountAmount", target = "discountAmount")
ProductGetResponseDto toMaxDiscountAmount(ProductGetResponseDto dto, int maxDiscountAmount);

RestPage<ProductGetResponseDto> toRestPageFromDiscountMax(List<ProductGetResponseDto> content, int size,
long total);

}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -78,34 +81,59 @@ public ProductGetResponseDto getProduct(String productId) {
* 상품 목록 조회
*/
@Transactional(readOnly = true)
public Page<ProductGetResponseDto> getProducts(ProductSearchCondition condition, Pageable pageable) {
public RestPage<ProductGetResponseDto> getProducts(ProductSearchCondition condition, Pageable pageable) {

String productSearchPagingKey = generateSearchCacheKey(condition, pageable);
Page<ProductGetResponseDto> cachedProducts = redisService.getProductDto(productSearchPagingKey, Page.class);
//레디스 조회
RestPage<ProductGetResponseDto> cachedProducts = redisService.getProductDto(productSearchPagingKey,
RestPage.class);
if (cachedProducts != null) {
return cachedProducts;
}

//db 조회
Page<ProductGetResponseDto> productsSearchWithCondition = productReader.readWithCondition(condition, pageable)
.map(productMapper::toProductGetResponseDto);

//todo : TimeSaleProduct api ,최저가 비교
// 최저가 비교

if (pageable.getPageSize() <= 5) {
redisService.setProductDtoWithExpire(productSearchPagingKey, productsSearchWithCondition,
PRODUCT_SEARCH_DURATION);
List<String> RequestMaxProductsDiscountIds = productsSearchWithCondition.stream()
.map(ProductGetResponseDto::productId)
.toList();

List<TimeSaleProductResponseDto> timeSaleMaxProducts = timeSaleClient.getMaxDiscountTimeSaleProducts(
RequestMaxProductsDiscountIds).data();

List<ProductGetResponseDto> 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<ProductGetResponseDto> response = productMapper.toRestPageFromDiscountMax(list,
productsSearchWithCondition.getSize(),
productsSearchWithCondition.getTotalElements());

if (pageable.getPageNumber() <= 5) {
redisService.setProductDtoWithExpire(productSearchPagingKey,
response,
PRODUCT_SEARCH_DURATION);
}
return response;
}


private ProductGetResponseDto fetchAndCacheTimeSaleProduct(Product product, String key) {

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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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/**")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -29,49 +28,49 @@ public class TimeSaleProductController {

@PostMapping("/timesale-products")
public ResponseEntity<? extends CommonResponse> 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<? extends CommonResponse> 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<? extends CommonResponse> getMaxDiscountTimeSaleProduct(
@RequestParam
List<String> productIds
@RequestParam
List<String> 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<? extends CommonResponse> getMaxDiscountTimeSaleProductHasStock(
@RequestParam
List<String> productIds
@RequestParam
List<String> 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)));
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -39,7 +38,7 @@ public Page<TimeSale> readByCondition(TimeSaleSearchCondition condition, Pageabl
public TimeSale readByProductId(String productId) {

return timeSaleQueryRepository.findByProductId(productId).orElseThrow(
() -> new BusinessException(NOT_FOUND)
() -> new BusinessException(NOT_FOUND)
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -34,18 +33,18 @@ public TimeSaleQueryRepositoryImpl(EntityManager em) {

@Override
public Page<TimeSale> findTimeSalesByCondition(TimeSaleSearchCondition condition,
Pageable pageable) {
Pageable pageable) {

List<TimeSale> 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<Long> total = queryFactory.select(timeSale.count())
.from(timeSale)
.where(titleEq(condition.title()));
.from(timeSale)
.where(titleEq(condition.title()));

return PageableExecutionUtils.getPage(content, pageable, total::fetchOne);
}
Expand All @@ -56,14 +55,14 @@ public Optional<TimeSale> 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
Expand All @@ -72,16 +71,17 @@ public List<TimeSale> findByProductIds(List<String> 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;
Expand All @@ -91,8 +91,8 @@ private List<OrderSpecifier> getOrderSpecifiers(Sort sort) {

List<OrderSpecifier> 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<TimeSale> orderByExpression = new PathBuilder<>(TimeSale.class, "timeSale");
return new OrderSpecifier(direction, orderByExpression.get(property));
Expand Down
Loading