Merge branch 'main' of https://github.com/dg04-hi/hi-backend
# Conflicts: # analytics/src/main/java/com/ktds/hi/analytics/infra/config/SwaggerConfig.java # member/src/main/java/com/ktds/hi/member/config/SwaggerConfig.java # recommend/src/main/java/com/ktds/hi/recommend/infra/config/SwaggerConfig.java
This commit is contained in:
@@ -1,402 +1,402 @@
|
||||
package com.ktds.hi.analytics.biz.service;
|
||||
|
||||
import com.ktds.hi.analytics.biz.domain.Analytics;
|
||||
import com.ktds.hi.analytics.biz.domain.AiFeedback;
|
||||
import com.ktds.hi.analytics.biz.usecase.in.AnalyticsUseCase;
|
||||
import com.ktds.hi.analytics.biz.usecase.out.*;
|
||||
import com.ktds.hi.analytics.infra.dto.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 분석 서비스 구현 클래스 (수정버전)
|
||||
* Clean Architecture의 UseCase를 구현하여 비즈니스 로직을 처리
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class AnalyticsService implements AnalyticsUseCase {
|
||||
|
||||
private final AnalyticsPort analyticsPort;
|
||||
private final AIServicePort aiServicePort;
|
||||
private final ExternalReviewPort externalReviewPort;
|
||||
private final OrderDataPort orderDataPort;
|
||||
private final CachePort cachePort;
|
||||
private final EventPort eventPort;
|
||||
|
||||
@Override
|
||||
@Cacheable(value = "storeAnalytics", key = "#storeId")
|
||||
public StoreAnalyticsResponse getStoreAnalytics(Long storeId) {
|
||||
log.info("매장 분석 데이터 조회 시작: storeId={}", storeId);
|
||||
|
||||
try {
|
||||
// 1. 캐시에서 먼저 확인
|
||||
String cacheKey = "analytics:store:" + storeId;
|
||||
var cachedResult = cachePort.getAnalyticsCache(cacheKey);
|
||||
if (cachedResult.isPresent()) {
|
||||
log.info("캐시에서 분석 데이터 반환: storeId={}", storeId);
|
||||
return (StoreAnalyticsResponse) cachedResult.get();
|
||||
}
|
||||
|
||||
// 2. 데이터베이스에서 기존 분석 데이터 조회
|
||||
var analytics = analyticsPort.findAnalyticsByStoreId(storeId);
|
||||
|
||||
if (analytics.isEmpty()) {
|
||||
// 3. 분석 데이터가 없으면 새로 생성
|
||||
analytics = Optional.of(generateNewAnalytics(storeId));
|
||||
}
|
||||
|
||||
// 4. 응답 생성
|
||||
StoreAnalyticsResponse response = StoreAnalyticsResponse.builder()
|
||||
.storeId(storeId)
|
||||
.totalReviews(analytics.get().getTotalReviews())
|
||||
.averageRating(analytics.get().getAverageRating())
|
||||
.sentimentScore(analytics.get().getSentimentScore())
|
||||
.positiveReviewRate(analytics.get().getPositiveReviewRate())
|
||||
.negativeReviewRate(analytics.get().getNegativeReviewRate())
|
||||
.lastAnalysisDate(analytics.get().getLastAnalysisDate())
|
||||
.build();
|
||||
|
||||
// 5. 캐시에 저장
|
||||
cachePort.putAnalyticsCache(cacheKey, response, java.time.Duration.ofHours(1));
|
||||
|
||||
log.info("매장 분석 데이터 조회 완료: storeId={}", storeId);
|
||||
return response;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("매장 분석 데이터 조회 중 오류 발생: storeId={}", storeId, e);
|
||||
throw new RuntimeException("분석 데이터 조회에 실패했습니다.", e);
|
||||
}
|
||||
}
|
||||
|
||||
// ... 나머지 메서드들은 이전과 동일 ...
|
||||
|
||||
@Override
|
||||
@Cacheable(value = "aiFeedback", key = "#storeId")
|
||||
public AiFeedbackDetailResponse getAIFeedbackDetail(Long storeId) {
|
||||
log.info("AI 피드백 상세 조회 시작: storeId={}", storeId);
|
||||
|
||||
try {
|
||||
// 1. 기존 AI 피드백 조회
|
||||
var aiFeedback = analyticsPort.findAIFeedbackByStoreId(storeId);
|
||||
|
||||
if (aiFeedback.isEmpty()) {
|
||||
// 2. AI 피드백이 없으면 새로 생성
|
||||
aiFeedback = Optional.of(generateAIFeedback(storeId));
|
||||
}
|
||||
|
||||
// 3. 응답 생성
|
||||
AiFeedbackDetailResponse response = AiFeedbackDetailResponse.builder()
|
||||
.storeId(storeId)
|
||||
.summary(aiFeedback.get().getSummary())
|
||||
.positivePoints(aiFeedback.get().getPositivePoints())
|
||||
.improvementPoints(aiFeedback.get().getImprovementPoints())
|
||||
.recommendations(aiFeedback.get().getRecommendations())
|
||||
.sentimentAnalysis(aiFeedback.get().getSentimentAnalysis())
|
||||
.confidenceScore(aiFeedback.get().getConfidenceScore())
|
||||
.generatedAt(aiFeedback.get().getGeneratedAt())
|
||||
.build();
|
||||
|
||||
log.info("AI 피드백 상세 조회 완료: storeId={}", storeId);
|
||||
return response;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("AI 피드백 조회 중 오류 발생: storeId={}", storeId, e);
|
||||
throw new RuntimeException("AI 피드백 조회에 실패했습니다.", e);
|
||||
}
|
||||
}
|
||||
|
||||
// 나머지 메서드들과 private 메서드들은 이전과 동일하게 구현
|
||||
// ... (getStoreStatistics, getAIFeedbackSummary, getReviewAnalysis 등)
|
||||
|
||||
@Override
|
||||
public StoreStatisticsResponse getStoreStatistics(Long storeId, LocalDate startDate, LocalDate endDate) {
|
||||
log.info("매장 통계 조회 시작: storeId={}, startDate={}, endDate={}", storeId, startDate, endDate);
|
||||
|
||||
try {
|
||||
// 1. 캐시 키 생성
|
||||
String cacheKey = String.format("statistics:store:%d:%s:%s", storeId, startDate, endDate);
|
||||
var cachedResult = cachePort.getAnalyticsCache(cacheKey);
|
||||
if (cachedResult.isPresent()) {
|
||||
log.info("캐시에서 통계 데이터 반환: storeId={}", storeId);
|
||||
return (StoreStatisticsResponse) cachedResult.get();
|
||||
}
|
||||
|
||||
// 2. 주문 통계 데이터 조회 (실제 OrderStatistics 도메인 필드 사용)
|
||||
var orderStatistics = orderDataPort.getOrderStatistics(storeId, startDate, endDate);
|
||||
|
||||
// 3. 응답 생성
|
||||
StoreStatisticsResponse response = StoreStatisticsResponse.builder()
|
||||
.storeId(storeId)
|
||||
.startDate(startDate)
|
||||
.endDate(endDate)
|
||||
.totalOrders(orderStatistics.getTotalOrders())
|
||||
.totalRevenue(orderStatistics.getTotalRevenue())
|
||||
.averageOrderValue(orderStatistics.getAverageOrderValue())
|
||||
.peakHour(orderStatistics.getPeakHour())
|
||||
.popularMenus(orderStatistics.getPopularMenus())
|
||||
.customerAgeDistribution(orderStatistics.getCustomerAgeDistribution())
|
||||
.build();
|
||||
|
||||
// 4. 캐시에 저장
|
||||
cachePort.putAnalyticsCache(cacheKey, response, java.time.Duration.ofMinutes(30));
|
||||
|
||||
log.info("매장 통계 조회 완료: storeId={}", storeId);
|
||||
return response;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("매장 통계 조회 중 오류 발생: storeId={}", storeId, e);
|
||||
throw new RuntimeException("매장 통계 조회에 실패했습니다.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AiFeedbackSummaryResponse getAIFeedbackSummary(Long storeId) {
|
||||
log.info("AI 피드백 요약 조회 시작: storeId={}", storeId);
|
||||
|
||||
try {
|
||||
// 1. 캐시에서 확인
|
||||
String cacheKey = "ai_feedback_summary:store:" + storeId;
|
||||
var cachedResult = cachePort.getAnalyticsCache(cacheKey);
|
||||
if (cachedResult.isPresent()) {
|
||||
return (AiFeedbackSummaryResponse) cachedResult.get();
|
||||
}
|
||||
|
||||
// 2. AI 피드백 조회
|
||||
var aiFeedback = analyticsPort.findAIFeedbackByStoreId(storeId);
|
||||
|
||||
if (aiFeedback.isEmpty()) {
|
||||
// 3. 피드백이 없으면 기본 응답 생성
|
||||
AiFeedbackSummaryResponse emptyResponse = AiFeedbackSummaryResponse.builder()
|
||||
.storeId(storeId)
|
||||
.hasData(false)
|
||||
.message("분석할 데이터가 부족합니다.")
|
||||
.lastUpdated(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
cachePort.putAnalyticsCache(cacheKey, emptyResponse, java.time.Duration.ofHours(1));
|
||||
return emptyResponse;
|
||||
}
|
||||
|
||||
// 4. 응답 생성
|
||||
AiFeedbackSummaryResponse response = AiFeedbackSummaryResponse.builder()
|
||||
.storeId(storeId)
|
||||
.hasData(true)
|
||||
.message("AI 분석이 완료되었습니다.")
|
||||
.overallScore(aiFeedback.get().getConfidenceScore())
|
||||
.keyInsight(aiFeedback.get().getSummary())
|
||||
.priorityRecommendation(getFirstRecommendation(aiFeedback.get()))
|
||||
.lastUpdated(aiFeedback.get().getUpdatedAt())
|
||||
.build();
|
||||
|
||||
// 5. 캐시에 저장
|
||||
cachePort.putAnalyticsCache(cacheKey, response, java.time.Duration.ofHours(2));
|
||||
|
||||
log.info("AI 피드백 요약 조회 완료: storeId={}", storeId);
|
||||
return response;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("AI 피드백 요약 조회 중 오류 발생: storeId={}", storeId, e);
|
||||
throw new RuntimeException("AI 피드백 요약 조회에 실패했습니다.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReviewAnalysisResponse getReviewAnalysis(Long storeId) {
|
||||
log.info("리뷰 분석 조회 시작: storeId={}", storeId);
|
||||
|
||||
try {
|
||||
// 1. 캐시에서 확인
|
||||
String cacheKey = "review_analysis:store:" + storeId;
|
||||
var cachedResult = cachePort.getAnalyticsCache(cacheKey);
|
||||
if (cachedResult.isPresent()) {
|
||||
return (ReviewAnalysisResponse) cachedResult.get();
|
||||
}
|
||||
|
||||
// 2. 최근 리뷰 데이터 조회 (30일)
|
||||
List<String> recentReviews = externalReviewPort.getRecentReviews(storeId, 30);
|
||||
|
||||
if (recentReviews.isEmpty()) {
|
||||
ReviewAnalysisResponse emptyResponse = ReviewAnalysisResponse.builder()
|
||||
.storeId(storeId)
|
||||
.totalReviews(0)
|
||||
.positiveReviewCount(0)
|
||||
.negativeReviewCount(0)
|
||||
.positiveRate(0.0)
|
||||
.negativeRate(0.0)
|
||||
.analysisDate(LocalDate.now())
|
||||
.build();
|
||||
|
||||
cachePort.putAnalyticsCache(cacheKey, emptyResponse, java.time.Duration.ofHours(1));
|
||||
return emptyResponse;
|
||||
}
|
||||
|
||||
// 3. 응답 생성
|
||||
int positiveCount = countPositiveReviews(recentReviews);
|
||||
int negativeCount = countNegativeReviews(recentReviews);
|
||||
int totalCount = recentReviews.size();
|
||||
|
||||
ReviewAnalysisResponse response = ReviewAnalysisResponse.builder()
|
||||
.storeId(storeId)
|
||||
.totalReviews(totalCount)
|
||||
.positiveReviewCount(positiveCount)
|
||||
.negativeReviewCount(negativeCount)
|
||||
.positiveRate((double) positiveCount / totalCount * 100)
|
||||
.negativeRate((double) negativeCount / totalCount * 100)
|
||||
.analysisDate(LocalDate.now())
|
||||
.build();
|
||||
|
||||
// 4. 캐시에 저장
|
||||
cachePort.putAnalyticsCache(cacheKey, response, java.time.Duration.ofHours(4));
|
||||
|
||||
log.info("리뷰 분석 조회 완료: storeId={}", storeId);
|
||||
return response;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("리뷰 분석 중 오류 발생: storeId={}", storeId, e);
|
||||
throw new RuntimeException("리뷰 분석에 실패했습니다.", e);
|
||||
}
|
||||
}
|
||||
|
||||
// private 메서드들
|
||||
@Transactional
|
||||
public Analytics generateNewAnalytics(Long storeId) {
|
||||
log.info("새로운 분석 데이터 생성 시작: storeId={}", storeId);
|
||||
|
||||
try {
|
||||
// 1. 리뷰 데이터 수집
|
||||
List<String> reviewData = externalReviewPort.getReviewData(storeId);
|
||||
int totalReviews = reviewData.size();
|
||||
|
||||
if (totalReviews == 0) {
|
||||
log.warn("리뷰 데이터가 없어 기본값으로 분석 데이터 생성: storeId={}", storeId);
|
||||
return createDefaultAnalytics(storeId);
|
||||
}
|
||||
|
||||
// 2. 기본 통계 계산
|
||||
double averageRating = 4.0; // 기본값
|
||||
double sentimentScore = 0.5; // 중립
|
||||
double positiveRate = 60.0;
|
||||
double negativeRate = 20.0;
|
||||
|
||||
// 3. Analytics 도메인 객체 생성
|
||||
Analytics analytics = Analytics.builder()
|
||||
.storeId(storeId)
|
||||
.totalReviews(totalReviews)
|
||||
.averageRating(averageRating)
|
||||
.sentimentScore(sentimentScore)
|
||||
.positiveReviewRate(positiveRate)
|
||||
.negativeReviewRate(negativeRate)
|
||||
.lastAnalysisDate(LocalDateTime.now())
|
||||
.createdAt(LocalDateTime.now())
|
||||
.updatedAt(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
// 4. 데이터베이스에 저장
|
||||
Analytics saved = analyticsPort.saveAnalytics(analytics);
|
||||
|
||||
log.info("새로운 분석 데이터 생성 완료: storeId={}", storeId);
|
||||
return saved;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("분석 데이터 생성 중 오류 발생: storeId={}", storeId, e);
|
||||
return createDefaultAnalytics(storeId);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public AiFeedback generateAIFeedback(Long storeId) {
|
||||
log.info("AI 피드백 생성 시작: storeId={}", storeId);
|
||||
|
||||
try {
|
||||
// 1. 최근 30일 리뷰 데이터 수집
|
||||
List<String> reviewData = externalReviewPort.getRecentReviews(storeId, 30);
|
||||
|
||||
if (reviewData.isEmpty()) {
|
||||
log.warn("AI 피드백 생성을 위한 리뷰 데이터가 없습니다: storeId={}", storeId);
|
||||
return createDefaultAIFeedback(storeId);
|
||||
}
|
||||
|
||||
// 2. AI 피드백 생성 (실제로는 AI 서비스 호출)
|
||||
AiFeedback aiFeedback = AiFeedback.builder()
|
||||
.storeId(storeId)
|
||||
.summary("고객들의 전반적인 만족도가 높습니다.")
|
||||
.positivePoints(List.of("맛이 좋다", "서비스가 친절하다", "분위기가 좋다"))
|
||||
.improvementPoints(List.of("대기시간 단축", "가격 경쟁력", "메뉴 다양성"))
|
||||
.recommendations(List.of("특별 메뉴 개발", "예약 시스템 도입", "고객 서비스 교육"))
|
||||
.sentimentAnalysis("POSITIVE")
|
||||
.confidenceScore(0.85)
|
||||
.generatedAt(LocalDateTime.now())
|
||||
.createdAt(LocalDateTime.now())
|
||||
.updatedAt(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
// 3. 데이터베이스에 저장
|
||||
AiFeedback saved = analyticsPort.saveAIFeedback(aiFeedback);
|
||||
|
||||
log.info("AI 피드백 생성 완료: storeId={}", storeId);
|
||||
return saved;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("AI 피드백 생성 중 오류 발생: storeId={}", storeId, e);
|
||||
return createDefaultAIFeedback(storeId);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private Analytics createDefaultAnalytics(Long storeId) {
|
||||
return Analytics.builder()
|
||||
.storeId(storeId)
|
||||
.totalReviews(0)
|
||||
.averageRating(0.0)
|
||||
.sentimentScore(0.0)
|
||||
.positiveReviewRate(0.0)
|
||||
.negativeReviewRate(0.0)
|
||||
.lastAnalysisDate(LocalDateTime.now())
|
||||
.createdAt(LocalDateTime.now())
|
||||
.updatedAt(LocalDateTime.now())
|
||||
.build();
|
||||
}
|
||||
|
||||
private AiFeedback createDefaultAIFeedback(Long storeId) {
|
||||
return AiFeedback.builder()
|
||||
.storeId(storeId)
|
||||
.summary("분석할 리뷰 데이터가 부족합니다.")
|
||||
.positivePoints(List.of("데이터 부족으로 분석 불가"))
|
||||
.improvementPoints(List.of("리뷰 데이터 수집 필요"))
|
||||
.recommendations(List.of("고객들의 리뷰 작성을 유도해보세요"))
|
||||
.sentimentAnalysis("NEUTRAL")
|
||||
.confidenceScore(0.0)
|
||||
.generatedAt(LocalDateTime.now())
|
||||
.createdAt(LocalDateTime.now())
|
||||
.updatedAt(LocalDateTime.now())
|
||||
.build();
|
||||
}
|
||||
|
||||
private String getFirstRecommendation(AiFeedback feedback) {
|
||||
if (feedback.getRecommendations() != null && !feedback.getRecommendations().isEmpty()) {
|
||||
return feedback.getRecommendations().get(0);
|
||||
}
|
||||
return "추천사항이 없습니다.";
|
||||
}
|
||||
|
||||
private int countPositiveReviews(List<String> reviews) {
|
||||
// 실제로는 AI 서비스를 통한 감정 분석 필요
|
||||
return (int) (reviews.size() * 0.6); // 60% 가정
|
||||
}
|
||||
|
||||
private int countNegativeReviews(List<String> reviews) {
|
||||
// 실제로는 AI 서비스를 통한 감정 분석 필요
|
||||
return (int) (reviews.size() * 0.2); // 20% 가정
|
||||
}
|
||||
}
|
||||
package com.ktds.hi.analytics.biz.service;
|
||||
|
||||
import com.ktds.hi.analytics.biz.domain.Analytics;
|
||||
import com.ktds.hi.analytics.biz.domain.AiFeedback;
|
||||
import com.ktds.hi.analytics.biz.usecase.in.AnalyticsUseCase;
|
||||
import com.ktds.hi.analytics.biz.usecase.out.*;
|
||||
import com.ktds.hi.analytics.infra.dto.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 분석 서비스 구현 클래스 (수정버전)
|
||||
* Clean Architecture의 UseCase를 구현하여 비즈니스 로직을 처리
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class AnalyticsService implements AnalyticsUseCase {
|
||||
|
||||
private final AnalyticsPort analyticsPort;
|
||||
private final AIServicePort aiServicePort;
|
||||
private final ExternalReviewPort externalReviewPort;
|
||||
private final OrderDataPort orderDataPort;
|
||||
private final CachePort cachePort;
|
||||
private final EventPort eventPort;
|
||||
|
||||
@Override
|
||||
@Cacheable(value = "storeAnalytics", key = "#storeId")
|
||||
public StoreAnalyticsResponse getStoreAnalytics(Long storeId) {
|
||||
log.info("매장 분석 데이터 조회 시작: storeId={}", storeId);
|
||||
|
||||
try {
|
||||
// 1. 캐시에서 먼저 확인
|
||||
String cacheKey = "analytics:store:" + storeId;
|
||||
var cachedResult = cachePort.getAnalyticsCache(cacheKey);
|
||||
if (cachedResult.isPresent()) {
|
||||
log.info("캐시에서 분석 데이터 반환: storeId={}", storeId);
|
||||
return (StoreAnalyticsResponse) cachedResult.get();
|
||||
}
|
||||
|
||||
// 2. 데이터베이스에서 기존 분석 데이터 조회
|
||||
var analytics = analyticsPort.findAnalyticsByStoreId(storeId);
|
||||
|
||||
if (analytics.isEmpty()) {
|
||||
// 3. 분석 데이터가 없으면 새로 생성
|
||||
analytics = Optional.of(generateNewAnalytics(storeId));
|
||||
}
|
||||
|
||||
// 4. 응답 생성
|
||||
StoreAnalyticsResponse response = StoreAnalyticsResponse.builder()
|
||||
.storeId(storeId)
|
||||
.totalReviews(analytics.get().getTotalReviews())
|
||||
.averageRating(analytics.get().getAverageRating())
|
||||
.sentimentScore(analytics.get().getSentimentScore())
|
||||
.positiveReviewRate(analytics.get().getPositiveReviewRate())
|
||||
.negativeReviewRate(analytics.get().getNegativeReviewRate())
|
||||
.lastAnalysisDate(analytics.get().getLastAnalysisDate())
|
||||
.build();
|
||||
|
||||
// 5. 캐시에 저장
|
||||
cachePort.putAnalyticsCache(cacheKey, response, java.time.Duration.ofHours(1));
|
||||
|
||||
log.info("매장 분석 데이터 조회 완료: storeId={}", storeId);
|
||||
return response;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("매장 분석 데이터 조회 중 오류 발생: storeId={}", storeId, e);
|
||||
throw new RuntimeException("분석 데이터 조회에 실패했습니다.", e);
|
||||
}
|
||||
}
|
||||
|
||||
// ... 나머지 메서드들은 이전과 동일 ...
|
||||
|
||||
@Override
|
||||
@Cacheable(value = "aiFeedback", key = "#storeId")
|
||||
public AiFeedbackDetailResponse getAIFeedbackDetail(Long storeId) {
|
||||
log.info("AI 피드백 상세 조회 시작: storeId={}", storeId);
|
||||
|
||||
try {
|
||||
// 1. 기존 AI 피드백 조회
|
||||
var aiFeedback = analyticsPort.findAIFeedbackByStoreId(storeId);
|
||||
|
||||
if (aiFeedback.isEmpty()) {
|
||||
// 2. AI 피드백이 없으면 새로 생성
|
||||
aiFeedback = Optional.of(generateAIFeedback(storeId));
|
||||
}
|
||||
|
||||
// 3. 응답 생성
|
||||
AiFeedbackDetailResponse response = AiFeedbackDetailResponse.builder()
|
||||
.storeId(storeId)
|
||||
.summary(aiFeedback.get().getSummary())
|
||||
.positivePoints(aiFeedback.get().getPositivePoints())
|
||||
.improvementPoints(aiFeedback.get().getImprovementPoints())
|
||||
.recommendations(aiFeedback.get().getRecommendations())
|
||||
.sentimentAnalysis(aiFeedback.get().getSentimentAnalysis())
|
||||
.confidenceScore(aiFeedback.get().getConfidenceScore())
|
||||
.generatedAt(aiFeedback.get().getGeneratedAt())
|
||||
.build();
|
||||
|
||||
log.info("AI 피드백 상세 조회 완료: storeId={}", storeId);
|
||||
return response;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("AI 피드백 조회 중 오류 발생: storeId={}", storeId, e);
|
||||
throw new RuntimeException("AI 피드백 조회에 실패했습니다.", e);
|
||||
}
|
||||
}
|
||||
|
||||
// 나머지 메서드들과 private 메서드들은 이전과 동일하게 구현
|
||||
// ... (getStoreStatistics, getAIFeedbackSummary, getReviewAnalysis 등)
|
||||
|
||||
@Override
|
||||
public StoreStatisticsResponse getStoreStatistics(Long storeId, LocalDate startDate, LocalDate endDate) {
|
||||
log.info("매장 통계 조회 시작: storeId={}, startDate={}, endDate={}", storeId, startDate, endDate);
|
||||
|
||||
try {
|
||||
// 1. 캐시 키 생성
|
||||
String cacheKey = String.format("statistics:store:%d:%s:%s", storeId, startDate, endDate);
|
||||
var cachedResult = cachePort.getAnalyticsCache(cacheKey);
|
||||
if (cachedResult.isPresent()) {
|
||||
log.info("캐시에서 통계 데이터 반환: storeId={}", storeId);
|
||||
return (StoreStatisticsResponse) cachedResult.get();
|
||||
}
|
||||
|
||||
// 2. 주문 통계 데이터 조회 (실제 OrderStatistics 도메인 필드 사용)
|
||||
var orderStatistics = orderDataPort.getOrderStatistics(storeId, startDate, endDate);
|
||||
|
||||
// 3. 응답 생성
|
||||
StoreStatisticsResponse response = StoreStatisticsResponse.builder()
|
||||
.storeId(storeId)
|
||||
.startDate(startDate)
|
||||
.endDate(endDate)
|
||||
.totalOrders(orderStatistics.getTotalOrders())
|
||||
.totalRevenue(orderStatistics.getTotalRevenue())
|
||||
.averageOrderValue(orderStatistics.getAverageOrderValue())
|
||||
.peakHour(orderStatistics.getPeakHour())
|
||||
.popularMenus(orderStatistics.getPopularMenus())
|
||||
.customerAgeDistribution(orderStatistics.getCustomerAgeDistribution())
|
||||
.build();
|
||||
|
||||
// 4. 캐시에 저장
|
||||
cachePort.putAnalyticsCache(cacheKey, response, java.time.Duration.ofMinutes(30));
|
||||
|
||||
log.info("매장 통계 조회 완료: storeId={}", storeId);
|
||||
return response;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("매장 통계 조회 중 오류 발생: storeId={}", storeId, e);
|
||||
throw new RuntimeException("매장 통계 조회에 실패했습니다.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AiFeedbackSummaryResponse getAIFeedbackSummary(Long storeId) {
|
||||
log.info("AI 피드백 요약 조회 시작: storeId={}", storeId);
|
||||
|
||||
try {
|
||||
// 1. 캐시에서 확인
|
||||
String cacheKey = "ai_feedback_summary:store:" + storeId;
|
||||
var cachedResult = cachePort.getAnalyticsCache(cacheKey);
|
||||
if (cachedResult.isPresent()) {
|
||||
return (AiFeedbackSummaryResponse) cachedResult.get();
|
||||
}
|
||||
|
||||
// 2. AI 피드백 조회
|
||||
var aiFeedback = analyticsPort.findAIFeedbackByStoreId(storeId);
|
||||
|
||||
if (aiFeedback.isEmpty()) {
|
||||
// 3. 피드백이 없으면 기본 응답 생성
|
||||
AiFeedbackSummaryResponse emptyResponse = AiFeedbackSummaryResponse.builder()
|
||||
.storeId(storeId)
|
||||
.hasData(false)
|
||||
.message("분석할 데이터가 부족합니다.")
|
||||
.lastUpdated(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
cachePort.putAnalyticsCache(cacheKey, emptyResponse, java.time.Duration.ofHours(1));
|
||||
return emptyResponse;
|
||||
}
|
||||
|
||||
// 4. 응답 생성
|
||||
AiFeedbackSummaryResponse response = AiFeedbackSummaryResponse.builder()
|
||||
.storeId(storeId)
|
||||
.hasData(true)
|
||||
.message("AI 분석이 완료되었습니다.")
|
||||
.overallScore(aiFeedback.get().getConfidenceScore())
|
||||
.keyInsight(aiFeedback.get().getSummary())
|
||||
.priorityRecommendation(getFirstRecommendation(aiFeedback.get()))
|
||||
.lastUpdated(aiFeedback.get().getUpdatedAt())
|
||||
.build();
|
||||
|
||||
// 5. 캐시에 저장
|
||||
cachePort.putAnalyticsCache(cacheKey, response, java.time.Duration.ofHours(2));
|
||||
|
||||
log.info("AI 피드백 요약 조회 완료: storeId={}", storeId);
|
||||
return response;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("AI 피드백 요약 조회 중 오류 발생: storeId={}", storeId, e);
|
||||
throw new RuntimeException("AI 피드백 요약 조회에 실패했습니다.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReviewAnalysisResponse getReviewAnalysis(Long storeId) {
|
||||
log.info("리뷰 분석 조회 시작: storeId={}", storeId);
|
||||
|
||||
try {
|
||||
// 1. 캐시에서 확인
|
||||
String cacheKey = "review_analysis:store:" + storeId;
|
||||
var cachedResult = cachePort.getAnalyticsCache(cacheKey);
|
||||
if (cachedResult.isPresent()) {
|
||||
return (ReviewAnalysisResponse) cachedResult.get();
|
||||
}
|
||||
|
||||
// 2. 최근 리뷰 데이터 조회 (30일)
|
||||
List<String> recentReviews = externalReviewPort.getRecentReviews(storeId, 30);
|
||||
|
||||
if (recentReviews.isEmpty()) {
|
||||
ReviewAnalysisResponse emptyResponse = ReviewAnalysisResponse.builder()
|
||||
.storeId(storeId)
|
||||
.totalReviews(0)
|
||||
.positiveReviewCount(0)
|
||||
.negativeReviewCount(0)
|
||||
.positiveRate(0.0)
|
||||
.negativeRate(0.0)
|
||||
.analysisDate(LocalDate.now())
|
||||
.build();
|
||||
|
||||
cachePort.putAnalyticsCache(cacheKey, emptyResponse, java.time.Duration.ofHours(1));
|
||||
return emptyResponse;
|
||||
}
|
||||
|
||||
// 3. 응답 생성
|
||||
int positiveCount = countPositiveReviews(recentReviews);
|
||||
int negativeCount = countNegativeReviews(recentReviews);
|
||||
int totalCount = recentReviews.size();
|
||||
|
||||
ReviewAnalysisResponse response = ReviewAnalysisResponse.builder()
|
||||
.storeId(storeId)
|
||||
.totalReviews(totalCount)
|
||||
.positiveReviewCount(positiveCount)
|
||||
.negativeReviewCount(negativeCount)
|
||||
.positiveRate((double) positiveCount / totalCount * 100)
|
||||
.negativeRate((double) negativeCount / totalCount * 100)
|
||||
.analysisDate(LocalDate.now())
|
||||
.build();
|
||||
|
||||
// 4. 캐시에 저장
|
||||
cachePort.putAnalyticsCache(cacheKey, response, java.time.Duration.ofHours(4));
|
||||
|
||||
log.info("리뷰 분석 조회 완료: storeId={}", storeId);
|
||||
return response;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("리뷰 분석 중 오류 발생: storeId={}", storeId, e);
|
||||
throw new RuntimeException("리뷰 분석에 실패했습니다.", e);
|
||||
}
|
||||
}
|
||||
|
||||
// private 메서드들
|
||||
@Transactional
|
||||
public Analytics generateNewAnalytics(Long storeId) {
|
||||
log.info("새로운 분석 데이터 생성 시작: storeId={}", storeId);
|
||||
|
||||
try {
|
||||
// 1. 리뷰 데이터 수집
|
||||
List<String> reviewData = externalReviewPort.getReviewData(storeId);
|
||||
int totalReviews = reviewData.size();
|
||||
|
||||
if (totalReviews == 0) {
|
||||
log.warn("리뷰 데이터가 없어 기본값으로 분석 데이터 생성: storeId={}", storeId);
|
||||
return createDefaultAnalytics(storeId);
|
||||
}
|
||||
|
||||
// 2. 기본 통계 계산
|
||||
double averageRating = 4.0; // 기본값
|
||||
double sentimentScore = 0.5; // 중립
|
||||
double positiveRate = 60.0;
|
||||
double negativeRate = 20.0;
|
||||
|
||||
// 3. Analytics 도메인 객체 생성
|
||||
Analytics analytics = Analytics.builder()
|
||||
.storeId(storeId)
|
||||
.totalReviews(totalReviews)
|
||||
.averageRating(averageRating)
|
||||
.sentimentScore(sentimentScore)
|
||||
.positiveReviewRate(positiveRate)
|
||||
.negativeReviewRate(negativeRate)
|
||||
.lastAnalysisDate(LocalDateTime.now())
|
||||
.createdAt(LocalDateTime.now())
|
||||
.updatedAt(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
// 4. 데이터베이스에 저장
|
||||
Analytics saved = analyticsPort.saveAnalytics(analytics);
|
||||
|
||||
log.info("새로운 분석 데이터 생성 완료: storeId={}", storeId);
|
||||
return saved;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("분석 데이터 생성 중 오류 발생: storeId={}", storeId, e);
|
||||
return createDefaultAnalytics(storeId);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public AiFeedback generateAIFeedback(Long storeId) {
|
||||
log.info("AI 피드백 생성 시작: storeId={}", storeId);
|
||||
|
||||
try {
|
||||
// 1. 최근 30일 리뷰 데이터 수집
|
||||
List<String> reviewData = externalReviewPort.getRecentReviews(storeId, 30);
|
||||
|
||||
if (reviewData.isEmpty()) {
|
||||
log.warn("AI 피드백 생성을 위한 리뷰 데이터가 없습니다: storeId={}", storeId);
|
||||
return createDefaultAIFeedback(storeId);
|
||||
}
|
||||
|
||||
// 2. AI 피드백 생성 (실제로는 AI 서비스 호출)
|
||||
AiFeedback aiFeedback = AiFeedback.builder()
|
||||
.storeId(storeId)
|
||||
.summary("고객들의 전반적인 만족도가 높습니다.")
|
||||
.positivePoints(List.of("맛이 좋다", "서비스가 친절하다", "분위기가 좋다"))
|
||||
.improvementPoints(List.of("대기시간 단축", "가격 경쟁력", "메뉴 다양성"))
|
||||
.recommendations(List.of("특별 메뉴 개발", "예약 시스템 도입", "고객 서비스 교육"))
|
||||
.sentimentAnalysis("POSITIVE")
|
||||
.confidenceScore(0.85)
|
||||
.generatedAt(LocalDateTime.now())
|
||||
.createdAt(LocalDateTime.now())
|
||||
.updatedAt(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
// 3. 데이터베이스에 저장
|
||||
AiFeedback saved = analyticsPort.saveAIFeedback(aiFeedback);
|
||||
|
||||
log.info("AI 피드백 생성 완료: storeId={}", storeId);
|
||||
return saved;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("AI 피드백 생성 중 오류 발생: storeId={}", storeId, e);
|
||||
return createDefaultAIFeedback(storeId);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private Analytics createDefaultAnalytics(Long storeId) {
|
||||
return Analytics.builder()
|
||||
.storeId(storeId)
|
||||
.totalReviews(0)
|
||||
.averageRating(0.0)
|
||||
.sentimentScore(0.0)
|
||||
.positiveReviewRate(0.0)
|
||||
.negativeReviewRate(0.0)
|
||||
.lastAnalysisDate(LocalDateTime.now())
|
||||
.createdAt(LocalDateTime.now())
|
||||
.updatedAt(LocalDateTime.now())
|
||||
.build();
|
||||
}
|
||||
|
||||
private AiFeedback createDefaultAIFeedback(Long storeId) {
|
||||
return AiFeedback.builder()
|
||||
.storeId(storeId)
|
||||
.summary("분석할 리뷰 데이터가 부족합니다.")
|
||||
.positivePoints(List.of("데이터 부족으로 분석 불가"))
|
||||
.improvementPoints(List.of("리뷰 데이터 수집 필요"))
|
||||
.recommendations(List.of("고객들의 리뷰 작성을 유도해보세요"))
|
||||
.sentimentAnalysis("NEUTRAL")
|
||||
.confidenceScore(0.0)
|
||||
.generatedAt(LocalDateTime.now())
|
||||
.createdAt(LocalDateTime.now())
|
||||
.updatedAt(LocalDateTime.now())
|
||||
.build();
|
||||
}
|
||||
|
||||
private String getFirstRecommendation(AiFeedback feedback) {
|
||||
if (feedback.getRecommendations() != null && !feedback.getRecommendations().isEmpty()) {
|
||||
return feedback.getRecommendations().get(0);
|
||||
}
|
||||
return "추천사항이 없습니다.";
|
||||
}
|
||||
|
||||
private int countPositiveReviews(List<String> reviews) {
|
||||
// 실제로는 AI 서비스를 통한 감정 분석 필요
|
||||
return (int) (reviews.size() * 0.6); // 60% 가정
|
||||
}
|
||||
|
||||
private int countNegativeReviews(List<String> reviews) {
|
||||
// 실제로는 AI 서비스를 통한 감정 분석 필요
|
||||
return (int) (reviews.size() * 0.2); // 20% 가정
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +1,51 @@
|
||||
package com.ktds.hi.analytics.infra.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* Analytics 서비스 보안 설정 클래스
|
||||
* 테스트를 위해 모든 엔드포인트를 인증 없이 접근 가능하도록 설정
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@RequiredArgsConstructor
|
||||
public class SecurityConfig {
|
||||
|
||||
private final CorsConfigurationSource corsConfigurationSource;
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.csrf(AbstractHttpConfigurer::disable)
|
||||
.cors(cors -> cors.configurationSource(corsConfigurationSource))
|
||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
// Swagger 관련 경로 모두 허용
|
||||
.requestMatchers("/swagger-ui.html","/swagger-ui/**", "/swagger-ui.html").permitAll()
|
||||
.requestMatchers("/api-docs/**", "/v3/api-docs/**").permitAll()
|
||||
.requestMatchers("/swagger-resources/**", "/webjars/**").permitAll()
|
||||
|
||||
// Analytics API 모두 허용 (테스트용)
|
||||
.requestMatchers("/api/analytics/**").permitAll()
|
||||
.requestMatchers("/api/action-plans/**").permitAll()
|
||||
|
||||
// Actuator 엔드포인트 허용
|
||||
.requestMatchers("/actuator/**").permitAll()
|
||||
|
||||
// 기타 모든 요청 허용 (테스트용)
|
||||
.anyRequest().permitAll()
|
||||
);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
package com.ktds.hi.analytics.infra.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* Analytics 서비스 보안 설정 클래스
|
||||
* 테스트를 위해 모든 엔드포인트를 인증 없이 접근 가능하도록 설정
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@RequiredArgsConstructor
|
||||
public class SecurityConfig {
|
||||
|
||||
|
||||
private final CorsConfigurationSource corsConfigurationSource;
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.csrf(AbstractHttpConfigurer::disable)
|
||||
.cors(cors -> cors.configurationSource(corsConfigurationSource))
|
||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
// Swagger 관련 경로 모두 허용
|
||||
.requestMatchers("/swagger-ui.html","/swagger-ui/**", "/swagger-ui.html").permitAll()
|
||||
.requestMatchers("/api-docs/**", "/v3/api-docs/**").permitAll()
|
||||
.requestMatchers("/swagger-resources/**", "/webjars/**").permitAll()
|
||||
|
||||
// Analytics API 모두 허용 (테스트용)
|
||||
.requestMatchers("/api/analytics/**").permitAll()
|
||||
.requestMatchers("/api/action-plans/**").permitAll()
|
||||
|
||||
// Actuator 엔드포인트 허용
|
||||
.requestMatchers("/actuator/**").permitAll()
|
||||
|
||||
// 기타 모든 요청 허용 (테스트용)
|
||||
.anyRequest().permitAll()
|
||||
);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,95 +1,95 @@
|
||||
server:
|
||||
port: ${ANALYTICS_SERVICE_PORT:8084}
|
||||
|
||||
logging:
|
||||
level:
|
||||
org.springframework.web.servlet.resource.ResourceHttpRequestHandler: ERROR
|
||||
org.springframework.web.servlet.DispatcherServlet: WARN
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: analytics-service
|
||||
|
||||
datasource:
|
||||
url: ${ANALYTICS_DB_URL:jdbc:postgresql://20.249.162.125:5432/hiorder_analytics}
|
||||
username: ${ANALYTICS_DB_USERNAME:hiorder_user}
|
||||
password: ${ANALYTICS_DB_PASSWORD:hiorder_pass}
|
||||
driver-class-name: org.postgresql.Driver
|
||||
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: ${JPA_DDL_AUTO:update}
|
||||
show-sql: ${JPA_SHOW_SQL:false}
|
||||
properties:
|
||||
hibernate:
|
||||
format_sql: true
|
||||
dialect: org.hibernate.dialect.PostgreSQLDialect
|
||||
|
||||
data:
|
||||
redis:
|
||||
host: ${REDIS_HOST:localhost}
|
||||
port: ${REDIS_PORT:6379}
|
||||
password: ${REDIS_PASSWORD:}
|
||||
|
||||
ai-api:
|
||||
openai:
|
||||
api-key: ${OPENAI_API_KEY:}
|
||||
base-url: https://api.openai.com/v1
|
||||
model: gpt-4o-mini
|
||||
claude:
|
||||
api-key: ${CLAUDE_API_KEY:}
|
||||
base-url: https://api.anthropic.com
|
||||
model: claude-3-sonnet-20240229
|
||||
|
||||
#external-api:
|
||||
# openai:
|
||||
# api-key: ${OPENAI_API_KEY:}
|
||||
# base-url: https://api.openai.com
|
||||
# claude:
|
||||
# api-key: ${CLAUDE_API_KEY:}
|
||||
# base-url: https://api.anthropic.com
|
||||
|
||||
# 외부 서비스 설정
|
||||
external:
|
||||
services:
|
||||
review: ${EXTERNAL_SERVICES_REVIEW:http://localhost:8082}
|
||||
store: ${EXTERNAL_SERVICES_STORE:http://localhost:8081}
|
||||
member: ${EXTERNAL_SERVICES_MEMBER:http://localhost:8080}
|
||||
|
||||
#springdoc:
|
||||
# api-docs:
|
||||
# path: /api-docs
|
||||
# swagger-ui:
|
||||
# path: /swagger-ui.html
|
||||
springdoc:
|
||||
swagger-ui:
|
||||
enabled: true
|
||||
path: /swagger-ui.html
|
||||
try-it-out-enabled: true
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: health,info,metrics
|
||||
|
||||
# AI 서비스 설정
|
||||
ai:
|
||||
azure:
|
||||
cognitive:
|
||||
endpoint: ${AI_AZURE_COGNITIVE_ENDPOINT:https://your-cognitive-service.cognitiveservices.azure.com}
|
||||
key: ${AI_AZURE_COGNITIVE_KEY:your-cognitive-service-key}
|
||||
openai:
|
||||
api-key: ${AI_OPENAI_API_KEY:your-openai-api-key}
|
||||
|
||||
# Azure Event Hub 설정
|
||||
azure:
|
||||
eventhub:
|
||||
connection-string: ${AZURE_EVENTHUB_CONNECTION_STRING:Endpoint=sb://your-eventhub.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=your-key}
|
||||
consumer-group: ${AZURE_EVENTHUB_CONSUMER_GROUP:analytics-consumer}
|
||||
event-hubs:
|
||||
review-events: ${AZURE_EVENTHUB_REVIEW_EVENTS:review-events}
|
||||
ai-analysis-events: ${AZURE_EVENTHUB_AI_ANALYSIS_EVENTS:ai-analysis-events}
|
||||
storage:
|
||||
connection-string: ${AZURE_STORAGE_CONNECTION_STRING:DefaultEndpointsProtocol=https;AccountName=yourstorageaccount;AccountKey=your-storage-key;EndpointSuffix=core.windows.net}
|
||||
container-name: ${AZURE_STORAGE_CONTAINER_NAME:eventhub-checkpoints}
|
||||
server:
|
||||
port: ${ANALYTICS_SERVICE_PORT:8084}
|
||||
|
||||
logging:
|
||||
level:
|
||||
org.springframework.web.servlet.resource.ResourceHttpRequestHandler: ERROR
|
||||
org.springframework.web.servlet.DispatcherServlet: WARN
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: analytics-service
|
||||
|
||||
datasource:
|
||||
url: ${ANALYTICS_DB_URL:jdbc:postgresql://20.249.162.125:5432/hiorder_analytics}
|
||||
username: ${ANALYTICS_DB_USERNAME:hiorder_user}
|
||||
password: ${ANALYTICS_DB_PASSWORD:hiorder_pass}
|
||||
driver-class-name: org.postgresql.Driver
|
||||
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: ${JPA_DDL_AUTO:update}
|
||||
show-sql: ${JPA_SHOW_SQL:false}
|
||||
properties:
|
||||
hibernate:
|
||||
format_sql: true
|
||||
dialect: org.hibernate.dialect.PostgreSQLDialect
|
||||
|
||||
data:
|
||||
redis:
|
||||
host: ${REDIS_HOST:localhost}
|
||||
port: ${REDIS_PORT:6379}
|
||||
password: ${REDIS_PASSWORD:}
|
||||
|
||||
ai-api:
|
||||
openai:
|
||||
api-key: ${OPENAI_API_KEY:}
|
||||
base-url: https://api.openai.com/v1
|
||||
model: gpt-4o-mini
|
||||
claude:
|
||||
api-key: ${CLAUDE_API_KEY:}
|
||||
base-url: https://api.anthropic.com
|
||||
model: claude-3-sonnet-20240229
|
||||
|
||||
#external-api:
|
||||
# openai:
|
||||
# api-key: ${OPENAI_API_KEY:}
|
||||
# base-url: https://api.openai.com
|
||||
# claude:
|
||||
# api-key: ${CLAUDE_API_KEY:}
|
||||
# base-url: https://api.anthropic.com
|
||||
|
||||
# 외부 서비스 설정
|
||||
external:
|
||||
services:
|
||||
review: ${EXTERNAL_SERVICES_REVIEW:http://localhost:8082}
|
||||
store: ${EXTERNAL_SERVICES_STORE:http://localhost:8081}
|
||||
member: ${EXTERNAL_SERVICES_MEMBER:http://localhost:8080}
|
||||
|
||||
#springdoc:
|
||||
# api-docs:
|
||||
# path: /api-docs
|
||||
# swagger-ui:
|
||||
# path: /swagger-ui.html
|
||||
springdoc:
|
||||
swagger-ui:
|
||||
enabled: true
|
||||
path: /swagger-ui.html
|
||||
try-it-out-enabled: true
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: health,info,metrics
|
||||
|
||||
# AI 서비스 설정
|
||||
ai:
|
||||
azure:
|
||||
cognitive:
|
||||
endpoint: ${AI_AZURE_COGNITIVE_ENDPOINT:https://your-cognitive-service.cognitiveservices.azure.com}
|
||||
key: ${AI_AZURE_COGNITIVE_KEY:your-cognitive-service-key}
|
||||
openai:
|
||||
api-key: ${AI_OPENAI_API_KEY:your-openai-api-key}
|
||||
|
||||
# Azure Event Hub 설정
|
||||
azure:
|
||||
eventhub:
|
||||
connection-string: ${AZURE_EVENTHUB_CONNECTION_STRING:Endpoint=sb://your-eventhub.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=your-key}
|
||||
consumer-group: ${AZURE_EVENTHUB_CONSUMER_GROUP:analytics-consumer}
|
||||
event-hubs:
|
||||
review-events: ${AZURE_EVENTHUB_REVIEW_EVENTS:review-events}
|
||||
ai-analysis-events: ${AZURE_EVENTHUB_AI_ANALYSIS_EVENTS:ai-analysis-events}
|
||||
storage:
|
||||
connection-string: ${AZURE_STORAGE_CONNECTION_STRING:DefaultEndpointsProtocol=https;AccountName=yourstorageaccount;AccountKey=your-storage-key;EndpointSuffix=core.windows.net}
|
||||
container-name: ${AZURE_STORAGE_CONTAINER_NAME:eventhub-checkpoints}
|
||||
|
||||
Reference in New Issue
Block a user