이벤트별 성과분석 날짜 로직 수정 및 설정 개선
- EventCreatedEvent, EventStats에 startDate, endDate 필드 추가 - EventCreatedConsumer에서 이벤트 시작/종료 날짜 저장 - SampleDataLoader에서 실제 날짜로 이벤트 발행 - evt_2025012301: 2025-01-23 시작 (ACTIVE) - evt_2025020101: 2025-02-01 시작 (ACTIVE) - evt_2025011501: 2025-01-15~2025-01-31 (COMPLETED) - AnalyticsService: 이벤트 시작일~종료일(또는 현재) 기간 계산 - UserAnalyticsService: 가장 빠른 이벤트 시작일~현재 기간 계산 - application.yml에서 중복된 context-path 제거 - Consumer Group ID를 analytics-service-consumers-v3로 통일 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
108ee10293
commit
f80418f5ee
@ -24,7 +24,7 @@
|
|||||||
<!-- Kafka Configuration (원격 서버) -->
|
<!-- Kafka Configuration (원격 서버) -->
|
||||||
<entry key="KAFKA_ENABLED" value="true" />
|
<entry key="KAFKA_ENABLED" value="true" />
|
||||||
<entry key="KAFKA_BOOTSTRAP_SERVERS" value="20.249.182.13:9095,4.217.131.59:9095" />
|
<entry key="KAFKA_BOOTSTRAP_SERVERS" value="20.249.182.13:9095,4.217.131.59:9095" />
|
||||||
<entry key="KAFKA_CONSUMER_GROUP_ID" value="analytics-service-consumers" />
|
<entry key="KAFKA_CONSUMER_GROUP_ID" value="analytics-service-consumers-v3" />
|
||||||
|
|
||||||
<!-- Sample Data Configuration (MVP Only) -->
|
<!-- Sample Data Configuration (MVP Only) -->
|
||||||
<!-- ⚠️ Kafka Producer로 이벤트 발행 (Consumer가 처리) -->
|
<!-- ⚠️ Kafka Producer로 이벤트 발행 (Consumer가 처리) -->
|
||||||
|
|||||||
@ -23,7 +23,7 @@ public class KafkaConsumerConfig {
|
|||||||
@Value("${spring.kafka.bootstrap-servers}")
|
@Value("${spring.kafka.bootstrap-servers}")
|
||||||
private String bootstrapServers;
|
private String bootstrapServers;
|
||||||
|
|
||||||
@Value("${spring.kafka.consumer.group-id:analytics-service}")
|
@Value("${spring.kafka.consumer.group-id:analytics-service-consumers-v3}")
|
||||||
private String groupId;
|
private String groupId;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import org.springframework.boot.ApplicationArguments;
|
|||||||
import org.springframework.boot.ApplicationRunner;
|
import org.springframework.boot.ApplicationRunner;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.kafka.core.KafkaAdmin;
|
import org.springframework.kafka.core.KafkaAdmin;
|
||||||
import org.springframework.kafka.core.KafkaTemplate;
|
import org.springframework.kafka.core.KafkaTemplate;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
@ -60,7 +61,8 @@ public class SampleDataLoader implements ApplicationRunner {
|
|||||||
|
|
||||||
private final Random random = new Random();
|
private final Random random = new Random();
|
||||||
|
|
||||||
private static final String CONSUMER_GROUP_ID = "analytics-service-consumers-v3";
|
@Value("${spring.kafka.consumer.group-id}")
|
||||||
|
private String consumerGroupId;
|
||||||
|
|
||||||
// Kafka Topic Names (MVP용 샘플 토픽)
|
// Kafka Topic Names (MVP용 샘플 토픽)
|
||||||
private static final String EVENT_CREATED_TOPIC = "sample.event.created";
|
private static final String EVENT_CREATED_TOPIC = "sample.event.created";
|
||||||
@ -181,7 +183,7 @@ public class SampleDataLoader implements ApplicationRunner {
|
|||||||
*/
|
*/
|
||||||
private void resetConsumerOffsets() {
|
private void resetConsumerOffsets() {
|
||||||
try (AdminClient adminClient = AdminClient.create(kafkaAdmin.getConfigurationProperties())) {
|
try (AdminClient adminClient = AdminClient.create(kafkaAdmin.getConfigurationProperties())) {
|
||||||
log.info("🔄 Kafka Consumer Offset 리셋 시작: group={}", CONSUMER_GROUP_ID);
|
log.info("🔄 Kafka Consumer Offset 리셋 시작: group={}", consumerGroupId);
|
||||||
|
|
||||||
// 모든 토픽의 offset 삭제
|
// 모든 토픽의 offset 삭제
|
||||||
Set<TopicPartition> partitions = new HashSet<>();
|
Set<TopicPartition> partitions = new HashSet<>();
|
||||||
@ -195,7 +197,7 @@ public class SampleDataLoader implements ApplicationRunner {
|
|||||||
|
|
||||||
// Consumer Group Offset 삭제
|
// Consumer Group Offset 삭제
|
||||||
DeleteConsumerGroupOffsetsResult result = adminClient.deleteConsumerGroupOffsets(
|
DeleteConsumerGroupOffsetsResult result = adminClient.deleteConsumerGroupOffsets(
|
||||||
CONSUMER_GROUP_ID,
|
consumerGroupId,
|
||||||
partitions
|
partitions
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -224,6 +226,8 @@ public class SampleDataLoader implements ApplicationRunner {
|
|||||||
.totalInvestment(new BigDecimal("5000000"))
|
.totalInvestment(new BigDecimal("5000000"))
|
||||||
.expectedRevenue(new BigDecimal("15000000")) // 투자 대비 3배 수익
|
.expectedRevenue(new BigDecimal("15000000")) // 투자 대비 3배 수익
|
||||||
.status("ACTIVE")
|
.status("ACTIVE")
|
||||||
|
.startDate(java.time.LocalDateTime.of(2025, 1, 23, 0, 0)) // 2025-01-23 시작
|
||||||
|
.endDate(null) // 진행중
|
||||||
.build();
|
.build();
|
||||||
publishEvent(EVENT_CREATED_TOPIC, event1);
|
publishEvent(EVENT_CREATED_TOPIC, event1);
|
||||||
|
|
||||||
@ -235,6 +239,8 @@ public class SampleDataLoader implements ApplicationRunner {
|
|||||||
.totalInvestment(new BigDecimal("3500000"))
|
.totalInvestment(new BigDecimal("3500000"))
|
||||||
.expectedRevenue(new BigDecimal("7000000")) // 투자 대비 2배 수익
|
.expectedRevenue(new BigDecimal("7000000")) // 투자 대비 2배 수익
|
||||||
.status("ACTIVE")
|
.status("ACTIVE")
|
||||||
|
.startDate(java.time.LocalDateTime.of(2025, 2, 1, 0, 0)) // 2025-02-01 시작
|
||||||
|
.endDate(null) // 진행중
|
||||||
.build();
|
.build();
|
||||||
publishEvent(EVENT_CREATED_TOPIC, event2);
|
publishEvent(EVENT_CREATED_TOPIC, event2);
|
||||||
|
|
||||||
@ -246,6 +252,8 @@ public class SampleDataLoader implements ApplicationRunner {
|
|||||||
.totalInvestment(new BigDecimal("2000000"))
|
.totalInvestment(new BigDecimal("2000000"))
|
||||||
.expectedRevenue(new BigDecimal("3000000")) // 투자 대비 1.5배 수익
|
.expectedRevenue(new BigDecimal("3000000")) // 투자 대비 1.5배 수익
|
||||||
.status("COMPLETED")
|
.status("COMPLETED")
|
||||||
|
.startDate(java.time.LocalDateTime.of(2025, 1, 15, 0, 0)) // 2025-01-15 시작
|
||||||
|
.endDate(java.time.LocalDateTime.of(2025, 1, 31, 23, 59)) // 2025-01-31 종료
|
||||||
.build();
|
.build();
|
||||||
publishEvent(EVENT_CREATED_TOPIC, event3);
|
publishEvent(EVENT_CREATED_TOPIC, event3);
|
||||||
|
|
||||||
|
|||||||
@ -97,6 +97,18 @@ public class EventStats extends BaseTimeEntity {
|
|||||||
@Column(length = 20)
|
@Column(length = 20)
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 이벤트 시작일
|
||||||
|
*/
|
||||||
|
@Column(name = "start_date")
|
||||||
|
private java.time.LocalDateTime startDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 이벤트 종료일 (null이면 진행중)
|
||||||
|
*/
|
||||||
|
@Column(name = "end_date")
|
||||||
|
private java.time.LocalDateTime endDate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 참여자 수 증가
|
* 참여자 수 증가
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -64,11 +64,13 @@ public class EventCreatedConsumer {
|
|||||||
.totalInvestment(event.getTotalInvestment())
|
.totalInvestment(event.getTotalInvestment())
|
||||||
.expectedRevenue(event.getExpectedRevenue() != null ? event.getExpectedRevenue() : BigDecimal.ZERO)
|
.expectedRevenue(event.getExpectedRevenue() != null ? event.getExpectedRevenue() : BigDecimal.ZERO)
|
||||||
.status(event.getStatus())
|
.status(event.getStatus())
|
||||||
|
.startDate(event.getStartDate())
|
||||||
|
.endDate(event.getEndDate())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
eventStatsRepository.save(eventStats);
|
eventStatsRepository.save(eventStats);
|
||||||
log.info("✅ 이벤트 통계 초기화 완료: eventId={}, userId={}, expectedRevenue={}",
|
log.info("✅ 이벤트 통계 초기화 완료: eventId={}, userId={}, startDate={}, endDate={}",
|
||||||
eventId, eventStats.getUserId(), event.getExpectedRevenue());
|
eventId, eventStats.getUserId(), event.getStartDate(), event.getEndDate());
|
||||||
|
|
||||||
// 3. 캐시 무효화 (다음 조회 시 최신 데이터 반영)
|
// 3. 캐시 무효화 (다음 조회 시 최신 데이터 반영)
|
||||||
String cacheKey = CACHE_KEY_PREFIX + eventId;
|
String cacheKey = CACHE_KEY_PREFIX + eventId;
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import lombok.Data;
|
|||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 이벤트 생성 이벤트
|
* 이벤트 생성 이벤트
|
||||||
@ -45,4 +46,14 @@ public class EventCreatedEvent {
|
|||||||
* 이벤트 상태
|
* 이벤트 상태
|
||||||
*/
|
*/
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 이벤트 시작일
|
||||||
|
*/
|
||||||
|
private LocalDateTime startDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 이벤트 종료일 (null이면 진행중)
|
||||||
|
*/
|
||||||
|
private LocalDateTime endDate;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -146,11 +146,12 @@ public class AnalyticsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 기간 정보 구성 (이벤트 생성일 ~ 현재)
|
* 기간 정보 구성 (이벤트 시작일 ~ 종료일 또는 현재)
|
||||||
*/
|
*/
|
||||||
private PeriodInfo buildPeriodInfo(EventStats eventStats) {
|
private PeriodInfo buildPeriodInfo(EventStats eventStats) {
|
||||||
LocalDateTime start = eventStats.getCreatedAt();
|
LocalDateTime start = eventStats.getStartDate();
|
||||||
LocalDateTime end = LocalDateTime.now();
|
LocalDateTime end = eventStats.getEndDate() != null ?
|
||||||
|
eventStats.getEndDate() : LocalDateTime.now();
|
||||||
|
|
||||||
long durationDays = ChronoUnit.DAYS.between(start, end);
|
long durationDays = ChronoUnit.DAYS.between(start, end);
|
||||||
|
|
||||||
|
|||||||
@ -301,20 +301,17 @@ public class UserAnalyticsService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 기간 정보 구성
|
* 기간 정보 구성
|
||||||
*/
|
*
|
||||||
/**
|
* 전체 이벤트 중 가장 빠른 시작일 ~ 현재까지의 기간 계산
|
||||||
* 전체 이벤트의 생성/수정 시간 기반으로 period 계산
|
|
||||||
*/
|
*/
|
||||||
private PeriodInfo buildPeriodFromEvents(List<EventStats> events) {
|
private PeriodInfo buildPeriodFromEvents(List<EventStats> events) {
|
||||||
LocalDateTime start = events.stream()
|
LocalDateTime start = events.stream()
|
||||||
.map(EventStats::getCreatedAt)
|
.map(EventStats::getStartDate)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
.min(LocalDateTime::compareTo)
|
.min(LocalDateTime::compareTo)
|
||||||
.orElse(LocalDateTime.now());
|
.orElse(LocalDateTime.now());
|
||||||
|
|
||||||
LocalDateTime end = events.stream()
|
LocalDateTime end = LocalDateTime.now();
|
||||||
.map(EventStats::getUpdatedAt)
|
|
||||||
.max(LocalDateTime::compareTo)
|
|
||||||
.orElse(LocalDateTime.now());
|
|
||||||
|
|
||||||
return PeriodInfo.builder()
|
return PeriodInfo.builder()
|
||||||
.startDate(start)
|
.startDate(start)
|
||||||
|
|||||||
@ -80,7 +80,6 @@ server:
|
|||||||
charset: UTF-8
|
charset: UTF-8
|
||||||
enabled: true
|
enabled: true
|
||||||
force: true
|
force: true
|
||||||
context-path: /api/v1/analytics
|
|
||||||
|
|
||||||
# JWT
|
# JWT
|
||||||
jwt:
|
jwt:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user