mirror of
https://github.com/ktds-dg0501/kt-event-marketing.git
synced 2025-12-06 10:06:24 +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:
parent
7b3ca40e22
commit
7735c8472b
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.
@ -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 (%)
|
||||
*/
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,4 +35,9 @@ public class DistributionCompletedEvent {
|
||||
* 배포 비용
|
||||
*/
|
||||
private BigDecimal distributionCost;
|
||||
|
||||
/**
|
||||
* 예상 노출 수
|
||||
*/
|
||||
private Integer expectedViews;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user