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

Merge Develop branch to Main branch #109

Merged
merged 20 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0106e9b
build.gradle actuator 추가
koo995 Dec 13, 2024
b8136c7
application.yml 엑츄에이터 노출 설정
koo995 Dec 13, 2024
637df6e
application.yml java, os 정보 표시
koo995 Dec 13, 2024
52afbb7
build.gradle 프로메테우스 추가
koo995 Dec 14, 2024
e0631c5
chore: 프로메테우스 톰캣 스레드 갯수 표시
koo995 Dec 14, 2024
aba2803
Merge pull request #99 from f-lab-edu/feature/#98
koo995 Dec 14, 2024
1a77051
application.yml sql init mode 수정
koo995 Dec 18, 2024
246253d
Merge pull request #100 from f-lab-edu/feature/#98
koo995 Dec 18, 2024
69df69b
Elastic Search 연결 실패
koo995 Dec 20, 2024
99aadea
build.gradle opensearch-rest-client-sniffer 제거
koo995 Dec 20, 2024
8cb947c
feat: product 검색 Elasticsearch 엔진으로 변경
koo995 Dec 20, 2024
e0e3eb9
Merge pull request #102 from f-lab-edu/feature/#101
koo995 Dec 21, 2024
661505b
refactor: product에 대한 review 와 tag 조회 리팩터링
koo995 Dec 21, 2024
a1af952
Merge pull request #104 from f-lab-edu/feature/#103
koo995 Dec 22, 2024
036b9bd
refactor: Elasticsearch MySQL 동기화
koo995 Jan 4, 2025
923b017
refactor: storeId로 product 조회 시 태그 함께 표현
koo995 Jan 6, 2025
49667b9
Merge pull request #106 from f-lab-edu/feature/#105
koo995 Jan 6, 2025
7360aeb
refactor: 상품 저장 비동기 처리
koo995 Jan 8, 2025
7cc6e76
refactor: 톰캣 스레드 수와 HikariCP 스레드 수 조절
koo995 Jan 9, 2025
b38c800
Merge pull request #108 from f-lab-edu/feature/#107
koo995 Jan 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'

// monitoring
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus'

// elasticsearch
implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'

// thymeleaf
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
// aws s3
Expand Down
9 changes: 8 additions & 1 deletion src/main/java/flab/nutridiary/NutridiaryApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,15 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync
@EnableJdbcRepositories(excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = "flab.nutridiary.search.*"))
@SpringBootApplication(exclude = {ElasticsearchDataAutoConfiguration.class})
public class NutridiaryApplication {

public static void main(String[] args) {
Expand Down
34 changes: 34 additions & 0 deletions src/main/java/flab/nutridiary/commom/config/RestClientConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package flab.nutridiary.commom.config;


import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;

@EnableElasticsearchRepositories(basePackages = "flab.nutridiary.search")
@Configuration
public class RestClientConfig extends ElasticsearchConfiguration {

@Value("${spring.elasticsearch.url}")
private String url;

@Value("${spring.elasticsearch.port}")
private int port;

@Value("${spring.elasticsearch.username}")
private String username;

@Value("${spring.elasticsearch.password}")
private String password;

@Override
public ClientConfiguration clientConfiguration() {
return ClientConfiguration.builder()
.connectedTo(url + ":" + port)
.usingSsl()
.withBasicAuth(username, password)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ public enum StatusConst {
VALIDATION_CHECK_FAIL(6001, "유효성 검사에 실패했습니다."),
NOT_ALLOWED_SERVING_UNIT(6002, "허용되지 않은 서빙 단위입니다."),
DUPLICATED_PRODUCT_REVIEW(4006, "이미 등록된 리뷰입니다."),
STORE_PRODUCT_NOT_FOUND(4007, "해당 매장에 등록된 상품이 없습니다."),;
STORE_PRODUCT_NOT_FOUND(4007, "해당 매장에 등록된 상품이 없습니다."),
PRODUCT_NOT_FOUND(4008, "해당 상품을 찾을 수 없습니다.");

private final int statusCode;
private final String message;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package flab.nutridiary.product.domain;

import flab.nutridiary.product.dto.request.NewProductRequest;
import flab.nutridiary.search.ProductDocument;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
Expand All @@ -20,6 +21,10 @@ public Product from(NewProductRequest productRequest) {
.build();
}

public ProductDocument toDocument(Long productId, Product product) {
return new ProductDocument(productId, product.getProductName(), product.getProductCorp());
}

private static NutritionFacts getNutritionFacts(NewProductRequest productRequest) {
BigDecimal productTotalCalories = productRequest.getCalories();
BigDecimal productTotalCarbohydrate = productRequest.getCarbohydrate();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
import lombok.Getter;
import lombok.ToString;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;

@Getter
@EqualsAndHashCode
Expand All @@ -14,16 +13,14 @@ public class ProductSearchResponse {
private Long productId;
private String productName;
private String productCorp;
private Integer reviewCount;
private BigDecimal reviewAvgRating;
private String top3_diet_tag_names;
private Long reviewCount;
private List<String> top3_diet_tag_names;

public ProductSearchResponse(Long productId, String productName, String productCorp, Integer reviewCount, BigDecimal reviewAvgRating, String top3_diet_tag_names) {
public ProductSearchResponse(Long productId, String productName, String productCorp, Long reviewCount , List<String> top3_diet_tag_names) {
this.productId = productId;
this.productName = productName;
this.productCorp = productCorp;
this.reviewCount = reviewCount;
this.reviewAvgRating = reviewAvgRating == null ? BigDecimal.ZERO : reviewAvgRating.setScale(1, RoundingMode.HALF_UP);
this.top3_diet_tag_names = top3_diet_tag_names;
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
import flab.nutridiary.product.dto.request.NewProductRequest;
import flab.nutridiary.product.dto.response.NewProductResponse;
import flab.nutridiary.product.repository.ProductRepository;
import flab.nutridiary.search.ProductDocumentAsyncRegisterService;
import flab.nutridiary.search.ProductDocumentRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -16,12 +19,19 @@
@Service
public class ProductRegisterService {
private final ProductRepository productRepository;
private final ProductDocumentAsyncRegisterService productDocumentAsyncRegisterService;
private final ProductValidator productValidator;
private final ProductMapper productMapper;

public NewProductResponse process(NewProductRequest productRequest) {
Product product = productMapper.from(productRequest);
productValidator.validate(product);
return NewProductResponse.of(productRepository.save(product).getId());
return NewProductResponse.of(saveProduct(product));
}

private Long saveProduct(Product product) {
Long id = productRepository.save(product).getId();
productDocumentAsyncRegisterService.saveDocumentAsync(productMapper.toDocument(id, product));
return id;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package flab.nutridiary.product.service;

import flab.nutridiary.product.domain.Product;
import flab.nutridiary.product.dto.response.ProductSearchResponse;
import flab.nutridiary.productDietTag.ProductDietTagDto;
import flab.nutridiary.review.repository.ProductReviewCount;
import flab.nutridiary.search.ProductDocument;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.stream.Collectors;

@Component
public class ProductSearchResponseMapper {

private static final int TOP_DIET_TAG_COUNT = 3;

public Page<ProductSearchResponse> toResponseList(
Page<ProductDocument> productDocumentsPage,
List<ProductReviewCount> productReviewCounts,
List<ProductDietTagDto> productDietTagDtos
) {
Map<Long, Long> reviewCountMap = buildReviewCountMap(productReviewCounts);
Map<Long, List<ProductDietTagDto>> tagsByProductId = buildTagsByProductId(productDietTagDtos);

List<ProductSearchResponse> responses = productDocumentsPage
.getContent()
.stream()
.map(document -> toProductSearchResponse(
document.getProductId(),
document.getProductName(),
document.getProductCorp(),
reviewCountMap,
tagsByProductId
))
.collect(Collectors.toList());

return new PageImpl<>(responses, productDocumentsPage.getPageable(), productDocumentsPage.getTotalElements());
}

public List<ProductSearchResponse> toResponseList(
List<Product> products,
List<ProductReviewCount> productReviewCounts,
List<ProductDietTagDto> productDietTagDtos
) {
Map<Long, Long> reviewCountMap = buildReviewCountMap(productReviewCounts);
Map<Long, List<ProductDietTagDto>> tagsByProductId = buildTagsByProductId(productDietTagDtos);

return products.stream()
.map(product -> toProductSearchResponse(
product.getId(),
product.getProductName(),
product.getProductCorp(),
reviewCountMap,
tagsByProductId
))
.collect(Collectors.toList());
}

private Map<Long, Long> buildReviewCountMap(List<ProductReviewCount> productReviewCounts) {
return productReviewCounts.stream()
.collect(Collectors.toMap(
ProductReviewCount::getProductId,
ProductReviewCount::getReviewCount
));
}

private Map<Long, List<ProductDietTagDto>> buildTagsByProductId(List<ProductDietTagDto> productDietTagDtos) {
return productDietTagDtos.stream()
.collect(Collectors.groupingBy(ProductDietTagDto::getProductId));
}

private ProductSearchResponse toProductSearchResponse(
Long productId,
String productName,
String productCorp,
Map<Long, Long> reviewCountMap,
Map<Long, List<ProductDietTagDto>> tagsByProductId
) {
Long reviewCount = reviewCountMap.getOrDefault(productId, 0L);
List<String> top3DietTags = Optional.ofNullable(tagsByProductId.get(productId))
.orElse(Collections.emptyList())
.stream()
.sorted(Comparator.comparing(ProductDietTagDto::getTag_count).reversed())
.limit(TOP_DIET_TAG_COUNT)
.map(ProductDietTagDto::getDietPlan)
.collect(Collectors.toList());

return new ProductSearchResponse(
productId,
productName,
productCorp,
reviewCount,
top3DietTags
);
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,47 @@
package flab.nutridiary.product.service;

import flab.nutridiary.commom.exception.BusinessException;
import flab.nutridiary.product.dto.response.ProductSearchResponse;
import flab.nutridiary.product.repository.ProductSearchRepository;
import flab.nutridiary.productDietTag.ProductDietTagDto;
import flab.nutridiary.productDietTag.repository.ProductDietTagRepository;
import flab.nutridiary.review.repository.ProductReviewCount;
import flab.nutridiary.review.repository.ReviewRepository;
import flab.nutridiary.search.ProductDocument;
import flab.nutridiary.search.ProductDocumentRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

import static flab.nutridiary.commom.exception.StatusConst.PRODUCT_NOT_FOUND;

@Slf4j
@RequiredArgsConstructor
@Transactional(readOnly = true)
@Service
public class ProductSearchService {
private final ProductSearchRepository productSearchRepository;
private final ProductDocumentRepository productDocumentRepository;
private final ReviewRepository reviewRepository;
private final ProductDietTagRepository productDietTagRepository;
private final ProductSearchResponseMapper productSearchResponseMapper;

public Page<ProductSearchResponse> search(String condition, Pageable pageable) {
return productSearchRepository.findFullTextSearch(condition, pageable);
Page<ProductDocument> productDocuments = productDocumentRepository.findByProductName(condition, pageable);

if (productDocuments.isEmpty()) {
throw new BusinessException(PRODUCT_NOT_FOUND);
}

List<Long> productIds = productDocuments.stream()
.map(ProductDocument::getProductId)
.toList();

List<ProductReviewCount> productReviewCounts = reviewRepository.countReviewsByProductIds(productIds);
List<ProductDietTagDto> tags = productDietTagRepository.findByProductIds(productIds);
return productSearchResponseMapper.toResponseList(productDocuments, productReviewCounts, tags);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package flab.nutridiary.productDietTag;

import lombok.Getter;
import lombok.ToString;

@ToString
@Getter
public class ProductDietTagDto {
private Long productId;
private String dietPlan;
private Long tag_count;

public ProductDietTagDto(Long productId, String dietPlan, Long tag_count) {
this.productId = productId;
this.dietPlan = dietPlan;
this.tag_count = tag_count;
}
}
Loading
Loading