recommend

This commit is contained in:
youbeen
2025-06-12 16:03:48 +09:00
parent 1a0d3c5268
commit ff127c1edc
10 changed files with 632 additions and 94 deletions
@@ -2,19 +2,22 @@ package com.ktds.hi.recommend;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
/**
* 추천 서비스 메인 애플리케이션
* 추천 서비스 메인 애플리케이션 클래스
* 가게 추천, 취향 분석 기능을 제공
*
* @author 하이오더 개발팀
* @version 1.0.0
*/
@SpringBootApplication(scanBasePackages = {
"com.ktds.hi.recommend",
"com.ktds.hi.common"
})
@EnableFeignClients
@EnableJpaAuditing
public class RecommendServiceApplication {
public static void main(String[] args) {
SpringApplication.run(RecommendServiceApplication.class, args);
}
@@ -1,4 +1,80 @@
package com.ktds.hi.recommend.infra.config;
public class RecommendWebClientConfig {
}
package com.ktds.hi.recommend.infra.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.client.RestTemplate;
import java.time.Duration;
/**
* 추천 서비스 WebClient 설정 클래스
* 외부 서비스와의 HTTP 통신을 위한 설정
*
* @author 하이오더 개발팀
* @version 1.0.0
*/
@Configuration
public class RecommendWebClientConfig {
@Value("${services.store.url:http://store-service:8082}")
private String storeServiceUrl;
@Value("${services.review.url:http://review-service:8083}")
private String reviewServiceUrl;
@Value("${services.member.url:http://member-service:8081}")
private String memberServiceUrl;
/**
* 매장 서비스와 통신하기 위한 WebClient
*/
@Bean("storeWebClient")
public WebClient storeWebClient() {
return WebClient.builder()
.baseUrl(storeServiceUrl)
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(1024 * 1024))
.build();
}
/**
* 리뷰 서비스와 통신하기 위한 WebClient
*/
@Bean("reviewWebClient")
public WebClient reviewWebClient() {
return WebClient.builder()
.baseUrl(reviewServiceUrl)
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(1024 * 1024))
.build();
}
/**
* 회원 서비스와 통신하기 위한 WebClient
*/
@Bean("memberWebClient")
public WebClient memberWebClient() {
return WebClient.builder()
.baseUrl(memberServiceUrl)
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(1024 * 1024))
.build();
}
/**
* 일반적인 외부 API 호출을 위한 WebClient
*/
@Bean("externalWebClient")
public WebClient externalWebClient() {
return WebClient.builder()
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024))
.build();
}
/**
* 호환성을 위한 RestTemplate (레거시 지원)
*/
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
@@ -1,4 +1,150 @@
package com.ktds.hi.recommend.infra.gateway;
public class ExternalServiceClient {
}
package com.ktds.hi.recommend.infra.gateway;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import java.time.Duration;
import java.util.List;
import java.util.Map;
/**
* 외부 서비스 클라이언트
* 다른 마이크로서비스와의 HTTP 통신을 담당
*
* @author 하이오더 개발팀
* @version 1.0.0
*/
@Component
@RequiredArgsConstructor
@Slf4j
public class ExternalServiceClient {
@Qualifier("storeWebClient")
private final WebClient storeWebClient;
@Qualifier("reviewWebClient")
private final WebClient reviewWebClient;
@Qualifier("memberWebClient")
private final WebClient memberWebClient;
/**
* 매장 정보 조회
*/
public Mono<Map<String, Object>> getStoreInfo(Long storeId) {
return storeWebClient
.get()
.uri("/api/stores/{storeId}", storeId)
.retrieve()
.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {})
.timeout(Duration.ofSeconds(5))
.doOnError(error -> log.error("매장 정보 조회 실패: storeId={}, error={}", storeId, error.getMessage()))
.onErrorReturn(Map.of());
}
/**
* 매장 목록 조회 (위치 기반)
*/
public Mono<List<Map<String, Object>>> getStoresByLocation(Double latitude, Double longitude, Integer radius) {
return storeWebClient
.get()
.uri(uriBuilder -> uriBuilder
.path("/api/stores/search")
.queryParam("latitude", latitude)
.queryParam("longitude", longitude)
.queryParam("radius", radius)
.build())
.retrieve()
.bodyToMono(new ParameterizedTypeReference<List<Map<String, Object>>>() {})
.timeout(Duration.ofSeconds(10))
.doOnError(error -> log.error("위치 기반 매장 조회 실패: lat={}, lng={}, radius={}, error={}",
latitude, longitude, radius, error.getMessage()))
.onErrorReturn(List.of());
}
/**
* 매장별 리뷰 조회
*/
public Mono<List<Map<String, Object>>> getStoreReviews(Long storeId, Integer limit) {
return reviewWebClient
.get()
.uri(uriBuilder -> uriBuilder
.path("/api/reviews/store/{storeId}")
.queryParam("limit", limit)
.build(storeId))
.retrieve()
.bodyToMono(new ParameterizedTypeReference<List<Map<String, Object>>>() {})
.timeout(Duration.ofSeconds(5))
.doOnError(error -> log.error("매장 리뷰 조회 실패: storeId={}, error={}", storeId, error.getMessage()))
.onErrorReturn(List.of());
}
/**
* 회원 취향 정보 조회
*/
public Mono<Map<String, Object>> getMemberPreferences(Long memberId) {
return memberWebClient
.get()
.uri("/api/members/{memberId}/preferences", memberId)
.retrieve()
.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {})
.timeout(Duration.ofSeconds(5))
.doOnError(error -> log.error("회원 취향 정보 조회 실패: memberId={}, error={}", memberId, error.getMessage()))
.onErrorReturn(Map.of());
}
/**
* 회원 주문 이력 조회
*/
public Mono<List<Map<String, Object>>> getMemberOrderHistory(Long memberId, Integer limit) {
return memberWebClient
.get()
.uri(uriBuilder -> uriBuilder
.path("/api/members/{memberId}/orders")
.queryParam("limit", limit)
.build(memberId))
.retrieve()
.bodyToMono(new ParameterizedTypeReference<List<Map<String, Object>>>() {})
.timeout(Duration.ofSeconds(5))
.doOnError(error -> log.error("회원 주문 이력 조회 실패: memberId={}, error={}", memberId, error.getMessage()))
.onErrorReturn(List.of());
}
/**
* 매장 평점 및 통계 조회
*/
public Mono<Map<String, Object>> getStoreStatistics(Long storeId) {
return storeWebClient
.get()
.uri("/api/stores/{storeId}/statistics", storeId)
.retrieve()
.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {})
.timeout(Duration.ofSeconds(5))
.doOnError(error -> log.error("매장 통계 조회 실패: storeId={}, error={}", storeId, error.getMessage()))
.onErrorReturn(Map.of("rating", 0.0, "reviewCount", 0));
}
/**
* 인기 매장 조회
*/
public Mono<List<Map<String, Object>>> getPopularStores(String category, Integer limit) {
return storeWebClient
.get()
.uri(uriBuilder -> uriBuilder
.path("/api/stores/popular")
.queryParam("category", category)
.queryParam("limit", limit)
.build())
.retrieve()
.bodyToMono(new ParameterizedTypeReference<List<Map<String, Object>>>() {})
.timeout(Duration.ofSeconds(10))
.doOnError(error -> log.error("인기 매장 조회 실패: category={}, limit={}, error={}",
category, limit, error.getMessage()))
.onErrorReturn(List.of());
}
}