mirror of
https://github.com/ktds-dg0501/kt-event-marketing.git
synced 2025-12-06 10:46:23 +00:00
Merge pull request #10 from ktds-dg0501/docker/participation
Docker/participation
This commit is contained in:
commit
e0fc4286c7
14
.claude/commands/deploy-actions-cicd-guide-back.md
Normal file
14
.claude/commands/deploy-actions-cicd-guide-back.md
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
command: "/deploy-actions-cicd-guide-back"
|
||||||
|
---
|
||||||
|
|
||||||
|
@cicd
|
||||||
|
'백엔드GitHubActions파이프라인작성가이드'에 따라 GitHub Actions를 이용한 CI/CD 가이드를 작성해 주세요.
|
||||||
|
프롬프트에 '[실행정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요.
|
||||||
|
{안내메시지}
|
||||||
|
'[실행정보]'섹션 하위에 아래 예와 같이 필요한 정보를 제시해 주세요.
|
||||||
|
[실행정보]
|
||||||
|
- ACR_NAME: acrdigitalgarage01
|
||||||
|
- RESOURCE_GROUP: rg-digitalgarage-01
|
||||||
|
- AKS_CLUSTER: aks-digitalgarage-01
|
||||||
|
- NAMESPACE: phonebill-dg0500
|
||||||
15
.claude/commands/deploy-actions-cicd-guide-front.md
Normal file
15
.claude/commands/deploy-actions-cicd-guide-front.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
command: "/deploy-actions-cicd-guide-front"
|
||||||
|
---
|
||||||
|
|
||||||
|
@cicd
|
||||||
|
'프론트엔드GitHubActions파이프라인작성가이드'에 따라 GitHub Actions를 이용한 CI/CD 가이드를 작성해 주세요.
|
||||||
|
프롬프트에 '[실행정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요.
|
||||||
|
{안내메시지}
|
||||||
|
'[실행정보]'섹션 하위에 아래 예와 같이 필요한 정보를 제시해 주세요.
|
||||||
|
[실행정보]
|
||||||
|
- SYSTEM_NAME: phonebill
|
||||||
|
- ACR_NAME: acrdigitalgarage01
|
||||||
|
- RESOURCE_GROUP: rg-digitalgarage-01
|
||||||
|
- AKS_CLUSTER: aks-digitalgarage-01
|
||||||
|
- NAMESPACE: phonebill-dg0500
|
||||||
6
.claude/commands/deploy-build-image-back.md
Normal file
6
.claude/commands/deploy-build-image-back.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
command: "/deploy-build-image-back"
|
||||||
|
---
|
||||||
|
|
||||||
|
@cicd
|
||||||
|
'백엔드컨테이너이미지작성가이드'에 따라 컨테이너 이미지를 작성해 주세요.
|
||||||
6
.claude/commands/deploy-build-image-front.md
Normal file
6
.claude/commands/deploy-build-image-front.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
command: "/deploy-build-image-front"
|
||||||
|
---
|
||||||
|
|
||||||
|
@cicd
|
||||||
|
'프론트엔드컨테이너이미지작성가이드'에 따라 컨테이너 이미지를 작성해 주세요.
|
||||||
81
.claude/commands/deploy-help.md
Normal file
81
.claude/commands/deploy-help.md
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
---
|
||||||
|
command: "/deploy-help"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 배포 작업 순서
|
||||||
|
|
||||||
|
## 1단계: 컨테이너 이미지 작성
|
||||||
|
### 백엔드
|
||||||
|
```
|
||||||
|
/deploy-build-image-back
|
||||||
|
```
|
||||||
|
- 백엔드컨테이너이미지작성가이드를 참고하여 컨테이너 이미지를 빌드합니다
|
||||||
|
|
||||||
|
### 프론트엔드
|
||||||
|
```
|
||||||
|
/deploy-build-image-front
|
||||||
|
```
|
||||||
|
- 프론트엔드컨테이너이미지작성가이드를 참고하여 컨테이너 이미지를 빌드합니다
|
||||||
|
|
||||||
|
## 2단계: 컨테이너 실행 가이드 작성
|
||||||
|
### 백엔드
|
||||||
|
```
|
||||||
|
/deploy-run-container-guide-back
|
||||||
|
```
|
||||||
|
- 백엔드컨테이너실행방법가이드를 참고하여 컨테이너 실행 방법을 작성합니다
|
||||||
|
- 실행정보(ACR명, VM정보)가 필요합니다
|
||||||
|
|
||||||
|
### 프론트엔드
|
||||||
|
```
|
||||||
|
/deploy-run-container-guide-front
|
||||||
|
```
|
||||||
|
- 프론트엔드컨테이너실행방법가이드를 참고하여 컨테이너 실행 방법을 작성합니다
|
||||||
|
- 실행정보(시스템명, ACR명, VM정보)가 필요합니다
|
||||||
|
|
||||||
|
## 3단계: Kubernetes 배포 가이드 작성
|
||||||
|
### 백엔드
|
||||||
|
```
|
||||||
|
/deploy-k8s-guide-back
|
||||||
|
```
|
||||||
|
- 백엔드배포가이드를 참고하여 쿠버네티스 배포 방법을 작성합니다
|
||||||
|
- 실행정보(ACR명, k8s명, 네임스페이스, 리소스 설정)가 필요합니다
|
||||||
|
|
||||||
|
### 프론트엔드
|
||||||
|
```
|
||||||
|
/deploy-k8s-guide-front
|
||||||
|
```
|
||||||
|
- 프론트엔드배포가이드를 참고하여 쿠버네티스 배포 방법을 작성합니다
|
||||||
|
- 실행정보(시스템명, ACR명, k8s명, 네임스페이스, Gateway Host, 리소스 설정)가 필요합니다
|
||||||
|
|
||||||
|
## 4단계: CI/CD 파이프라인 구성
|
||||||
|
|
||||||
|
### Jenkins 사용 시
|
||||||
|
#### 백엔드
|
||||||
|
```
|
||||||
|
/deploy-jenkins-cicd-guide-back
|
||||||
|
```
|
||||||
|
- 백엔드Jenkins파이프라인작성가이드를 참고하여 Jenkins CI/CD 파이프라인을 구성합니다
|
||||||
|
|
||||||
|
#### 프론트엔드
|
||||||
|
```
|
||||||
|
/deploy-jenkins-cicd-guide-front
|
||||||
|
```
|
||||||
|
- 프론트엔드Jenkins파이프라인작성가이드를 참고하여 Jenkins CI/CD 파이프라인을 구성합니다
|
||||||
|
|
||||||
|
### GitHub Actions 사용 시
|
||||||
|
#### 백엔드
|
||||||
|
```
|
||||||
|
/deploy-actions-cicd-guide-back
|
||||||
|
```
|
||||||
|
- 백엔드GitHubActions파이프라인작성가이드를 참고하여 GitHub Actions CI/CD 파이프라인을 구성합니다
|
||||||
|
|
||||||
|
#### 프론트엔드
|
||||||
|
```
|
||||||
|
/deploy-actions-cicd-guide-front
|
||||||
|
```
|
||||||
|
- 프론트엔드GitHubActions파이프라인작성가이드를 참고하여 GitHub Actions CI/CD 파이프라인을 구성합니다
|
||||||
|
|
||||||
|
## 참고사항
|
||||||
|
- 각 명령 실행 전 필요한 실행정보를 프롬프트에 포함해야 합니다
|
||||||
|
- 실행정보가 없으면 안내 메시지가 표시되며 작업이 중단됩니다
|
||||||
|
- CI/CD 도구는 Jenkins 또는 GitHub Actions 중 선택하여 사용합니다
|
||||||
14
.claude/commands/deploy-jenkins-cicd-guide-back.md
Normal file
14
.claude/commands/deploy-jenkins-cicd-guide-back.md
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
command: "/deploy-jenkins-cicd-guide-back"
|
||||||
|
---
|
||||||
|
|
||||||
|
@cicd
|
||||||
|
'백엔드Jenkins파이프라인작성가이드'에 따라 Jenkins를 이용한 CI/CD 가이드를 작성해 주세요.
|
||||||
|
프롬프트에 '[실행정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요.
|
||||||
|
{안내메시지}
|
||||||
|
'[실행정보]'섹션 하위에 아래 예와 같이 필요한 정보를 제시해 주세요.
|
||||||
|
[실행정보]
|
||||||
|
- ACR_NAME: acrdigitalgarage01
|
||||||
|
- RESOURCE_GROUP: rg-digitalgarage-01
|
||||||
|
- AKS_CLUSTER: aks-digitalgarage-01
|
||||||
|
- NAMESPACE: phonebill-dg0500
|
||||||
15
.claude/commands/deploy-jenkins-cicd-guide-front.md
Normal file
15
.claude/commands/deploy-jenkins-cicd-guide-front.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
command: "/deploy-jenkins-cicd-guide-front"
|
||||||
|
---
|
||||||
|
|
||||||
|
@cicd
|
||||||
|
'프론트엔드Jenkins파이프라인작성가이드'에 따라 Jenkins를 이용한 CI/CD 가이드를 작성해 주세요.
|
||||||
|
프롬프트에 '[실행정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요.
|
||||||
|
{안내메시지}
|
||||||
|
'[실행정보]'섹션 하위에 아래 예와 같이 필요한 정보를 제시해 주세요.
|
||||||
|
[실행정보]
|
||||||
|
- SYSTEM_NAME: phonebill
|
||||||
|
- ACR_NAME: acrdigitalgarage01
|
||||||
|
- RESOURCE_GROUP: rg-digitalgarage-01
|
||||||
|
- AKS_CLUSTER: aks-digitalgarage-01
|
||||||
|
- NAMESPACE: phonebill-dg0500
|
||||||
16
.claude/commands/deploy-k8s-guide-back.md
Normal file
16
.claude/commands/deploy-k8s-guide-back.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
command: "/deploy-k8s-guide-back"
|
||||||
|
---
|
||||||
|
|
||||||
|
@cicd
|
||||||
|
'백엔드배포가이드'에 따라 백엔드 서비스 배포 방법을 작성해 주세요.
|
||||||
|
프롬프트에 '[실행정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요.
|
||||||
|
{안내메시지}
|
||||||
|
'[실행정보]'섹션 하위에 아래 예와 같이 필요한 정보를 제시해 주세요.
|
||||||
|
[실행정보]
|
||||||
|
- ACR명: acrdigitalgarage01
|
||||||
|
- k8s명: aks-digitalgarage-01
|
||||||
|
- 네임스페이스: tripgen
|
||||||
|
- 파드수: 2
|
||||||
|
- 리소스(CPU): 256m/1024m
|
||||||
|
- 리소스(메모리): 256Mi/1024Mi
|
||||||
18
.claude/commands/deploy-k8s-guide-front.md
Normal file
18
.claude/commands/deploy-k8s-guide-front.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
command: "/deploy-k8s-guide-front"
|
||||||
|
---
|
||||||
|
|
||||||
|
@cicd
|
||||||
|
'프론트엔드배포가이드'에 따라 프론트엔드 서비스 배포 방법을 작성해 주세요.
|
||||||
|
프롬프트에 '[실행정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요.
|
||||||
|
{안내메시지}
|
||||||
|
'[실행정보]'섹션 하위에 아래 예와 같이 필요한 정보를 제시해 주세요.
|
||||||
|
[실행정보]
|
||||||
|
- 시스템명: tripgen
|
||||||
|
- ACR명: acrdigitalgarage01
|
||||||
|
- k8s명: aks-digitalgarage-01
|
||||||
|
- 네임스페이스: tripgen
|
||||||
|
- 파드수: 2
|
||||||
|
- 리소스(CPU): 256m/1024m
|
||||||
|
- 리소스(메모리): 256Mi/1024Mi
|
||||||
|
- Gateway Host: http://tripgen-api.20.214.196.128.nip.io
|
||||||
15
.claude/commands/deploy-run-container-guide-back.md
Normal file
15
.claude/commands/deploy-run-container-guide-back.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
command: "/deploy-run-container-guide-back"
|
||||||
|
---
|
||||||
|
|
||||||
|
@cicd
|
||||||
|
'백엔드컨테이너실행방법가이드'에 따라 컨테이너 실행 가이드를 작성해 주세요.
|
||||||
|
프롬프트에 '[실행정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요.
|
||||||
|
{안내메시지}
|
||||||
|
'[실행정보]'섹션 하위에 아래 예와 같이 필요한 정보를 제시해 주세요.
|
||||||
|
[실행정보]
|
||||||
|
- ACR명: acrdigitalgarage01
|
||||||
|
- VM
|
||||||
|
- KEY파일: ~/home/bastion-dg0500
|
||||||
|
- USERID: azureuser
|
||||||
|
- IP: 4.230.5.6
|
||||||
16
.claude/commands/deploy-run-container-guide-front.md
Normal file
16
.claude/commands/deploy-run-container-guide-front.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
command: "/deploy-run-container-guide-front"
|
||||||
|
---
|
||||||
|
|
||||||
|
@cicd
|
||||||
|
'프론트엔드컨테이너실행방법가이드'에 따라 컨테이너 실행 가이드를 작성해 주세요.
|
||||||
|
프롬프트에 '[실행정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요.
|
||||||
|
{안내메시지}
|
||||||
|
'[실행정보]'섹션 하위에 아래 예와 같이 필요한 정보를 제시해 주세요.
|
||||||
|
[실행정보]
|
||||||
|
- 시스템명: tripgen
|
||||||
|
- ACR명: acrdigitalgarage01
|
||||||
|
- VM
|
||||||
|
- KEY파일: ~/home/bastion-dg0500
|
||||||
|
- USERID: azureuser
|
||||||
|
- IP: 4.230.5.6
|
||||||
@ -1,3 +1,6 @@
|
|||||||
|
---
|
||||||
|
command: "/design-api"
|
||||||
|
---
|
||||||
@architecture
|
@architecture
|
||||||
API를 설계해 주세요:
|
API를 설계해 주세요:
|
||||||
- '공통설계원칙'과 'API설계가이드'를 준용하여 설계
|
- '공통설계원칙'과 'API설계가이드'를 준용하여 설계
|
||||||
@ -1,3 +1,6 @@
|
|||||||
|
---
|
||||||
|
command: "/design-class"
|
||||||
|
---
|
||||||
@architecture
|
@architecture
|
||||||
'공통설계원칙'과 '클래스설계가이드'를 준용하여 클래스를 설계해 주세요.
|
'공통설계원칙'과 '클래스설계가이드'를 준용하여 클래스를 설계해 주세요.
|
||||||
프롬프트에 '[클래스설계 정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시합니다.
|
프롬프트에 '[클래스설계 정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시합니다.
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
---
|
||||||
|
command: "/design-data"
|
||||||
|
---
|
||||||
@architecture
|
@architecture
|
||||||
데이터 설계를 해주세요:
|
데이터 설계를 해주세요:
|
||||||
- '공통설계원칙'과 '데이터설계가이드'를 준용하여 설계
|
- '공통설계원칙'과 '데이터설계가이드'를 준용하여 설계
|
||||||
@ -1,3 +1,6 @@
|
|||||||
|
---
|
||||||
|
command: "/design-fix-prototype"
|
||||||
|
---
|
||||||
@fix as @front
|
@fix as @front
|
||||||
'[오류내용]'섹션에 제공된 오류를 해결해 주세요.
|
'[오류내용]'섹션에 제공된 오류를 해결해 주세요.
|
||||||
프롬프트에 '[오류내용]'섹션이 없으면 수행 중단하고 안내 메시지 표시
|
프롬프트에 '[오류내용]'섹션이 없으면 수행 중단하고 안내 메시지 표시
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
---
|
||||||
|
command: "/design-front"
|
||||||
|
---
|
||||||
@plan as @front
|
@plan as @front
|
||||||
'프론트엔드설계가이드'를 준용하여 **프론트엔드설계서**를 작성해 주세요.
|
'프론트엔드설계가이드'를 준용하여 **프론트엔드설계서**를 작성해 주세요.
|
||||||
프롬프트에 '[백엔드시스템]'항목이 없으면 수행을 중단하고 안내 메시지를 표시합니다.
|
프롬프트에 '[백엔드시스템]'항목이 없으면 수행을 중단하고 안내 메시지를 표시합니다.
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
---
|
||||||
|
command: "/design-high-level"
|
||||||
|
---
|
||||||
@architecture
|
@architecture
|
||||||
'HighLevel아키텍처정의가이드'를 준용하여 High Level 아키텍처 정의서를 작성해 주세요.
|
'HighLevel아키텍처정의가이드'를 준용하여 High Level 아키텍처 정의서를 작성해 주세요.
|
||||||
'CLOUD' 정보가 없으면 수행을 중단하고 안내메시지를 표시하세요.
|
'CLOUD' 정보가 없으면 수행을 중단하고 안내메시지를 표시하세요.
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
---
|
||||||
|
command: "/design-improve-prototype"
|
||||||
|
---
|
||||||
@improve as @front
|
@improve as @front
|
||||||
'[개선내용]'섹션에 있는 내용을 개선해 주세요.
|
'[개선내용]'섹션에 있는 내용을 개선해 주세요.
|
||||||
프롬프트에 '[개선내용]'항목이 없으면 수행을 중단하고 안내 메시지 표시
|
프롬프트에 '[개선내용]'항목이 없으면 수행을 중단하고 안내 메시지 표시
|
||||||
|
|||||||
@ -1,2 +1,5 @@
|
|||||||
|
---
|
||||||
|
command: "/design-improve-userstory"
|
||||||
|
---
|
||||||
@analyze as @front 프로토타입을 웹브라우저에서 분석한 후,
|
@analyze as @front 프로토타입을 웹브라우저에서 분석한 후,
|
||||||
@document as @scribe 수정된 프로토타입에 따라 유저스토리를 업데이트 해주십시오.
|
@document as @scribe 수정된 프로토타입에 따라 유저스토리를 업데이트 해주십시오.
|
||||||
@ -1,3 +1,6 @@
|
|||||||
|
---
|
||||||
|
command: "/design-logical"
|
||||||
|
---
|
||||||
@architecture
|
@architecture
|
||||||
논리 아키텍처를 설계해 주세요:
|
논리 아키텍처를 설계해 주세요:
|
||||||
- '공통설계원칙'과 '논리아키텍처 설계 가이드'를 준용하여 설계
|
- '공통설계원칙'과 '논리아키텍처 설계 가이드'를 준용하여 설계
|
||||||
@ -1,3 +1,6 @@
|
|||||||
|
---
|
||||||
|
command: "/design-pattern"
|
||||||
|
---
|
||||||
@design-pattern
|
@design-pattern
|
||||||
클라우드 아키텍처 패턴 적용 방안을 작성해 주세요:
|
클라우드 아키텍처 패턴 적용 방안을 작성해 주세요:
|
||||||
- '클라우드아키텍처패턴선정가이드'를 준용하여 작성
|
- '클라우드아키텍처패턴선정가이드'를 준용하여 작성
|
||||||
@ -1,3 +1,6 @@
|
|||||||
|
---
|
||||||
|
command: "/design-physical"
|
||||||
|
---
|
||||||
@architecture
|
@architecture
|
||||||
'물리아키텍처설계가이드'를 준용하여 물리아키텍처를 설계해 주세요.
|
'물리아키텍처설계가이드'를 준용하여 물리아키텍처를 설계해 주세요.
|
||||||
'CLOUD' 정보가 없으면 수행을 중단하고 안내메시지를 표시하세요.
|
'CLOUD' 정보가 없으면 수행을 중단하고 안내메시지를 표시하세요.
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
---
|
||||||
|
command: "/design-prototype"
|
||||||
|
---
|
||||||
@prototype
|
@prototype
|
||||||
프로토타입을 작성해 주세요:
|
프로토타입을 작성해 주세요:
|
||||||
- '프로토타입작성가이드'를 준용하여 작성
|
- '프로토타입작성가이드'를 준용하여 작성
|
||||||
@ -1,3 +1,6 @@
|
|||||||
|
---
|
||||||
|
command: "/design-seq-inner"
|
||||||
|
---
|
||||||
@architecture
|
@architecture
|
||||||
내부 시퀀스 설계를 해 주세요:
|
내부 시퀀스 설계를 해 주세요:
|
||||||
- '공통설계원칙'과 '내부시퀀스설계 가이드'를 준용하여 설계
|
- '공통설계원칙'과 '내부시퀀스설계 가이드'를 준용하여 설계
|
||||||
@ -1,3 +1,6 @@
|
|||||||
|
---
|
||||||
|
command: "/design-seq-outer"
|
||||||
|
---
|
||||||
@architecture
|
@architecture
|
||||||
외부 시퀀스 설계를 해 주세요:
|
외부 시퀀스 설계를 해 주세요:
|
||||||
- '공통설계원칙'과 '외부시퀀스설계가이드'를 준용하여 설계
|
- '공통설계원칙'과 '외부시퀀스설계가이드'를 준용하여 설계
|
||||||
@ -1,2 +1,5 @@
|
|||||||
|
---
|
||||||
|
command: "/design-test-prototype"
|
||||||
|
---
|
||||||
@test-front
|
@test-front
|
||||||
프로토타입을 테스트 해 주세요.
|
프로토타입을 테스트 해 주세요.
|
||||||
@ -1,3 +1,6 @@
|
|||||||
|
---
|
||||||
|
command: "/design-uiux"
|
||||||
|
---
|
||||||
@uiux
|
@uiux
|
||||||
UI/UX 설계를 해주세요:
|
UI/UX 설계를 해주세요:
|
||||||
- 'UI/UX설계가이드'를 준용하여 작성
|
- 'UI/UX설계가이드'를 준용하여 작성
|
||||||
@ -1,2 +1,5 @@
|
|||||||
|
---
|
||||||
|
command: "/design-update-uiux"
|
||||||
|
---
|
||||||
@document @front
|
@document @front
|
||||||
현재 프로토타입과 유저스토리를 기준으로 UI/UX설계서와 스타일가이드를 수정해 주세요.
|
현재 프로토타입과 유저스토리를 기준으로 UI/UX설계서와 스타일가이드를 수정해 주세요.
|
||||||
@ -1,5 +1,5 @@
|
|||||||
@test-backend
|
@test-backend
|
||||||
'서비스실행파일작성가이드'에 따라 테스트를 해 주세요.
|
'서비스실행프로파일작성가이드'에 따라 테스트를 해 주세요.
|
||||||
프롬프트에 '[작성정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요.
|
프롬프트에 '[작성정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요.
|
||||||
DB나 Redis의 접근 정보는 지정할 필요 없습니다. 특별히 없으면 '[작성정보]'섹션에 '없음'이라고 하세요.
|
DB나 Redis의 접근 정보는 지정할 필요 없습니다. 특별히 없으면 '[작성정보]'섹션에 '없음'이라고 하세요.
|
||||||
{안내메시지}
|
{안내메시지}
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
---
|
||||||
|
command: "/think-help"
|
||||||
|
---
|
||||||
기획 작업 순서
|
기획 작업 순서
|
||||||
|
|
||||||
1단계: 서비스 기획
|
1단계: 서비스 기획
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
---
|
||||||
|
command: "/think-planning"
|
||||||
|
---
|
||||||
아래 내용을 터미널에 표시만 하고 수행을 하지는 않습니다.
|
아래 내용을 터미널에 표시만 하고 수행을 하지는 않습니다.
|
||||||
```
|
```
|
||||||
아래 가이드를 참고하여 서비스 기획을 수행합니다.
|
아래 가이드를 참고하여 서비스 기획을 수행합니다.
|
||||||
|
|||||||
@ -1,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
command: "/think-userstory"
|
||||||
|
---
|
||||||
|
```
|
||||||
@document
|
@document
|
||||||
유저스토리를 작성하세요.
|
유저스토리를 작성하세요.
|
||||||
프롬프트에 '[요구사항]'섹션이 없으면 수행을 중단하고 안내 메시지를 표시합니다.
|
프롬프트에 '[요구사항]'섹션이 없으면 수행을 중단하고 안내 메시지를 표시합니다.
|
||||||
@ -16,3 +20,5 @@ Case 2) 다른 방법으로 이벤트스토밍을 한 경우는 요구사항을
|
|||||||
2. 유저스토리 작성
|
2. 유저스토리 작성
|
||||||
- '유저스토리작성방법'과 '유저스토리예제'를 참고하여 유저스토리를 작성
|
- '유저스토리작성방법'과 '유저스토리예제'를 참고하여 유저스토리를 작성
|
||||||
- 결과파일은 'design/userstory.md'에 생성
|
- 결과파일은 'design/userstory.md'에 생성
|
||||||
|
|
||||||
|
```
|
||||||
|
|||||||
@ -15,7 +15,40 @@
|
|||||||
"Bash(git add:*)",
|
"Bash(git add:*)",
|
||||||
"Bash(git commit:*)",
|
"Bash(git commit:*)",
|
||||||
"Bash(git push)",
|
"Bash(git push)",
|
||||||
"Bash(git pull:*)"
|
"Bash(git pull:*)",
|
||||||
|
"Bash(netstat:*)",
|
||||||
|
"Bash(findstr:*)",
|
||||||
|
"Bash(./gradlew analytics-service:compileJava:*)",
|
||||||
|
"Bash(python -m json.tool:*)",
|
||||||
|
"Bash(powershell:*)"
|
||||||
|
"Bash(./gradlew participation-service:compileJava:*)",
|
||||||
|
"Bash(find:*)",
|
||||||
|
"Bash(netstat:*)",
|
||||||
|
"Bash(findstr:*)",
|
||||||
|
"Bash(docker-compose up:*)",
|
||||||
|
"Bash(docker --version:*)",
|
||||||
|
"Bash(timeout 60 bash:*)",
|
||||||
|
"Bash(docker ps:*)",
|
||||||
|
"Bash(docker exec:*)",
|
||||||
|
"Bash(docker-compose down:*)",
|
||||||
|
"Bash(git rm:*)",
|
||||||
|
"Bash(git restore:*)",
|
||||||
|
"Bash(./gradlew participation-service:test:*)",
|
||||||
|
"Bash(timeout 30 bash:*)",
|
||||||
|
"Bash(helm list:*)",
|
||||||
|
"Bash(helm upgrade:*)",
|
||||||
|
"Bash(helm repo add:*)",
|
||||||
|
"Bash(helm repo update:*)",
|
||||||
|
"Bash(kubectl get:*)",
|
||||||
|
"Bash(python3:*)",
|
||||||
|
"Bash(timeout 120 bash -c 'while true; do sleep 5; kubectl get pods -n kt-event-marketing | grep kafka | grep -v Running && continue; echo \"\"\"\"All Kafka pods are Running!\"\"\"\"; break; done')",
|
||||||
|
"Bash(kubectl delete:*)",
|
||||||
|
"Bash(kubectl logs:*)",
|
||||||
|
"Bash(kubectl describe:*)",
|
||||||
|
"Bash(kubectl exec:*)",
|
||||||
|
"mcp__context7__resolve-library-id",
|
||||||
|
"mcp__context7__get-library-docs",
|
||||||
|
"Bash(python -m json.tool:*)"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|||||||
23
.gitignore
vendored
23
.gitignore
vendored
@ -20,6 +20,16 @@ Thumbs.db
|
|||||||
dist/
|
dist/
|
||||||
build/
|
build/
|
||||||
*.log
|
*.log
|
||||||
|
.gradle/
|
||||||
|
logs/
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
.gradle/
|
||||||
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
|
||||||
# Environment
|
# Environment
|
||||||
.env
|
.env
|
||||||
@ -30,3 +40,16 @@ build/
|
|||||||
tmp/
|
tmp/
|
||||||
temp/
|
temp/
|
||||||
*.tmp
|
*.tmp
|
||||||
|
|
||||||
|
# Kubernetes Secrets (민감한 정보 포함)
|
||||||
|
k8s/**/secret.yaml
|
||||||
|
k8s/**/*-secret.yaml
|
||||||
|
k8s/**/*-prod.yaml
|
||||||
|
k8s/**/*-dev.yaml
|
||||||
|
k8s/**/*-local.yaml
|
||||||
|
|
||||||
|
# IntelliJ 실행 프로파일 (민감한 환경 변수 포함 가능)
|
||||||
|
.run/*.run.xml
|
||||||
|
|
||||||
|
# Gradle (로컬 환경 설정)
|
||||||
|
gradle.properties
|
||||||
|
|||||||
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.
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.
Binary file not shown.
Binary file not shown.
@ -1,2 +0,0 @@
|
|||||||
#Thu Oct 23 17:51:21 KST 2025
|
|
||||||
gradle.version=8.10
|
|
||||||
Binary file not shown.
Binary file not shown.
69
.run/ParticipationServiceApplication.run.xml
Normal file
69
.run/ParticipationServiceApplication.run.xml
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="ParticipationServiceApplication" type="GradleRunConfiguration" factoryName="Gradle">
|
||||||
|
<ExternalSystemSettings>
|
||||||
|
<option name="env">
|
||||||
|
<map>
|
||||||
|
<!-- 서버 설정 -->
|
||||||
|
<entry key="SERVER_PORT" value="8084" />
|
||||||
|
|
||||||
|
<!-- 데이터베이스 설정 -->
|
||||||
|
<entry key="DB_HOST" value="4.230.72.147" />
|
||||||
|
<entry key="DB_PORT" value="5432" />
|
||||||
|
<entry key="DB_NAME" value="participationdb" />
|
||||||
|
<entry key="DB_USERNAME" value="eventuser" />
|
||||||
|
<entry key="DB_PASSWORD" value="Hi5Jessica!" />
|
||||||
|
|
||||||
|
<!-- JPA 설정 -->
|
||||||
|
<entry key="DDL_AUTO" value="none" />
|
||||||
|
<entry key="SHOW_SQL" value="true" />
|
||||||
|
|
||||||
|
<!-- Redis 설정 -->
|
||||||
|
<entry key="REDIS_HOST" value="20.214.210.71" />
|
||||||
|
<entry key="REDIS_PORT" value="6379" />
|
||||||
|
<entry key="REDIS_PASSWORD" value="Hi5Jessica!" />
|
||||||
|
|
||||||
|
<!-- Kafka 설정 -->
|
||||||
|
<entry key="KAFKA_BOOTSTRAP_SERVERS" value="20.249.182.13:9095,4.217.131.59:9095" />
|
||||||
|
|
||||||
|
<!-- JWT 설정 -->
|
||||||
|
<entry key="JWT_SECRET" value="kt-event-marketing-secret-key-for-development-only-change-in-production" />
|
||||||
|
<entry key="JWT_EXPIRATION" value="86400000" />
|
||||||
|
|
||||||
|
<!-- 로깅 설정 -->
|
||||||
|
<entry key="LOG_LEVEL" value="INFO" />
|
||||||
|
<entry key="LOG_FILE" value="logs/participation-service.log" />
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
<option name="executionName" />
|
||||||
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
|
<option name="externalSystemIdString" value="GRADLE" />
|
||||||
|
<option name="scriptParameters" value="" />
|
||||||
|
<option name="taskDescriptions">
|
||||||
|
<list />
|
||||||
|
</option>
|
||||||
|
<option name="taskNames">
|
||||||
|
<list>
|
||||||
|
<option value="participation-service:bootRun" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="vmOptions" />
|
||||||
|
</ExternalSystemSettings>
|
||||||
|
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||||
|
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||||
|
<EXTENSION ID="com.intellij.execution.ExternalSystemRunConfigurationJavaExtension">
|
||||||
|
<extension name="net.ashald.envfile">
|
||||||
|
<option name="IS_ENABLED" value="false" />
|
||||||
|
<option name="IS_SUBST" value="false" />
|
||||||
|
<option name="IS_PATH_MACRO_SUPPORTED" value="false" />
|
||||||
|
<option name="IS_IGNORE_MISSING_FILES" value="false" />
|
||||||
|
<option name="IS_ENABLE_EXPERIMENTAL_INTEGRATIONS" value="false" />
|
||||||
|
<ENTRIES>
|
||||||
|
<ENTRY IS_ENABLED="true" PARSER="runconfig" IS_EXECUTABLE="false" />
|
||||||
|
</ENTRIES>
|
||||||
|
</extension>
|
||||||
|
</EXTENSION>
|
||||||
|
<DebugAllEnabled>false</DebugAllEnabled>
|
||||||
|
<RunAsTest>false</RunAsTest>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"liveServer.settings.port": 5501
|
|
||||||
}
|
|
||||||
84
analytics-service/.run/analytics-service.run.xml
Normal file
84
analytics-service/.run/analytics-service.run.xml
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="analytics-service" type="GradleRunConfiguration" factoryName="Gradle">
|
||||||
|
<ExternalSystemSettings>
|
||||||
|
<option name="env">
|
||||||
|
<map>
|
||||||
|
<!-- Database Configuration -->
|
||||||
|
<entry key="DB_KIND" value="postgresql" />
|
||||||
|
<entry key="DB_HOST" value="4.230.49.9" />
|
||||||
|
<entry key="DB_PORT" value="5432" />
|
||||||
|
<entry key="DB_NAME" value="analyticdb" />
|
||||||
|
<entry key="DB_USERNAME" value="eventuser" />
|
||||||
|
<entry key="DB_PASSWORD" value="Hi5Jessica!" />
|
||||||
|
|
||||||
|
<!-- JPA Configuration -->
|
||||||
|
<entry key="DDL_AUTO" value="update" />
|
||||||
|
<entry key="SHOW_SQL" value="true" />
|
||||||
|
|
||||||
|
<!-- Redis Configuration -->
|
||||||
|
<entry key="REDIS_HOST" value="20.214.210.71" />
|
||||||
|
<entry key="REDIS_PORT" value="6379" />
|
||||||
|
<entry key="REDIS_PASSWORD" value="Hi5Jessica!" />
|
||||||
|
<entry key="REDIS_DATABASE" value="5" />
|
||||||
|
|
||||||
|
<!-- Kafka Configuration (원격 서버) -->
|
||||||
|
<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_CONSUMER_GROUP_ID" value="analytics-service-consumers" />
|
||||||
|
|
||||||
|
<!-- Sample Data Configuration (MVP Only) -->
|
||||||
|
<!-- ⚠️ Kafka Producer로 이벤트 발행 (Consumer가 처리) -->
|
||||||
|
<entry key="SAMPLE_DATA_ENABLED" value="true" />
|
||||||
|
|
||||||
|
<!-- Server Configuration -->
|
||||||
|
<entry key="SERVER_PORT" value="8086" />
|
||||||
|
|
||||||
|
<!-- JWT Configuration -->
|
||||||
|
<entry key="JWT_SECRET" value="dev-jwt-secret-key-for-development-only-kt-event-marketing" />
|
||||||
|
<entry key="JWT_ACCESS_TOKEN_VALIDITY" value="1800" />
|
||||||
|
<entry key="JWT_REFRESH_TOKEN_VALIDITY" value="86400" />
|
||||||
|
|
||||||
|
<!-- CORS Configuration -->
|
||||||
|
<entry key="CORS_ALLOWED_ORIGINS" value="http://localhost:*" />
|
||||||
|
|
||||||
|
<!-- Logging Configuration -->
|
||||||
|
<entry key="LOG_FILE" value="logs/analytics-service.log" />
|
||||||
|
<entry key="LOG_LEVEL_APP" value="DEBUG" />
|
||||||
|
<entry key="LOG_LEVEL_WEB" value="INFO" />
|
||||||
|
<entry key="LOG_LEVEL_SQL" value="DEBUG" />
|
||||||
|
<entry key="LOG_LEVEL_SQL_TYPE" value="TRACE" />
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
<option name="executionName" />
|
||||||
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
|
<option name="externalSystemIdString" value="GRADLE" />
|
||||||
|
<option name="scriptParameters" value="" />
|
||||||
|
<option name="taskDescriptions">
|
||||||
|
<list />
|
||||||
|
</option>
|
||||||
|
<option name="taskNames">
|
||||||
|
<list>
|
||||||
|
<option value="analytics-service:bootRun" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="vmOptions" />
|
||||||
|
</ExternalSystemSettings>
|
||||||
|
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||||
|
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||||
|
<EXTENSION ID="com.intellij.execution.ExternalSystemRunConfigurationJavaExtension">
|
||||||
|
<extension name="net.ashald.envfile">
|
||||||
|
<option name="IS_ENABLED" value="false" />
|
||||||
|
<option name="IS_SUBST" value="false" />
|
||||||
|
<option name="IS_PATH_MACRO_SUPPORTED" value="false" />
|
||||||
|
<option name="IS_IGNORE_MISSING_FILES" value="false" />
|
||||||
|
<option name="IS_ENABLE_EXPERIMENTAL_INTEGRATIONS" value="false" />
|
||||||
|
<ENTRIES>
|
||||||
|
<ENTRY IS_ENABLED="true" PARSER="runconfig" IS_EXECUTABLE="false" />
|
||||||
|
</ENTRIES>
|
||||||
|
</extension>
|
||||||
|
</EXTENSION>
|
||||||
|
<DebugAllEnabled>false</DebugAllEnabled>
|
||||||
|
<RunAsTest>false</RunAsTest>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
package com.kt.event.analytics;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.domain.EntityScan;
|
||||||
|
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||||
|
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||||
|
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||||
|
import org.springframework.kafka.annotation.EnableKafka;
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analytics Service 애플리케이션 메인 클래스
|
||||||
|
*
|
||||||
|
* 실시간 효과 측정 및 통합 대시보드를 제공하는 Analytics Service
|
||||||
|
*/
|
||||||
|
@SpringBootApplication(scanBasePackages = {"com.kt.event.analytics", "com.kt.event.common"})
|
||||||
|
@EntityScan(basePackages = {"com.kt.event.analytics.entity", "com.kt.event.common.entity"})
|
||||||
|
@EnableJpaRepositories(basePackages = "com.kt.event.analytics.repository")
|
||||||
|
@EnableJpaAuditing
|
||||||
|
@EnableFeignClients
|
||||||
|
@EnableKafka
|
||||||
|
@EnableScheduling
|
||||||
|
public class AnalyticsServiceApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(AnalyticsServiceApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,116 @@
|
|||||||
|
package com.kt.event.analytics.batch;
|
||||||
|
|
||||||
|
import com.kt.event.analytics.entity.EventStats;
|
||||||
|
import com.kt.event.analytics.repository.EventStatsRepository;
|
||||||
|
import com.kt.event.analytics.service.AnalyticsService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analytics 배치 스케줄러
|
||||||
|
*
|
||||||
|
* 5분 단위로 Analytics 대시보드 데이터를 갱신하는 배치 작업
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AnalyticsBatchScheduler {
|
||||||
|
|
||||||
|
private final AnalyticsService analyticsService;
|
||||||
|
private final EventStatsRepository eventStatsRepository;
|
||||||
|
private final RedisTemplate<String, String> redisTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 5분 단위 Analytics 데이터 갱신 배치
|
||||||
|
*
|
||||||
|
* - 각 이벤트마다 Redis 캐시 확인
|
||||||
|
* - 캐시 있음 → 건너뛰기 (1시간 유효)
|
||||||
|
* - 캐시 없음 → PostgreSQL + 외부 API → Redis 저장
|
||||||
|
*/
|
||||||
|
@Scheduled(fixedRate = 300000) // 5분 = 300,000ms
|
||||||
|
public void refreshAnalyticsDashboard() {
|
||||||
|
log.info("===== Analytics 배치 시작: {} =====", LocalDateTime.now());
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. 모든 활성 이벤트 조회
|
||||||
|
List<EventStats> activeEvents = eventStatsRepository.findAll();
|
||||||
|
log.info("활성 이벤트 수: {}", activeEvents.size());
|
||||||
|
|
||||||
|
// 2. 각 이벤트별로 캐시 확인 및 갱신
|
||||||
|
int successCount = 0;
|
||||||
|
int skipCount = 0;
|
||||||
|
int failCount = 0;
|
||||||
|
|
||||||
|
for (EventStats event : activeEvents) {
|
||||||
|
String cacheKey = "analytics:dashboard:" + event.getEventId();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 2-1. Redis 캐시 확인
|
||||||
|
if (redisTemplate.hasKey(cacheKey)) {
|
||||||
|
log.debug("✅ 캐시 유효, 건너뜀: eventId={}", event.getEventId());
|
||||||
|
skipCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2-2. 캐시 없음 → 데이터 갱신
|
||||||
|
log.info("캐시 만료, 갱신 시작: eventId={}, title={}",
|
||||||
|
event.getEventId(), event.getEventTitle());
|
||||||
|
|
||||||
|
// refresh=true로 호출하여 캐시 갱신 및 외부 API 호출
|
||||||
|
analyticsService.getDashboardData(event.getEventId(), null, null, true);
|
||||||
|
|
||||||
|
successCount++;
|
||||||
|
log.info("✅ 배치 갱신 완료: eventId={}", event.getEventId());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
failCount++;
|
||||||
|
log.error("❌ 배치 갱신 실패: eventId={}, error={}",
|
||||||
|
event.getEventId(), e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("===== Analytics 배치 완료: 성공={}, 건너뜀={}, 실패={}, 종료시각={} =====",
|
||||||
|
successCount, skipCount, failCount, LocalDateTime.now());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Analytics 배치 실행 중 오류 발생: {}", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 초기 데이터 로딩 (애플리케이션 시작 후 30초 뒤 1회 실행)
|
||||||
|
*
|
||||||
|
* - 서버 시작 직후 캐시 워밍업
|
||||||
|
* - 첫 API 요청 시 응답 시간 단축
|
||||||
|
*/
|
||||||
|
@Scheduled(initialDelay = 30000, fixedDelay = Long.MAX_VALUE)
|
||||||
|
public void initialDataLoad() {
|
||||||
|
log.info("===== 초기 데이터 로딩 시작: {} =====", LocalDateTime.now());
|
||||||
|
|
||||||
|
try {
|
||||||
|
List<EventStats> allEvents = eventStatsRepository.findAll();
|
||||||
|
log.info("초기 로딩 대상 이벤트 수: {}", allEvents.size());
|
||||||
|
|
||||||
|
for (EventStats event : allEvents) {
|
||||||
|
try {
|
||||||
|
analyticsService.getDashboardData(event.getEventId(), null, null, true);
|
||||||
|
log.debug("초기 데이터 로딩 완료: eventId={}", event.getEventId());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("초기 데이터 로딩 실패: eventId={}, error={}",
|
||||||
|
event.getEventId(), e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("===== 초기 데이터 로딩 완료: {} =====", LocalDateTime.now());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("초기 데이터 로딩 중 오류 발생: {}", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
package com.kt.event.analytics.config;
|
||||||
|
|
||||||
|
import org.apache.kafka.clients.consumer.ConsumerConfig;
|
||||||
|
import org.apache.kafka.common.serialization.StringDeserializer;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
|
||||||
|
import org.springframework.kafka.core.ConsumerFactory;
|
||||||
|
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kafka Consumer 설정
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnProperty(name = "spring.kafka.enabled", havingValue = "true", matchIfMissing = true)
|
||||||
|
public class KafkaConsumerConfig {
|
||||||
|
|
||||||
|
@Value("${spring.kafka.bootstrap-servers}")
|
||||||
|
private String bootstrapServers;
|
||||||
|
|
||||||
|
@Value("${spring.kafka.consumer.group-id:analytics-service}")
|
||||||
|
private String groupId;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ConsumerFactory<String, String> consumerFactory() {
|
||||||
|
Map<String, Object> props = new HashMap<>();
|
||||||
|
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
|
||||||
|
props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
|
||||||
|
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
|
||||||
|
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
|
||||||
|
props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
|
||||||
|
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, true);
|
||||||
|
return new DefaultKafkaConsumerFactory<>(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory() {
|
||||||
|
ConcurrentKafkaListenerContainerFactory<String, String> factory =
|
||||||
|
new ConcurrentKafkaListenerContainerFactory<>();
|
||||||
|
factory.setConsumerFactory(consumerFactory());
|
||||||
|
// Kafka Consumer 자동 시작 활성화
|
||||||
|
factory.setAutoStartup(true);
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
package com.kt.event.analytics.config;
|
||||||
|
|
||||||
|
import org.apache.kafka.clients.admin.NewTopic;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.kafka.config.TopicBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kafka 토픽 자동 생성 설정
|
||||||
|
*
|
||||||
|
* ⚠️ MVP 전용: 샘플 데이터용 토픽을 생성합니다.
|
||||||
|
* 실제 운영 토픽(event.created 등)과 구분하기 위해 "sample." 접두사 사용
|
||||||
|
*
|
||||||
|
* 서비스 시작 시 필요한 Kafka 토픽을 자동으로 생성합니다.
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnProperty(name = "spring.kafka.enabled", havingValue = "true", matchIfMissing = false)
|
||||||
|
public class KafkaTopicConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sample.event.created 토픽 (MVP 샘플 데이터용)
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public NewTopic eventCreatedTopic() {
|
||||||
|
return TopicBuilder.name("sample.event.created")
|
||||||
|
.partitions(3)
|
||||||
|
.replicas(1)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sample.participant.registered 토픽 (MVP 샘플 데이터용)
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public NewTopic participantRegisteredTopic() {
|
||||||
|
return TopicBuilder.name("sample.participant.registered")
|
||||||
|
.partitions(3)
|
||||||
|
.replicas(1)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sample.distribution.completed 토픽 (MVP 샘플 데이터용)
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public NewTopic distributionCompletedTopic() {
|
||||||
|
return TopicBuilder.name("sample.distribution.completed")
|
||||||
|
.partitions(3)
|
||||||
|
.replicas(1)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
package com.kt.event.analytics.config;
|
||||||
|
|
||||||
|
import io.lettuce.core.ReadFrom;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||||
|
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
|
||||||
|
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redis 캐시 설정
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class RedisConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory connectionFactory) {
|
||||||
|
RedisTemplate<String, String> template = new RedisTemplate<>();
|
||||||
|
template.setConnectionFactory(connectionFactory);
|
||||||
|
template.setKeySerializer(new StringRedisSerializer());
|
||||||
|
template.setValueSerializer(new StringRedisSerializer());
|
||||||
|
template.setHashKeySerializer(new StringRedisSerializer());
|
||||||
|
template.setHashValueSerializer(new StringRedisSerializer());
|
||||||
|
|
||||||
|
// Read-only 오류 방지: 마스터 노드 우선 사용
|
||||||
|
if (connectionFactory instanceof LettuceConnectionFactory) {
|
||||||
|
LettuceConnectionFactory lettuceFactory = (LettuceConnectionFactory) connectionFactory;
|
||||||
|
lettuceFactory.setValidateConnection(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
package com.kt.event.analytics.config;
|
||||||
|
|
||||||
|
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
|
||||||
|
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resilience4j Circuit Breaker 설정
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class Resilience4jConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public CircuitBreakerRegistry circuitBreakerRegistry() {
|
||||||
|
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
|
||||||
|
.failureRateThreshold(50)
|
||||||
|
.waitDurationInOpenState(Duration.ofSeconds(30))
|
||||||
|
.slidingWindowSize(10)
|
||||||
|
.permittedNumberOfCallsInHalfOpenState(3)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return CircuitBreakerRegistry.of(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,361 @@
|
|||||||
|
package com.kt.event.analytics.config;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.kt.event.analytics.messaging.event.DistributionCompletedEvent;
|
||||||
|
import com.kt.event.analytics.messaging.event.EventCreatedEvent;
|
||||||
|
import com.kt.event.analytics.messaging.event.ParticipantRegisteredEvent;
|
||||||
|
import com.kt.event.analytics.repository.ChannelStatsRepository;
|
||||||
|
import com.kt.event.analytics.repository.EventStatsRepository;
|
||||||
|
import com.kt.event.analytics.repository.TimelineDataRepository;
|
||||||
|
import jakarta.annotation.PreDestroy;
|
||||||
|
import jakarta.persistence.EntityManager;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.boot.ApplicationArguments;
|
||||||
|
import org.springframework.boot.ApplicationRunner;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.kafka.core.KafkaTemplate;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 샘플 데이터 로더 (Kafka Producer 방식)
|
||||||
|
*
|
||||||
|
* ⚠️ MVP 전용: 다른 마이크로서비스(Event, Participant, Distribution)가
|
||||||
|
* 없는 환경에서 해당 서비스들의 역할을 시뮬레이션합니다.
|
||||||
|
*
|
||||||
|
* ⚠️ 실제 운영: Analytics Service는 순수 Consumer 역할만 수행해야 하며,
|
||||||
|
* 이 클래스는 비활성화되어야 합니다.
|
||||||
|
* → SAMPLE_DATA_ENABLED=false 설정
|
||||||
|
*
|
||||||
|
* - 서비스 시작 시: Kafka 이벤트 발행하여 샘플 데이터 자동 생성
|
||||||
|
* - 서비스 종료 시: PostgreSQL 전체 데이터 삭제
|
||||||
|
*
|
||||||
|
* 활성화 조건: spring.sample-data.enabled=true (기본값: true)
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@ConditionalOnProperty(name = "spring.sample-data.enabled", havingValue = "true", matchIfMissing = true)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SampleDataLoader implements ApplicationRunner {
|
||||||
|
|
||||||
|
private final KafkaTemplate<String, String> kafkaTemplate;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
private final EventStatsRepository eventStatsRepository;
|
||||||
|
private final ChannelStatsRepository channelStatsRepository;
|
||||||
|
private final TimelineDataRepository timelineDataRepository;
|
||||||
|
private final EntityManager entityManager;
|
||||||
|
private final RedisTemplate<String, String> redisTemplate;
|
||||||
|
|
||||||
|
private final Random random = new Random();
|
||||||
|
|
||||||
|
// Kafka Topic Names (MVP용 샘플 토픽)
|
||||||
|
private static final String EVENT_CREATED_TOPIC = "sample.event.created";
|
||||||
|
private static final String PARTICIPANT_REGISTERED_TOPIC = "sample.participant.registered";
|
||||||
|
private static final String DISTRIBUTION_COMPLETED_TOPIC = "sample.distribution.completed";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void run(ApplicationArguments args) {
|
||||||
|
log.info("========================================");
|
||||||
|
log.info("🚀 서비스 시작: Kafka 이벤트 발행하여 샘플 데이터 생성");
|
||||||
|
log.info("========================================");
|
||||||
|
|
||||||
|
// 항상 기존 데이터 삭제 후 새로 생성
|
||||||
|
long existingCount = eventStatsRepository.count();
|
||||||
|
if (existingCount > 0) {
|
||||||
|
log.info("기존 데이터 {} 건 삭제 중...", existingCount);
|
||||||
|
timelineDataRepository.deleteAll();
|
||||||
|
channelStatsRepository.deleteAll();
|
||||||
|
eventStatsRepository.deleteAll();
|
||||||
|
|
||||||
|
// 삭제 커밋 보장
|
||||||
|
entityManager.flush();
|
||||||
|
entityManager.clear();
|
||||||
|
|
||||||
|
log.info("✅ 기존 데이터 삭제 완료");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redis 멱등성 키 삭제 (새로운 이벤트 처리를 위해)
|
||||||
|
log.info("Redis 멱등성 키 삭제 중...");
|
||||||
|
redisTemplate.delete("processed_events");
|
||||||
|
redisTemplate.delete("distribution_completed");
|
||||||
|
redisTemplate.delete("processed_participants");
|
||||||
|
log.info("✅ Redis 멱등성 키 삭제 완료");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. EventCreated 이벤트 발행 (3개 이벤트)
|
||||||
|
publishEventCreatedEvents();
|
||||||
|
log.info("⏳ EventStats 생성 대기 중... (5초)");
|
||||||
|
Thread.sleep(5000); // EventCreatedConsumer가 EventStats 생성할 시간
|
||||||
|
|
||||||
|
// 2. DistributionCompleted 이벤트 발행 (각 이벤트당 4개 채널)
|
||||||
|
publishDistributionCompletedEvents();
|
||||||
|
log.info("⏳ ChannelStats 생성 대기 중... (3초)");
|
||||||
|
Thread.sleep(3000); // DistributionCompletedConsumer가 ChannelStats 생성할 시간
|
||||||
|
|
||||||
|
// 3. ParticipantRegistered 이벤트 발행 (각 이벤트당 다수 참여자)
|
||||||
|
publishParticipantRegisteredEvents();
|
||||||
|
|
||||||
|
log.info("========================================");
|
||||||
|
log.info("🎉 Kafka 이벤트 발행 완료! (Consumer가 처리 중...)");
|
||||||
|
log.info("========================================");
|
||||||
|
log.info("발행된 이벤트:");
|
||||||
|
log.info(" - EventCreated: 3건");
|
||||||
|
log.info(" - DistributionCompleted: 3건 (각 이벤트당 4개 채널 배열)");
|
||||||
|
log.info(" - ParticipantRegistered: 180건 (MVP 테스트용)");
|
||||||
|
log.info("========================================");
|
||||||
|
|
||||||
|
// Consumer 처리 대기 (5초)
|
||||||
|
log.info("⏳ 참여자 수 업데이트 대기 중... (5초)");
|
||||||
|
Thread.sleep(5000);
|
||||||
|
|
||||||
|
// 4. TimelineData 생성 (시간대별 데이터)
|
||||||
|
createTimelineData();
|
||||||
|
log.info("✅ TimelineData 생성 완료");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("샘플 데이터 적재 중 오류 발생", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 서비스 종료 시 전체 데이터 삭제
|
||||||
|
*/
|
||||||
|
@PreDestroy
|
||||||
|
@Transactional
|
||||||
|
public void onShutdown() {
|
||||||
|
log.info("========================================");
|
||||||
|
log.info("🛑 서비스 종료: PostgreSQL 전체 데이터 삭제");
|
||||||
|
log.info("========================================");
|
||||||
|
|
||||||
|
try {
|
||||||
|
long timelineCount = timelineDataRepository.count();
|
||||||
|
long channelCount = channelStatsRepository.count();
|
||||||
|
long eventCount = eventStatsRepository.count();
|
||||||
|
|
||||||
|
log.info("삭제 대상: 이벤트={}, 채널={}, 타임라인={}",
|
||||||
|
eventCount, channelCount, timelineCount);
|
||||||
|
|
||||||
|
timelineDataRepository.deleteAll();
|
||||||
|
channelStatsRepository.deleteAll();
|
||||||
|
eventStatsRepository.deleteAll();
|
||||||
|
|
||||||
|
// 삭제 커밋 보장
|
||||||
|
entityManager.flush();
|
||||||
|
entityManager.clear();
|
||||||
|
|
||||||
|
log.info("✅ 모든 샘플 데이터 삭제 완료!");
|
||||||
|
log.info("========================================");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("샘플 데이터 삭제 중 오류 발생", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EventCreated 이벤트 발행
|
||||||
|
*/
|
||||||
|
private void publishEventCreatedEvents() throws Exception {
|
||||||
|
// 이벤트 1: 신년맞이 할인 이벤트 (진행중, 높은 성과)
|
||||||
|
EventCreatedEvent event1 = EventCreatedEvent.builder()
|
||||||
|
.eventId("evt_2025012301")
|
||||||
|
.eventTitle("신년맞이 20% 할인 이벤트")
|
||||||
|
.storeId("store_001")
|
||||||
|
.totalInvestment(new BigDecimal("5000000"))
|
||||||
|
.status("ACTIVE")
|
||||||
|
.build();
|
||||||
|
publishEvent(EVENT_CREATED_TOPIC, event1);
|
||||||
|
|
||||||
|
// 이벤트 2: 설날 특가 이벤트 (진행중, 중간 성과)
|
||||||
|
EventCreatedEvent event2 = EventCreatedEvent.builder()
|
||||||
|
.eventId("evt_2025020101")
|
||||||
|
.eventTitle("설날 특가 선물세트 이벤트")
|
||||||
|
.storeId("store_001")
|
||||||
|
.totalInvestment(new BigDecimal("3500000"))
|
||||||
|
.status("ACTIVE")
|
||||||
|
.build();
|
||||||
|
publishEvent(EVENT_CREATED_TOPIC, event2);
|
||||||
|
|
||||||
|
// 이벤트 3: 겨울 신메뉴 런칭 이벤트 (종료, 저조한 성과)
|
||||||
|
EventCreatedEvent event3 = EventCreatedEvent.builder()
|
||||||
|
.eventId("evt_2025011501")
|
||||||
|
.eventTitle("겨울 신메뉴 런칭 이벤트")
|
||||||
|
.storeId("store_001")
|
||||||
|
.totalInvestment(new BigDecimal("2000000"))
|
||||||
|
.status("COMPLETED")
|
||||||
|
.build();
|
||||||
|
publishEvent(EVENT_CREATED_TOPIC, event3);
|
||||||
|
|
||||||
|
log.info("✅ EventCreated 이벤트 3건 발행 완료");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DistributionCompleted 이벤트 발행 (설계서 기준 - 이벤트당 1번 발행, 여러 채널 배열)
|
||||||
|
*/
|
||||||
|
private void publishDistributionCompletedEvents() throws Exception {
|
||||||
|
String[] eventIds = {"evt_2025012301", "evt_2025020101", "evt_2025011501"};
|
||||||
|
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];
|
||||||
|
|
||||||
|
// 4개 채널을 배열로 구성
|
||||||
|
List<DistributionCompletedEvent.ChannelDistribution> channels = new ArrayList<>();
|
||||||
|
|
||||||
|
// 1. 우리동네TV (TV)
|
||||||
|
channels.add(DistributionCompletedEvent.ChannelDistribution.builder()
|
||||||
|
.channel("우리동네TV")
|
||||||
|
.channelType("TV")
|
||||||
|
.status("SUCCESS")
|
||||||
|
.expectedViews(expectedViews[i][0])
|
||||||
|
.build());
|
||||||
|
|
||||||
|
// 2. 지니TV (TV)
|
||||||
|
channels.add(DistributionCompletedEvent.ChannelDistribution.builder()
|
||||||
|
.channel("지니TV")
|
||||||
|
.channelType("TV")
|
||||||
|
.status("SUCCESS")
|
||||||
|
.expectedViews(expectedViews[i][1])
|
||||||
|
.build());
|
||||||
|
|
||||||
|
// 3. 링고비즈 (CALL)
|
||||||
|
channels.add(DistributionCompletedEvent.ChannelDistribution.builder()
|
||||||
|
.channel("링고비즈")
|
||||||
|
.channelType("CALL")
|
||||||
|
.status("SUCCESS")
|
||||||
|
.expectedViews(expectedViews[i][2])
|
||||||
|
.build());
|
||||||
|
|
||||||
|
// 4. SNS (SNS)
|
||||||
|
channels.add(DistributionCompletedEvent.ChannelDistribution.builder()
|
||||||
|
.channel("SNS")
|
||||||
|
.channelType("SNS")
|
||||||
|
.status("SUCCESS")
|
||||||
|
.expectedViews(expectedViews[i][3])
|
||||||
|
.build());
|
||||||
|
|
||||||
|
// 이벤트 발행 (채널 배열 포함)
|
||||||
|
DistributionCompletedEvent event = DistributionCompletedEvent.builder()
|
||||||
|
.eventId(eventId)
|
||||||
|
.distributedChannels(channels)
|
||||||
|
.completedAt(java.time.LocalDateTime.now())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
publishEvent(DISTRIBUTION_COMPLETED_TOPIC, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("✅ DistributionCompleted 이벤트 3건 발행 완료 (3 이벤트 × 4 채널 배열)");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ParticipantRegistered 이벤트 발행
|
||||||
|
*/
|
||||||
|
private void publishParticipantRegisteredEvents() throws Exception {
|
||||||
|
String[] eventIds = {"evt_2025012301", "evt_2025020101", "evt_2025011501"};
|
||||||
|
int[] totalParticipants = {100, 50, 30}; // MVP 테스트용 샘플 데이터 (총 180명)
|
||||||
|
String[] channels = {"우리동네TV", "지니TV", "링고비즈", "SNS"};
|
||||||
|
|
||||||
|
int totalPublished = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < eventIds.length; i++) {
|
||||||
|
String eventId = eventIds[i];
|
||||||
|
int participants = totalParticipants[i];
|
||||||
|
|
||||||
|
// 각 이벤트에 대해 참여자 수만큼 ParticipantRegistered 이벤트 발행
|
||||||
|
for (int j = 0; j < participants; j++) {
|
||||||
|
String participantId = UUID.randomUUID().toString();
|
||||||
|
String channel = channels[j % channels.length]; // 채널 순환 배정
|
||||||
|
|
||||||
|
ParticipantRegisteredEvent event = ParticipantRegisteredEvent.builder()
|
||||||
|
.eventId(eventId)
|
||||||
|
.participantId(participantId)
|
||||||
|
.channel(channel)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
publishEvent(PARTICIPANT_REGISTERED_TOPIC, event);
|
||||||
|
totalPublished++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("✅ ParticipantRegistered 이벤트 {}건 발행 완료", totalPublished);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TimelineData 생성 (시간대별 샘플 데이터)
|
||||||
|
*
|
||||||
|
* - 각 이벤트마다 30일 치 daily 데이터 생성
|
||||||
|
* - 참여자 수, 조회수, 참여행동, 전환수, 누적 참여자 수
|
||||||
|
*/
|
||||||
|
private void createTimelineData() {
|
||||||
|
log.info("📊 TimelineData 생성 시작...");
|
||||||
|
|
||||||
|
String[] eventIds = {"evt_2025012301", "evt_2025020101", "evt_2025011501"};
|
||||||
|
|
||||||
|
// 각 이벤트별 기준 참여자 수 (이벤트 성과에 따라 다름)
|
||||||
|
int[] baseParticipants = {20, 12, 5}; // 이벤트1(높음), 이벤트2(중간), 이벤트3(낮음)
|
||||||
|
|
||||||
|
for (int eventIndex = 0; eventIndex < eventIds.length; eventIndex++) {
|
||||||
|
String eventId = eventIds[eventIndex];
|
||||||
|
int baseParticipant = baseParticipants[eventIndex];
|
||||||
|
int cumulativeParticipants = 0;
|
||||||
|
|
||||||
|
// 30일 치 데이터 생성 (2024-09-24부터)
|
||||||
|
java.time.LocalDateTime startDate = java.time.LocalDateTime.of(2024, 9, 24, 0, 0);
|
||||||
|
|
||||||
|
for (int day = 0; day < 30; day++) {
|
||||||
|
java.time.LocalDateTime timestamp = startDate.plusDays(day);
|
||||||
|
|
||||||
|
// 랜덤한 참여자 수 생성 (기준값 ± 50%)
|
||||||
|
int dailyParticipants = baseParticipant + random.nextInt(baseParticipant + 1);
|
||||||
|
cumulativeParticipants += dailyParticipants;
|
||||||
|
|
||||||
|
// 조회수는 참여자의 3~5배
|
||||||
|
int dailyViews = dailyParticipants * (3 + random.nextInt(3));
|
||||||
|
|
||||||
|
// 참여행동은 참여자의 1~2배
|
||||||
|
int dailyEngagement = dailyParticipants * (1 + random.nextInt(2));
|
||||||
|
|
||||||
|
// 전환수는 참여자의 50~80%
|
||||||
|
int dailyConversions = (int) (dailyParticipants * (0.5 + random.nextDouble() * 0.3));
|
||||||
|
|
||||||
|
// TimelineData 생성
|
||||||
|
com.kt.event.analytics.entity.TimelineData timelineData =
|
||||||
|
com.kt.event.analytics.entity.TimelineData.builder()
|
||||||
|
.eventId(eventId)
|
||||||
|
.timestamp(timestamp)
|
||||||
|
.participants(dailyParticipants)
|
||||||
|
.views(dailyViews)
|
||||||
|
.engagement(dailyEngagement)
|
||||||
|
.conversions(dailyConversions)
|
||||||
|
.cumulativeParticipants(cumulativeParticipants)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
timelineDataRepository.save(timelineData);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("✅ TimelineData 생성 완료: eventId={}, 30일 데이터", eventId);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("✅ 전체 TimelineData 생성 완료: 3개 이벤트 × 30일 = 90건");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kafka 이벤트 발행 공통 메서드
|
||||||
|
*/
|
||||||
|
private void publishEvent(String topic, Object event) throws Exception {
|
||||||
|
String jsonMessage = objectMapper.writeValueAsString(event);
|
||||||
|
kafkaTemplate.send(topic, jsonMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,79 @@
|
|||||||
|
package com.kt.event.analytics.config;
|
||||||
|
|
||||||
|
import com.kt.event.common.security.JwtAuthenticationFilter;
|
||||||
|
import com.kt.event.common.security.JwtTokenProvider;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
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;
|
||||||
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring Security 설정
|
||||||
|
* JWT 기반 인증 및 API 보안 설정
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
private final JwtTokenProvider jwtTokenProvider;
|
||||||
|
|
||||||
|
@Value("${cors.allowed-origins:http://localhost:*}")
|
||||||
|
private String allowedOrigins;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
|
return http
|
||||||
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
|
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
||||||
|
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
|
.authorizeHttpRequests(auth -> auth
|
||||||
|
// Actuator endpoints
|
||||||
|
.requestMatchers("/actuator/**").permitAll()
|
||||||
|
// Swagger UI endpoints
|
||||||
|
.requestMatchers("/swagger-ui/**", "/swagger-ui.html", "/v3/api-docs/**", "/swagger-resources/**", "/webjars/**").permitAll()
|
||||||
|
// Health check
|
||||||
|
.requestMatchers("/health").permitAll()
|
||||||
|
// Analytics API endpoints (테스트 및 개발 용도로 공개)
|
||||||
|
.requestMatchers("/api/**").permitAll()
|
||||||
|
// All other requests require authentication
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
)
|
||||||
|
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider),
|
||||||
|
UsernamePasswordAuthenticationFilter.class)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public CorsConfigurationSource corsConfigurationSource() {
|
||||||
|
CorsConfiguration configuration = new CorsConfiguration();
|
||||||
|
|
||||||
|
String[] origins = allowedOrigins.split(",");
|
||||||
|
configuration.setAllowedOriginPatterns(Arrays.asList(origins));
|
||||||
|
|
||||||
|
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
|
||||||
|
|
||||||
|
configuration.setAllowedHeaders(Arrays.asList(
|
||||||
|
"Authorization", "Content-Type", "X-Requested-With", "Accept",
|
||||||
|
"Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers"
|
||||||
|
));
|
||||||
|
|
||||||
|
configuration.setAllowCredentials(true);
|
||||||
|
configuration.setMaxAge(3600L);
|
||||||
|
|
||||||
|
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||||
|
source.registerCorsConfiguration("/**", configuration);
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
package com.kt.event.analytics.config;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.models.Components;
|
||||||
|
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.security.SecurityRequirement;
|
||||||
|
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||||
|
import io.swagger.v3.oas.models.servers.Server;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Swagger/OpenAPI 설정
|
||||||
|
* Analytics Service API 문서화를 위한 설정
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class SwaggerConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public OpenAPI openAPI() {
|
||||||
|
return new OpenAPI()
|
||||||
|
.info(apiInfo())
|
||||||
|
.addServersItem(new Server()
|
||||||
|
.url("http://localhost:8086")
|
||||||
|
.description("Local Development"))
|
||||||
|
.addServersItem(new Server()
|
||||||
|
.url("{protocol}://{host}:{port}")
|
||||||
|
.description("Custom Server")
|
||||||
|
.variables(new io.swagger.v3.oas.models.servers.ServerVariables()
|
||||||
|
.addServerVariable("protocol", new io.swagger.v3.oas.models.servers.ServerVariable()
|
||||||
|
._default("http")
|
||||||
|
.description("Protocol (http or https)")
|
||||||
|
.addEnumItem("http")
|
||||||
|
.addEnumItem("https"))
|
||||||
|
.addServerVariable("host", new io.swagger.v3.oas.models.servers.ServerVariable()
|
||||||
|
._default("localhost")
|
||||||
|
.description("Server host"))
|
||||||
|
.addServerVariable("port", new io.swagger.v3.oas.models.servers.ServerVariable()
|
||||||
|
._default("8086")
|
||||||
|
.description("Server port"))))
|
||||||
|
.addSecurityItem(new SecurityRequirement().addList("Bearer Authentication"))
|
||||||
|
.components(new Components()
|
||||||
|
.addSecuritySchemes("Bearer Authentication", createAPIKeyScheme()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Info apiInfo() {
|
||||||
|
return new Info()
|
||||||
|
.title("Analytics Service API")
|
||||||
|
.description("실시간 효과 측정 및 통합 대시보드를 제공하는 Analytics Service API")
|
||||||
|
.version("1.0.0")
|
||||||
|
.contact(new Contact()
|
||||||
|
.name("Digital Garage Team")
|
||||||
|
.email("support@kt-event-marketing.com"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private SecurityScheme createAPIKeyScheme() {
|
||||||
|
return new SecurityScheme()
|
||||||
|
.type(SecurityScheme.Type.HTTP)
|
||||||
|
.bearerFormat("JWT")
|
||||||
|
.scheme("bearer");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,71 @@
|
|||||||
|
package com.kt.event.analytics.controller;
|
||||||
|
|
||||||
|
import com.kt.event.analytics.dto.response.AnalyticsDashboardResponse;
|
||||||
|
import com.kt.event.analytics.service.AnalyticsService;
|
||||||
|
import com.kt.event.common.dto.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analytics Dashboard Controller
|
||||||
|
*
|
||||||
|
* 이벤트 성과 대시보드 API
|
||||||
|
*/
|
||||||
|
@Tag(name = "Analytics", description = "이벤트 성과 분석 및 대시보드 API")
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/events")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AnalyticsDashboardController {
|
||||||
|
|
||||||
|
private final AnalyticsService analyticsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 성과 대시보드 조회
|
||||||
|
*
|
||||||
|
* @param eventId 이벤트 ID
|
||||||
|
* @param startDate 조회 시작 날짜
|
||||||
|
* @param endDate 조회 종료 날짜
|
||||||
|
* @param refresh 캐시 갱신 여부
|
||||||
|
* @return 성과 대시보드
|
||||||
|
*/
|
||||||
|
@Operation(
|
||||||
|
summary = "성과 대시보드 조회",
|
||||||
|
description = "이벤트의 전체 성과를 통합하여 조회합니다."
|
||||||
|
)
|
||||||
|
@GetMapping("/{eventId}/analytics")
|
||||||
|
public ResponseEntity<ApiResponse<AnalyticsDashboardResponse>> getEventAnalytics(
|
||||||
|
@Parameter(description = "이벤트 ID", required = true)
|
||||||
|
@PathVariable String eventId,
|
||||||
|
|
||||||
|
@Parameter(description = "조회 시작 날짜 (ISO 8601 format)")
|
||||||
|
@RequestParam(required = false)
|
||||||
|
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
|
||||||
|
LocalDateTime startDate,
|
||||||
|
|
||||||
|
@Parameter(description = "조회 종료 날짜 (ISO 8601 format)")
|
||||||
|
@RequestParam(required = false)
|
||||||
|
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
|
||||||
|
LocalDateTime endDate,
|
||||||
|
|
||||||
|
@Parameter(description = "캐시 갱신 여부 (true인 경우 외부 API 호출)")
|
||||||
|
@RequestParam(required = false, defaultValue = "false")
|
||||||
|
Boolean refresh
|
||||||
|
) {
|
||||||
|
log.info("성과 대시보드 조회 API 호출: eventId={}, refresh={}", eventId, refresh);
|
||||||
|
|
||||||
|
AnalyticsDashboardResponse response = analyticsService.getDashboardData(
|
||||||
|
eventId, startDate, endDate, refresh
|
||||||
|
);
|
||||||
|
|
||||||
|
return ResponseEntity.ok(ApiResponse.success(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,73 @@
|
|||||||
|
package com.kt.event.analytics.controller;
|
||||||
|
|
||||||
|
import com.kt.event.analytics.dto.response.ChannelAnalyticsResponse;
|
||||||
|
import com.kt.event.analytics.service.ChannelAnalyticsService;
|
||||||
|
import com.kt.event.common.dto.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Channel Analytics Controller
|
||||||
|
*
|
||||||
|
* 채널별 성과 분석 API
|
||||||
|
*/
|
||||||
|
@Tag(name = "Channels", description = "채널별 성과 분석 API")
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/events")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ChannelAnalyticsController {
|
||||||
|
|
||||||
|
private final ChannelAnalyticsService channelAnalyticsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 채널별 성과 분석
|
||||||
|
*
|
||||||
|
* @param eventId 이벤트 ID
|
||||||
|
* @param channels 조회할 채널 목록 (쉼표로 구분)
|
||||||
|
* @param sortBy 정렬 기준
|
||||||
|
* @param order 정렬 순서
|
||||||
|
* @return 채널별 성과 분석
|
||||||
|
*/
|
||||||
|
@Operation(
|
||||||
|
summary = "채널별 성과 분석",
|
||||||
|
description = "각 배포 채널별 성과를 상세하게 분석합니다."
|
||||||
|
)
|
||||||
|
@GetMapping("/{eventId}/analytics/channels")
|
||||||
|
public ResponseEntity<ApiResponse<ChannelAnalyticsResponse>> getChannelAnalytics(
|
||||||
|
@Parameter(description = "이벤트 ID", required = true)
|
||||||
|
@PathVariable String eventId,
|
||||||
|
|
||||||
|
@Parameter(description = "조회할 채널 목록 (쉼표로 구분, 미지정 시 전체)")
|
||||||
|
@RequestParam(required = false)
|
||||||
|
String channels,
|
||||||
|
|
||||||
|
@Parameter(description = "정렬 기준 (views, participants, engagement_rate, conversion_rate, roi)")
|
||||||
|
@RequestParam(required = false, defaultValue = "roi")
|
||||||
|
String sortBy,
|
||||||
|
|
||||||
|
@Parameter(description = "정렬 순서 (asc, desc)")
|
||||||
|
@RequestParam(required = false, defaultValue = "desc")
|
||||||
|
String order
|
||||||
|
) {
|
||||||
|
log.info("채널별 성과 분석 API 호출: eventId={}, sortBy={}", eventId, sortBy);
|
||||||
|
|
||||||
|
List<String> channelList = channels != null && !channels.isBlank()
|
||||||
|
? Arrays.asList(channels.split(","))
|
||||||
|
: null;
|
||||||
|
|
||||||
|
ChannelAnalyticsResponse response = channelAnalyticsService.getChannelAnalytics(
|
||||||
|
eventId, channelList, sortBy, order
|
||||||
|
);
|
||||||
|
|
||||||
|
return ResponseEntity.ok(ApiResponse.success(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
package com.kt.event.analytics.controller;
|
||||||
|
|
||||||
|
import com.kt.event.analytics.dto.response.RoiAnalyticsResponse;
|
||||||
|
import com.kt.event.analytics.service.RoiAnalyticsService;
|
||||||
|
import com.kt.event.common.dto.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ROI Analytics Controller
|
||||||
|
*
|
||||||
|
* 투자 대비 수익률 분석 API
|
||||||
|
*/
|
||||||
|
@Tag(name = "ROI", description = "투자 대비 수익률 분석 API")
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/events")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class RoiAnalyticsController {
|
||||||
|
|
||||||
|
private final RoiAnalyticsService roiAnalyticsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 투자 대비 수익률 상세
|
||||||
|
*
|
||||||
|
* @param eventId 이벤트 ID
|
||||||
|
* @param includeProjection 예상 수익 포함 여부
|
||||||
|
* @return ROI 상세 분석
|
||||||
|
*/
|
||||||
|
@Operation(
|
||||||
|
summary = "투자 대비 수익률 상세",
|
||||||
|
description = "이벤트의 투자 대비 수익률을 상세하게 분석합니다."
|
||||||
|
)
|
||||||
|
@GetMapping("/{eventId}/analytics/roi")
|
||||||
|
public ResponseEntity<ApiResponse<RoiAnalyticsResponse>> getRoiAnalytics(
|
||||||
|
@Parameter(description = "이벤트 ID", required = true)
|
||||||
|
@PathVariable String eventId,
|
||||||
|
|
||||||
|
@Parameter(description = "예상 수익 포함 여부")
|
||||||
|
@RequestParam(required = false, defaultValue = "true")
|
||||||
|
Boolean includeProjection
|
||||||
|
) {
|
||||||
|
log.info("ROI 상세 분석 API 호출: eventId={}, includeProjection={}", eventId, includeProjection);
|
||||||
|
|
||||||
|
RoiAnalyticsResponse response = roiAnalyticsService.getRoiAnalytics(eventId, includeProjection);
|
||||||
|
|
||||||
|
return ResponseEntity.ok(ApiResponse.success(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,82 @@
|
|||||||
|
package com.kt.event.analytics.controller;
|
||||||
|
|
||||||
|
import com.kt.event.analytics.dto.response.TimelineAnalyticsResponse;
|
||||||
|
import com.kt.event.analytics.service.TimelineAnalyticsService;
|
||||||
|
import com.kt.event.common.dto.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timeline Analytics Controller
|
||||||
|
*
|
||||||
|
* 시간대별 분석 API
|
||||||
|
*/
|
||||||
|
@Tag(name = "Timeline", description = "시간대별 분석 API")
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/events")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class TimelineAnalyticsController {
|
||||||
|
|
||||||
|
private final TimelineAnalyticsService timelineAnalyticsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 시간대별 참여 추이
|
||||||
|
*
|
||||||
|
* @param eventId 이벤트 ID
|
||||||
|
* @param interval 시간 간격 단위
|
||||||
|
* @param startDate 조회 시작 날짜
|
||||||
|
* @param endDate 조회 종료 날짜
|
||||||
|
* @param metrics 조회할 지표 목록
|
||||||
|
* @return 시간대별 참여 추이
|
||||||
|
*/
|
||||||
|
@Operation(
|
||||||
|
summary = "시간대별 참여 추이",
|
||||||
|
description = "이벤트 기간 동안의 시간대별 참여 추이를 분석합니다."
|
||||||
|
)
|
||||||
|
@GetMapping("/{eventId}/analytics/timeline")
|
||||||
|
public ResponseEntity<ApiResponse<TimelineAnalyticsResponse>> getTimelineAnalytics(
|
||||||
|
@Parameter(description = "이벤트 ID", required = true)
|
||||||
|
@PathVariable String eventId,
|
||||||
|
|
||||||
|
@Parameter(description = "시간 간격 단위 (hourly, daily, weekly)")
|
||||||
|
@RequestParam(required = false, defaultValue = "daily")
|
||||||
|
String interval,
|
||||||
|
|
||||||
|
@Parameter(description = "조회 시작 날짜 (ISO 8601 format)")
|
||||||
|
@RequestParam(required = false)
|
||||||
|
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
|
||||||
|
LocalDateTime startDate,
|
||||||
|
|
||||||
|
@Parameter(description = "조회 종료 날짜 (ISO 8601 format)")
|
||||||
|
@RequestParam(required = false)
|
||||||
|
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
|
||||||
|
LocalDateTime endDate,
|
||||||
|
|
||||||
|
@Parameter(description = "조회할 지표 목록 (쉼표로 구분)")
|
||||||
|
@RequestParam(required = false)
|
||||||
|
String metrics
|
||||||
|
) {
|
||||||
|
log.info("시간대별 참여 추이 API 호출: eventId={}, interval={}", eventId, interval);
|
||||||
|
|
||||||
|
List<String> metricList = metrics != null && !metrics.isBlank()
|
||||||
|
? Arrays.asList(metrics.split(","))
|
||||||
|
: null;
|
||||||
|
|
||||||
|
TimelineAnalyticsResponse response = timelineAnalyticsService.getTimelineAnalytics(
|
||||||
|
eventId, interval, startDate, endDate, metricList
|
||||||
|
);
|
||||||
|
|
||||||
|
return ResponseEntity.ok(ApiResponse.success(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
package com.kt.event.analytics.dto.response;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 이벤트 성과 대시보드 응답
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class AnalyticsDashboardResponse {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 이벤트 ID
|
||||||
|
*/
|
||||||
|
private String eventId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 이벤트 제목
|
||||||
|
*/
|
||||||
|
private String eventTitle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 조회 기간 정보
|
||||||
|
*/
|
||||||
|
private PeriodInfo period;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 성과 요약
|
||||||
|
*/
|
||||||
|
private AnalyticsSummary summary;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 채널별 성과 요약
|
||||||
|
*/
|
||||||
|
private List<ChannelSummary> channelPerformance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ROI 요약
|
||||||
|
*/
|
||||||
|
private RoiSummary roi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 마지막 업데이트 시간
|
||||||
|
*/
|
||||||
|
private LocalDateTime lastUpdatedAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 데이터 출처 (real-time, cached, fallback)
|
||||||
|
*/
|
||||||
|
private String dataSource;
|
||||||
|
}
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
package com.kt.event.analytics.dto.response;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 성과 요약
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class AnalyticsSummary {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 총 참여자 수
|
||||||
|
*/
|
||||||
|
private Integer totalParticipants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 총 조회수
|
||||||
|
*/
|
||||||
|
private Integer totalViews;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 총 도달 수
|
||||||
|
*/
|
||||||
|
private Integer totalReach;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 참여율 (%)
|
||||||
|
*/
|
||||||
|
private Double engagementRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전환율 (%)
|
||||||
|
*/
|
||||||
|
private Double conversionRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 평균 참여 시간 (초)
|
||||||
|
*/
|
||||||
|
private Integer averageEngagementTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SNS 반응 통계
|
||||||
|
*/
|
||||||
|
private SocialInteractionStats socialInteractions;
|
||||||
|
}
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
package com.kt.event.analytics.dto.response;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 채널별 상세 분석
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ChannelAnalytics {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 채널명
|
||||||
|
*/
|
||||||
|
private String channelName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 채널 유형
|
||||||
|
*/
|
||||||
|
private String channelType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 채널 지표
|
||||||
|
*/
|
||||||
|
private ChannelMetrics metrics;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 성과 지표
|
||||||
|
*/
|
||||||
|
private ChannelPerformance performance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 비용 정보
|
||||||
|
*/
|
||||||
|
private ChannelCosts costs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 외부 API 연동 상태 (success, fallback, failed)
|
||||||
|
*/
|
||||||
|
private String externalApiStatus;
|
||||||
|
}
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
package com.kt.event.analytics.dto.response;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 채널별 성과 분석 응답
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ChannelAnalyticsResponse {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 이벤트 ID
|
||||||
|
*/
|
||||||
|
private String eventId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 채널별 상세 분석
|
||||||
|
*/
|
||||||
|
private List<ChannelAnalytics> channels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 채널 간 비교 분석
|
||||||
|
*/
|
||||||
|
private ChannelComparison comparison;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 마지막 업데이트 시간
|
||||||
|
*/
|
||||||
|
private LocalDateTime lastUpdatedAt;
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
package com.kt.event.analytics.dto.response;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 채널 간 비교 분석
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ChannelComparison {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 최고 성과 채널
|
||||||
|
*/
|
||||||
|
private Map<String, String> bestPerforming;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전체 채널 평균 지표
|
||||||
|
*/
|
||||||
|
private Map<String, Double> averageMetrics;
|
||||||
|
}
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
package com.kt.event.analytics.dto.response;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 채널별 비용
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ChannelCosts {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 배포 비용 (원)
|
||||||
|
*/
|
||||||
|
private BigDecimal distributionCost;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 조회당 비용 (CPV, 원)
|
||||||
|
*/
|
||||||
|
private Double costPerView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 클릭당 비용 (CPC, 원)
|
||||||
|
*/
|
||||||
|
private Double costPerClick;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 고객 획득 비용 (CPA, 원)
|
||||||
|
*/
|
||||||
|
private Double costPerAcquisition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ROI (%)
|
||||||
|
*/
|
||||||
|
private Double roi;
|
||||||
|
}
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
package com.kt.event.analytics.dto.response;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 채널 지표
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ChannelMetrics {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 노출 수
|
||||||
|
*/
|
||||||
|
private Integer impressions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 조회수
|
||||||
|
*/
|
||||||
|
private Integer views;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 클릭 수
|
||||||
|
*/
|
||||||
|
private Integer clicks;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 참여자 수
|
||||||
|
*/
|
||||||
|
private Integer participants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전환 수
|
||||||
|
*/
|
||||||
|
private Integer conversions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SNS 반응 통계
|
||||||
|
*/
|
||||||
|
private SocialInteractionStats socialInteractions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 링고비즈 통화 통계
|
||||||
|
*/
|
||||||
|
private VoiceCallStats voiceCallStats;
|
||||||
|
}
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
package com.kt.event.analytics.dto.response;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 채널 성과 지표
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ChannelPerformance {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 클릭률 (CTR, %)
|
||||||
|
*/
|
||||||
|
private Double clickThroughRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 참여율 (%)
|
||||||
|
*/
|
||||||
|
private Double engagementRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전환율 (%)
|
||||||
|
*/
|
||||||
|
private Double conversionRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 평균 참여 시간 (초)
|
||||||
|
*/
|
||||||
|
private Integer averageEngagementTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 이탈율 (%)
|
||||||
|
*/
|
||||||
|
private Double bounceRate;
|
||||||
|
}
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
package com.kt.event.analytics.dto.response;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 채널별 성과 요약
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ChannelSummary {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 채널명
|
||||||
|
*/
|
||||||
|
private String channelName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 조회수
|
||||||
|
*/
|
||||||
|
private Integer views;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 참여자 수
|
||||||
|
*/
|
||||||
|
private Integer participants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 참여율 (%)
|
||||||
|
*/
|
||||||
|
private Double engagementRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전환율 (%)
|
||||||
|
*/
|
||||||
|
private Double conversionRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ROI (%)
|
||||||
|
*/
|
||||||
|
private Double roi;
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
package com.kt.event.analytics.dto.response;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 비용 효율성
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class CostEfficiency {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 참여자당 비용 (원)
|
||||||
|
*/
|
||||||
|
private Double costPerParticipant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전환당 비용 (원)
|
||||||
|
*/
|
||||||
|
private Double costPerConversion;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 조회당 비용 (원)
|
||||||
|
*/
|
||||||
|
private Double costPerView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 참여자당 수익 (원)
|
||||||
|
*/
|
||||||
|
private Double revenuePerParticipant;
|
||||||
|
}
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
package com.kt.event.analytics.dto.response;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 투자 비용 상세
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class InvestmentDetails {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 콘텐츠 제작비 (원)
|
||||||
|
*/
|
||||||
|
private BigDecimal contentCreation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 배포 비용 (원)
|
||||||
|
*/
|
||||||
|
private BigDecimal distribution;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 운영 비용 (원)
|
||||||
|
*/
|
||||||
|
private BigDecimal operation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 총 투자 비용 (원)
|
||||||
|
*/
|
||||||
|
private BigDecimal total;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 채널별 비용 상세
|
||||||
|
*/
|
||||||
|
private List<Map<String, Object>> breakdown;
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
package com.kt.event.analytics.dto.response;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 피크 타임 정보
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class PeakTimeInfo {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 피크 시간
|
||||||
|
*/
|
||||||
|
private LocalDateTime timestamp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 피크 지표 (participants, views, engagement, conversions)
|
||||||
|
*/
|
||||||
|
private String metric;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 피크 값
|
||||||
|
*/
|
||||||
|
private Integer value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 피크 설명
|
||||||
|
*/
|
||||||
|
private String description;
|
||||||
|
}
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
package com.kt.event.analytics.dto.response;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 조회 기간 정보
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class PeriodInfo {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 조회 시작 날짜
|
||||||
|
*/
|
||||||
|
private LocalDateTime startDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 조회 종료 날짜
|
||||||
|
*/
|
||||||
|
private LocalDateTime endDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 기간 (일)
|
||||||
|
*/
|
||||||
|
private Integer durationDays;
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
package com.kt.event.analytics.dto.response;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 수익 상세
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class RevenueDetails {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 직접 매출 (원)
|
||||||
|
*/
|
||||||
|
private BigDecimal directSales;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 예상 추가 매출 (원)
|
||||||
|
*/
|
||||||
|
private BigDecimal expectedSales;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 브랜드 가치 향상 추정액 (원)
|
||||||
|
*/
|
||||||
|
private BigDecimal brandValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 총 수익 (원)
|
||||||
|
*/
|
||||||
|
private BigDecimal total;
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
package com.kt.event.analytics.dto.response;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 수익 예측
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class RevenueProjection {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 현재 누적 수익 (원)
|
||||||
|
*/
|
||||||
|
private BigDecimal currentRevenue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 예상 최종 수익 (원)
|
||||||
|
*/
|
||||||
|
private BigDecimal projectedFinalRevenue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 예측 신뢰도 (%)
|
||||||
|
*/
|
||||||
|
private Double confidenceLevel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 예측 기반
|
||||||
|
*/
|
||||||
|
private String basedOn;
|
||||||
|
}
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
package com.kt.event.analytics.dto.response;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ROI 상세 분석 응답
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class RoiAnalyticsResponse {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 이벤트 ID
|
||||||
|
*/
|
||||||
|
private String eventId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 투자 비용 상세
|
||||||
|
*/
|
||||||
|
private InvestmentDetails investment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 수익 상세
|
||||||
|
*/
|
||||||
|
private RevenueDetails revenue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ROI 계산
|
||||||
|
*/
|
||||||
|
private RoiCalculation roi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 비용 효율성
|
||||||
|
*/
|
||||||
|
private CostEfficiency costEfficiency;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 수익 예측
|
||||||
|
*/
|
||||||
|
private RevenueProjection projection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 마지막 업데이트 시간
|
||||||
|
*/
|
||||||
|
private LocalDateTime lastUpdatedAt;
|
||||||
|
}
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
package com.kt.event.analytics.dto.response;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ROI 계산
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class RoiCalculation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 순이익 (원)
|
||||||
|
*/
|
||||||
|
private BigDecimal netProfit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ROI (%)
|
||||||
|
*/
|
||||||
|
private Double roiPercentage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 손익분기점 도달 시점
|
||||||
|
*/
|
||||||
|
private LocalDateTime breakEvenPoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 투자 회수 기간 (일)
|
||||||
|
*/
|
||||||
|
private Integer paybackPeriod;
|
||||||
|
}
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
package com.kt.event.analytics.dto.response;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ROI 요약
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class RoiSummary {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 총 투자 비용 (원)
|
||||||
|
*/
|
||||||
|
private BigDecimal totalInvestment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 예상 매출 증대 (원)
|
||||||
|
*/
|
||||||
|
private BigDecimal expectedRevenue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 순이익 (원)
|
||||||
|
*/
|
||||||
|
private BigDecimal netProfit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ROI (%)
|
||||||
|
*/
|
||||||
|
private Double roi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 고객 획득 비용 (CPA, 원)
|
||||||
|
*/
|
||||||
|
private Double costPerAcquisition;
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
package com.kt.event.analytics.dto.response;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SNS 반응 통계
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class SocialInteractionStats {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 좋아요 수
|
||||||
|
*/
|
||||||
|
private Integer likes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 댓글 수
|
||||||
|
*/
|
||||||
|
private Integer comments;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 공유 수
|
||||||
|
*/
|
||||||
|
private Integer shares;
|
||||||
|
}
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
package com.kt.event.analytics.dto.response;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 시간대별 참여 추이 응답
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class TimelineAnalyticsResponse {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 이벤트 ID
|
||||||
|
*/
|
||||||
|
private String eventId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 시간 간격 (hourly, daily, weekly)
|
||||||
|
*/
|
||||||
|
private String interval;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 시간대별 데이터
|
||||||
|
*/
|
||||||
|
private List<TimelineDataPoint> dataPoints;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 추세 분석
|
||||||
|
*/
|
||||||
|
private TrendAnalysis trends;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 피크 타임 정보
|
||||||
|
*/
|
||||||
|
private List<PeakTimeInfo> peakTimes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 마지막 업데이트 시간
|
||||||
|
*/
|
||||||
|
private LocalDateTime lastUpdatedAt;
|
||||||
|
}
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
package com.kt.event.analytics.dto.response;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 시간대별 데이터 포인트
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class TimelineDataPoint {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 시간
|
||||||
|
*/
|
||||||
|
private LocalDateTime timestamp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 참여자 수
|
||||||
|
*/
|
||||||
|
private Integer participants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 조회수
|
||||||
|
*/
|
||||||
|
private Integer views;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 참여 행동 수
|
||||||
|
*/
|
||||||
|
private Integer engagement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전환 수
|
||||||
|
*/
|
||||||
|
private Integer conversions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 누적 참여자 수
|
||||||
|
*/
|
||||||
|
private Integer cumulativeParticipants;
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
package com.kt.event.analytics.dto.response;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 추세 분석
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class TrendAnalysis {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전체 추세 (increasing, stable, decreasing)
|
||||||
|
*/
|
||||||
|
private String overallTrend;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 증가율 (%)
|
||||||
|
*/
|
||||||
|
private Double growthRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 예상 참여자 수 (기간 종료 시점)
|
||||||
|
*/
|
||||||
|
private Integer projectedParticipants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 피크 기간
|
||||||
|
*/
|
||||||
|
private String peakPeriod;
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
package com.kt.event.analytics.dto.response;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 링고비즈 음성 통화 통계
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class VoiceCallStats {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 총 통화 수
|
||||||
|
*/
|
||||||
|
private Integer totalCalls;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 완료된 통화 수
|
||||||
|
*/
|
||||||
|
private Integer completedCalls;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 평균 통화 시간 (초)
|
||||||
|
*/
|
||||||
|
private Integer averageDuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 통화 완료율 (%)
|
||||||
|
*/
|
||||||
|
private Double completionRate;
|
||||||
|
}
|
||||||
@ -0,0 +1,128 @@
|
|||||||
|
package com.kt.event.analytics.entity;
|
||||||
|
|
||||||
|
import com.kt.event.common.entity.BaseTimeEntity;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 채널별 통계 엔티티
|
||||||
|
*
|
||||||
|
* 각 배포 채널별 성과 데이터를 저장
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "channel_stats", indexes = {
|
||||||
|
@Index(name = "idx_event_id", columnList = "event_id"),
|
||||||
|
@Index(name = "idx_event_channel", columnList = "event_id, channel_name")
|
||||||
|
})
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
public class ChannelStats extends BaseTimeEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 이벤트 ID
|
||||||
|
*/
|
||||||
|
@Column(name = "event_id", nullable = false, length = 50)
|
||||||
|
private String eventId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 채널명 (우리동네TV, 지니TV, 링고비즈, SNS)
|
||||||
|
*/
|
||||||
|
@Column(name = "channel_name", nullable = false, length = 50)
|
||||||
|
private String channelName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 채널 유형
|
||||||
|
*/
|
||||||
|
@Column(name = "channel_type", length = 30)
|
||||||
|
private String channelType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 노출 수
|
||||||
|
*/
|
||||||
|
@Column(nullable = false)
|
||||||
|
@Builder.Default
|
||||||
|
private Integer impressions = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 조회수
|
||||||
|
*/
|
||||||
|
@Column(nullable = false)
|
||||||
|
@Builder.Default
|
||||||
|
private Integer views = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 클릭 수
|
||||||
|
*/
|
||||||
|
@Column(nullable = false)
|
||||||
|
@Builder.Default
|
||||||
|
private Integer clicks = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 참여자 수
|
||||||
|
*/
|
||||||
|
@Column(nullable = false)
|
||||||
|
@Builder.Default
|
||||||
|
private Integer participants = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전환 수
|
||||||
|
*/
|
||||||
|
@Column(nullable = false)
|
||||||
|
@Builder.Default
|
||||||
|
private Integer conversions = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 배포 비용 (원)
|
||||||
|
*/
|
||||||
|
@Column(name = "distribution_cost", precision = 15, scale = 2)
|
||||||
|
@Builder.Default
|
||||||
|
private BigDecimal distributionCost = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 좋아요 수 (SNS 전용)
|
||||||
|
*/
|
||||||
|
@Builder.Default
|
||||||
|
private Integer likes = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 댓글 수 (SNS 전용)
|
||||||
|
*/
|
||||||
|
@Builder.Default
|
||||||
|
private Integer comments = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 공유 수 (SNS 전용)
|
||||||
|
*/
|
||||||
|
@Builder.Default
|
||||||
|
private Integer shares = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 통화 수 (링고비즈 전용)
|
||||||
|
*/
|
||||||
|
@Column(name = "total_calls")
|
||||||
|
@Builder.Default
|
||||||
|
private Integer totalCalls = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 완료된 통화 수 (링고비즈 전용)
|
||||||
|
*/
|
||||||
|
@Column(name = "completed_calls")
|
||||||
|
@Builder.Default
|
||||||
|
private Integer completedCalls = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 평균 통화 시간 (초) (링고비즈 전용)
|
||||||
|
*/
|
||||||
|
@Column(name = "average_duration")
|
||||||
|
@Builder.Default
|
||||||
|
private Integer averageDuration = 0;
|
||||||
|
}
|
||||||
@ -0,0 +1,106 @@
|
|||||||
|
package com.kt.event.analytics.entity;
|
||||||
|
|
||||||
|
import com.kt.event.common.entity.BaseTimeEntity;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 이벤트 통계 엔티티
|
||||||
|
*
|
||||||
|
* Kafka Event Subscription을 통해 실시간으로 업데이트되는 이벤트 통계 정보
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "event_stats")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
public class EventStats extends BaseTimeEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 이벤트 ID
|
||||||
|
*/
|
||||||
|
@Column(nullable = false, unique = true, length = 50)
|
||||||
|
private String eventId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 이벤트 제목
|
||||||
|
*/
|
||||||
|
@Column(nullable = false, length = 200)
|
||||||
|
private String eventTitle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 매장 ID (소유자)
|
||||||
|
*/
|
||||||
|
@Column(nullable = false, length = 50)
|
||||||
|
private String storeId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 총 참여자 수
|
||||||
|
*/
|
||||||
|
@Column(nullable = false)
|
||||||
|
@Builder.Default
|
||||||
|
private Integer totalParticipants = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 총 노출 수 (모든 채널의 노출 수 합계)
|
||||||
|
*/
|
||||||
|
@Column(nullable = false)
|
||||||
|
@Builder.Default
|
||||||
|
private Integer totalViews = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 예상 ROI (%)
|
||||||
|
*/
|
||||||
|
@Column(precision = 10, scale = 2)
|
||||||
|
@Builder.Default
|
||||||
|
private BigDecimal estimatedRoi = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 매출 증가율 (%)
|
||||||
|
*/
|
||||||
|
@Column(precision = 10, scale = 2)
|
||||||
|
@Builder.Default
|
||||||
|
private BigDecimal salesGrowthRate = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 총 투자 비용 (원)
|
||||||
|
*/
|
||||||
|
@Column(precision = 15, scale = 2)
|
||||||
|
@Builder.Default
|
||||||
|
private BigDecimal totalInvestment = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 예상 수익 (원)
|
||||||
|
*/
|
||||||
|
@Column(precision = 15, scale = 2)
|
||||||
|
@Builder.Default
|
||||||
|
private BigDecimal expectedRevenue = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 이벤트 상태
|
||||||
|
*/
|
||||||
|
@Column(length = 20)
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 참여자 수 증가
|
||||||
|
*/
|
||||||
|
public void incrementParticipants() {
|
||||||
|
this.totalParticipants++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 참여자 수 증가 (특정 수)
|
||||||
|
*/
|
||||||
|
public void incrementParticipants(int count) {
|
||||||
|
this.totalParticipants += count;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,75 @@
|
|||||||
|
package com.kt.event.analytics.entity;
|
||||||
|
|
||||||
|
import com.kt.event.common.entity.BaseTimeEntity;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 시간대별 데이터 엔티티
|
||||||
|
*
|
||||||
|
* 이벤트 기간 동안의 시간대별 참여 추이 데이터
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "timeline_data", indexes = {
|
||||||
|
@Index(name = "idx_event_timestamp", columnList = "event_id, timestamp")
|
||||||
|
})
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
public class TimelineData extends BaseTimeEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 이벤트 ID
|
||||||
|
*/
|
||||||
|
@Column(name = "event_id", nullable = false, length = 50)
|
||||||
|
private String eventId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 시간 (집계 기준 시간)
|
||||||
|
*/
|
||||||
|
@Column(nullable = false)
|
||||||
|
private LocalDateTime timestamp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 참여자 수
|
||||||
|
*/
|
||||||
|
@Column(nullable = false)
|
||||||
|
@Builder.Default
|
||||||
|
private Integer participants = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 조회수
|
||||||
|
*/
|
||||||
|
@Column(nullable = false)
|
||||||
|
@Builder.Default
|
||||||
|
private Integer views = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 참여 행동 수
|
||||||
|
*/
|
||||||
|
@Column(nullable = false)
|
||||||
|
@Builder.Default
|
||||||
|
private Integer engagement = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전환 수
|
||||||
|
*/
|
||||||
|
@Column(nullable = false)
|
||||||
|
@Builder.Default
|
||||||
|
private Integer conversions = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 누적 참여자 수
|
||||||
|
*/
|
||||||
|
@Column(name = "cumulative_participants", nullable = false)
|
||||||
|
@Builder.Default
|
||||||
|
private Integer cumulativeParticipants = 0;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user