diff --git a/event-service/src/main/java/com/kt/event/eventservice/application/service/EventService.java b/event-service/src/main/java/com/kt/event/eventservice/application/service/EventService.java index 1443ac2..5616742 100644 --- a/event-service/src/main/java/com/kt/event/eventservice/application/service/EventService.java +++ b/event-service/src/main/java/com/kt/event/eventservice/application/service/EventService.java @@ -10,7 +10,9 @@ import com.kt.event.eventservice.domain.entity.*; import com.kt.event.eventservice.domain.enums.EventStatus; import com.kt.event.eventservice.domain.repository.EventRepository; import com.kt.event.eventservice.domain.repository.JobRepository; +import com.kt.event.eventservice.infrastructure.client.AIServiceClient; import com.kt.event.eventservice.infrastructure.client.ContentServiceClient; +import com.kt.event.eventservice.infrastructure.client.dto.AIRecommendationResponse; import com.kt.event.eventservice.infrastructure.client.dto.ContentImageGenerationRequest; import com.kt.event.eventservice.infrastructure.client.dto.ContentJobResponse; import com.kt.event.eventservice.infrastructure.kafka.AIJobKafkaProducer; @@ -43,6 +45,7 @@ public class EventService { private final EventRepository eventRepository; private final JobRepository jobRepository; + private final AIServiceClient aiServiceClient; private final ContentServiceClient contentServiceClient; private final AIJobKafkaProducer aiJobKafkaProducer; private final ImageJobKafkaProducer imageJobKafkaProducer; @@ -611,4 +614,30 @@ public class EventService { .updatedAt(event.getUpdatedAt()) .build(); } + + /** + * AI 추천안 조회 (AI Service에서 직접 조회) + * + * @param userId 사용자 ID + * @param eventId 이벤트 ID + * @return AI 추천 결과 + */ + public AIRecommendationResponse getAiRecommendations(String userId, String eventId) { + log.info("AI 추천안 조회 - userId: {}, eventId: {}", userId, eventId); + + // 이벤트 권한 확인 + Event event = eventRepository.findByEventIdAndUserId(eventId, userId) + .orElseThrow(() -> new BusinessException(ErrorCode.EVENT_001)); + + // AI Service에서 추천안 조회 + try { + AIRecommendationResponse response = aiServiceClient.getRecommendation(eventId); + log.info("AI 추천안 조회 성공 - eventId: {}, 추천안 수: {}", + eventId, response.getRecommendations() != null ? response.getRecommendations().size() : 0); + return response; + } catch (Exception e) { + log.error("AI 추천안 조회 실패 - eventId: {}", eventId, e); + throw new BusinessException(ErrorCode.AI_004); + } + } } diff --git a/event-service/src/main/java/com/kt/event/eventservice/infrastructure/client/AIServiceClient.java b/event-service/src/main/java/com/kt/event/eventservice/infrastructure/client/AIServiceClient.java new file mode 100644 index 0000000..e1d2f02 --- /dev/null +++ b/event-service/src/main/java/com/kt/event/eventservice/infrastructure/client/AIServiceClient.java @@ -0,0 +1,31 @@ +package com.kt.event.eventservice.infrastructure.client; + +import com.kt.event.eventservice.infrastructure.client.dto.AIRecommendationResponse; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; + +/** + * AI Service Feign Client + * + * AI Service의 추천안 조회 API를 호출합니다. + * + * @author Event Service Team + * @version 1.0.0 + * @since 2025-10-30 + */ +@FeignClient( + name = "ai-service", + url = "${feign.ai-service.url:http://localhost:8083}" +) +public interface AIServiceClient { + + /** + * AI 추천 결과 조회 + * + * @param eventId 이벤트 ID + * @return AI 추천 결과 + */ + @GetMapping("/recommendations/{eventId}") + AIRecommendationResponse getRecommendation(@PathVariable("eventId") String eventId); +} diff --git a/event-service/src/main/java/com/kt/event/eventservice/infrastructure/client/dto/AIRecommendationResponse.java b/event-service/src/main/java/com/kt/event/eventservice/infrastructure/client/dto/AIRecommendationResponse.java new file mode 100644 index 0000000..0dbbf94 --- /dev/null +++ b/event-service/src/main/java/com/kt/event/eventservice/infrastructure/client/dto/AIRecommendationResponse.java @@ -0,0 +1,123 @@ +package com.kt.event.eventservice.infrastructure.client.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +/** + * AI Service 추천안 응답 DTO + * + * @author Event Service Team + * @version 1.0.0 + * @since 2025-10-30 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class AIRecommendationResponse { + + private String eventId; + private TrendAnalysis trendAnalysis; + private List recommendations; + private LocalDateTime generatedAt; + private LocalDateTime expiresAt; + private String aiProvider; + + /** + * 트렌드 분석 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class TrendAnalysis { + private List industryTrends; + private List regionalTrends; + private List seasonalTrends; + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class TrendKeyword { + private String keyword; + private Double relevance; + private String description; + } + } + + /** + * 이벤트 추천안 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class EventRecommendation { + private Integer optionNumber; + private String concept; + private String title; + private String description; + private String targetAudience; + private Duration duration; + private Mechanics mechanics; + private List promotionChannels; + private EstimatedCost estimatedCost; + private ExpectedMetrics expectedMetrics; + private String differentiator; + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Duration { + private Integer recommendedDays; + private String recommendedPeriod; + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Mechanics { + private String type; + private String details; + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class EstimatedCost { + private Integer min; + private Integer max; + private Map breakdown; + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class ExpectedMetrics { + private Range newCustomers; + private Range revenueIncrease; + private Range roi; + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Range { + private Double min; + private Double max; + } + } + } +} diff --git a/event-service/src/main/java/com/kt/event/eventservice/presentation/controller/EventController.java b/event-service/src/main/java/com/kt/event/eventservice/presentation/controller/EventController.java index c0e016c..98549f9 100644 --- a/event-service/src/main/java/com/kt/event/eventservice/presentation/controller/EventController.java +++ b/event-service/src/main/java/com/kt/event/eventservice/presentation/controller/EventController.java @@ -6,6 +6,7 @@ import com.kt.event.common.security.UserPrincipal; import com.kt.event.eventservice.application.dto.request.*; import com.kt.event.eventservice.application.dto.response.*; import com.kt.event.eventservice.application.service.EventService; +import com.kt.event.eventservice.infrastructure.client.dto.AIRecommendationResponse; import com.kt.event.eventservice.domain.enums.EventStatus; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -287,6 +288,30 @@ public class EventController { .body(ApiResponse.success(response)); } + /** + * AI 추천안 조회 (Step 2-1) + * + * @param eventId 이벤트 ID + * @param userPrincipal 인증된 사용자 정보 + * @return AI 추천 결과 + */ + @GetMapping("/{eventId}/ai-recommendations") + @Operation(summary = "AI 추천안 조회", description = "AI Service에서 생성된 추천안을 조회합니다.") + public ResponseEntity> getAiRecommendations( + @PathVariable String eventId, + @AuthenticationPrincipal UserPrincipal userPrincipal) { + + log.info("AI 추천안 조회 API 호출 - userId: {}, eventId: {}", + userPrincipal.getUserId(), eventId); + + AIRecommendationResponse response = eventService.getAiRecommendations( + userPrincipal.getUserId(), + eventId + ); + + return ResponseEntity.ok(ApiResponse.success(response)); + } + /** * AI 추천 선택 (Step 2-2) *