diff --git a/smarketing-java/ai-recommend/src/main/resources/application.yml b/smarketing-java/ai-recommend/src/main/resources/application.yml index 72aa217..d392c82 100644 --- a/smarketing-java/ai-recommend/src/main/resources/application.yml +++ b/smarketing-java/ai-recommend/src/main/resources/application.yml @@ -50,6 +50,11 @@ management: show-details: always info: enabled: true + health: + livenessState: + enabled: true + readinessState: + enabled: true logging: level: diff --git a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/application/service/PosterContentService.java b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/application/service/PosterContentService.java index e89b5c5..55a1b19 100644 --- a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/application/service/PosterContentService.java +++ b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/application/service/PosterContentService.java @@ -16,6 +16,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; +import java.util.HashMap; import java.util.Map; /** @@ -32,25 +33,20 @@ public class PosterContentService implements PosterContentUseCase { /** * 포스터 콘텐츠 생성 - * + * * @param request 포스터 콘텐츠 생성 요청 * @return 생성된 포스터 콘텐츠 정보 */ @Override @Transactional public PosterContentCreateResponse generatePosterContent(PosterContentCreateRequest request) { - // AI를 사용하여 포스터 생성 + String generatedPoster = aiPosterGenerator.generatePoster(request); - - // 다양한 사이즈의 포스터 생성 - Map posterSizes = aiPosterGenerator.generatePosterSizes(generatedPoster); // 생성 조건 정보 구성 CreationConditions conditions = CreationConditions.builder() .category(request.getCategory()) .requirement(request.getRequirement()) - // .toneAndManner(request.getToneAndManner()) - // .emotionIntensity(request.getEmotionIntensity()) .eventName(request.getEventName()) .startDate(request.getStartDate()) .endDate(request.getEndDate()) @@ -62,47 +58,41 @@ public class PosterContentService implements PosterContentUseCase { .contentType(ContentType.POSTER.name()) .title(request.getTitle()) .posterImage(generatedPoster) - .posterSizes(posterSizes) + .posterSizes(new HashMap<>()) // 빈 맵 반환 (사이즈 변환 안함) .status(ContentStatus.DRAFT.name()) - //.createdAt(LocalDateTime.now()) .build(); } /** * 포스터 콘텐츠 저장 - * + * * @param request 포스터 콘텐츠 저장 요청 */ @Override @Transactional public void savePosterContent(PosterContentSaveRequest request) { - // 생성 조건 정보 구성 + // 생성 조건 구성 CreationConditions conditions = CreationConditions.builder() .category(request.getCategory()) .requirement(request.getRequirement()) - // .toneAndManner(request.getToneAndManner()) - // .emotionIntensity(request.getEmotionIntensity()) .eventName(request.getEventName()) .startDate(request.getStartDate()) .endDate(request.getEndDate()) .photoStyle(request.getPhotoStyle()) .build(); - // 콘텐츠 엔티티 생성 및 저장 + // 콘텐츠 엔티티 생성 Content content = Content.builder() .contentType(ContentType.POSTER) - .platform(Platform.GENERAL) // 포스터는 범용 .title(request.getTitle()) - .content(null) // 포스터는 이미지가 주 콘텐츠 - .hashtags(null) + .content(request.getContent()) .images(request.getImages()) .status(ContentStatus.PUBLISHED) .creationConditions(conditions) .storeId(request.getStoreId()) - .createdAt(LocalDateTime.now()) - .updatedAt(LocalDateTime.now()) .build(); + // 저장 contentRepository.save(content); } -} +} \ No newline at end of file diff --git a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/external/ClaudeAiPosterGenerator.java b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/external/ClaudeAiPosterGenerator.java deleted file mode 100644 index 7495966..0000000 --- a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/external/ClaudeAiPosterGenerator.java +++ /dev/null @@ -1,86 +0,0 @@ -// marketing-content/src/main/java/com/won/smarketing/content/infrastructure/external/ClaudeAiPosterGenerator.java -package com.won.smarketing.content.infrastructure.external; - -import com.won.smarketing.content.domain.service.AiPosterGenerator; // 도메인 인터페이스 import -import com.won.smarketing.content.presentation.dto.PosterContentCreateRequest; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -import java.util.HashMap; -import java.util.Map; - -/** - * Claude AI를 활용한 포스터 생성 구현체 - * Clean Architecture의 Infrastructure Layer에 위치 - */ -@Component -@RequiredArgsConstructor -@Slf4j -public class ClaudeAiPosterGenerator implements AiPosterGenerator { - - /** - * 포스터 생성 - * - * @param request 포스터 생성 요청 - * @return 생성된 포스터 이미지 URL - */ - @Override - public String generatePoster(PosterContentCreateRequest request) { - try { - // Claude AI API 호출 로직 - String prompt = buildPosterPrompt(request); - - // TODO: 실제 Claude AI API 호출 - // 현재는 더미 데이터 반환 - return generateDummyPosterUrl(request.getTitle()); - - } catch (Exception e) { - log.error("AI 포스터 생성 실패: {}", e.getMessage(), e); - return generateFallbackPosterUrl(); - } - } - - /** - * 다양한 사이즈의 포스터 생성 - * - * @param baseImage 기본 이미지 - * @return 사이즈별 포스터 URL 맵 - */ - @Override - public Map generatePosterSizes(String baseImage) { - Map sizes = new HashMap<>(); - - // 다양한 사이즈 생성 (더미 구현) - sizes.put("instagram_square", baseImage + "_1080x1080.jpg"); - sizes.put("instagram_story", baseImage + "_1080x1920.jpg"); - sizes.put("facebook_post", baseImage + "_1200x630.jpg"); - sizes.put("a4_poster", baseImage + "_2480x3508.jpg"); - - return sizes; - } - - private String buildPosterPrompt(PosterContentCreateRequest request) { - StringBuilder prompt = new StringBuilder(); - prompt.append("포스터 제목: ").append(request.getTitle()).append("\n"); - prompt.append("카테고리: ").append(request.getCategory()).append("\n"); - - if (request.getRequirement() != null) { - prompt.append("요구사항: ").append(request.getRequirement()).append("\n"); - } - - if (request.getToneAndManner() != null) { - prompt.append("톤앤매너: ").append(request.getToneAndManner()).append("\n"); - } - - return prompt.toString(); - } - - private String generateDummyPosterUrl(String title) { - return "https://dummy-ai-service.com/posters/" + title.hashCode() + ".jpg"; - } - - private String generateFallbackPosterUrl() { - return "https://dummy-ai-service.com/posters/fallback.jpg"; - } -} \ No newline at end of file diff --git a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/external/PythonAiPosterGenerator.java b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/external/PythonAiPosterGenerator.java new file mode 100644 index 0000000..2318ce0 --- /dev/null +++ b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/external/PythonAiPosterGenerator.java @@ -0,0 +1,151 @@ +package com.won.smarketing.content.infrastructure.external; + +import com.won.smarketing.content.domain.service.AiPosterGenerator; // 도메인 인터페이스 import +import com.won.smarketing.content.presentation.dto.PosterContentCreateRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClient; + +import java.time.Duration; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.Map; + +/** + * Claude AI를 활용한 포스터 생성 구현체 + * Clean Architecture의 Infrastructure Layer에 위치 + */ +@Component +@RequiredArgsConstructor +@Slf4j +public class PythonAiPosterGenerator implements AiPosterGenerator { + + private final WebClient webClient; + + @Value("${external.ai-service.base-url:http://20.249.139.88:5001}") + private String aiServiceBaseUrl; + + /** + * 포스터 생성 - Python AI 서비스 호출 + * + * @param request 포스터 생성 요청 + * @return 생성된 포스터 이미지 URL + */ + @Override + public String generatePoster(PosterContentCreateRequest request) { + try { + log.info("Python AI 포스터 서비스 호출: {}/api/ai/poster", aiServiceBaseUrl); + + // 요청 데이터 구성 + Map requestBody = buildRequestBody(request); + + log.debug("포스터 생성 요청 데이터: {}", requestBody); + + // Python AI 서비스 호출 + Map response = webClient + .post() + .uri(aiServiceBaseUrl + "/api/ai/poster") + .header("Content-Type", "application/json") + .bodyValue(requestBody) + .retrieve() + .bodyToMono(Map.class) + .timeout(Duration.ofSeconds(60)) // 포스터 생성은 시간이 오래 걸릴 수 있음 + .block(); + + // 응답에서 content(이미지 URL) 추출 + if (response != null && response.containsKey("content")) { + String imageUrl = (String) response.get("content"); + log.info("AI 포스터 생성 성공: imageUrl={}", imageUrl); + return imageUrl; + } else { + log.warn("AI 포스터 생성 응답에 content가 없음: {}", response); + return generateFallbackPosterUrl(request.getTitle()); + } + + } catch (Exception e) { + log.error("AI 포스터 생성 실패: {}", e.getMessage(), e); + return generateFallbackPosterUrl(request.getTitle()); + } + } + + /** + * 다양한 사이즈의 포스터 생성 (사용하지 않음) + * 1개의 이미지만 생성하므로 빈 맵 반환 + * + * @param baseImage 기본 이미지 URL + * @return 빈 맵 + */ + @Override + public Map generatePosterSizes(String baseImage) { + log.info("포스터 사이즈 변환 기능은 사용하지 않음: baseImage={}", baseImage); + return new HashMap<>(); + } + + /** + * Python AI 서비스 요청 데이터 구성 + * Python 서비스의 PosterContentGetRequest 모델에 맞춤 + */ + private Map buildRequestBody(PosterContentCreateRequest request) { + Map requestBody = new HashMap<>(); + + // 기본 정보 + requestBody.put("title", request.getTitle()); + requestBody.put("category", request.getCategory()); + requestBody.put("contentType", request.getContentType()); + + // 이미지 정보 + if (request.getImages() != null && !request.getImages().isEmpty()) { + requestBody.put("images", request.getImages()); + } + + // 스타일 정보 + if (request.getPhotoStyle() != null) { + requestBody.put("photoStyle", request.getPhotoStyle()); + } + + // 요구사항 + if (request.getRequirement() != null) { + requestBody.put("requirement", request.getRequirement()); + } + + // 톤앤매너 + if (request.getToneAndManner() != null) { + requestBody.put("toneAndManner", request.getToneAndManner()); + } + + // 감정 강도 + if (request.getEmotionIntensity() != null) { + requestBody.put("emotionIntensity", request.getEmotionIntensity()); + } + + // 메뉴명 + if (request.getMenuName() != null) { + requestBody.put("menuName", request.getMenuName()); + } + + // 이벤트 정보 + if (request.getEventName() != null) { + requestBody.put("eventName", request.getEventName()); + } + + // 날짜 정보 (LocalDate를 String으로 변환) + if (request.getStartDate() != null) { + requestBody.put("startDate", request.getStartDate().format(DateTimeFormatter.ISO_LOCAL_DATE)); + } + + if (request.getEndDate() != null) { + requestBody.put("endDate", request.getEndDate().format(DateTimeFormatter.ISO_LOCAL_DATE)); + } + + return requestBody; + } + + /** + * 폴백 포스터 URL 생성 + */ + private String generateFallbackPosterUrl(String title) { + // 기본 포스터 템플릿 URL 반환 + return "https://stdigitalgarage02.blob.core.windows.net/ai-content/fallback-poster.jpg"; + } +} \ No newline at end of file diff --git a/smarketing-java/marketing-content/src/main/resources/application.yml b/smarketing-java/marketing-content/src/main/resources/application.yml index aefce5c..4157124 100644 --- a/smarketing-java/marketing-content/src/main/resources/application.yml +++ b/smarketing-java/marketing-content/src/main/resources/application.yml @@ -49,6 +49,11 @@ management: show-details: always info: enabled: true + health: + livenessState: + enabled: true + readinessState: + enabled: true info: app: diff --git a/smarketing-java/member/src/main/resources/application.yml b/smarketing-java/member/src/main/resources/application.yml index 80c2281..92741bc 100644 --- a/smarketing-java/member/src/main/resources/application.yml +++ b/smarketing-java/member/src/main/resources/application.yml @@ -43,6 +43,11 @@ management: show-details: always info: enabled: true + health: + livenessState: + enabled: true + readinessState: + enabled: true info: app: diff --git a/smarketing-java/store/src/main/resources/application.yml b/smarketing-java/store/src/main/resources/application.yml index bd9d023..18a8934 100644 --- a/smarketing-java/store/src/main/resources/application.yml +++ b/smarketing-java/store/src/main/resources/application.yml @@ -58,6 +58,11 @@ management: show-details: always info: enabled: true + health: + livenessState: + enabled: true + readinessState: + enabled: true info: app: