mirror of
https://github.com/ktds-dg0501/kt-event-marketing.git
synced 2026-06-12 23:19:10 +00:00
totalViews 필드 추가 및 배포완료 이벤트 개선
변경 사항: 1. EventStats에 totalViews 필드 추가 (모든 채널 노출 수 합계) 2. DistributionCompletedEvent에 expectedViews 필드 추가 3. DistributionCompletedConsumer 개선: - ChannelStats.impressions에 expectedViews 저장 - updateTotalViews() 메서드로 전체 노출 수 집계 4. SampleDataLoader에 채널별 예상 노출 수 설정: - 이벤트1: 총 20,000 (우리동네TV 5K, 지니TV 10K, 링고비즈 3K, SNS 2K) - 이벤트2: 총 14,000 - 이벤트3: 총 6,000 설계 다이어그램과 일치: - 채널별 예상 노출 수 저장 - 총 노출 수 실시간 집계 - 멱등성 및 캐시 무효화 유지 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+11
-5
@@ -188,6 +188,11 @@ public class SampleDataLoader implements ApplicationRunner {
|
||||
new BigDecimal("3500000"),
|
||||
new BigDecimal("2000000")
|
||||
};
|
||||
int[][] expectedViews = {
|
||||
{5000, 10000, 3000, 2000}, // 이벤트1: 우리동네TV, 지니TV, 링고비즈, SNS
|
||||
{3500, 7000, 2000, 1500}, // 이벤트2
|
||||
{1500, 3000, 1000, 500} // 이벤트3
|
||||
};
|
||||
|
||||
for (int i = 0; i < eventIds.length; i++) {
|
||||
String eventId = eventIds[i];
|
||||
@@ -195,19 +200,19 @@ public class SampleDataLoader implements ApplicationRunner {
|
||||
|
||||
// 1. 우리동네TV (TV)
|
||||
publishDistributionEvent(eventId, "우리동네TV", "TV",
|
||||
distributionBudget.multiply(new BigDecimal("0.3")));
|
||||
distributionBudget.multiply(new BigDecimal("0.3")), expectedViews[i][0]);
|
||||
|
||||
// 2. 지니TV (TV)
|
||||
publishDistributionEvent(eventId, "지니TV", "TV",
|
||||
distributionBudget.multiply(new BigDecimal("0.3")));
|
||||
distributionBudget.multiply(new BigDecimal("0.3")), expectedViews[i][1]);
|
||||
|
||||
// 3. 링고비즈 (CALL)
|
||||
publishDistributionEvent(eventId, "링고비즈", "CALL",
|
||||
distributionBudget.multiply(new BigDecimal("0.2")));
|
||||
distributionBudget.multiply(new BigDecimal("0.2")), expectedViews[i][2]);
|
||||
|
||||
// 4. SNS (SNS)
|
||||
publishDistributionEvent(eventId, "SNS", "SNS",
|
||||
distributionBudget.multiply(new BigDecimal("0.2")));
|
||||
distributionBudget.multiply(new BigDecimal("0.2")), expectedViews[i][3]);
|
||||
}
|
||||
|
||||
log.info("✅ DistributionCompleted 이벤트 12건 발행 완료 (3 이벤트 × 4 채널)");
|
||||
@@ -217,12 +222,13 @@ public class SampleDataLoader implements ApplicationRunner {
|
||||
* 개별 DistributionCompleted 이벤트 발행
|
||||
*/
|
||||
private void publishDistributionEvent(String eventId, String channelName, String channelType,
|
||||
BigDecimal distributionCost) throws Exception {
|
||||
BigDecimal distributionCost, Integer expectedViews) throws Exception {
|
||||
DistributionCompletedEvent event = DistributionCompletedEvent.builder()
|
||||
.eventId(eventId)
|
||||
.channelName(channelName)
|
||||
.channelType(channelType)
|
||||
.distributionCost(distributionCost)
|
||||
.expectedViews(expectedViews)
|
||||
.build();
|
||||
publishEvent(DISTRIBUTION_COMPLETED_TOPIC, event);
|
||||
}
|
||||
|
||||
@@ -49,6 +49,13 @@ public class EventStats extends BaseTimeEntity {
|
||||
@Builder.Default
|
||||
private Integer totalParticipants = 0;
|
||||
|
||||
/**
|
||||
* 총 노출 수 (모든 채널의 노출 수 합계)
|
||||
*/
|
||||
@Column(nullable = false)
|
||||
@Builder.Default
|
||||
private Integer totalViews = 0;
|
||||
|
||||
/**
|
||||
* 예상 ROI (%)
|
||||
*/
|
||||
|
||||
+45
-4
@@ -3,6 +3,7 @@ package com.kt.event.analytics.messaging.consumer;
|
||||
import com.kt.event.analytics.entity.ChannelStats;
|
||||
import com.kt.event.analytics.messaging.event.DistributionCompletedEvent;
|
||||
import com.kt.event.analytics.repository.ChannelStatsRepository;
|
||||
import com.kt.event.analytics.repository.EventStatsRepository;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -11,6 +12,7 @@ import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.kafka.annotation.KafkaListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
@@ -25,6 +27,7 @@ import java.util.concurrent.TimeUnit;
|
||||
public class DistributionCompletedConsumer {
|
||||
|
||||
private final ChannelStatsRepository channelStatsRepository;
|
||||
private final EventStatsRepository eventStatsRepository;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final RedisTemplate<String, String> redisTemplate;
|
||||
|
||||
@@ -64,15 +67,25 @@ public class DistributionCompletedConsumer {
|
||||
.build());
|
||||
|
||||
channelStats.setDistributionCost(event.getDistributionCost());
|
||||
channelStatsRepository.save(channelStats);
|
||||
log.info("✅ 채널 통계 업데이트: eventId={}, channel={}", eventId, channelName);
|
||||
|
||||
// 3. 캐시 무효화 (다음 조회 시 최신 배포 통계 반영)
|
||||
// 예상 노출 수 저장
|
||||
if (event.getExpectedViews() != null) {
|
||||
channelStats.setImpressions(event.getExpectedViews());
|
||||
}
|
||||
|
||||
channelStatsRepository.save(channelStats);
|
||||
log.info("✅ 채널 통계 업데이트: eventId={}, channel={}, expectedViews={}",
|
||||
eventId, channelName, event.getExpectedViews());
|
||||
|
||||
// 3. EventStats의 totalViews 업데이트 (모든 채널 노출 수 합계)
|
||||
updateTotalViews(eventId);
|
||||
|
||||
// 4. 캐시 무효화 (다음 조회 시 최신 배포 통계 반영)
|
||||
String cacheKey = CACHE_KEY_PREFIX + eventId;
|
||||
redisTemplate.delete(cacheKey);
|
||||
log.debug("🗑️ 캐시 무효화: {}", cacheKey);
|
||||
|
||||
// 4. 멱등성 처리 완료 기록 (7일 TTL)
|
||||
// 5. 멱등성 처리 완료 기록 (7일 TTL)
|
||||
redisTemplate.opsForSet().add(PROCESSED_DISTRIBUTIONS_KEY, distributionKey);
|
||||
redisTemplate.expire(PROCESSED_DISTRIBUTIONS_KEY, IDEMPOTENCY_TTL_DAYS, TimeUnit.DAYS);
|
||||
log.debug("✅ 멱등성 기록: distributionKey={}", distributionKey);
|
||||
@@ -82,4 +95,32 @@ public class DistributionCompletedConsumer {
|
||||
throw new RuntimeException("DistributionCompleted 처리 실패", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 모든 채널의 예상 노출 수를 합산하여 EventStats.totalViews 업데이트
|
||||
*/
|
||||
private void updateTotalViews(String eventId) {
|
||||
try {
|
||||
// 모든 채널 통계 조회
|
||||
List<ChannelStats> channelStatsList = channelStatsRepository.findByEventId(eventId);
|
||||
|
||||
// 총 노출 수 계산
|
||||
int totalViews = channelStatsList.stream()
|
||||
.mapToInt(ChannelStats::getImpressions)
|
||||
.sum();
|
||||
|
||||
// EventStats 업데이트
|
||||
eventStatsRepository.findByEventId(eventId)
|
||||
.ifPresentOrElse(
|
||||
eventStats -> {
|
||||
eventStats.setTotalViews(totalViews);
|
||||
eventStatsRepository.save(eventStats);
|
||||
log.info("✅ 총 노출 수 업데이트: eventId={}, totalViews={}", eventId, totalViews);
|
||||
},
|
||||
() -> log.warn("⚠️ 이벤트 통계 없음: eventId={}", eventId)
|
||||
);
|
||||
} catch (Exception e) {
|
||||
log.error("❌ totalViews 업데이트 실패: eventId={}", eventId, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -35,4 +35,9 @@ public class DistributionCompletedEvent {
|
||||
* 배포 비용
|
||||
*/
|
||||
private BigDecimal distributionCost;
|
||||
|
||||
/**
|
||||
* 예상 노출 수
|
||||
*/
|
||||
private Integer expectedViews;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user