refactor: actuator health

This commit is contained in:
yuhalog 2025-06-17 14:43:23 +09:00
parent 14c5164c41
commit 70ea983da4
7 changed files with 181 additions and 106 deletions

View File

@ -50,6 +50,11 @@ management:
show-details: always show-details: always
info: info:
enabled: true enabled: true
health:
livenessState:
enabled: true
readinessState:
enabled: true
logging: logging:
level: level:

View File

@ -16,6 +16,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
/** /**
@ -32,25 +33,20 @@ public class PosterContentService implements PosterContentUseCase {
/** /**
* 포스터 콘텐츠 생성 * 포스터 콘텐츠 생성
* *
* @param request 포스터 콘텐츠 생성 요청 * @param request 포스터 콘텐츠 생성 요청
* @return 생성된 포스터 콘텐츠 정보 * @return 생성된 포스터 콘텐츠 정보
*/ */
@Override @Override
@Transactional @Transactional
public PosterContentCreateResponse generatePosterContent(PosterContentCreateRequest request) { public PosterContentCreateResponse generatePosterContent(PosterContentCreateRequest request) {
// AI를 사용하여 포스터 생성
String generatedPoster = aiPosterGenerator.generatePoster(request); String generatedPoster = aiPosterGenerator.generatePoster(request);
// 다양한 사이즈의 포스터 생성
Map<String, String> posterSizes = aiPosterGenerator.generatePosterSizes(generatedPoster);
// 생성 조건 정보 구성 // 생성 조건 정보 구성
CreationConditions conditions = CreationConditions.builder() CreationConditions conditions = CreationConditions.builder()
.category(request.getCategory()) .category(request.getCategory())
.requirement(request.getRequirement()) .requirement(request.getRequirement())
// .toneAndManner(request.getToneAndManner())
// .emotionIntensity(request.getEmotionIntensity())
.eventName(request.getEventName()) .eventName(request.getEventName())
.startDate(request.getStartDate()) .startDate(request.getStartDate())
.endDate(request.getEndDate()) .endDate(request.getEndDate())
@ -62,47 +58,41 @@ public class PosterContentService implements PosterContentUseCase {
.contentType(ContentType.POSTER.name()) .contentType(ContentType.POSTER.name())
.title(request.getTitle()) .title(request.getTitle())
.posterImage(generatedPoster) .posterImage(generatedPoster)
.posterSizes(posterSizes) .posterSizes(new HashMap<>()) // 반환 (사이즈 변환 안함)
.status(ContentStatus.DRAFT.name()) .status(ContentStatus.DRAFT.name())
//.createdAt(LocalDateTime.now())
.build(); .build();
} }
/** /**
* 포스터 콘텐츠 저장 * 포스터 콘텐츠 저장
* *
* @param request 포스터 콘텐츠 저장 요청 * @param request 포스터 콘텐츠 저장 요청
*/ */
@Override @Override
@Transactional @Transactional
public void savePosterContent(PosterContentSaveRequest request) { public void savePosterContent(PosterContentSaveRequest request) {
// 생성 조건 정보 구성 // 생성 조건 구성
CreationConditions conditions = CreationConditions.builder() CreationConditions conditions = CreationConditions.builder()
.category(request.getCategory()) .category(request.getCategory())
.requirement(request.getRequirement()) .requirement(request.getRequirement())
// .toneAndManner(request.getToneAndManner())
// .emotionIntensity(request.getEmotionIntensity())
.eventName(request.getEventName()) .eventName(request.getEventName())
.startDate(request.getStartDate()) .startDate(request.getStartDate())
.endDate(request.getEndDate()) .endDate(request.getEndDate())
.photoStyle(request.getPhotoStyle()) .photoStyle(request.getPhotoStyle())
.build(); .build();
// 콘텐츠 엔티티 생성 저장 // 콘텐츠 엔티티 생성
Content content = Content.builder() Content content = Content.builder()
.contentType(ContentType.POSTER) .contentType(ContentType.POSTER)
.platform(Platform.GENERAL) // 포스터는 범용
.title(request.getTitle()) .title(request.getTitle())
.content(null) // 포스터는 이미지가 콘텐츠 .content(request.getContent())
.hashtags(null)
.images(request.getImages()) .images(request.getImages())
.status(ContentStatus.PUBLISHED) .status(ContentStatus.PUBLISHED)
.creationConditions(conditions) .creationConditions(conditions)
.storeId(request.getStoreId()) .storeId(request.getStoreId())
.createdAt(LocalDateTime.now())
.updatedAt(LocalDateTime.now())
.build(); .build();
// 저장
contentRepository.save(content); contentRepository.save(content);
} }
} }

View File

@ -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<String, String> generatePosterSizes(String baseImage) {
Map<String, String> 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";
}
}

View File

@ -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<String, Object> requestBody = buildRequestBody(request);
log.debug("포스터 생성 요청 데이터: {}", requestBody);
// Python AI 서비스 호출
Map<String, Object> 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<String, String> generatePosterSizes(String baseImage) {
log.info("포스터 사이즈 변환 기능은 사용하지 않음: baseImage={}", baseImage);
return new HashMap<>();
}
/**
* Python AI 서비스 요청 데이터 구성
* Python 서비스의 PosterContentGetRequest 모델에 맞춤
*/
private Map<String, Object> buildRequestBody(PosterContentCreateRequest request) {
Map<String, Object> 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";
}
}

View File

@ -49,6 +49,11 @@ management:
show-details: always show-details: always
info: info:
enabled: true enabled: true
health:
livenessState:
enabled: true
readinessState:
enabled: true
info: info:
app: app:

View File

@ -43,6 +43,11 @@ management:
show-details: always show-details: always
info: info:
enabled: true enabled: true
health:
livenessState:
enabled: true
readinessState:
enabled: true
info: info:
app: app:

View File

@ -58,6 +58,11 @@ management:
show-details: always show-details: always
info: info:
enabled: true enabled: true
health:
livenessState:
enabled: true
readinessState:
enabled: true
info: info:
app: app: