mirror of
https://github.com/won-ktds/smarketing-backend.git
synced 2025-12-06 07:06:24 +00:00
fix marketing-content
This commit is contained in:
parent
d265bb2065
commit
de6b160aca
@ -3,6 +3,7 @@ package com.won.smarketing.content;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.domain.EntityScan;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
|
||||
/**
|
||||
@ -17,8 +18,9 @@ import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
"com.won.smarketing.content.infrastructure.repository"
|
||||
})
|
||||
@EntityScan(basePackages = {
|
||||
"com.won.smarketing.content.domain.model"
|
||||
"com.won.smarketing.content.infrastructure.entity"
|
||||
})
|
||||
@EnableJpaAuditing
|
||||
public class MarketingContentServiceApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
package com.won.smarketing.content.config;
|
||||
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "com.won.smarketing.content")
|
||||
public class ContentConfig {
|
||||
}
|
||||
@ -1,8 +1,10 @@
|
||||
// marketing-content/src/main/java/com/won/smarketing/content/infrastructure/external/ClaudeAiContentGenerator.java
|
||||
package com.won.smarketing.content.infrastructure.external;
|
||||
|
||||
// 수정: domain 패키지의 인터페이스를 import
|
||||
import com.won.smarketing.content.domain.service.AiContentGenerator;
|
||||
import com.won.smarketing.content.domain.model.Platform;
|
||||
import com.won.smarketing.content.domain.model.CreationConditions;
|
||||
import com.won.smarketing.content.presentation.dto.SnsContentCreateRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
@ -21,105 +23,73 @@ public class ClaudeAiContentGenerator implements AiContentGenerator {
|
||||
|
||||
/**
|
||||
* SNS 콘텐츠 생성
|
||||
* Claude AI API를 호출하여 SNS 게시물을 생성합니다.
|
||||
*
|
||||
* @param title 제목
|
||||
* @param category 카테고리
|
||||
* @param platform 플랫폼
|
||||
* @param conditions 생성 조건
|
||||
* @return 생성된 콘텐츠 텍스트
|
||||
*/
|
||||
@Override
|
||||
public String generateSnsContent(String title, String category, Platform platform, CreationConditions conditions) {
|
||||
public String generateSnsContent(SnsContentCreateRequest request) {
|
||||
try {
|
||||
// Claude AI API 호출 로직 (실제 구현에서는 HTTP 클라이언트를 사용)
|
||||
String prompt = buildContentPrompt(title, category, platform, conditions);
|
||||
|
||||
// TODO: 실제 Claude AI API 호출
|
||||
// 현재는 더미 데이터 반환
|
||||
return generateDummySnsContent(title, platform);
|
||||
|
||||
String prompt = buildContentPrompt(request);
|
||||
return generateDummySnsContent(request.getTitle(), Platform.fromString(request.getPlatform()));
|
||||
} catch (Exception e) {
|
||||
log.error("AI 콘텐츠 생성 실패: {}", e.getMessage(), e);
|
||||
return generateFallbackContent(title, platform);
|
||||
return generateFallbackContent(request.getTitle(), Platform.fromString(request.getPlatform()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 해시태그 생성
|
||||
* 콘텐츠 내용을 분석하여 관련 해시태그를 생성합니다.
|
||||
*
|
||||
* @param content 콘텐츠 내용
|
||||
* @param platform 플랫폼
|
||||
* @return 생성된 해시태그 목록
|
||||
* 플랫폼별 해시태그 생성
|
||||
*/
|
||||
@Override
|
||||
public List<String> generateHashtags(String content, Platform platform) {
|
||||
try {
|
||||
// TODO: 실제 Claude AI API 호출하여 해시태그 생성
|
||||
// 현재는 더미 데이터 반환
|
||||
return generateDummyHashtags(platform);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("해시태그 생성 실패: {}", e.getMessage(), e);
|
||||
return Arrays.asList("#맛집", "#신메뉴", "#추천");
|
||||
return generateFallbackHashtags();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AI 프롬프트 생성
|
||||
*/
|
||||
private String buildContentPrompt(String title, String category, Platform platform, CreationConditions conditions) {
|
||||
private String buildContentPrompt(SnsContentCreateRequest request) {
|
||||
StringBuilder prompt = new StringBuilder();
|
||||
prompt.append("다음 조건에 맞는 ").append(platform.getDisplayName()).append(" 게시물을 작성해주세요:\n");
|
||||
prompt.append("제목: ").append(title).append("\n");
|
||||
prompt.append("카테고리: ").append(category).append("\n");
|
||||
prompt.append("제목: ").append(request.getTitle()).append("\n");
|
||||
prompt.append("카테고리: ").append(request.getCategory()).append("\n");
|
||||
prompt.append("플랫폼: ").append(request.getPlatform()).append("\n");
|
||||
|
||||
if (conditions.getRequirement() != null) {
|
||||
prompt.append("요구사항: ").append(conditions.getRequirement()).append("\n");
|
||||
if (request.getRequirement() != null) {
|
||||
prompt.append("요구사항: ").append(request.getRequirement()).append("\n");
|
||||
}
|
||||
if (conditions.getToneAndManner() != null) {
|
||||
prompt.append("톤앤매너: ").append(conditions.getToneAndManner()).append("\n");
|
||||
}
|
||||
if (conditions.getEmotionIntensity() != null) {
|
||||
prompt.append("감정 강도: ").append(conditions.getEmotionIntensity()).append("\n");
|
||||
|
||||
if (request.getToneAndManner() != null) {
|
||||
prompt.append("톤앤매너: ").append(request.getToneAndManner()).append("\n");
|
||||
}
|
||||
|
||||
return prompt.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 더미 SNS 콘텐츠 생성 (개발용)
|
||||
*/
|
||||
private String generateDummySnsContent(String title, Platform platform) {
|
||||
switch (platform) {
|
||||
case INSTAGRAM:
|
||||
return String.format("🎉 %s\n\n맛있는 순간을 놓치지 마세요! 새로운 맛의 경험이 여러분을 기다리고 있어요. 따뜻한 분위기에서 즐기는 특별한 시간을 만들어보세요.\n\n📍 지금 바로 방문해보세요!", title);
|
||||
case NAVER_BLOG:
|
||||
return String.format("안녕하세요! 오늘은 %s에 대해 소개해드리려고 해요.\n\n정성스럽게 준비한 새로운 메뉴로 고객 여러분께 더 나은 경험을 선사하고 싶습니다. 많은 관심과 사랑 부탁드려요!", title);
|
||||
default:
|
||||
return String.format("%s - 새로운 경험을 만나보세요!", title);
|
||||
String baseContent = "🌟 " + title + "를 소개합니다! 🌟\n\n" +
|
||||
"저희 매장에서 특별한 경험을 만나보세요.\n" +
|
||||
"고객 여러분의 소중한 시간을 더욱 특별하게 만들어드리겠습니다.\n\n";
|
||||
|
||||
if (platform == Platform.INSTAGRAM) {
|
||||
return baseContent + "더 많은 정보는 프로필 링크에서 확인하세요! 📸";
|
||||
} else {
|
||||
return baseContent + "자세한 내용은 저희 블로그를 방문해 주세요! ✨";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 더미 해시태그 생성 (개발용)
|
||||
*/
|
||||
private List<String> generateDummyHashtags(Platform platform) {
|
||||
switch (platform) {
|
||||
case INSTAGRAM:
|
||||
return Arrays.asList("#맛집", "#신메뉴", "#인스타그램", "#데일리", "#추천", "#음식스타그램");
|
||||
case NAVER_BLOG:
|
||||
return Arrays.asList("#맛집", "#리뷰", "#추천", "#신메뉴", "#블로그");
|
||||
default:
|
||||
return Arrays.asList("#맛집", "#신메뉴", "#추천");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 폴백 콘텐츠 생성 (AI 서비스 실패 시)
|
||||
*/
|
||||
private String generateFallbackContent(String title, Platform platform) {
|
||||
return String.format("🎉 %s\n\n새로운 소식을 전해드립니다. 많은 관심 부탁드려요!", title);
|
||||
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("#소상공인", "#마케팅", "#홍보");
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,8 @@
|
||||
// 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.model.CreationConditions;
|
||||
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;
|
||||
@ -19,23 +20,20 @@ import java.util.Map;
|
||||
public class ClaudeAiPosterGenerator implements AiPosterGenerator {
|
||||
|
||||
/**
|
||||
* 포스터 이미지 생성
|
||||
* Claude AI API를 호출하여 홍보 포스터를 생성합니다.
|
||||
* 포스터 생성
|
||||
*
|
||||
* @param title 제목
|
||||
* @param category 카테고리
|
||||
* @param conditions 생성 조건
|
||||
* @param request 포스터 생성 요청
|
||||
* @return 생성된 포스터 이미지 URL
|
||||
*/
|
||||
@Override
|
||||
public String generatePoster(String title, String category, CreationConditions conditions) {
|
||||
public String generatePoster(PosterContentCreateRequest request) {
|
||||
try {
|
||||
// Claude AI API 호출 로직 (실제 구현에서는 HTTP 클라이언트를 사용)
|
||||
String prompt = buildPosterPrompt(title, category, conditions);
|
||||
// Claude AI API 호출 로직
|
||||
String prompt = buildPosterPrompt(request);
|
||||
|
||||
// TODO: 실제 Claude AI API 호출
|
||||
// 현재는 더미 데이터 반환
|
||||
return generateDummyPosterUrl(title);
|
||||
return generateDummyPosterUrl(request.getTitle());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("AI 포스터 생성 실패: {}", e.getMessage(), e);
|
||||
@ -44,75 +42,45 @@ public class ClaudeAiPosterGenerator implements AiPosterGenerator {
|
||||
}
|
||||
|
||||
/**
|
||||
* 포스터 다양한 사이즈 생성
|
||||
* 원본 포스터를 기반으로 다양한 사이즈의 포스터를 생성합니다.
|
||||
* 다양한 사이즈의 포스터 생성
|
||||
*
|
||||
* @param originalImage 원본 이미지 URL
|
||||
* @return 사이즈별 이미지 URL 맵
|
||||
* @param baseImage 기본 이미지
|
||||
* @return 사이즈별 포스터 URL 맵
|
||||
*/
|
||||
@Override
|
||||
public Map<String, String> generatePosterSizes(String originalImage) {
|
||||
try {
|
||||
// TODO: 실제 이미지 리사이징 API 호출
|
||||
// 현재는 더미 데이터 반환
|
||||
return generateDummyPosterSizes(originalImage);
|
||||
public Map<String, String> generatePosterSizes(String baseImage) {
|
||||
Map<String, String> sizes = new HashMap<>();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("포스터 사이즈 생성 실패: {}", e.getMessage(), e);
|
||||
return 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* AI 포스터 프롬프트 생성
|
||||
*/
|
||||
private String buildPosterPrompt(String title, String category, CreationConditions conditions) {
|
||||
private String buildPosterPrompt(PosterContentCreateRequest request) {
|
||||
StringBuilder prompt = new StringBuilder();
|
||||
prompt.append("다음 조건에 맞는 홍보 포스터를 생성해주세요:\n");
|
||||
prompt.append("제목: ").append(title).append("\n");
|
||||
prompt.append("카테고리: ").append(category).append("\n");
|
||||
prompt.append("포스터 제목: ").append(request.getTitle()).append("\n");
|
||||
prompt.append("카테고리: ").append(request.getCategory()).append("\n");
|
||||
|
||||
if (conditions.getPhotoStyle() != null) {
|
||||
prompt.append("사진 스타일: ").append(conditions.getPhotoStyle()).append("\n");
|
||||
if (request.getRequirement() != null) {
|
||||
prompt.append("요구사항: ").append(request.getRequirement()).append("\n");
|
||||
}
|
||||
if (conditions.getRequirement() != null) {
|
||||
prompt.append("요구사항: ").append(conditions.getRequirement()).append("\n");
|
||||
}
|
||||
if (conditions.getToneAndManner() != null) {
|
||||
prompt.append("톤앤매너: ").append(conditions.getToneAndManner()).append("\n");
|
||||
|
||||
if (request.getToneAndManner() != null) {
|
||||
prompt.append("톤앤매너: ").append(request.getToneAndManner()).append("\n");
|
||||
}
|
||||
|
||||
return prompt.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 더미 포스터 URL 생성 (개발용)
|
||||
*/
|
||||
private String generateDummyPosterUrl(String title) {
|
||||
return String.format("https://example.com/posters/%s-poster.jpg",
|
||||
title.replaceAll("\\s+", "-").toLowerCase());
|
||||
return "https://dummy-ai-service.com/posters/" + title.hashCode() + ".jpg";
|
||||
}
|
||||
|
||||
/**
|
||||
* 더미 포스터 사이즈별 URL 생성 (개발용)
|
||||
*/
|
||||
private Map<String, String> generateDummyPosterSizes(String originalImage) {
|
||||
Map<String, String> sizes = new HashMap<>();
|
||||
String baseUrl = originalImage.substring(0, originalImage.lastIndexOf("."));
|
||||
String extension = originalImage.substring(originalImage.lastIndexOf("."));
|
||||
|
||||
sizes.put("small", baseUrl + "-small" + extension);
|
||||
sizes.put("medium", baseUrl + "-medium" + extension);
|
||||
sizes.put("large", baseUrl + "-large" + extension);
|
||||
sizes.put("xlarge", baseUrl + "-xlarge" + extension);
|
||||
|
||||
return sizes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 폴백 포스터 URL 생성 (AI 서비스 실패 시)
|
||||
*/
|
||||
private String generateFallbackPosterUrl() {
|
||||
return "https://example.com/posters/default-poster.jpg";
|
||||
return "https://dummy-ai-service.com/posters/fallback.jpg";
|
||||
}
|
||||
}
|
||||
@ -1,51 +0,0 @@
|
||||
package com.won.smarketing.content.infrastructure.repository;
|
||||
|
||||
import com.won.smarketing.content.infrastructure.entity.ContentJpaEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Spring Data JPA 콘텐츠 Repository
|
||||
*
|
||||
* @author smarketing-team
|
||||
* @version 1.0
|
||||
*/
|
||||
@Repository
|
||||
public interface SpringDataContentRepository extends JpaRepository<ContentJpaEntity, Long> {
|
||||
|
||||
/**
|
||||
* 필터 조건으로 콘텐츠를 조회합니다.
|
||||
*
|
||||
* @param contentType 콘텐츠 타입
|
||||
* @param platform 플랫폼
|
||||
* @param period 기간
|
||||
* @param sortBy 정렬 기준
|
||||
* @return 콘텐츠 목록
|
||||
*/
|
||||
@Query("SELECT c FROM ContentJpaEntity c WHERE " +
|
||||
"(:contentType IS NULL OR c.contentType = :contentType) AND " +
|
||||
"(:platform IS NULL OR c.platform = :platform) AND " +
|
||||
"(:period IS NULL OR DATE(c.createdAt) >= CURRENT_DATE - INTERVAL :period DAY) " +
|
||||
"ORDER BY " +
|
||||
"CASE WHEN :sortBy = 'latest' THEN c.createdAt END DESC, " +
|
||||
"CASE WHEN :sortBy = 'oldest' THEN c.createdAt END ASC")
|
||||
List<ContentJpaEntity> findByFilters(@Param("contentType") String contentType,
|
||||
@Param("platform") String platform,
|
||||
@Param("period") String period,
|
||||
@Param("sortBy") String sortBy);
|
||||
|
||||
/**
|
||||
* 진행 중인 콘텐츠를 조회합니다.
|
||||
*
|
||||
* @param period 기간
|
||||
* @return 진행 중인 콘텐츠 목록
|
||||
*/
|
||||
@Query("SELECT c FROM ContentJpaEntity c " +
|
||||
"WHERE c.status = 'PUBLISHED' AND " +
|
||||
"(:period IS NULL OR DATE(c.createdAt) >= CURRENT_DATE - INTERVAL :period DAY)")
|
||||
List<ContentJpaEntity> findOngoingContents(@Param("period") String period);
|
||||
}
|
||||
@ -17,21 +17,17 @@ spring:
|
||||
hibernate:
|
||||
dialect: org.hibernate.dialect.PostgreSQLDialect
|
||||
format_sql: true
|
||||
ai:
|
||||
service:
|
||||
url: ${AI_SERVICE_URL:http://localhost:8080/ai}
|
||||
timeout: ${AI_SERVICE_TIMEOUT:30000}
|
||||
data:
|
||||
redis:
|
||||
host: ${REDIS_HOST:localhost}
|
||||
port: ${REDIS_PORT:6379}
|
||||
password: ${REDIS_PASSWORD:}
|
||||
|
||||
external:
|
||||
claude-ai:
|
||||
api-key: ${CLAUDE_AI_API_KEY:your-claude-api-key}
|
||||
base-url: ${CLAUDE_AI_BASE_URL:https://api.anthropic.com}
|
||||
model: ${CLAUDE_AI_MODEL:claude-3-sonnet-20240229}
|
||||
max-tokens: ${CLAUDE_AI_MAX_TOKENS:4000}
|
||||
jwt:
|
||||
secret: ${JWT_SECRET:mySecretKeyForJWTTokenGenerationAndValidation123456789}
|
||||
access-token-validity: ${JWT_ACCESS_VALIDITY:3600000}
|
||||
refresh-token-validity: ${JWT_REFRESH_VALIDITY:604800000}
|
||||
|
||||
logging:
|
||||
level:
|
||||
com.won.smarketing.content: ${LOG_LEVEL:DEBUG}
|
||||
com.won.smarketing: ${LOG_LEVEL:DEBUG}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user