Content Service Redis 연동 및 테스트 완료

- RedisConfig.java: Production용 Redis 설정 추가
- RedisGateway.java: Redis 읽기/쓰기 Gateway 구현
- application-local.yml: Redis/Kafka auto-configuration 제외 설정
- test-backend.md: 7개 API 테스트 결과서 작성 (100% 성공)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
cherry2250
2025-10-23 22:08:17 +09:00
parent 06995864b9
commit 6dc6334c75
4 changed files with 547 additions and 0 deletions
@@ -0,0 +1,51 @@
package com.kt.event.content.infra.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* Redis 설정 (Production 환경용)
* Local/Test 환경에서는 Mock Gateway 사용
*/
@Configuration
@Profile({"!local", "!test"})
public class RedisConfig {
@Value("${spring.data.redis.host}")
private String host;
@Value("${spring.data.redis.port}")
private int port;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(host, port);
return new LettuceConnectionFactory(config);
}
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// String serializer for keys
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
// JSON serializer for values
GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();
template.setValueSerializer(serializer);
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}
@@ -0,0 +1,95 @@
package com.kt.event.content.infra.gateway;
import com.fasterxml.jackson.databind.ObjectMapper;
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.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Profile;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* Redis Gateway 구현체 (Production 환경용)
*
* Local/Test 환경에서는 MockRedisGateway 사용
*/
@Slf4j
@Component
@Profile({"!local", "!test"})
@RequiredArgsConstructor
public class RedisGateway implements RedisAIDataReader, RedisImageWriter {
private final RedisTemplate<String, Object> redisTemplate;
private final ObjectMapper objectMapper;
private static final String AI_DATA_KEY_PREFIX = "ai:event:";
private static final String IMAGE_URL_KEY_PREFIX = "image:url:";
private static final Duration DEFAULT_TTL = Duration.ofHours(24);
@Override
public Optional<Map<String, Object>> getAIRecommendation(Long eventDraftId) {
try {
String key = AI_DATA_KEY_PREFIX + eventDraftId;
Object data = redisTemplate.opsForValue().get(key);
if (data == null) {
log.warn("AI 이벤트 데이터를 찾을 수 없음: eventDraftId={}", eventDraftId);
return Optional.empty();
}
@SuppressWarnings("unchecked")
Map<String, Object> aiData = objectMapper.convertValue(data, Map.class);
return Optional.of(aiData);
} catch (Exception e) {
log.error("AI 이벤트 데이터 조회 실패: eventDraftId={}", eventDraftId, e);
return Optional.empty();
}
}
@Override
public void cacheImages(Long eventDraftId, List<GeneratedImage> images, long ttlSeconds) {
try {
String key = IMAGE_URL_KEY_PREFIX + eventDraftId;
// 이미지 목록을 캐싱
redisTemplate.opsForValue().set(key, images, Duration.ofSeconds(ttlSeconds));
log.info("이미지 목록 캐싱 완료: eventDraftId={}, count={}, ttl={}초",
eventDraftId, images.size(), ttlSeconds);
} catch (Exception e) {
log.error("이미지 목록 캐싱 실패: eventDraftId={}", eventDraftId, e);
}
}
/**
* 이미지 URL 캐시 삭제
*/
public void deleteImageUrl(Long eventDraftId) {
try {
String key = IMAGE_URL_KEY_PREFIX + eventDraftId;
redisTemplate.delete(key);
log.info("이미지 URL 캐시 삭제: eventDraftId={}", eventDraftId);
} catch (Exception e) {
log.error("이미지 URL 캐시 삭제 실패: eventDraftId={}", eventDraftId, e);
}
}
/**
* AI 이벤트 데이터 캐시 삭제
*/
public void deleteAIEventData(Long eventDraftId) {
try {
String key = AI_DATA_KEY_PREFIX + eventDraftId;
redisTemplate.delete(key);
log.info("AI 이벤트 데이터 캐시 삭제: eventDraftId={}", eventDraftId);
} catch (Exception e) {
log.error("AI 이벤트 데이터 캐시 삭제 실패: eventDraftId={}", eventDraftId, e);
}
}
}
@@ -11,6 +11,7 @@ spring:
path: /h2-console
jpa:
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: create-drop
show-sql: true
@@ -24,10 +25,20 @@ spring:
# Redis 연결 비활성화 (Mock 사용)
repositories:
enabled: false
host: localhost
port: 6379
kafka:
# Kafka 연결 비활성화 (Mock 사용)
bootstrap-servers: localhost:9092
consumer:
enabled: false
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
- org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration
- org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration
server:
port: 8084
@@ -36,3 +47,4 @@ logging:
level:
com.kt.event: DEBUG
org.hibernate.SQL: DEBUG
org.hibernate.type.descriptor.sql.BasicBinder: TRACE