mirror of
https://github.com/won-ktds/smarketing-backend.git
synced 2025-12-06 07:06:24 +00:00
add python call
This commit is contained in:
parent
3e09e77707
commit
faaf690db2
@ -1,4 +1,7 @@
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':common')
|
implementation project(':common')
|
||||||
runtimeOnly 'org.postgresql:postgresql'
|
runtimeOnly 'org.postgresql:postgresql'
|
||||||
|
|
||||||
|
// WebClient를 위한 Spring WebFlux 의존성
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-webflux'
|
||||||
}
|
}
|
||||||
@ -99,24 +99,24 @@ public class ContentQueryService implements ContentQueryUseCase {
|
|||||||
* @param contentId 콘텐츠 ID
|
* @param contentId 콘텐츠 ID
|
||||||
* @return 콘텐츠 상세 정보
|
* @return 콘텐츠 상세 정보
|
||||||
*/
|
*/
|
||||||
@Override
|
// @Override
|
||||||
public ContentDetailResponse getContentDetail(Long contentId) {
|
// public ContentDetailResponse getContentDetail(Long contentId) {
|
||||||
Content content = contentRepository.findById(ContentId.of(contentId))
|
// Content content = contentRepository.findById(ContentId.of(contentId))
|
||||||
.orElseThrow(() -> new BusinessException(ErrorCode.CONTENT_NOT_FOUND));
|
// .orElseThrow(() -> new BusinessException(ErrorCode.CONTENT_NOT_FOUND));
|
||||||
|
//
|
||||||
return ContentDetailResponse.builder()
|
// return ContentDetailResponse.builder()
|
||||||
.contentId(content.getId())
|
// .contentId(content.getId())
|
||||||
.contentType(content.getContentType().name())
|
// .contentType(content.getContentType().name())
|
||||||
.platform(content.getPlatform().name())
|
// .platform(content.getPlatform().name())
|
||||||
.title(content.getTitle())
|
// .title(content.getTitle())
|
||||||
.content(content.getContent())
|
// .content(content.getContent())
|
||||||
.hashtags(content.getHashtags())
|
// .hashtags(content.getHashtags())
|
||||||
.images(content.getImages())
|
// .images(content.getImages())
|
||||||
.status(content.getStatus().name())
|
// .status(content.getStatus().name())
|
||||||
.creationConditions(toCreationConditionsDto(content.getCreationConditions()))
|
// .creationConditions(toCreationConditionsDto(content.getCreationConditions()))
|
||||||
.createdAt(content.getCreatedAt())
|
// .createdAt(content.getCreatedAt())
|
||||||
.build();
|
// .build();
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 콘텐츠 삭제
|
* 콘텐츠 삭제
|
||||||
@ -177,15 +177,15 @@ public class ContentQueryService implements ContentQueryUseCase {
|
|||||||
* @param conditions CreationConditions 도메인 객체
|
* @param conditions CreationConditions 도메인 객체
|
||||||
* @return CreationConditionsDto
|
* @return CreationConditionsDto
|
||||||
*/
|
*/
|
||||||
private ContentDetailResponse.CreationConditionsDto toCreationConditionsDto(CreationConditions conditions) {
|
// private ContentDetailResponse.CreationConditionsDto toCreationConditionsDto(CreationConditions conditions) {
|
||||||
if (conditions == null) {
|
// if (conditions == null) {
|
||||||
return null;
|
// return null;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
return ContentDetailResponse.CreationConditionsDto.builder()
|
// return ContentDetailResponse.CreationConditionsDto.builder()
|
||||||
.toneAndManner(conditions.getToneAndManner())
|
// .toneAndManner(conditions.getToneAndManner())
|
||||||
.emotionIntensity(conditions.getEmotionIntensity())
|
// .emotionIntensity(conditions.getEmotionIntensity())
|
||||||
.eventName(conditions.getEventName())
|
// .eventName(conditions.getEventName())
|
||||||
.build();
|
// .build();
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -49,8 +49,8 @@ public class PosterContentService implements PosterContentUseCase {
|
|||||||
CreationConditions conditions = CreationConditions.builder()
|
CreationConditions conditions = CreationConditions.builder()
|
||||||
.category(request.getCategory())
|
.category(request.getCategory())
|
||||||
.requirement(request.getRequirement())
|
.requirement(request.getRequirement())
|
||||||
.toneAndManner(request.getToneAndManner())
|
// .toneAndManner(request.getToneAndManner())
|
||||||
.emotionIntensity(request.getEmotionIntensity())
|
// .emotionIntensity(request.getEmotionIntensity())
|
||||||
.eventName(request.getEventName())
|
.eventName(request.getEventName())
|
||||||
.startDate(request.getStartDate())
|
.startDate(request.getStartDate())
|
||||||
.endDate(request.getEndDate())
|
.endDate(request.getEndDate())
|
||||||
@ -80,8 +80,8 @@ public class PosterContentService implements PosterContentUseCase {
|
|||||||
CreationConditions conditions = CreationConditions.builder()
|
CreationConditions conditions = CreationConditions.builder()
|
||||||
.category(request.getCategory())
|
.category(request.getCategory())
|
||||||
.requirement(request.getRequirement())
|
.requirement(request.getRequirement())
|
||||||
.toneAndManner(request.getToneAndManner())
|
// .toneAndManner(request.getToneAndManner())
|
||||||
.emotionIntensity(request.getEmotionIntensity())
|
// .emotionIntensity(request.getEmotionIntensity())
|
||||||
.eventName(request.getEventName())
|
.eventName(request.getEventName())
|
||||||
.startDate(request.getStartDate())
|
.startDate(request.getStartDate())
|
||||||
.endDate(request.getEndDate())
|
.endDate(request.getEndDate())
|
||||||
|
|||||||
@ -41,49 +41,12 @@ public class SnsContentService implements SnsContentUseCase {
|
|||||||
@Transactional
|
@Transactional
|
||||||
public SnsContentCreateResponse generateSnsContent(SnsContentCreateRequest request) {
|
public SnsContentCreateResponse generateSnsContent(SnsContentCreateRequest request) {
|
||||||
// AI를 사용하여 SNS 콘텐츠 생성
|
// AI를 사용하여 SNS 콘텐츠 생성
|
||||||
String generatedContent = aiContentGenerator.generateSnsContent(request);
|
String content = aiContentGenerator.generateSnsContent(request);
|
||||||
|
|
||||||
// 플랫폼에 맞는 해시태그 생성
|
|
||||||
Platform platform = Platform.fromString(request.getPlatform());
|
|
||||||
List<String> hashtags = aiContentGenerator.generateHashtags(generatedContent, platform);
|
|
||||||
|
|
||||||
// 생성 조건 정보 구성
|
|
||||||
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())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
// 임시 콘텐츠 생성 (저장하지 않음)
|
|
||||||
Content content = Content.builder()
|
|
||||||
// .contentType(ContentType.SNS_POST)
|
|
||||||
.platform(platform)
|
|
||||||
.title(request.getTitle())
|
|
||||||
.content(generatedContent)
|
|
||||||
.hashtags(hashtags)
|
|
||||||
.images(request.getImages())
|
|
||||||
.status(ContentStatus.DRAFT)
|
|
||||||
.creationConditions(conditions)
|
|
||||||
.storeId(request.getStoreId())
|
|
||||||
.createdAt(LocalDateTime.now())
|
|
||||||
.updatedAt(LocalDateTime.now())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
return SnsContentCreateResponse.builder()
|
return SnsContentCreateResponse.builder()
|
||||||
.contentId(null) // 임시 생성이므로 ID 없음
|
.content(content)
|
||||||
.contentType(content.getContentType().name())
|
|
||||||
.platform(content.getPlatform().name())
|
|
||||||
.title(content.getTitle())
|
|
||||||
.content(content.getContent())
|
|
||||||
.hashtags(content.getHashtags())
|
|
||||||
.fixedImages(content.getImages())
|
|
||||||
.status(content.getStatus().name())
|
|
||||||
.createdAt(content.getCreatedAt())
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -98,8 +61,8 @@ public class SnsContentService implements SnsContentUseCase {
|
|||||||
CreationConditions conditions = CreationConditions.builder()
|
CreationConditions conditions = CreationConditions.builder()
|
||||||
.category(request.getCategory())
|
.category(request.getCategory())
|
||||||
.requirement(request.getRequirement())
|
.requirement(request.getRequirement())
|
||||||
.toneAndManner(request.getToneAndManner())
|
//.toneAndManner(request.getToneAndManner())
|
||||||
.emotionIntensity(request.getEmotionIntensity())
|
//.emotionIntensity(request.getEmotionIntensity())
|
||||||
.eventName(request.getEventName())
|
.eventName(request.getEventName())
|
||||||
.startDate(request.getStartDate())
|
.startDate(request.getStartDate())
|
||||||
.endDate(request.getEndDate())
|
.endDate(request.getEndDate())
|
||||||
|
|||||||
@ -44,7 +44,7 @@ public interface ContentQueryUseCase {
|
|||||||
* @param contentId 콘텐츠 ID
|
* @param contentId 콘텐츠 ID
|
||||||
* @return 콘텐츠 상세 정보
|
* @return 콘텐츠 상세 정보
|
||||||
*/
|
*/
|
||||||
ContentDetailResponse getContentDetail(Long contentId);
|
//ContentDetailResponse getContentDetail(Long contentId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 콘텐츠 삭제
|
* 콘텐츠 삭제
|
||||||
|
|||||||
@ -0,0 +1,31 @@
|
|||||||
|
// marketing-content/src/main/java/com/won/smarketing/content/config/WebClientConfig.java
|
||||||
|
package com.won.smarketing.content.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
|
import io.netty.channel.ChannelOption;
|
||||||
|
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||||
|
import reactor.netty.http.client.HttpClient;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebClient 설정
|
||||||
|
* Python AI 서비스 호출을 위한 WebClient 구성
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class WebClientConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public WebClient webClient() {
|
||||||
|
HttpClient httpClient = HttpClient.create()
|
||||||
|
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
|
||||||
|
.responseTimeout(Duration.ofMillis(30000));
|
||||||
|
|
||||||
|
return WebClient.builder()
|
||||||
|
.clientConnector(new ReactorClientHttpConnector(httpClient))
|
||||||
|
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(1024 * 1024))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -24,8 +24,11 @@ public class CreationConditions {
|
|||||||
private String id;
|
private String id;
|
||||||
private String category;
|
private String category;
|
||||||
private String requirement;
|
private String requirement;
|
||||||
private String toneAndManner;
|
// private String toneAndManner;
|
||||||
private String emotionIntensity;
|
// private String emotionIntensity;
|
||||||
|
private String storeName;
|
||||||
|
private String storeType;
|
||||||
|
private String target;
|
||||||
private String eventName;
|
private String eventName;
|
||||||
private LocalDate startDate;
|
private LocalDate startDate;
|
||||||
private LocalDate endDate;
|
private LocalDate endDate;
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package com.won.smarketing.content.domain.service;
|
|||||||
|
|
||||||
import com.won.smarketing.content.domain.model.Platform;
|
import com.won.smarketing.content.domain.model.Platform;
|
||||||
import com.won.smarketing.content.presentation.dto.SnsContentCreateRequest;
|
import com.won.smarketing.content.presentation.dto.SnsContentCreateRequest;
|
||||||
|
import com.won.smarketing.content.presentation.dto.SnsContentCreateResponse;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -18,13 +19,4 @@ public interface AiContentGenerator {
|
|||||||
* @return 생성된 콘텐츠
|
* @return 생성된 콘텐츠
|
||||||
*/
|
*/
|
||||||
String generateSnsContent(SnsContentCreateRequest request);
|
String generateSnsContent(SnsContentCreateRequest request);
|
||||||
|
|
||||||
/**
|
|
||||||
* 플랫폼별 해시태그 생성
|
|
||||||
*
|
|
||||||
* @param content 콘텐츠 내용
|
|
||||||
* @param platform 플랫폼
|
|
||||||
* @return 해시태그 목록
|
|
||||||
*/
|
|
||||||
List<String> generateHashtags(String content, Platform platform);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,95 +1,99 @@
|
|||||||
// marketing-content/src/main/java/com/won/smarketing/content/infrastructure/external/ClaudeAiContentGenerator.java
|
|
||||||
package com.won.smarketing.content.infrastructure.external;
|
package com.won.smarketing.content.infrastructure.external;
|
||||||
|
|
||||||
// 수정: domain 패키지의 인터페이스를 import
|
|
||||||
import com.won.smarketing.content.domain.service.AiContentGenerator;
|
import com.won.smarketing.content.domain.service.AiContentGenerator;
|
||||||
import com.won.smarketing.content.domain.model.Platform;
|
|
||||||
import com.won.smarketing.content.presentation.dto.SnsContentCreateRequest;
|
import com.won.smarketing.content.presentation.dto.SnsContentCreateRequest;
|
||||||
|
import com.won.smarketing.content.presentation.dto.SnsContentCreateResponse;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.time.Duration;
|
||||||
import java.util.List;
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Claude AI를 활용한 콘텐츠 생성 구현체
|
* Python AI SNS Content Service를 활용한 콘텐츠 생성 구현체
|
||||||
* Clean Architecture의 Infrastructure Layer에 위치
|
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class ClaudeAiContentGenerator implements AiContentGenerator {
|
public class ClaudeAiContentGenerator implements AiContentGenerator {
|
||||||
|
|
||||||
|
private final WebClient webClient;
|
||||||
|
|
||||||
|
@Value("${external.ai-service.base-url:http://20.249.139.88:5001}")
|
||||||
|
private String aiServiceBaseUrl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SNS 콘텐츠 생성
|
* SNS 콘텐츠 생성 - Python AI 서비스 호출
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String generateSnsContent(SnsContentCreateRequest request) {
|
public String generateSnsContent(SnsContentCreateRequest request) {
|
||||||
try {
|
log.info("Python AI 서비스 호출: {}/api/ai/sns", aiServiceBaseUrl);
|
||||||
String prompt = buildContentPrompt(request);
|
|
||||||
return generateDummySnsContent(request.getTitle(), Platform.fromString(request.getPlatform()));
|
// 요청 데이터 구성
|
||||||
} catch (Exception e) {
|
Map<String, Object> requestBody = new HashMap<>();
|
||||||
log.error("AI 콘텐츠 생성 실패: {}", e.getMessage(), e);
|
requestBody.put("storeId", request.getStoreId());
|
||||||
return generateFallbackContent(request.getTitle(), Platform.fromString(request.getPlatform()));
|
requestBody.put("storeName", request.getStoreName());
|
||||||
|
requestBody.put("storeType", request.getStoreType());
|
||||||
|
requestBody.put("platform", request.getPlatform());
|
||||||
|
requestBody.put("title", request.getTitle());
|
||||||
|
requestBody.put("category", request.getCategory());
|
||||||
|
requestBody.put("contentType", request.getContentType());
|
||||||
|
requestBody.put("requirement", request.getRequirement());
|
||||||
|
|
||||||
|
//requestBody.put("tone_and_manner", request.getToneAndManner());
|
||||||
|
// requestBody.put("emotion_intensity", request.getEmotionIntensity());
|
||||||
|
requestBody.put("target", request.getTarget());
|
||||||
|
|
||||||
|
requestBody.put("event_name", request.getEventName());
|
||||||
|
requestBody.put("start_date", request.getStartDate());
|
||||||
|
requestBody.put("end_date", request.getEndDate());
|
||||||
|
|
||||||
|
requestBody.put("images", request.getImages());
|
||||||
|
|
||||||
|
// Python AI 서비스 호출
|
||||||
|
Map<String, Object> response = webClient
|
||||||
|
.method(HttpMethod.GET)
|
||||||
|
.uri(aiServiceBaseUrl + "/api/ai/sns")
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.bodyValue(requestBody)
|
||||||
|
.retrieve()
|
||||||
|
.bodyToMono(Map.class)
|
||||||
|
.timeout(Duration.ofSeconds(30))
|
||||||
|
.block();
|
||||||
|
|
||||||
|
String content = "";
|
||||||
|
|
||||||
|
// 응답에서 content 추출
|
||||||
|
if (response != null && response.containsKey("content")) {
|
||||||
|
content = (String) response.get("content");
|
||||||
|
log.info("AI 서비스 응답 성공: contentLength={}", content.length());
|
||||||
|
|
||||||
|
return content;
|
||||||
}
|
}
|
||||||
|
return content;
|
||||||
|
// } catch (Exception e) {
|
||||||
|
// log.error("AI 서비스 호출 실패: {}", e.getMessage(), e);
|
||||||
|
// return generateFallbackContent(request.getTitle(), Platform.fromString(request.getPlatform()));
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 플랫폼별 해시태그 생성
|
* 폴백 콘텐츠 생성
|
||||||
*/
|
*/
|
||||||
@Override
|
// private String generateFallbackContent(String title, Platform platform) {
|
||||||
public List<String> generateHashtags(String content, Platform platform) {
|
// String baseContent = "🌟 " + title + "를 소개합니다! 🌟\n\n" +
|
||||||
try {
|
// "저희 매장에서 특별한 경험을 만나보세요.\n" +
|
||||||
return generateDummyHashtags(platform);
|
// "고객 여러분의 소중한 시간을 더욱 특별하게 만들어드리겠습니다.\n\n";
|
||||||
} catch (Exception e) {
|
//
|
||||||
log.error("해시태그 생성 실패: {}", e.getMessage(), e);
|
// if (platform == Platform.INSTAGRAM) {
|
||||||
return generateFallbackHashtags();
|
// return baseContent + "더 많은 정보는 프로필 링크에서 확인하세요! 📸";
|
||||||
}
|
// } else {
|
||||||
}
|
// return baseContent + "자세한 내용은 저희 블로그를 방문해 주세요! ✨";
|
||||||
|
// }
|
||||||
private String buildContentPrompt(SnsContentCreateRequest request) {
|
// }
|
||||||
StringBuilder prompt = new StringBuilder();
|
|
||||||
prompt.append("제목: ").append(request.getTitle()).append("\n");
|
|
||||||
prompt.append("카테고리: ").append(request.getCategory()).append("\n");
|
|
||||||
prompt.append("플랫폼: ").append(request.getPlatform()).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 generateDummySnsContent(String title, Platform platform) {
|
|
||||||
String baseContent = "🌟 " + title + "를 소개합니다! 🌟\n\n" +
|
|
||||||
"저희 매장에서 특별한 경험을 만나보세요.\n" +
|
|
||||||
"고객 여러분의 소중한 시간을 더욱 특별하게 만들어드리겠습니다.\n\n";
|
|
||||||
|
|
||||||
if (platform == Platform.INSTAGRAM) {
|
|
||||||
return baseContent + "더 많은 정보는 프로필 링크에서 확인하세요! 📸";
|
|
||||||
} else {
|
|
||||||
return baseContent + "자세한 내용은 저희 블로그를 방문해 주세요! ✨";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String generateFallbackContent(String title, Platform platform) {
|
|
||||||
return title + "에 대한 멋진 콘텐츠입니다. 많은 관심 부탁드립니다!";
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> generateDummyHashtags(Platform platform) {
|
|
||||||
if (platform == Platform.INSTAGRAM) {
|
|
||||||
return Arrays.asList("#맛집", "#데일리", "#소상공인", "#추천", "#인스타그램");
|
|
||||||
} else {
|
|
||||||
return Arrays.asList("#맛집추천", "#블로그", "#리뷰", "#맛있는곳", "#소상공인응원");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> generateFallbackHashtags() {
|
|
||||||
return Arrays.asList("#소상공인", "#마케팅", "#홍보");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -105,8 +105,8 @@ public class ContentMapper {
|
|||||||
ContentConditionsJpaEntity entity = new ContentConditionsJpaEntity();
|
ContentConditionsJpaEntity entity = new ContentConditionsJpaEntity();
|
||||||
entity.setCategory(conditions.getCategory());
|
entity.setCategory(conditions.getCategory());
|
||||||
entity.setRequirement(conditions.getRequirement());
|
entity.setRequirement(conditions.getRequirement());
|
||||||
entity.setToneAndManner(conditions.getToneAndManner());
|
// entity.setToneAndManner(conditions.getToneAndManner());
|
||||||
entity.setEmotionIntensity(conditions.getEmotionIntensity());
|
// entity.setEmotionIntensity(conditions.getEmotionIntensity());
|
||||||
entity.setEventName(conditions.getEventName());
|
entity.setEventName(conditions.getEventName());
|
||||||
entity.setStartDate(conditions.getStartDate());
|
entity.setStartDate(conditions.getStartDate());
|
||||||
entity.setEndDate(conditions.getEndDate());
|
entity.setEndDate(conditions.getEndDate());
|
||||||
@ -126,8 +126,8 @@ public class ContentMapper {
|
|||||||
return CreationConditions.builder()
|
return CreationConditions.builder()
|
||||||
.category(entity.getCategory())
|
.category(entity.getCategory())
|
||||||
.requirement(entity.getRequirement())
|
.requirement(entity.getRequirement())
|
||||||
.toneAndManner(entity.getToneAndManner())
|
// .toneAndManner(entity.getToneAndManner())
|
||||||
.emotionIntensity(entity.getEmotionIntensity())
|
// .emotionIntensity(entity.getEmotionIntensity())
|
||||||
.eventName(entity.getEventName())
|
.eventName(entity.getEventName())
|
||||||
.startDate(entity.getStartDate())
|
.startDate(entity.getStartDate())
|
||||||
.endDate(entity.getEndDate())
|
.endDate(entity.getEndDate())
|
||||||
|
|||||||
@ -143,14 +143,14 @@ public class ContentController {
|
|||||||
* @param contentId 조회할 콘텐츠 ID
|
* @param contentId 조회할 콘텐츠 ID
|
||||||
* @return 콘텐츠 상세 정보
|
* @return 콘텐츠 상세 정보
|
||||||
*/
|
*/
|
||||||
@Operation(summary = "콘텐츠 상세 조회", description = "특정 콘텐츠의 상세 정보를 조회합니다.")
|
// @Operation(summary = "콘텐츠 상세 조회", description = "특정 콘텐츠의 상세 정보를 조회합니다.")
|
||||||
@GetMapping("/{contentId}")
|
// @GetMapping("/{contentId}")
|
||||||
public ResponseEntity<ApiResponse<ContentDetailResponse>> getContentDetail(
|
// public ResponseEntity<ApiResponse<ContentDetailResponse>> getContentDetail(
|
||||||
@Parameter(description = "콘텐츠 ID", required = true)
|
// @Parameter(description = "콘텐츠 ID", required = true)
|
||||||
@PathVariable Long contentId) {
|
// @PathVariable Long contentId) {
|
||||||
ContentDetailResponse response = contentQueryUseCase.getContentDetail(contentId);
|
// ContentDetailResponse response = contentQueryUseCase.getContentDetail(contentId);
|
||||||
return ResponseEntity.ok(ApiResponse.success(response));
|
// return ResponseEntity.ok(ApiResponse.success(response));
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 콘텐츠 삭제
|
* 콘텐츠 삭제
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package com.won.smarketing.content.presentation.dto;
|
package com.won.smarketing.content.presentation.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
@ -23,6 +24,7 @@ import java.util.List;
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@Builder
|
@Builder
|
||||||
@Schema(description = "SNS 콘텐츠 생성 요청")
|
@Schema(description = "SNS 콘텐츠 생성 요청")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
public class SnsContentCreateRequest {
|
public class SnsContentCreateRequest {
|
||||||
|
|
||||||
// ==================== 기본 정보 ====================
|
// ==================== 기본 정보 ====================
|
||||||
@ -31,6 +33,12 @@ public class SnsContentCreateRequest {
|
|||||||
@NotNull(message = "매장 ID는 필수입니다")
|
@NotNull(message = "매장 ID는 필수입니다")
|
||||||
private Long storeId;
|
private Long storeId;
|
||||||
|
|
||||||
|
@Schema(description = "매장 이름", example = "명륜진사갈비")
|
||||||
|
private String storeName;
|
||||||
|
|
||||||
|
@Schema(description = "업종", example = "한식")
|
||||||
|
private String storeType;
|
||||||
|
|
||||||
@Schema(description = "대상 플랫폼",
|
@Schema(description = "대상 플랫폼",
|
||||||
example = "INSTAGRAM",
|
example = "INSTAGRAM",
|
||||||
allowableValues = {"INSTAGRAM", "NAVER_BLOG", "FACEBOOK", "KAKAO_STORY"},
|
allowableValues = {"INSTAGRAM", "NAVER_BLOG", "FACEBOOK", "KAKAO_STORY"},
|
||||||
@ -54,15 +62,21 @@ public class SnsContentCreateRequest {
|
|||||||
@Size(max = 500, message = "요구사항은 500자 이하로 입력해주세요")
|
@Size(max = 500, message = "요구사항은 500자 이하로 입력해주세요")
|
||||||
private String requirement;
|
private String requirement;
|
||||||
|
|
||||||
@Schema(description = "톤앤매너",
|
@Schema(description = "타겟층", example = "10대 청소년")
|
||||||
example = "친근함",
|
private String target;
|
||||||
allowableValues = {"친근함", "전문적", "유머러스", "감성적", "트렌디"})
|
|
||||||
private String toneAndManner;
|
|
||||||
|
|
||||||
@Schema(description = "감정 강도",
|
@Schema(description = "콘텐츠 타입", example = "SNS 게시물")
|
||||||
example = "보통",
|
private String contentType;
|
||||||
allowableValues = {"약함", "보통", "강함"})
|
|
||||||
private String emotionIntensity;
|
// @Schema(description = "톤앤매너",
|
||||||
|
// example = "친근함",
|
||||||
|
// allowableValues = {"친근함", "전문적", "유머러스", "감성적", "트렌디"})
|
||||||
|
// private String toneAndManner;
|
||||||
|
|
||||||
|
// @Schema(description = "감정 강도",
|
||||||
|
// example = "보통",
|
||||||
|
// allowableValues = {"약함", "보통", "강함"})
|
||||||
|
// private String emotionIntensity;
|
||||||
|
|
||||||
// ==================== 이벤트 정보 ====================
|
// ==================== 이벤트 정보 ====================
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package com.won.smarketing.content.presentation.dto;
|
package com.won.smarketing.content.presentation.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
@ -20,364 +21,8 @@ import java.util.List;
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@Builder
|
@Builder
|
||||||
@Schema(description = "SNS 콘텐츠 생성 응답")
|
@Schema(description = "SNS 콘텐츠 생성 응답")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
public class SnsContentCreateResponse {
|
public class SnsContentCreateResponse {
|
||||||
|
@Schema(description = "생성된 콘텐츠")
|
||||||
// ==================== 기본 식별 정보 ====================
|
|
||||||
|
|
||||||
@Schema(description = "생성된 콘텐츠 ID", example = "1")
|
|
||||||
private Long contentId;
|
|
||||||
|
|
||||||
@Schema(description = "콘텐츠 타입", example = "SNS_POST")
|
|
||||||
private String contentType;
|
|
||||||
|
|
||||||
@Schema(description = "대상 플랫폼", example = "INSTAGRAM",
|
|
||||||
allowableValues = {"INSTAGRAM", "NAVER_BLOG", "FACEBOOK", "KAKAO_STORY"})
|
|
||||||
private String platform;
|
|
||||||
|
|
||||||
// ==================== AI 생성 콘텐츠 ====================
|
|
||||||
|
|
||||||
@Schema(description = "AI가 생성한 콘텐츠 제목",
|
|
||||||
example = "맛있는 신메뉴를 소개합니다! ✨")
|
|
||||||
private String title;
|
|
||||||
|
|
||||||
@Schema(description = "AI가 생성한 콘텐츠 내용",
|
|
||||||
example = "안녕하세요! 😊\n\n특별한 신메뉴가 출시되었습니다!\n진짜 맛있어서 꼭 한번 드셔보세요 🍽️\n\n매장에서 기다리고 있을게요! 💫")
|
|
||||||
private String content;
|
private String content;
|
||||||
|
|
||||||
@Schema(description = "AI가 생성한 해시태그 목록",
|
|
||||||
example = "[\"맛집\", \"신메뉴\", \"추천\", \"인스타그램\", \"일상\", \"좋아요\", \"팔로우\", \"맛있어요\"]")
|
|
||||||
private List<String> hashtags;
|
|
||||||
|
|
||||||
// ==================== 플랫폼별 최적화 정보 ====================
|
|
||||||
|
|
||||||
@Schema(description = "플랫폼별 최적화된 콘텐츠 길이", example = "280")
|
|
||||||
private Integer contentLength;
|
|
||||||
|
|
||||||
@Schema(description = "플랫폼별 권장 해시태그 개수", example = "8")
|
|
||||||
private Integer recommendedHashtagCount;
|
|
||||||
|
|
||||||
@Schema(description = "플랫폼별 최대 해시태그 개수", example = "15")
|
|
||||||
private Integer maxHashtagCount;
|
|
||||||
|
|
||||||
// ==================== 생성 조건 정보 ====================
|
|
||||||
|
|
||||||
@Schema(description = "콘텐츠 생성에 사용된 조건들")
|
|
||||||
private GenerationConditionsDto generationConditions;
|
|
||||||
|
|
||||||
// ==================== 상태 및 메타데이터 ====================
|
|
||||||
|
|
||||||
@Schema(description = "생성 상태", example = "DRAFT",
|
|
||||||
allowableValues = {"DRAFT", "PUBLISHED", "SCHEDULED"})
|
|
||||||
private String status;
|
|
||||||
|
|
||||||
@Schema(description = "생성 일시", example = "2024-01-15T10:30:00")
|
|
||||||
private LocalDateTime createdAt;
|
|
||||||
|
|
||||||
@Schema(description = "AI 모델 버전", example = "gpt-4-turbo")
|
|
||||||
private String aiModelVersion;
|
|
||||||
|
|
||||||
@Schema(description = "생성 시간 (초)", example = "3.5")
|
|
||||||
private Double generationTimeSeconds;
|
|
||||||
|
|
||||||
// ==================== 추가 정보 ====================
|
|
||||||
|
|
||||||
@Schema(description = "업로드된 원본 이미지 URL 목록")
|
|
||||||
private List<String> originalImages;
|
|
||||||
|
|
||||||
@Schema(description = "콘텐츠 품질 점수 (1-100)", example = "85")
|
|
||||||
private Integer qualityScore;
|
|
||||||
|
|
||||||
@Schema(description = "예상 참여율 (%)", example = "12.5")
|
|
||||||
private Double expectedEngagementRate;
|
|
||||||
|
|
||||||
@Schema(description = "콘텐츠 카테고리", example = "음식/메뉴소개")
|
|
||||||
private String category;
|
|
||||||
|
|
||||||
@Schema(description = "보정된 이미지 URL 목록")
|
|
||||||
private List<String> fixedImages;
|
|
||||||
|
|
||||||
// ==================== 편집 가능 여부 ====================
|
|
||||||
|
|
||||||
@Schema(description = "제목 편집 가능 여부", example = "true")
|
|
||||||
@Builder.Default
|
|
||||||
private Boolean titleEditable = true;
|
|
||||||
|
|
||||||
@Schema(description = "내용 편집 가능 여부", example = "true")
|
|
||||||
@Builder.Default
|
|
||||||
private Boolean contentEditable = true;
|
|
||||||
|
|
||||||
@Schema(description = "해시태그 편집 가능 여부", example = "true")
|
|
||||||
@Builder.Default
|
|
||||||
private Boolean hashtagsEditable = true;
|
|
||||||
|
|
||||||
// ==================== 대안 콘텐츠 ====================
|
|
||||||
|
|
||||||
@Schema(description = "대안 제목 목록 (사용자 선택용)")
|
|
||||||
private List<String> alternativeTitles;
|
|
||||||
|
|
||||||
@Schema(description = "대안 해시태그 세트 목록")
|
|
||||||
private List<List<String>> alternativeHashtagSets;
|
|
||||||
|
|
||||||
// ==================== 내부 DTO 클래스 ====================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 콘텐츠 생성 조건 DTO
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
@Schema(description = "콘텐츠 생성 조건")
|
|
||||||
public static class GenerationConditionsDto {
|
|
||||||
|
|
||||||
@Schema(description = "홍보 대상", example = "메뉴")
|
|
||||||
private String targetAudience;
|
|
||||||
|
|
||||||
@Schema(description = "이벤트명", example = "신메뉴 출시 이벤트")
|
|
||||||
private String eventName;
|
|
||||||
|
|
||||||
@Schema(description = "톤앤매너", example = "친근함")
|
|
||||||
private String toneAndManner;
|
|
||||||
|
|
||||||
@Schema(description = "프로모션 유형", example = "할인 정보")
|
|
||||||
private String promotionType;
|
|
||||||
|
|
||||||
@Schema(description = "감정 강도", example = "보통")
|
|
||||||
private String emotionIntensity;
|
|
||||||
|
|
||||||
@Schema(description = "홍보 시작일", example = "2024-01-15T09:00:00")
|
|
||||||
private LocalDateTime promotionStartDate;
|
|
||||||
|
|
||||||
@Schema(description = "홍보 종료일", example = "2024-01-22T23:59:59")
|
|
||||||
private LocalDateTime promotionEndDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 비즈니스 메서드 ====================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 플랫폼별 콘텐츠 최적화 여부 확인
|
|
||||||
*
|
|
||||||
* @return 콘텐츠가 플랫폼 권장 사항을 만족하면 true
|
|
||||||
*/
|
|
||||||
public boolean isOptimizedForPlatform() {
|
|
||||||
if (content == null || hashtags == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 플랫폼별 최적화 기준
|
|
||||||
switch (platform.toUpperCase()) {
|
|
||||||
case "INSTAGRAM":
|
|
||||||
return content.length() <= 2200 &&
|
|
||||||
hashtags.size() <= 15 &&
|
|
||||||
hashtags.size() >= 5;
|
|
||||||
case "NAVER_BLOG":
|
|
||||||
return content.length() >= 300 &&
|
|
||||||
hashtags.size() <= 10 &&
|
|
||||||
hashtags.size() >= 3;
|
|
||||||
case "FACEBOOK":
|
|
||||||
return content.length() <= 500 &&
|
|
||||||
hashtags.size() <= 5;
|
|
||||||
default:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 고품질 콘텐츠 여부 확인
|
|
||||||
*
|
|
||||||
* @return 품질 점수가 80점 이상이면 true
|
|
||||||
*/
|
|
||||||
public boolean isHighQuality() {
|
|
||||||
return qualityScore != null && qualityScore >= 80;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 참여율 예상 등급 반환
|
|
||||||
*
|
|
||||||
* @return 예상 참여율 등급 (HIGH, MEDIUM, LOW)
|
|
||||||
*/
|
|
||||||
public String getExpectedEngagementLevel() {
|
|
||||||
if (expectedEngagementRate == null) {
|
|
||||||
return "UNKNOWN";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expectedEngagementRate >= 15.0) {
|
|
||||||
return "HIGH";
|
|
||||||
} else if (expectedEngagementRate >= 8.0) {
|
|
||||||
return "MEDIUM";
|
|
||||||
} else {
|
|
||||||
return "LOW";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 해시태그를 문자열로 변환 (# 포함)
|
|
||||||
*
|
|
||||||
* @return #으로 시작하는 해시태그 문자열
|
|
||||||
*/
|
|
||||||
public String getHashtagsAsString() {
|
|
||||||
if (hashtags == null || hashtags.isEmpty()) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return hashtags.stream()
|
|
||||||
.map(tag -> "#" + tag)
|
|
||||||
.reduce((a, b) -> a + " " + b)
|
|
||||||
.orElse("");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 콘텐츠 요약 생성
|
|
||||||
*
|
|
||||||
* @param maxLength 최대 길이
|
|
||||||
* @return 요약된 콘텐츠
|
|
||||||
*/
|
|
||||||
public String getContentSummary(int maxLength) {
|
|
||||||
if (content == null || content.length() <= maxLength) {
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
return content.substring(0, maxLength) + "...";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 플랫폼별 최적화 제안사항 반환
|
|
||||||
*
|
|
||||||
* @return 최적화 제안사항 목록
|
|
||||||
*/
|
|
||||||
public List<String> getOptimizationSuggestions() {
|
|
||||||
List<String> suggestions = new java.util.ArrayList<>();
|
|
||||||
|
|
||||||
if (!isOptimizedForPlatform()) {
|
|
||||||
switch (platform.toUpperCase()) {
|
|
||||||
case "INSTAGRAM":
|
|
||||||
if (content != null && content.length() > 2200) {
|
|
||||||
suggestions.add("콘텐츠 길이를 2200자 이하로 줄여주세요.");
|
|
||||||
}
|
|
||||||
if (hashtags != null && hashtags.size() > 15) {
|
|
||||||
suggestions.add("해시태그를 15개 이하로 줄여주세요.");
|
|
||||||
}
|
|
||||||
if (hashtags != null && hashtags.size() < 5) {
|
|
||||||
suggestions.add("해시태그를 5개 이상 추가해주세요.");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "NAVER_BLOG":
|
|
||||||
if (content != null && content.length() < 300) {
|
|
||||||
suggestions.add("블로그 포스팅을 위해 내용을 300자 이상으로 늘려주세요.");
|
|
||||||
}
|
|
||||||
if (hashtags != null && hashtags.size() > 10) {
|
|
||||||
suggestions.add("네이버 블로그는 해시태그를 10개 이하로 사용하는 것이 좋습니다.");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "FACEBOOK":
|
|
||||||
if (content != null && content.length() > 500) {
|
|
||||||
suggestions.add("페이스북에서는 500자 이하의 짧은 글이 더 효과적입니다.");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return suggestions;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 팩토리 메서드 ====================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 도메인 엔티티에서 SnsContentCreateResponse 생성
|
|
||||||
*
|
|
||||||
* @param content 콘텐츠 도메인 엔티티
|
|
||||||
* @param aiMetadata AI 생성 메타데이터
|
|
||||||
* @return SnsContentCreateResponse
|
|
||||||
*/
|
|
||||||
public static SnsContentCreateResponse fromDomain(
|
|
||||||
com.won.smarketing.content.domain.model.Content content,
|
|
||||||
AiGenerationMetadata aiMetadata) {
|
|
||||||
|
|
||||||
SnsContentCreateResponseBuilder builder = SnsContentCreateResponse.builder()
|
|
||||||
.contentId(content.getId())
|
|
||||||
.contentType(content.getContentType().name())
|
|
||||||
.platform(content.getPlatform().name())
|
|
||||||
.title(content.getTitle())
|
|
||||||
.content(content.getContent())
|
|
||||||
.hashtags(content.getHashtags())
|
|
||||||
.status(content.getStatus().name())
|
|
||||||
.createdAt(content.getCreatedAt())
|
|
||||||
.originalImages(content.getImages());
|
|
||||||
|
|
||||||
// 생성 조건 정보 설정
|
|
||||||
if (content.getCreationConditions() != null) {
|
|
||||||
builder.generationConditions(GenerationConditionsDto.builder()
|
|
||||||
//.targetAudience(content.getCreationConditions().getTargetAudience())
|
|
||||||
.eventName(content.getCreationConditions().getEventName())
|
|
||||||
.toneAndManner(content.getCreationConditions().getToneAndManner())
|
|
||||||
.promotionType(content.getCreationConditions().getPromotionType())
|
|
||||||
.emotionIntensity(content.getCreationConditions().getEmotionIntensity())
|
|
||||||
.promotionStartDate(content.getPromotionStartDate())
|
|
||||||
.promotionEndDate(content.getPromotionEndDate())
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
// AI 메타데이터 설정
|
|
||||||
if (aiMetadata != null) {
|
|
||||||
builder.aiModelVersion(aiMetadata.getModelVersion())
|
|
||||||
.generationTimeSeconds(aiMetadata.getGenerationTime())
|
|
||||||
.qualityScore(aiMetadata.getQualityScore())
|
|
||||||
.expectedEngagementRate(aiMetadata.getExpectedEngagementRate())
|
|
||||||
.alternativeTitles(aiMetadata.getAlternativeTitles())
|
|
||||||
.alternativeHashtagSets(aiMetadata.getAlternativeHashtagSets());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 플랫폼별 최적화 정보 설정
|
|
||||||
SnsContentCreateResponse response = builder.build();
|
|
||||||
response.setContentLength(response.getContent() != null ? response.getContent().length() : 0);
|
|
||||||
response.setRecommendedHashtagCount(getRecommendedHashtagCount(content.getPlatform().name()));
|
|
||||||
response.setMaxHashtagCount(getMaxHashtagCount(content.getPlatform().name()));
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 플랫폼별 권장 해시태그 개수 반환
|
|
||||||
*/
|
|
||||||
private static Integer getRecommendedHashtagCount(String platform) {
|
|
||||||
switch (platform.toUpperCase()) {
|
|
||||||
case "INSTAGRAM": return 8;
|
|
||||||
case "NAVER_BLOG": return 5;
|
|
||||||
case "FACEBOOK": return 3;
|
|
||||||
case "KAKAO_STORY": return 5;
|
|
||||||
default: return 5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 플랫폼별 최대 해시태그 개수 반환
|
|
||||||
*/
|
|
||||||
private static Integer getMaxHashtagCount(String platform) {
|
|
||||||
switch (platform.toUpperCase()) {
|
|
||||||
case "INSTAGRAM": return 15;
|
|
||||||
case "NAVER_BLOG": return 10;
|
|
||||||
case "FACEBOOK": return 5;
|
|
||||||
case "KAKAO_STORY": return 8;
|
|
||||||
default: return 10;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== AI 생성 메타데이터 DTO ====================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AI 생성 메타데이터
|
|
||||||
* AI 생성 과정에서 나온 부가 정보들
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
public static class AiGenerationMetadata {
|
|
||||||
private String modelVersion;
|
|
||||||
private Double generationTime;
|
|
||||||
private Integer qualityScore;
|
|
||||||
private Double expectedEngagementRate;
|
|
||||||
private List<String> alternativeTitles;
|
|
||||||
private List<List<String>> alternativeHashtagSets;
|
|
||||||
private String category;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -2,6 +2,9 @@ server:
|
|||||||
port: ${SERVER_PORT:8083}
|
port: ${SERVER_PORT:8083}
|
||||||
|
|
||||||
spring:
|
spring:
|
||||||
|
jackson:
|
||||||
|
deserialization:
|
||||||
|
fail-on-unknown-properties: false
|
||||||
application:
|
application:
|
||||||
name: marketing-content-service
|
name: marketing-content-service
|
||||||
datasource:
|
datasource:
|
||||||
@ -31,3 +34,6 @@ jwt:
|
|||||||
logging:
|
logging:
|
||||||
level:
|
level:
|
||||||
com.won.smarketing: ${LOG_LEVEL:DEBUG}
|
com.won.smarketing: ${LOG_LEVEL:DEBUG}
|
||||||
|
external:
|
||||||
|
ai-service:
|
||||||
|
base-url: ${AI_SERVICE_BASE_URL:http://20.249.139.88:5001}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user