Merge branch 'main' of https://github.com/dg04-hi/hi-backend
This commit is contained in:
commit
d86c6f4d74
@ -4,6 +4,7 @@ import com.ktds.hi.analytics.biz.domain.ActionPlan;
|
|||||||
import com.ktds.hi.analytics.biz.domain.Analytics;
|
import com.ktds.hi.analytics.biz.domain.Analytics;
|
||||||
import com.ktds.hi.analytics.biz.domain.AiFeedback;
|
import com.ktds.hi.analytics.biz.domain.AiFeedback;
|
||||||
import com.ktds.hi.analytics.biz.domain.PlanStatus;
|
import com.ktds.hi.analytics.biz.domain.PlanStatus;
|
||||||
|
import com.ktds.hi.analytics.biz.domain.SentimentType;
|
||||||
import com.ktds.hi.analytics.biz.usecase.in.AnalyticsUseCase;
|
import com.ktds.hi.analytics.biz.usecase.in.AnalyticsUseCase;
|
||||||
import com.ktds.hi.analytics.biz.usecase.out.*;
|
import com.ktds.hi.analytics.biz.usecase.out.*;
|
||||||
import com.ktds.hi.analytics.infra.dto.*;
|
import com.ktds.hi.analytics.infra.dto.*;
|
||||||
@ -13,10 +14,14 @@ import org.springframework.cache.annotation.Cacheable;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 분석 서비스 구현 클래스 (수정버전)
|
* 분석 서비스 구현 클래스 (수정버전)
|
||||||
@ -35,6 +40,7 @@ public class AnalyticsService implements AnalyticsUseCase {
|
|||||||
private final CachePort cachePort;
|
private final CachePort cachePort;
|
||||||
private final EventPort eventPort;
|
private final EventPort eventPort;
|
||||||
private final ActionPlanPort actionPlanPort; // 추가된 의존성
|
private final ActionPlanPort actionPlanPort; // 추가된 의존성
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
// @Cacheable(value = "storeAnalytics", key = "#storeId")
|
// @Cacheable(value = "storeAnalytics", key = "#storeId")
|
||||||
@ -270,8 +276,10 @@ public class AnalyticsService implements AnalyticsUseCase {
|
|||||||
.totalReviews(0)
|
.totalReviews(0)
|
||||||
.positiveReviewCount(0)
|
.positiveReviewCount(0)
|
||||||
.negativeReviewCount(0)
|
.negativeReviewCount(0)
|
||||||
|
.neutralReviewCount(0)
|
||||||
.positiveRate(0.0)
|
.positiveRate(0.0)
|
||||||
.negativeRate(0.0)
|
.negativeRate(0.0)
|
||||||
|
.neutralRate(0.0)
|
||||||
.analysisDate(LocalDate.now())
|
.analysisDate(LocalDate.now())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@ -280,8 +288,10 @@ public class AnalyticsService implements AnalyticsUseCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. 응답 생성
|
// 3. 응답 생성
|
||||||
int positiveCount = countPositiveReviews(recentReviews);
|
ReviewSentimentCount sentimentCount = analyzeReviewSentiments(recentReviews);
|
||||||
int negativeCount = countNegativeReviews(recentReviews);
|
int positiveCount = sentimentCount.getPositiveCount();
|
||||||
|
int negativeCount = sentimentCount.getNegativeCount();
|
||||||
|
int neutralCount = sentimentCount.getNeutralCount();
|
||||||
int totalCount = recentReviews.size();
|
int totalCount = recentReviews.size();
|
||||||
|
|
||||||
ReviewAnalysisResponse response = ReviewAnalysisResponse.builder()
|
ReviewAnalysisResponse response = ReviewAnalysisResponse.builder()
|
||||||
@ -289,8 +299,10 @@ public class AnalyticsService implements AnalyticsUseCase {
|
|||||||
.totalReviews(totalCount)
|
.totalReviews(totalCount)
|
||||||
.positiveReviewCount(positiveCount)
|
.positiveReviewCount(positiveCount)
|
||||||
.negativeReviewCount(negativeCount)
|
.negativeReviewCount(negativeCount)
|
||||||
.positiveRate(Math.floor((double) positiveCount / totalCount * 100) / 10.0)
|
.neutralReviewCount(neutralCount)
|
||||||
.negativeRate(Math.floor((double) negativeCount / totalCount * 100) / 10.0)
|
.positiveRate(Math.floor((double) positiveCount / totalCount * 1000) / 10.0)
|
||||||
|
.negativeRate(Math.floor((double) negativeCount / totalCount * 1000) / 10.0)
|
||||||
|
.neutralRate(Math.floor((double) neutralCount / totalCount * 1000) / 10.0)
|
||||||
.analysisDate(LocalDate.now())
|
.analysisDate(LocalDate.now())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@ -305,7 +317,75 @@ public class AnalyticsService implements AnalyticsUseCase {
|
|||||||
throw new RuntimeException("리뷰 분석에 실패했습니다.", e);
|
throw new RuntimeException("리뷰 분석에 실패했습니다.", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 기존 analyzeReviewSentiments 메서드를 대량 분석 방식으로 개선
|
||||||
|
* 개별 AI 호출 대신 한 번의 호출로 모든 리뷰 분석
|
||||||
|
*/
|
||||||
|
private ReviewSentimentCount analyzeReviewSentiments(List<String> reviews) {
|
||||||
|
log.info("LLM 기반 리뷰 감정 분석 시작: 총 리뷰 수={}", reviews.size());
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (reviews.isEmpty()) {
|
||||||
|
return new ReviewSentimentCount(0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 유효한 리뷰만 필터링
|
||||||
|
List<String> validReviews = reviews.stream()
|
||||||
|
.filter(review -> review != null && !review.trim().isEmpty())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (validReviews.isEmpty()) {
|
||||||
|
return new ReviewSentimentCount(0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 기존 개별 분석 대신 대량 분석 사용
|
||||||
|
Map<SentimentType, Integer> sentimentCounts = aiServicePort.analyzeBulkSentiments(validReviews);
|
||||||
|
|
||||||
|
int positiveCount = sentimentCounts.get(SentimentType.POSITIVE);
|
||||||
|
int negativeCount = sentimentCounts.get(SentimentType.NEGATIVE);
|
||||||
|
int neutralCount = sentimentCounts.get(SentimentType.NEUTRAL);
|
||||||
|
|
||||||
|
ReviewSentimentCount result = new ReviewSentimentCount(positiveCount, negativeCount, neutralCount);
|
||||||
|
|
||||||
|
log.info("리뷰 감정 분석 완료: 긍정={}, 부정={}, 중립={}, 전체={}",
|
||||||
|
positiveCount, negativeCount, neutralCount, validReviews.size());
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("리뷰 감정 분석 중 전체 오류 발생, fallback 사용", e);
|
||||||
|
// 오류 시 기존 가정값 사용
|
||||||
|
int total = reviews.size();
|
||||||
|
return new ReviewSentimentCount(
|
||||||
|
(int) (total * 0.6), // 60% 긍정
|
||||||
|
(int) (total * 0.2), // 20% 부정
|
||||||
|
total - (int) (total * 0.6) - (int) (total * 0.2) // 나머지 중립
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 리뷰 감정 분석 결과를 담는 내부 클래스
|
||||||
|
*/
|
||||||
|
public static class ReviewSentimentCount {
|
||||||
|
private final int positiveCount;
|
||||||
|
private final int negativeCount;
|
||||||
|
private final int neutralCount;
|
||||||
|
|
||||||
|
public ReviewSentimentCount(int positiveCount, int negativeCount, int neutralCount) {
|
||||||
|
this.positiveCount = positiveCount;
|
||||||
|
this.negativeCount = negativeCount;
|
||||||
|
this.neutralCount = neutralCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPositiveCount() { return positiveCount; }
|
||||||
|
public int getNegativeCount() { return negativeCount; }
|
||||||
|
public int getNeutralCount() { return neutralCount; }
|
||||||
|
public int getTotalCount() { return positiveCount + negativeCount + neutralCount; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// private 메서드들
|
// private 메서드들
|
||||||
@Transactional
|
@Transactional
|
||||||
public Analytics generateNewAnalytics(Long storeId) {
|
public Analytics generateNewAnalytics(Long storeId) {
|
||||||
@ -429,15 +509,6 @@ public class AnalyticsService implements AnalyticsUseCase {
|
|||||||
return "추천사항이 없습니다.";
|
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% 가정
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import com.ktds.hi.analytics.biz.domain.AiFeedback;
|
|||||||
import com.ktds.hi.analytics.biz.domain.SentimentType;
|
import com.ktds.hi.analytics.biz.domain.SentimentType;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AI 서비스 포트 인터페이스
|
* AI 서비스 포트 인터페이스
|
||||||
@ -20,6 +21,15 @@ public interface AIServicePort {
|
|||||||
* 감정 분석
|
* 감정 분석
|
||||||
*/
|
*/
|
||||||
SentimentType analyzeSentiment(String content);
|
SentimentType analyzeSentiment(String content);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 대량 리뷰 감정 분석 (새로 추가)
|
||||||
|
* 여러 리뷰를 한 번에 분석하여 긍정/부정/중립 개수 반환
|
||||||
|
*
|
||||||
|
* @param reviews 분석할 리뷰 목록
|
||||||
|
* @return 감정 타입별 개수 맵
|
||||||
|
*/
|
||||||
|
Map<SentimentType, Integer> analyzeBulkSentiments(List<String> reviews);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 실행 계획 생성
|
* 실행 계획 생성
|
||||||
|
|||||||
@ -20,7 +20,9 @@ public class ReviewAnalysisResponse {
|
|||||||
private Integer totalReviews;
|
private Integer totalReviews;
|
||||||
private Integer positiveReviewCount;
|
private Integer positiveReviewCount;
|
||||||
private Integer negativeReviewCount;
|
private Integer negativeReviewCount;
|
||||||
|
private Integer neutralReviewCount;
|
||||||
private Double positiveRate;
|
private Double positiveRate;
|
||||||
private Double negativeRate;
|
private Double negativeRate;
|
||||||
|
private Double neutralRate;
|
||||||
private LocalDate analysisDate;
|
private LocalDate analysisDate;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,8 +30,10 @@ import org.springframework.web.client.RestTemplate;
|
|||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AI 서비스 어댑터 클래스
|
* AI 서비스 어댑터 클래스
|
||||||
@ -99,6 +101,116 @@ public class AIServiceAdapter implements AIServicePort {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<SentimentType, Integer> analyzeBulkSentiments(List<String> reviews) {
|
||||||
|
log.info("대량 리뷰 감정 분석 시작: 리뷰 수={}", reviews.size());
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (reviews.isEmpty()) {
|
||||||
|
return createEmptyResultMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 유효한 리뷰만 필터링
|
||||||
|
List<String> validReviews = reviews.stream()
|
||||||
|
.filter(review -> review != null && !review.trim().isEmpty())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (validReviews.isEmpty()) {
|
||||||
|
return createEmptyResultMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 리뷰를 번호와 함께 포맷팅
|
||||||
|
StringBuilder reviewsText = new StringBuilder();
|
||||||
|
for (int i = 0; i < validReviews.size(); i++) {
|
||||||
|
reviewsText.append(String.format("%d. %s\n", i + 1, validReviews.get(i)));
|
||||||
|
}
|
||||||
|
|
||||||
|
String prompt = String.format(
|
||||||
|
"""
|
||||||
|
다음 리뷰들을 분석하여 긍정, 부정, 중립의 개수를 세어주세요.
|
||||||
|
|
||||||
|
리뷰 목록:
|
||||||
|
%s
|
||||||
|
|
||||||
|
결과를 다음 JSON 형식으로만 답변해주세요:
|
||||||
|
{
|
||||||
|
"positive": 긍정_개수,
|
||||||
|
"negative": 부정_개수,
|
||||||
|
"neutral": 중립_개수
|
||||||
|
}
|
||||||
|
|
||||||
|
다른 설명은 하지 말고 JSON만 답변해주세요.
|
||||||
|
긍정,부정,중립 개수를 모두 더했을때, 총 리뷰수와 동일해야 합니다.
|
||||||
|
정확하게 세어주세요.
|
||||||
|
""",
|
||||||
|
reviewsText.toString()
|
||||||
|
);
|
||||||
|
|
||||||
|
// 기존 callOpenAI 메서드 활용
|
||||||
|
String result = callOpenAI(prompt);
|
||||||
|
|
||||||
|
// 결과 파싱
|
||||||
|
Map<SentimentType, Integer> sentimentMap = parseBulkSentimentResult(result, validReviews.size());
|
||||||
|
|
||||||
|
log.info("대량 리뷰 감정 분석 완료: 긍정={}, 부정={}, 중립={}",
|
||||||
|
sentimentMap.get(SentimentType.POSITIVE),
|
||||||
|
sentimentMap.get(SentimentType.NEGATIVE),
|
||||||
|
sentimentMap.get(SentimentType.NEUTRAL));
|
||||||
|
|
||||||
|
return sentimentMap;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("대량 리뷰 감정 분석 중 오류 발생, fallback 사용", e);
|
||||||
|
return createFallbackResultMap(reviews.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<SentimentType, Integer> parseBulkSentimentResult(String result, int totalReviews) {
|
||||||
|
try {
|
||||||
|
// 기존 objectMapper 필드 사용
|
||||||
|
Map<String, Object> jsonResult = objectMapper.readValue(result.trim(), Map.class);
|
||||||
|
|
||||||
|
int positive = ((Number) jsonResult.getOrDefault("positive", 0)).intValue();
|
||||||
|
int negative = ((Number) jsonResult.getOrDefault("negative", 0)).intValue();
|
||||||
|
int neutral = ((Number) jsonResult.getOrDefault("neutral", 0)).intValue();
|
||||||
|
|
||||||
|
// 결과 검증 및 보정
|
||||||
|
int totalAnalyzed = positive + negative + neutral;
|
||||||
|
if (totalAnalyzed != totalReviews) {
|
||||||
|
log.warn("분석 결과 불일치 보정: 분석된 수={}, 실제 리뷰 수={}", totalAnalyzed, totalReviews);
|
||||||
|
int difference = totalReviews - totalAnalyzed;
|
||||||
|
neutral += difference;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<SentimentType, Integer> resultMap = new HashMap<>();
|
||||||
|
resultMap.put(SentimentType.POSITIVE, Math.max(0, positive));
|
||||||
|
resultMap.put(SentimentType.NEGATIVE, Math.max(0, negative));
|
||||||
|
resultMap.put(SentimentType.NEUTRAL, Math.max(0, neutral));
|
||||||
|
|
||||||
|
return resultMap;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("대량 감정 분석 결과 파싱 실패: {}", result, e);
|
||||||
|
return createFallbackResultMap(totalReviews);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<SentimentType, Integer> createEmptyResultMap() {
|
||||||
|
Map<SentimentType, Integer> result = new HashMap<>();
|
||||||
|
result.put(SentimentType.POSITIVE, 0);
|
||||||
|
result.put(SentimentType.NEGATIVE, 0);
|
||||||
|
result.put(SentimentType.NEUTRAL, 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<SentimentType, Integer> createFallbackResultMap(int totalReviews) {
|
||||||
|
Map<SentimentType, Integer> result = new HashMap<>();
|
||||||
|
result.put(SentimentType.POSITIVE, (int) (totalReviews * 0.6));
|
||||||
|
result.put(SentimentType.NEGATIVE, (int) (totalReviews * 0.2));
|
||||||
|
result.put(SentimentType.NEUTRAL, totalReviews - (int) (totalReviews * 0.6) - (int) (totalReviews * 0.2));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SentimentType analyzeSentiment(String content) {
|
public SentimentType analyzeSentiment(String content) {
|
||||||
|
|||||||
@ -43,7 +43,7 @@ public class EventHubAdapter {
|
|||||||
private final ExecutorService executorService = Executors.newFixedThreadPool(3);
|
private final ExecutorService executorService = Executors.newFixedThreadPool(3);
|
||||||
private volatile boolean isRunning = false;
|
private volatile boolean isRunning = false;
|
||||||
|
|
||||||
@PostConstruct
|
// @PostConstruct
|
||||||
public void startEventListening() {
|
public void startEventListening() {
|
||||||
log.info("Event Hub 리스너 시작");
|
log.info("Event Hub 리스너 시작");
|
||||||
isRunning = true;
|
isRunning = true;
|
||||||
@ -52,7 +52,7 @@ public class EventHubAdapter {
|
|||||||
executorService.submit(this::listenToReviewEvents);
|
executorService.submit(this::listenToReviewEvents);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreDestroy
|
// @PreDestroy
|
||||||
public void stopEventListening() {
|
public void stopEventListening() {
|
||||||
log.info("Event Hub 리스너 종료");
|
log.info("Event Hub 리스너 종료");
|
||||||
isRunning = false;
|
isRunning = false;
|
||||||
|
|||||||
BIN
logs/recommend-service.log.2025-06-17.0.gz
Normal file
BIN
logs/recommend-service.log.2025-06-17.0.gz
Normal file
Binary file not shown.
@ -3,6 +3,7 @@ package com.ktds.hi.store.biz.service;
|
|||||||
|
|
||||||
import com.ktds.hi.store.biz.usecase.in.StoreUseCase;
|
import com.ktds.hi.store.biz.usecase.in.StoreUseCase;
|
||||||
import com.ktds.hi.store.infra.dto.*;
|
import com.ktds.hi.store.infra.dto.*;
|
||||||
|
import com.ktds.hi.store.infra.dto.response.StoreListResponse;
|
||||||
import com.ktds.hi.store.infra.gateway.entity.StoreEntity;
|
import com.ktds.hi.store.infra.gateway.entity.StoreEntity;
|
||||||
import com.ktds.hi.store.infra.gateway.entity.TagEntity;
|
import com.ktds.hi.store.infra.gateway.entity.TagEntity;
|
||||||
import com.ktds.hi.store.infra.gateway.repository.StoreJpaRepository;
|
import com.ktds.hi.store.infra.gateway.repository.StoreJpaRepository;
|
||||||
@ -93,6 +94,27 @@ public class StoreService implements StoreUseCase {
|
|||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<StoreListResponse> getAllStores() {
|
||||||
|
|
||||||
|
List<StoreEntity> stores = storeJpaRepository.findAll();
|
||||||
|
|
||||||
|
return stores.stream()
|
||||||
|
.map(store -> StoreListResponse.builder()
|
||||||
|
.storeId(store.getId())
|
||||||
|
.storeName(store.getStoreName())
|
||||||
|
.address(store.getAddress())
|
||||||
|
.category(store.getCategory())
|
||||||
|
.rating(store.getRating())
|
||||||
|
.reviewCount(store.getReviewCount())
|
||||||
|
.status("운영중")
|
||||||
|
.imageUrl(store.getImageUrl())
|
||||||
|
.operatingHours(store.getOperatingHours())
|
||||||
|
.build())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StoreDetailResponse getStoreDetail(Long storeId) {
|
public StoreDetailResponse getStoreDetail(Long storeId) {
|
||||||
|
|
||||||
@ -157,6 +179,10 @@ public class StoreService implements StoreUseCase {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<StoreSearchResponse> searchStores(String keyword, String category, String tags,
|
public List<StoreSearchResponse> searchStores(String keyword, String category, String tags,
|
||||||
Double latitude, Double longitude, Integer radius,
|
Double latitude, Double longitude, Integer radius,
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package com.ktds.hi.store.biz.usecase.in;
|
package com.ktds.hi.store.biz.usecase.in;
|
||||||
|
|
||||||
import com.ktds.hi.store.infra.dto.*;
|
import com.ktds.hi.store.infra.dto.*;
|
||||||
|
import com.ktds.hi.store.infra.dto.response.StoreListResponse;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -30,6 +31,8 @@ public interface StoreUseCase {
|
|||||||
*/
|
*/
|
||||||
List<MyStoreListResponse> getMyStores(Long ownerId);
|
List<MyStoreListResponse> getMyStores(Long ownerId);
|
||||||
|
|
||||||
|
List<StoreListResponse> getAllStores();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 매장 상세 조회
|
* 매장 상세 조회
|
||||||
*
|
*
|
||||||
@ -73,4 +76,6 @@ public interface StoreUseCase {
|
|||||||
List<StoreSearchResponse> searchStores(String keyword, String category, String tags,
|
List<StoreSearchResponse> searchStores(String keyword, String category, String tags,
|
||||||
Double latitude, Double longitude, Integer radius,
|
Double latitude, Double longitude, Integer radius,
|
||||||
Integer page, Integer size);
|
Integer page, Integer size);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,14 @@
|
|||||||
// store/src/main/java/com/ktds/hi/store/infra/controller/StoreController.java
|
// store/src/main/java/com/ktds/hi/store/infra/controller/StoreController.java
|
||||||
package com.ktds.hi.store.infra.controller;
|
package com.ktds.hi.store.infra.controller;
|
||||||
|
|
||||||
|
import com.ktds.hi.store.biz.service.StoreService;
|
||||||
import com.ktds.hi.store.biz.usecase.in.MenuUseCase;
|
import com.ktds.hi.store.biz.usecase.in.MenuUseCase;
|
||||||
import com.ktds.hi.store.biz.usecase.in.StoreUseCase;
|
import com.ktds.hi.store.biz.usecase.in.StoreUseCase;
|
||||||
|
import com.ktds.hi.store.domain.Store;
|
||||||
import com.ktds.hi.store.infra.dto.*;
|
import com.ktds.hi.store.infra.dto.*;
|
||||||
import com.ktds.hi.common.dto.ApiResponse;
|
import com.ktds.hi.common.dto.ApiResponse;
|
||||||
import com.ktds.hi.common.security.JwtTokenProvider;
|
import com.ktds.hi.common.security.JwtTokenProvider;
|
||||||
|
import com.ktds.hi.store.infra.dto.response.StoreListResponse;
|
||||||
import com.ktds.hi.store.infra.dto.response.StoreMenuListResponse;
|
import com.ktds.hi.store.infra.dto.response.StoreMenuListResponse;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
@ -38,6 +41,7 @@ import java.util.List;
|
|||||||
public class StoreController {
|
public class StoreController {
|
||||||
|
|
||||||
private final StoreUseCase storeUseCase;
|
private final StoreUseCase storeUseCase;
|
||||||
|
private final StoreService storeService;
|
||||||
private final JwtTokenProvider jwtTokenProvider;
|
private final JwtTokenProvider jwtTokenProvider;
|
||||||
private final MenuUseCase menuUseCase;
|
private final MenuUseCase menuUseCase;
|
||||||
|
|
||||||
@ -68,6 +72,14 @@ public class StoreController {
|
|||||||
return ResponseEntity.ok(ApiResponse.success(responses, "내 매장 목록 조회 완료"));
|
return ResponseEntity.ok(ApiResponse.success(responses, "내 매장 목록 조회 완료"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/stores/all")
|
||||||
|
@Operation(summary = "매장 전체 리스트")
|
||||||
|
public ResponseEntity<ApiResponse<List<StoreListResponse>>> getAllStores() {
|
||||||
|
|
||||||
|
List<StoreListResponse> responses = storeUseCase.getAllStores();
|
||||||
|
return ResponseEntity.ok(ApiResponse.success(responses));
|
||||||
|
}
|
||||||
|
|
||||||
@Operation(summary = "매장 상세 조회", description = "매장의 상세 정보를 조회합니다.")
|
@Operation(summary = "매장 상세 조회", description = "매장의 상세 정보를 조회합니다.")
|
||||||
@GetMapping("/{storeId}")
|
@GetMapping("/{storeId}")
|
||||||
public ResponseEntity<ApiResponse<StoreDetailResponse>> getStoreDetail(
|
public ResponseEntity<ApiResponse<StoreDetailResponse>> getStoreDetail(
|
||||||
@ -133,6 +145,8 @@ public class StoreController {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@GetMapping("/{storeId}/menus/popular")
|
@GetMapping("/{storeId}/menus/popular")
|
||||||
@Operation(summary = "매장 인기 메뉴 조회", description = "매장의 인기 메뉴(주문 횟수 기준)를 조회합니다.")
|
@Operation(summary = "매장 인기 메뉴 조회", description = "매장의 인기 메뉴(주문 횟수 기준)를 조회합니다.")
|
||||||
public ResponseEntity<ApiResponse<List<StoreMenuListResponse>>> getPopularMenus(
|
public ResponseEntity<ApiResponse<List<StoreMenuListResponse>>> getPopularMenus(
|
||||||
|
|||||||
@ -0,0 +1,42 @@
|
|||||||
|
package com.ktds.hi.store.infra.dto.response;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "내 매장 목록 응답")
|
||||||
|
public class StoreListResponse {
|
||||||
|
|
||||||
|
|
||||||
|
@Schema(description = "매장 ID", example = "1")
|
||||||
|
private Long storeId;
|
||||||
|
|
||||||
|
@Schema(description = "매장명", example = "맛집 한번 가볼래?")
|
||||||
|
private String storeName;
|
||||||
|
|
||||||
|
@Schema(description = "주소", example = "서울시 강남구 테헤란로 123")
|
||||||
|
private String address;
|
||||||
|
|
||||||
|
@Schema(description = "카테고리", example = "한식")
|
||||||
|
private String category;
|
||||||
|
|
||||||
|
@Schema(description = "평점", example = "4.5")
|
||||||
|
private Double rating;
|
||||||
|
|
||||||
|
@Schema(description = "리뷰 수", example = "127")
|
||||||
|
private Integer reviewCount;
|
||||||
|
|
||||||
|
@Schema(description = "운영 상태", example = "운영중")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@Schema(description = "운영시간", example = "월-금 09:00-21:00")
|
||||||
|
private String operatingHours;
|
||||||
|
@Schema(description = "매장 이미지")
|
||||||
|
private String imageUrl;
|
||||||
|
}
|
||||||
@ -24,6 +24,7 @@ public interface StoreJpaRepository extends JpaRepository<StoreEntity, Long> {
|
|||||||
@Query("SELECT s FROM StoreEntity s WHERE s.status = 'ACTIVE' ORDER BY s.rating DESC")
|
@Query("SELECT s FROM StoreEntity s WHERE s.status = 'ACTIVE' ORDER BY s.rating DESC")
|
||||||
Page<StoreEntity> findAllByOrderByRatingDesc(Pageable pageable);
|
Page<StoreEntity> findAllByOrderByRatingDesc(Pageable pageable);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 점주 ID로 매장 목록 조회
|
* 점주 ID로 매장 목록 조회
|
||||||
*/
|
*/
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user