mirror of
https://github.com/ktds-dg0501/kt-event-marketing.git
synced 2025-12-06 15:26:23 +00:00
Content Service API 테스트 구현 추가
- REST API Controller 구현 (이미지 생성, Job 조회, 콘텐츠 조회 등) - Gateway 어댑터 구현 (ContentGateway, JobGateway) - Mock Gateway 구현 (Redis, CDN, AI 이미지 생성기) - Mock UseCase 구현 (실제 이미지 생성 시뮬레이션) - Security 및 Swagger 설정 추가 - 로컬 테스트를 위한 H2 데이터베이스 설정 (application-local.yml) - 비동기 처리를 위한 @EnableAsync 설정 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
3d1dbda74b
commit
06995864b9
@ -17,4 +17,7 @@ dependencies {
|
|||||||
|
|
||||||
// Jackson for JSON
|
// Jackson for JSON
|
||||||
implementation 'com.fasterxml.jackson.core:jackson-databind'
|
implementation 'com.fasterxml.jackson.core:jackson-databind'
|
||||||
|
|
||||||
|
// H2 Database for local testing
|
||||||
|
runtimeOnly 'com.h2database:h2'
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,163 @@
|
|||||||
|
package com.kt.event.content.biz.service.mock;
|
||||||
|
|
||||||
|
import com.kt.event.content.biz.domain.Content;
|
||||||
|
import com.kt.event.content.biz.domain.GeneratedImage;
|
||||||
|
import com.kt.event.content.biz.domain.ImageStyle;
|
||||||
|
import com.kt.event.content.biz.domain.Job;
|
||||||
|
import com.kt.event.content.biz.domain.Platform;
|
||||||
|
import com.kt.event.content.biz.dto.ContentCommand;
|
||||||
|
import com.kt.event.content.biz.dto.JobInfo;
|
||||||
|
import com.kt.event.content.biz.usecase.in.GenerateImagesUseCase;
|
||||||
|
import com.kt.event.content.biz.usecase.out.ContentWriter;
|
||||||
|
import com.kt.event.content.biz.usecase.out.JobWriter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
|
import org.springframework.scheduling.annotation.Async;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock 이미지 생성 서비스 (테스트용)
|
||||||
|
* 실제 Kafka 연동 전까지 사용
|
||||||
|
*
|
||||||
|
* 테스트를 위해 실제로 Content와 GeneratedImage를 생성합니다.
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@Profile({"local", "test"})
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class MockGenerateImagesService implements GenerateImagesUseCase {
|
||||||
|
|
||||||
|
private final JobWriter jobWriter;
|
||||||
|
private final ContentWriter contentWriter;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JobInfo execute(ContentCommand.GenerateImages command) {
|
||||||
|
log.info("[MOCK] 이미지 생성 요청: eventDraftId={}, styles={}, platforms={}",
|
||||||
|
command.getEventDraftId(), command.getStyles(), command.getPlatforms());
|
||||||
|
|
||||||
|
// Mock Job 생성
|
||||||
|
String jobId = "job-mock-" + UUID.randomUUID().toString().substring(0, 8);
|
||||||
|
|
||||||
|
Job job = Job.builder()
|
||||||
|
.id(jobId)
|
||||||
|
.eventDraftId(command.getEventDraftId())
|
||||||
|
.jobType("image-generation")
|
||||||
|
.status(Job.Status.PENDING)
|
||||||
|
.progress(0)
|
||||||
|
.createdAt(java.time.LocalDateTime.now())
|
||||||
|
.updatedAt(java.time.LocalDateTime.now())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Job 저장
|
||||||
|
Job savedJob = jobWriter.save(job);
|
||||||
|
log.info("[MOCK] Job 생성 완료: jobId={}", jobId);
|
||||||
|
|
||||||
|
// 비동기로 이미지 생성 시뮬레이션
|
||||||
|
processImageGeneration(jobId, command);
|
||||||
|
|
||||||
|
return JobInfo.from(savedJob);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Async
|
||||||
|
private void processImageGeneration(String jobId, ContentCommand.GenerateImages command) {
|
||||||
|
try {
|
||||||
|
log.info("[MOCK] 이미지 생성 시작: jobId={}", jobId);
|
||||||
|
|
||||||
|
// 1초 대기 (이미지 생성 시뮬레이션)
|
||||||
|
Thread.sleep(1000);
|
||||||
|
|
||||||
|
// Content 생성 또는 조회
|
||||||
|
Content content = Content.builder()
|
||||||
|
.eventDraftId(command.getEventDraftId())
|
||||||
|
.eventTitle("Mock 이벤트 제목 " + command.getEventDraftId())
|
||||||
|
.eventDescription("Mock 이벤트 설명입니다. 테스트를 위한 Mock 데이터입니다.")
|
||||||
|
.createdAt(java.time.LocalDateTime.now())
|
||||||
|
.updatedAt(java.time.LocalDateTime.now())
|
||||||
|
.build();
|
||||||
|
Content savedContent = contentWriter.save(content);
|
||||||
|
log.info("[MOCK] Content 생성 완료: contentId={}", savedContent.getId());
|
||||||
|
|
||||||
|
// 스타일 x 플랫폼 조합으로 이미지 생성
|
||||||
|
List<ImageStyle> styles = command.getStyles() != null && !command.getStyles().isEmpty()
|
||||||
|
? command.getStyles()
|
||||||
|
: List.of(ImageStyle.FANCY, ImageStyle.SIMPLE);
|
||||||
|
|
||||||
|
List<Platform> platforms = command.getPlatforms() != null && !command.getPlatforms().isEmpty()
|
||||||
|
? command.getPlatforms()
|
||||||
|
: List.of(Platform.INSTAGRAM, Platform.KAKAO);
|
||||||
|
|
||||||
|
List<GeneratedImage> images = new ArrayList<>();
|
||||||
|
int count = 0;
|
||||||
|
for (ImageStyle style : styles) {
|
||||||
|
for (Platform platform : platforms) {
|
||||||
|
count++;
|
||||||
|
String mockCdnUrl = String.format(
|
||||||
|
"https://mock-cdn.azure.com/images/%d/%s_%s_%s.png",
|
||||||
|
command.getEventDraftId(),
|
||||||
|
style.name().toLowerCase(),
|
||||||
|
platform.name().toLowerCase(),
|
||||||
|
UUID.randomUUID().toString().substring(0, 8)
|
||||||
|
);
|
||||||
|
|
||||||
|
GeneratedImage image = GeneratedImage.builder()
|
||||||
|
.eventDraftId(command.getEventDraftId())
|
||||||
|
.style(style)
|
||||||
|
.platform(platform)
|
||||||
|
.cdnUrl(mockCdnUrl)
|
||||||
|
.prompt(String.format("Mock prompt for %s style on %s platform", style, platform))
|
||||||
|
.selected(false)
|
||||||
|
.createdAt(java.time.LocalDateTime.now())
|
||||||
|
.updatedAt(java.time.LocalDateTime.now())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 첫 번째 이미지를 선택된 이미지로 설정
|
||||||
|
if (count == 1) {
|
||||||
|
image.select();
|
||||||
|
}
|
||||||
|
|
||||||
|
GeneratedImage savedImage = contentWriter.saveImage(image);
|
||||||
|
images.add(savedImage);
|
||||||
|
log.info("[MOCK] 이미지 생성: imageId={}, style={}, platform={}",
|
||||||
|
savedImage.getId(), style, platform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Job 상태 업데이트: COMPLETED
|
||||||
|
Job completedJob = Job.builder()
|
||||||
|
.id(jobId)
|
||||||
|
.eventDraftId(command.getEventDraftId())
|
||||||
|
.jobType("image-generation")
|
||||||
|
.status(Job.Status.COMPLETED)
|
||||||
|
.progress(100)
|
||||||
|
.resultMessage(String.format("%d개의 이미지가 성공적으로 생성되었습니다.", images.size()))
|
||||||
|
.createdAt(java.time.LocalDateTime.now())
|
||||||
|
.updatedAt(java.time.LocalDateTime.now())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
jobWriter.save(completedJob);
|
||||||
|
log.info("[MOCK] Job 완료: jobId={}, 생성된 이미지 수={}", jobId, images.size());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[MOCK] 이미지 생성 실패: jobId={}", jobId, e);
|
||||||
|
|
||||||
|
// Job 상태 업데이트: FAILED
|
||||||
|
Job failedJob = Job.builder()
|
||||||
|
.id(jobId)
|
||||||
|
.eventDraftId(command.getEventDraftId())
|
||||||
|
.jobType("image-generation")
|
||||||
|
.status(Job.Status.FAILED)
|
||||||
|
.progress(0)
|
||||||
|
.errorMessage(e.getMessage())
|
||||||
|
.createdAt(java.time.LocalDateTime.now())
|
||||||
|
.updatedAt(java.time.LocalDateTime.now())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
jobWriter.save(failedJob);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
package com.kt.event.content.biz.service.mock;
|
||||||
|
|
||||||
|
import com.kt.event.content.biz.domain.Job;
|
||||||
|
import com.kt.event.content.biz.dto.ContentCommand;
|
||||||
|
import com.kt.event.content.biz.dto.JobInfo;
|
||||||
|
import com.kt.event.content.biz.usecase.in.RegenerateImageUseCase;
|
||||||
|
import com.kt.event.content.biz.usecase.out.JobWriter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock 이미지 재생성 서비스 (테스트용)
|
||||||
|
* 실제 구현 전까지 사용
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@Profile({"local", "test"})
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class MockRegenerateImageService implements RegenerateImageUseCase {
|
||||||
|
|
||||||
|
private final JobWriter jobWriter;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JobInfo execute(ContentCommand.RegenerateImage command) {
|
||||||
|
log.info("[MOCK] 이미지 재생성 요청: imageId={}", command.getImageId());
|
||||||
|
|
||||||
|
// Mock Job 생성
|
||||||
|
String jobId = "job-regen-" + UUID.randomUUID().toString().substring(0, 8);
|
||||||
|
|
||||||
|
Job job = Job.builder()
|
||||||
|
.id(jobId)
|
||||||
|
.eventDraftId(999L) // Mock event ID
|
||||||
|
.jobType("image-regeneration")
|
||||||
|
.status(Job.Status.PENDING)
|
||||||
|
.progress(0)
|
||||||
|
.createdAt(java.time.LocalDateTime.now())
|
||||||
|
.updatedAt(java.time.LocalDateTime.now())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Job 저장
|
||||||
|
Job savedJob = jobWriter.save(job);
|
||||||
|
|
||||||
|
log.info("[MOCK] 재생성 Job 생성 완료: jobId={}", jobId);
|
||||||
|
|
||||||
|
return JobInfo.from(savedJob);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,6 +5,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|||||||
import org.springframework.boot.autoconfigure.domain.EntityScan;
|
import org.springframework.boot.autoconfigure.domain.EntityScan;
|
||||||
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||||
|
import org.springframework.scheduling.annotation.EnableAsync;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Content Service Application
|
* Content Service Application
|
||||||
@ -19,6 +20,7 @@ import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
|||||||
})
|
})
|
||||||
@EnableJpaRepositories(basePackages = "com.kt.event.content.infra.gateway.repository")
|
@EnableJpaRepositories(basePackages = "com.kt.event.content.infra.gateway.repository")
|
||||||
@EnableJpaAuditing
|
@EnableJpaAuditing
|
||||||
|
@EnableAsync
|
||||||
public class ContentApplication {
|
public class ContentApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|||||||
@ -0,0 +1,39 @@
|
|||||||
|
package com.kt.event.content.infra.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring Security 설정
|
||||||
|
* API 테스트를 위해 일단 모든 요청 허용 (추후 JWT 인증 추가)
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
// CSRF 비활성화 (REST API는 CSRF 불필요)
|
||||||
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
|
|
||||||
|
// 세션 사용 안 함 (JWT 기반 인증)
|
||||||
|
.sessionManagement(session ->
|
||||||
|
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 모든 요청 허용 (테스트용, 추후 JWT 필터 추가 필요)
|
||||||
|
.authorizeHttpRequests(auth -> auth
|
||||||
|
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
|
||||||
|
.requestMatchers("/actuator/**").permitAll()
|
||||||
|
.anyRequest().permitAll() // TODO: 추후 authenticated()로 변경
|
||||||
|
);
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
package com.kt.event.content.infra.config;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.models.OpenAPI;
|
||||||
|
import io.swagger.v3.oas.models.info.Contact;
|
||||||
|
import io.swagger.v3.oas.models.info.Info;
|
||||||
|
import io.swagger.v3.oas.models.servers.Server;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Swagger/OpenAPI 설정
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class SwaggerConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public OpenAPI openAPI() {
|
||||||
|
return new OpenAPI()
|
||||||
|
.info(new Info()
|
||||||
|
.title("Content Service API")
|
||||||
|
.version("1.0.0")
|
||||||
|
.description("""
|
||||||
|
# KT AI 기반 소상공인 이벤트 자동 생성 서비스 - Content Service API
|
||||||
|
|
||||||
|
## 주요 기능
|
||||||
|
- **SNS 이미지 생성**: AI 기반 이벤트 이미지 자동 생성
|
||||||
|
- **콘텐츠 편집**: 생성된 이미지 조회, 재생성, 삭제
|
||||||
|
- **3가지 스타일**: 심플(SIMPLE), 화려한(FANCY), 트렌디(TRENDY)
|
||||||
|
- **3개 플랫폼 최적화**: Instagram (1080x1080), Naver (800x600), Kakao (800x800)
|
||||||
|
""")
|
||||||
|
.contact(new Contact()
|
||||||
|
.name("Digital Garage Team")
|
||||||
|
.email("support@kt-event-marketing.com")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.servers(List.of(
|
||||||
|
new Server()
|
||||||
|
.url("http://localhost:8084")
|
||||||
|
.description("Local Development Server"),
|
||||||
|
new Server()
|
||||||
|
.url("https://dev-api.kt-event-marketing.com/content/v1")
|
||||||
|
.description("Development Server"),
|
||||||
|
new Server()
|
||||||
|
.url("https://api.kt-event-marketing.com/content/v1")
|
||||||
|
.description("Production Server")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,119 @@
|
|||||||
|
package com.kt.event.content.infra.gateway;
|
||||||
|
|
||||||
|
import com.kt.event.content.biz.domain.Content;
|
||||||
|
import com.kt.event.content.biz.domain.GeneratedImage;
|
||||||
|
import com.kt.event.content.biz.usecase.out.ContentReader;
|
||||||
|
import com.kt.event.content.biz.usecase.out.ContentWriter;
|
||||||
|
import com.kt.event.content.infra.gateway.entity.ContentEntity;
|
||||||
|
import com.kt.event.content.infra.gateway.entity.GeneratedImageEntity;
|
||||||
|
import com.kt.event.content.infra.gateway.repository.ContentJpaRepository;
|
||||||
|
import com.kt.event.content.infra.gateway.repository.GeneratedImageJpaRepository;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Content 영속성 Gateway
|
||||||
|
* ContentReader, ContentWriter outbound port 구현
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ContentGateway implements ContentReader, ContentWriter {
|
||||||
|
|
||||||
|
private final ContentJpaRepository contentRepository;
|
||||||
|
private final GeneratedImageJpaRepository imageRepository;
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// ContentReader 구현
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Optional<Content> findByEventDraftIdWithImages(Long eventDraftId) {
|
||||||
|
log.debug("이벤트 콘텐츠 조회 (with images): eventDraftId={}", eventDraftId);
|
||||||
|
return contentRepository.findByEventDraftIdWithImages(eventDraftId)
|
||||||
|
.map(ContentEntity::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Optional<GeneratedImage> findImageById(Long imageId) {
|
||||||
|
log.debug("이미지 조회: imageId={}", imageId);
|
||||||
|
return imageRepository.findById(imageId)
|
||||||
|
.map(GeneratedImageEntity::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public List<GeneratedImage> findImagesByEventDraftId(Long eventDraftId) {
|
||||||
|
log.debug("이미지 목록 조회: eventDraftId={}", eventDraftId);
|
||||||
|
return imageRepository.findByEventDraftId(eventDraftId).stream()
|
||||||
|
.map(GeneratedImageEntity::toDomain)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// ContentWriter 구현
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public Content save(Content content) {
|
||||||
|
log.debug("콘텐츠 저장: eventDraftId={}", content.getEventDraftId());
|
||||||
|
|
||||||
|
// Content Entity 조회 또는 생성
|
||||||
|
ContentEntity contentEntity = contentRepository.findByEventDraftId(content.getEventDraftId())
|
||||||
|
.orElseGet(() -> ContentEntity.create(
|
||||||
|
content.getEventDraftId(),
|
||||||
|
content.getEventTitle(),
|
||||||
|
content.getEventDescription()
|
||||||
|
));
|
||||||
|
|
||||||
|
// Content 업데이트
|
||||||
|
contentEntity.update(content.getEventTitle(), content.getEventDescription());
|
||||||
|
|
||||||
|
// 저장
|
||||||
|
ContentEntity saved = contentRepository.save(contentEntity);
|
||||||
|
|
||||||
|
return saved.toDomain();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public GeneratedImage saveImage(GeneratedImage image) {
|
||||||
|
log.debug("이미지 저장: eventDraftId={}, style={}, platform={}",
|
||||||
|
image.getEventDraftId(), image.getStyle(), image.getPlatform());
|
||||||
|
|
||||||
|
// Content Entity 조회
|
||||||
|
ContentEntity contentEntity = contentRepository.findByEventDraftId(image.getEventDraftId())
|
||||||
|
.orElseThrow(() -> new IllegalStateException("Content를 먼저 저장해야 합니다"));
|
||||||
|
|
||||||
|
// GeneratedImageEntity 생성
|
||||||
|
GeneratedImageEntity imageEntity = GeneratedImageEntity.create(
|
||||||
|
image.getEventDraftId(),
|
||||||
|
image.getStyle(),
|
||||||
|
image.getPlatform(),
|
||||||
|
image.getCdnUrl(),
|
||||||
|
image.getPrompt()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Content와 연결
|
||||||
|
contentEntity.addImage(imageEntity);
|
||||||
|
|
||||||
|
// 선택 상태 설정
|
||||||
|
if (image.isSelected()) {
|
||||||
|
imageEntity.select();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 저장
|
||||||
|
GeneratedImageEntity saved = imageRepository.save(imageEntity);
|
||||||
|
|
||||||
|
return saved.toDomain();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,98 @@
|
|||||||
|
package com.kt.event.content.infra.gateway;
|
||||||
|
|
||||||
|
import com.kt.event.content.biz.domain.Job;
|
||||||
|
import com.kt.event.content.biz.usecase.out.JobReader;
|
||||||
|
import com.kt.event.content.biz.usecase.out.JobWriter;
|
||||||
|
import com.kt.event.content.infra.gateway.entity.JobEntity;
|
||||||
|
import com.kt.event.content.infra.gateway.repository.JobJpaRepository;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Job 영속성 Gateway
|
||||||
|
* JobReader, JobWriter outbound port 구현
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class JobGateway implements JobReader, JobWriter {
|
||||||
|
|
||||||
|
private final JobJpaRepository jobRepository;
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// JobReader 구현
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Optional<Job> findById(String jobId) {
|
||||||
|
log.debug("Job 조회: jobId={}", jobId);
|
||||||
|
return jobRepository.findById(jobId)
|
||||||
|
.map(JobEntity::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 이벤트별 Job 조회 (추가 메서드)
|
||||||
|
*/
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public List<Job> findByEventDraftId(Long eventDraftId) {
|
||||||
|
log.debug("이벤트별 Job 조회: eventDraftId={}", eventDraftId);
|
||||||
|
return jobRepository.findByEventDraftId(eventDraftId).stream()
|
||||||
|
.map(JobEntity::toDomain)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 최신 Job 조회 (추가 메서드)
|
||||||
|
*/
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Optional<Job> findLatestByEventDraftIdAndJobType(Long eventDraftId, String jobType) {
|
||||||
|
log.debug("최신 Job 조회: eventDraftId={}, jobType={}", eventDraftId, jobType);
|
||||||
|
return jobRepository.findFirstByEventDraftIdAndJobTypeOrderByCreatedAtDesc(eventDraftId, jobType)
|
||||||
|
.map(JobEntity::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// JobWriter 구현
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public Job save(Job job) {
|
||||||
|
log.debug("Job 저장: jobId={}, status={}", job.getId(), job.getStatus());
|
||||||
|
|
||||||
|
JobEntity entity = jobRepository.findById(job.getId())
|
||||||
|
.orElseGet(() -> JobEntity.create(
|
||||||
|
job.getId(),
|
||||||
|
job.getEventDraftId(),
|
||||||
|
job.getJobType()
|
||||||
|
));
|
||||||
|
|
||||||
|
// Job 상태 업데이트
|
||||||
|
entity.updateStatus(job.getStatus(), job.getProgress());
|
||||||
|
if (job.getResultMessage() != null) {
|
||||||
|
entity.setResultMessage(job.getResultMessage());
|
||||||
|
}
|
||||||
|
if (job.getErrorMessage() != null) {
|
||||||
|
entity.setErrorMessage(job.getErrorMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
JobEntity saved = jobRepository.save(entity);
|
||||||
|
return saved.toDomain();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Job 삭제 (추가 메서드)
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public void delete(String jobId) {
|
||||||
|
log.debug("Job 삭제: jobId={}", jobId);
|
||||||
|
jobRepository.deleteById(jobId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -86,6 +86,17 @@ public class ContentEntity extends BaseTimeEntity {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 콘텐츠 정보 업데이트
|
||||||
|
*
|
||||||
|
* @param eventTitle 이벤트 제목
|
||||||
|
* @param eventDescription 이벤트 설명
|
||||||
|
*/
|
||||||
|
public void update(String eventTitle, String eventDescription) {
|
||||||
|
this.eventTitle = eventTitle;
|
||||||
|
this.eventDescription = eventDescription;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 이미지 추가
|
* 이미지 추가
|
||||||
*
|
*
|
||||||
|
|||||||
@ -140,4 +140,33 @@ public class JobEntity extends BaseTimeEntity {
|
|||||||
this.status = Job.Status.FAILED;
|
this.status = Job.Status.FAILED;
|
||||||
this.errorMessage = errorMessage;
|
this.errorMessage = errorMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Job 상태 업데이트
|
||||||
|
*
|
||||||
|
* @param status 새 상태
|
||||||
|
* @param progress 진행률
|
||||||
|
*/
|
||||||
|
public void updateStatus(Job.Status status, int progress) {
|
||||||
|
this.status = status;
|
||||||
|
this.progress = progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 결과 메시지 설정
|
||||||
|
*
|
||||||
|
* @param resultMessage 결과 메시지
|
||||||
|
*/
|
||||||
|
public void setResultMessage(String resultMessage) {
|
||||||
|
this.resultMessage = resultMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 에러 메시지 설정
|
||||||
|
*
|
||||||
|
* @param errorMessage 에러 메시지
|
||||||
|
*/
|
||||||
|
public void setErrorMessage(String errorMessage) {
|
||||||
|
this.errorMessage = errorMessage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,31 @@
|
|||||||
|
package com.kt.event.content.infra.gateway.mock;
|
||||||
|
|
||||||
|
import com.kt.event.content.biz.usecase.out.CDNUploader;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock CDN Uploader (테스트용)
|
||||||
|
* 실제 Azure Blob Storage 연동 전까지 사용
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@Profile({"local", "test"})
|
||||||
|
public class MockCDNUploader implements CDNUploader {
|
||||||
|
|
||||||
|
private static final String MOCK_CDN_BASE_URL = "https://cdn.kt-event.com/images/mock";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String upload(byte[] imageData, String fileName) {
|
||||||
|
log.info("[MOCK] CDN에 이미지 업로드: fileName={}, size={} bytes",
|
||||||
|
fileName, imageData.length);
|
||||||
|
|
||||||
|
// Mock CDN URL 생성
|
||||||
|
String mockUrl = String.format("%s/%s", MOCK_CDN_BASE_URL, fileName);
|
||||||
|
|
||||||
|
log.info("[MOCK] 업로드된 CDN URL: {}", mockUrl);
|
||||||
|
|
||||||
|
return mockUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
package com.kt.event.content.infra.gateway.mock;
|
||||||
|
|
||||||
|
import com.kt.event.content.biz.domain.ImageStyle;
|
||||||
|
import com.kt.event.content.biz.domain.Platform;
|
||||||
|
import com.kt.event.content.biz.usecase.out.ImageGeneratorCaller;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock Image Generator (테스트용)
|
||||||
|
* 실제 AI 이미지 생성 API 연동 전까지 사용
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@Profile({"local", "test"})
|
||||||
|
public class MockImageGenerator implements ImageGeneratorCaller {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] generateImage(String prompt, ImageStyle style, Platform platform) {
|
||||||
|
log.info("[MOCK] AI 이미지 생성: prompt='{}', style={}, platform={}",
|
||||||
|
prompt, style, platform);
|
||||||
|
|
||||||
|
// Mock: 빈 바이트 배열 반환 (실제로는 AI가 생성한 이미지 데이터)
|
||||||
|
byte[] mockImageData = createMockImageData(style, platform);
|
||||||
|
|
||||||
|
log.info("[MOCK] 이미지 생성 완료: size={} bytes", mockImageData.length);
|
||||||
|
|
||||||
|
return mockImageData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock 이미지 데이터 생성
|
||||||
|
* 실제로는 PNG/JPEG 이미지 바이너리 데이터
|
||||||
|
*/
|
||||||
|
private byte[] createMockImageData(ImageStyle style, Platform platform) {
|
||||||
|
// 간단한 Mock 데이터 생성 (실제로는 이미지 바이너리)
|
||||||
|
String mockContent = String.format("MOCK_IMAGE_DATA[style=%s,platform=%s]", style, platform);
|
||||||
|
return mockContent.getBytes();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
package com.kt.event.content.infra.gateway.mock;
|
||||||
|
|
||||||
|
import com.kt.event.content.biz.domain.GeneratedImage;
|
||||||
|
import com.kt.event.content.biz.usecase.out.RedisAIDataReader;
|
||||||
|
import com.kt.event.content.biz.usecase.out.RedisImageWriter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock Redis Gateway (테스트용)
|
||||||
|
* 실제 Redis 연동 전까지 사용
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@Profile({"local", "test"})
|
||||||
|
public class MockRedisGateway implements RedisAIDataReader, RedisImageWriter {
|
||||||
|
|
||||||
|
private final Map<Long, Map<String, Object>> aiDataCache = new HashMap<>();
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// RedisAIDataReader 구현
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Map<String, Object>> getAIRecommendation(Long eventDraftId) {
|
||||||
|
log.info("[MOCK] Redis에서 AI 추천 데이터 조회: eventDraftId={}", eventDraftId);
|
||||||
|
|
||||||
|
// Mock 데이터 반환
|
||||||
|
Map<String, Object> mockData = new HashMap<>();
|
||||||
|
mockData.put("title", "테스트 이벤트 제목");
|
||||||
|
mockData.put("description", "테스트 이벤트 설명");
|
||||||
|
mockData.put("brandColor", "#FF5733");
|
||||||
|
|
||||||
|
return Optional.of(mockData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// RedisImageWriter 구현
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cacheImages(Long eventDraftId, List<GeneratedImage> images, long ttlSeconds) {
|
||||||
|
log.info("[MOCK] Redis에 이미지 캐싱: eventDraftId={}, count={}, ttl={}초",
|
||||||
|
eventDraftId, images.size(), ttlSeconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,170 @@
|
|||||||
|
package com.kt.event.content.infra.web.controller;
|
||||||
|
|
||||||
|
import com.kt.event.content.biz.dto.ContentCommand;
|
||||||
|
import com.kt.event.content.biz.dto.ContentInfo;
|
||||||
|
import com.kt.event.content.biz.dto.ImageInfo;
|
||||||
|
import com.kt.event.content.biz.dto.JobInfo;
|
||||||
|
import com.kt.event.content.biz.usecase.in.GenerateImagesUseCase;
|
||||||
|
import com.kt.event.content.biz.usecase.in.GetEventContentUseCase;
|
||||||
|
import com.kt.event.content.biz.usecase.in.GetImageDetailUseCase;
|
||||||
|
import com.kt.event.content.biz.usecase.in.GetImageListUseCase;
|
||||||
|
import com.kt.event.content.biz.usecase.in.GetJobStatusUseCase;
|
||||||
|
import com.kt.event.content.biz.usecase.in.RegenerateImageUseCase;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Content Service REST API Controller
|
||||||
|
*
|
||||||
|
* API 명세: content-service-api.yaml
|
||||||
|
* - 이미지 생성 요청 및 Job 상태 조회
|
||||||
|
* - 생성된 콘텐츠 조회 및 관리
|
||||||
|
* - 이미지 재생성 및 삭제
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/content")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ContentController {
|
||||||
|
|
||||||
|
private final GenerateImagesUseCase generateImagesUseCase;
|
||||||
|
private final GetJobStatusUseCase getJobStatusUseCase;
|
||||||
|
private final GetEventContentUseCase getEventContentUseCase;
|
||||||
|
private final GetImageListUseCase getImageListUseCase;
|
||||||
|
private final GetImageDetailUseCase getImageDetailUseCase;
|
||||||
|
private final RegenerateImageUseCase regenerateImageUseCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /content/images/generate
|
||||||
|
* SNS 이미지 생성 요청 (비동기)
|
||||||
|
*
|
||||||
|
* @param command 이미지 생성 요청 정보
|
||||||
|
* @return 202 ACCEPTED - Job ID 반환
|
||||||
|
*/
|
||||||
|
@PostMapping("/images/generate")
|
||||||
|
public ResponseEntity<JobInfo> generateImages(@RequestBody ContentCommand.GenerateImages command) {
|
||||||
|
log.info("이미지 생성 요청: eventDraftId={}, styles={}, platforms={}",
|
||||||
|
command.getEventDraftId(), command.getStyles(), command.getPlatforms());
|
||||||
|
|
||||||
|
JobInfo jobInfo = generateImagesUseCase.execute(command);
|
||||||
|
|
||||||
|
return ResponseEntity.status(HttpStatus.ACCEPTED).body(jobInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /content/images/jobs/{jobId}
|
||||||
|
* 이미지 생성 작업 상태 조회 (폴링)
|
||||||
|
*
|
||||||
|
* @param jobId Job ID
|
||||||
|
* @return 200 OK - Job 상태 정보
|
||||||
|
*/
|
||||||
|
@GetMapping("/images/jobs/{jobId}")
|
||||||
|
public ResponseEntity<JobInfo> getJobStatus(@PathVariable String jobId) {
|
||||||
|
log.info("Job 상태 조회: jobId={}", jobId);
|
||||||
|
|
||||||
|
JobInfo jobInfo = getJobStatusUseCase.execute(jobId);
|
||||||
|
|
||||||
|
return ResponseEntity.ok(jobInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /content/events/{eventDraftId}
|
||||||
|
* 이벤트의 생성된 콘텐츠 조회
|
||||||
|
*
|
||||||
|
* @param eventDraftId 이벤트 초안 ID
|
||||||
|
* @return 200 OK - 콘텐츠 정보 (이미지 목록 포함)
|
||||||
|
*/
|
||||||
|
@GetMapping("/events/{eventDraftId}")
|
||||||
|
public ResponseEntity<ContentInfo> getContentByEventId(@PathVariable Long eventDraftId) {
|
||||||
|
log.info("이벤트 콘텐츠 조회: eventDraftId={}", eventDraftId);
|
||||||
|
|
||||||
|
ContentInfo contentInfo = getEventContentUseCase.execute(eventDraftId);
|
||||||
|
|
||||||
|
return ResponseEntity.ok(contentInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /content/events/{eventDraftId}/images
|
||||||
|
* 이벤트의 이미지 목록 조회 (필터링)
|
||||||
|
*
|
||||||
|
* @param eventDraftId 이벤트 초안 ID
|
||||||
|
* @param style 이미지 스타일 필터 (선택)
|
||||||
|
* @param platform 플랫폼 필터 (선택)
|
||||||
|
* @return 200 OK - 이미지 목록
|
||||||
|
*/
|
||||||
|
@GetMapping("/events/{eventDraftId}/images")
|
||||||
|
public ResponseEntity<List<ImageInfo>> getImages(
|
||||||
|
@PathVariable Long eventDraftId,
|
||||||
|
@RequestParam(required = false) String style,
|
||||||
|
@RequestParam(required = false) String platform) {
|
||||||
|
log.info("이미지 목록 조회: eventDraftId={}, style={}, platform={}", eventDraftId, style, platform);
|
||||||
|
|
||||||
|
// TODO: 필터링 기능 추가 (현재는 전체 목록 반환)
|
||||||
|
List<ImageInfo> images = getImageListUseCase.execute(eventDraftId);
|
||||||
|
|
||||||
|
return ResponseEntity.ok(images);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /content/images/{imageId}
|
||||||
|
* 특정 이미지 상세 조회
|
||||||
|
*
|
||||||
|
* @param imageId 이미지 ID
|
||||||
|
* @return 200 OK - 이미지 상세 정보
|
||||||
|
*/
|
||||||
|
@GetMapping("/images/{imageId}")
|
||||||
|
public ResponseEntity<ImageInfo> getImageById(@PathVariable Long imageId) {
|
||||||
|
log.info("이미지 상세 조회: imageId={}", imageId);
|
||||||
|
|
||||||
|
ImageInfo imageInfo = getImageDetailUseCase.execute(imageId);
|
||||||
|
|
||||||
|
return ResponseEntity.ok(imageInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DELETE /content/images/{imageId}
|
||||||
|
* 생성된 이미지 삭제
|
||||||
|
*
|
||||||
|
* @param imageId 이미지 ID
|
||||||
|
* @return 204 NO CONTENT
|
||||||
|
*/
|
||||||
|
@DeleteMapping("/images/{imageId}")
|
||||||
|
public ResponseEntity<Void> deleteImage(@PathVariable Long imageId) {
|
||||||
|
log.info("이미지 삭제 요청: imageId={}", imageId);
|
||||||
|
|
||||||
|
// TODO: DeleteImageUseCase 구현 필요
|
||||||
|
// deleteImageUseCase.execute(imageId);
|
||||||
|
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /content/images/{imageId}/regenerate
|
||||||
|
* 이미지 재생성 요청
|
||||||
|
*
|
||||||
|
* @param imageId 이미지 ID
|
||||||
|
* @param requestBody 재생성 요청 정보 (선택)
|
||||||
|
* @return 202 ACCEPTED - Job ID 반환
|
||||||
|
*/
|
||||||
|
@PostMapping("/images/{imageId}/regenerate")
|
||||||
|
public ResponseEntity<JobInfo> regenerateImage(
|
||||||
|
@PathVariable Long imageId,
|
||||||
|
@RequestBody(required = false) ContentCommand.RegenerateImage requestBody) {
|
||||||
|
log.info("이미지 재생성 요청: imageId={}", imageId);
|
||||||
|
|
||||||
|
// imageId를 포함한 command 생성
|
||||||
|
ContentCommand.RegenerateImage command = ContentCommand.RegenerateImage.builder()
|
||||||
|
.imageId(imageId)
|
||||||
|
.newPrompt(requestBody != null ? requestBody.getNewPrompt() : null)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
JobInfo jobInfo = regenerateImageUseCase.execute(command);
|
||||||
|
|
||||||
|
return ResponseEntity.status(HttpStatus.ACCEPTED).body(jobInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
38
content-service/src/main/resources/application-local.yml
Normal file
38
content-service/src/main/resources/application-local.yml
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
url: jdbc:h2:mem:contentdb
|
||||||
|
username: sa
|
||||||
|
password:
|
||||||
|
driver-class-name: org.h2.Driver
|
||||||
|
|
||||||
|
h2:
|
||||||
|
console:
|
||||||
|
enabled: true
|
||||||
|
path: /h2-console
|
||||||
|
|
||||||
|
jpa:
|
||||||
|
hibernate:
|
||||||
|
ddl-auto: create-drop
|
||||||
|
show-sql: true
|
||||||
|
properties:
|
||||||
|
hibernate:
|
||||||
|
format_sql: true
|
||||||
|
dialect: org.hibernate.dialect.H2Dialect
|
||||||
|
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
# Redis 연결 비활성화 (Mock 사용)
|
||||||
|
repositories:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
kafka:
|
||||||
|
# Kafka 연결 비활성화 (Mock 사용)
|
||||||
|
bootstrap-servers: localhost:9092
|
||||||
|
|
||||||
|
server:
|
||||||
|
port: 8084
|
||||||
|
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
com.kt.event: DEBUG
|
||||||
|
org.hibernate.SQL: DEBUG
|
||||||
Loading…
x
Reference in New Issue
Block a user