곡μ λ μ¬κΈ°μ, μ°¨λ¨μ μ κΈ°μ? NO!!
κ°μ‘± λ°μ΄ν° 곡μ + μ¬μ© μ μ΄, ν©μ΄μ§ κΈ°λ₯μ νλμ ν΅ν© μλΉμ€λ‘
ADMIN-BE λ ν¬μ§ν 리λ κ°μ‘± 곡μ λ°μ΄ν°/μ°¨λ¨ μ μ±
/μ¬μ©λμ μ΄μμκ° μμ νκ² κ΄λ¦¬ν μ μλλ‘ μ€κ³λ λ°±μλμ
λλ€.
λμ©λ κ°μ
μ/νμ λ°μ΄ν°λ₯Ό κΈ°λ°μΌλ‘ κ°μ‘± μ‘°ν, κ°μ‘± μμ² μΉμΈ, μ μ±
μ΄μ, μ¬μ©λ μ‘°ν, μ νλ²νΈ λ³΄νΈ μ²λ¦¬, outbox μ΄λ²€νΈ λ°νκΉμ§ μ΄μ μμ€ν
μ ν΅μ¬ νλ¦μ μ 곡ν©λλ€.
π HotSpot Admin-BE: κ΄λ¦¬μ νμ΄μ§
- π κ°μ
- π₯ κ΄λ¦¬μ κΆν λ° μν
- β¨ νμ¬ μ 곡 κΈ°λ₯
- π οΈ ν΅μ¬ μ΄μ μ μ±
- ποΈ κΈ°μ μ μ€κ³
πΎ λ°μ΄ν°λ² μ΄μ€ λ° μ΄μ ν¬μΈνΈ
π κ΄λ¦¬μ νμ΄μ§ μ΄μ ν¬μΈνΈ
κ°μ‘± μ΄μ, μ μ± μ΄μ, μ¬μ©λ μ΄μμ ν΅ν©νλ ν΅μ¬ κ΄λ¦¬μ API μλΉμ€
μλΉμ€μ μ΄μμ μ μ (Admin Web)μ μ§μνλ λ°±μλ μλ²
- κ΄λ¦¬μ λ‘κ·ΈμΈ λ° JWT μΏ ν€ κΈ°λ° μΈμ¦
- κ°μ‘± λͺ©λ‘/κ²μ/μμ½ μ‘°ν
- κ°μ‘± ꡬμ±μλ³ μ μ΄ μν λ° μ μ± μ μ© μν κ΄λ¦¬
- μκ° μ μ± / μ± μ μ± μμ±, μ‘°ν, νμ±ν/λΉνμ±ν, μμ
- κ°μ‘± μμ±/μΆκ°/μμ μμ² μ‘°ν λ° μΉμΈ/λ°λ €
- κ°μ‘±/νμ λ¨μ λ°μ΄ν° μ¬μ©λ μ‘°ν
- μ΄μ μ ν©μ± μ€μ¬: κ°μ‘± μ μ± , μμ² μΉμΈ, μ μ± νμ± μνλ₯Ό μ€μ μ΄μ κ·μΉμ λ§μΆ° μ°κ²°
- κ°μΈμ 보 보νΈ: μ νλ²νΈλ ν΄μ κΈ°λ° κ²μ + 볡νΈν ν λ§μ€νΉ μλ΅
- μ€λ°μ΄ν° λμ μνΈν:
subscription_keyκΈ°λ° DEK 볡νΈν ꡬ쑰 μ μ© - μ½κΈ° μ±λ₯ κ³ λ €: κ°μ‘± λͺ©λ‘/μμ½/κ²μ 쿼리 μ΅μ ν λ° μΈλ±μ€ μ λ΅ λ°μ
- νμ₯ κ°λ₯ν ꡬ쑰: Controller / Service / Port / Infrastructure λΆλ¦¬
λ―Όκ° μ‘μ μ μ ν©μ±κ³Ό μΆμ κ°λ₯μ±μ μ μ λ‘ μ΄μν©λλ€.
- κ΄λ¦¬μ μ½λ κΈ°λ° λ‘κ·ΈμΈ
- κ°μ‘± μ‘°ν λ° μ νλ²νΈ κΈ°λ° κ²μ
- κ°μ‘± ꡬμ±μ μ μ΄ μν μμ
- κ°μ‘± ꡬμ±μ μκ°/μ± μ μ± μ μ© μν μμ
- μ μ± μμ±/νμ±ν/λΉνμ±ν/μμ
- κ°μ‘± μμ±/μΆκ°/μμ μμ² μΉμΈ λ° λ°λ €
- κ΄λ¦¬μ λ‘κ·ΈμΈ
- JWT access token λ°κΈ
HttpOnlyμΏ ν€ κΈ°λ° μΈμ¦
- κ°μ‘± λͺ©λ‘ μ‘°ν
- κ°μ‘± μμΈ μμ½ μ‘°ν
- μ νλ²νΈ κΈ°λ° κ°μ‘± κ²μ
- κ°μ‘± μ μ΄ μν μ‘°ν
- ꡬμ±μλ³ λ°μ΄ν° νλ / μ°¨λ¨ μν / μν μμ
- κ°μ‘± μ°μ μμ νμ (FIFO / PRIORITY) μμ
- μκ° μ μ± λͺ©λ‘ μ‘°ν
- μ± μ μ± λͺ©λ‘ μ‘°ν
- μκ° μ μ± μμ±
- μ± μ μ± μμ±
- μ μ± νμ± / λΉνμ± λ³κ²½
- μ μ± μμ
- ꡬμ±μλ³ μκ° μ μ± μ μ© νν© μ‘°ν
- ꡬμ±μλ³ μ± μ μ± μ μ© νν© μ‘°ν
- ꡬμ±μλ³ μκ° μ μ± μ μ© μ¬λΆ μμ
- ꡬμ±μλ³ μ± μ μ± μ μ© μ¬λΆ μμ
- κ°μ‘± μμ±/μΆκ°/μμ μμ² λͺ©λ‘ μ‘°ν
- μμ² μΉμΈ / λ°λ €
- κ°μ‘± λ¨μ μ¬μ©λ μ‘°ν
- νμ λ¨μ μ¬μ©λ μ‘°ν
- Redis κΈ°λ° μ¬μ©λ/μ λ¬Ό λ°μ΄ν° μ§κ³ μ‘°ν
- ν
νλ¦Ώ μ μ±
(
BLOCK_POLICY,APP_BLOCKED_SERVICE): μ΄μ νμ€ μ μ± λ±λ‘/κ΄λ¦¬ - νμ μ μ© μ μ±
(
POLICY_SUB,BLOCKED_SERVICE_SUB): μ€μ ꡬμ±μλ³ μ μ± νμ± μν λ°μ
- μμ² μν:
PENDING,APPROVED,REJECTED,CANCELED - μμ² λͺ©λ‘μ νμ¬
family_apply_idκΈ°μ€ μ€λ¦μ°¨μ μ λ ¬ - μΉμΈ/λ°λ €λ κ°μ‘± μ΄μ λ°μ΄ν°μ νμ μ²λ¦¬ νλ¦μΌλ‘ μ΄μ΄μ§
- μ νλ²νΈλ
phone_enc+phone_hashꡬ쑰 - κ²μμ ν΄μ κΈ°λ°
- μλ΅ νμλ 볡νΈν ν λ§μ€νΉ
- 볡νΈνλ
decryptPhone(encryptedPhone, subId)ꡬ쑰 μ¬μ©
subscription.phone_key_bucket_id,subscription.phone_key_versionμ¬μ©subscription_key(bucket_id, key_version, encrypted_dek, kek_key_id, status)μ°Έμ‘°ENCRYPTION_PROVIDER=local|kmsλΆκΈ° μ§μgcm:prefix μ°μ 볡νΈν- legacy CBC fallback μ§μ
- λΉνμ± μ± μ μ± μ κ°μ‘± μ μ± μ‘°ν κ²°κ³Όμμ μ μΈ
- λΉνμ± μ± μ μ± μ κ°μ‘± μ μ± μ μ© λμμΌλ‘ μΈμ νμ§ μμ
- μ± μ μ±
λΉνμ±ν μ κΈ°μ‘΄
blocked_service_subμ°κ²°λ μΌκ΄ λΉνμ±ν
flowchart LR
A[Admin Frontend] -->|JWT Cookie| B[HOTSPOT-ADMIN-BE]
B --> C[(PostgreSQL)]
B --> D[(Redis)]
B --> E[Outbox Events]
F[Dummy Data Generator] -->|Seed Data| C
flowchart TD
C1[Controller] --> S1[Service]
S1 --> P1[Port Interface]
P1 --> I1[Infrastructure]
I1 --> DB[(PostgreSQL / Redis)]
- Java 17
- Spring Boot 3
- Spring Web / Validation / Security
- Spring Data JPA / Redis
- PostgreSQL
- Redis
- JWT
- AWS SDK KMS
- JUnit5 / Mockito
erDiagram
MEMBER{
BIGINT member_id PK
VARCHAR(10) name
VARCHAR(6) birth
VARCHAR(10) status
BOOL is_deleted
DATETIME created_time
DATETIME modified_time
}
FAMILY {
BIGINT family_id PK
INTEGER family_num
BIGINT family_data_amount
ENUM priority_type
BOOL is_deleted
DATETIME created_time
DATETIME modified_time
}
FAMILY_SUB {
BIGINT family_sub_id PK
BIGINT sub_id FK
BIGINT family_id FK
ENUM family_role
INTEGER priority
BIGINT data_limit
}
SUBSCRIPTION {
BIGINT sub_id PK
BIGINT plan_id FK
BIGINT member_id FK
VARCHAR(255) phone_enc
VARCHAR(64) phone_hash
INTEGER phone_key_bucket_id
INTEGER phone_key_version
BOOL is_locked
BOOL is_deleted
DATETIME created_time
DATETIME modified_time
}
SUBSCRIPTION_KEY {
BIGINT subscription_key_id PK
INTEGER bucket_id
INTEGER key_version
TEXT encrypted_dek
VARCHAR(255) kek_key_id
VARCHAR(20) status
DATETIME created_time
DATETIME modified_time
}
PLAN {
BIGINT plan_id PK
VARCHAR(20) plan_name
BIGINT plan_data_amount
VARCHAR(10) data_period
BOOL is_deleted
DATETIME created_time
DATETIME modified_time
}
SOCIAL_ACCOUNT {
BIGINT social_account_id PK
BIGINT member_id FK
VARCHAR(50) email
VARCHAR(50) social_id
VARCHAR(10) provider
BOOL is_deleted
DATETIME created_time
DATETIME modified_time
}
APP_BLOCKED_SERVICE {
BIGINT app_blocked_service_id PK
VARCHAR(30) blocked_service_name
VARCHAR(30) blocked_service_code
BOOL is_active
BOOL is_deleted
}
BLOCKED_SERVICE_SUB {
BIGINT blocked_service_sub_id PK
BIGINT sub_id FK
BIGINT blocked_service_id FK
BOOL is_active
}
BLOCK_POLICY {
BIGINT block_policy_id PK
VARCHAR(30) policy_name
ENUM policy_type
JSON policy_snapshot
BOOL is_active
BOOL is_deleted
}
POLICY_SUB {
BIGINT policy_sub_id PK
BIGINT sub_id FK
BIGINT block_policy_id FK
BOOL is_active
}
FAMILY_APPLY {
BIGINT family_apply_id PK
BIGINT requester_sub_id FK
BIGINT family_id FK
ENUM apply_type
VARCHAR(255) doc_url
ENUM status
DATETIME created_time
DATETIME modified_time
}
FAMILY_APPLY_TARGET {
BIGINT family_apply_target_id PK
BIGINT family_apply_id FK
BIGINT target_sub_id FK
ENUM target_family_role
}
FAMILY_REMOVE_SCHEDULE {
BIGINT family_remove_schedule_id PK
BIGINT target_sub_id FK
BIGINT family_id FK
ENUM status
DATE schedule_date
DATETIME created_time
DATETIME modified_time
}
NOTIFICATION {
BIGINT notification_id PK
BIGINT sub_id FK
VARCHAR(50) notification_type
VARCHAR(100) notification_title
VARCHAR(200) notification_content
DATETIME created_time
BOOL is_read
VARCHAR(100) event_id
}
NOTIFICATION_ALLOW {
BIGINT notification_allow_id PK
BIGINT sub_id FK
VARCHAR(20) notification_category
BOOL notification_allow
BOOL is_deleted
DATETIME created_time
DATETIME modified_time
}
PRESENT_DATA {
BIGINT present_data_id PK
BIGINT target_sub_id FK
BIGINT provide_sub_id FK
BIGINT data_amount
DATETIME created_time
}
MEMBER ||--o{ SUBSCRIPTION : owns
MEMBER ||--o{ SOCIAL_ACCOUNT : has
PLAN ||--o{ SUBSCRIPTION : provides
FAMILY ||--o{ FAMILY_SUB : has
SUBSCRIPTION ||--o{ FAMILY_SUB : mapped
SUBSCRIPTION ||--o{ NOTIFICATION : receives
SUBSCRIPTION ||--o{ NOTIFICATION_ALLOW : configures
SUBSCRIPTION ||--o{ PRESENT_DATA : target_sub
SUBSCRIPTION ||--o{ PRESENT_DATA : provide_sub
SUBSCRIPTION ||--o{ POLICY_SUB : applies
BLOCK_POLICY ||--o{ POLICY_SUB : mapped
SUBSCRIPTION ||--o{ BLOCKED_SERVICE_SUB : applies
APP_BLOCKED_SERVICE ||--o{ BLOCKED_SERVICE_SUB : mapped
FAMILY ||--o{ FAMILY_APPLY : owns
FAMILY_APPLY ||--o{ FAMILY_APPLY_TARGET : has
SUBSCRIPTION ||--o{ FAMILY_APPLY : requester
SUBSCRIPTION ||--o{ FAMILY_APPLY_TARGET : target
FAMILY ||--o{ FAMILY_REMOVE_SCHEDULE : schedules
SUBSCRIPTION ||--o{ FAMILY_REMOVE_SCHEDULE : target
| μκΈμ λͺ | λ°μ΄ν° μ 곡λ | μ 곡λ κΈ°μ€ |
|---|---|---|
| 5G μκ·Έλμ² | 무μ ν | MONTH |
| 5G μ€ν λ€λ | 150GB | MONTH |
| 5G λ² μ΄μ§+ | 24GB | MONTH |
| LTE λ°μ΄ν° 33 | 1.5GB | MONTH |
| LTE λ€μ΄λ νΈ 45 | 1GB | DAY |
| μλΉμ€λͺ | μλΉμ€ μ½λ |
|---|---|
| μΉ΄μΉ΄μ€ν‘ | MSG_KAKAO |
| λΌμΈ | MSG_LINE |
| YouTube | MEDIA_YOUTUBE |
| Netflix | MEDIA_NETFLIX |
| μΉμ§μ§ | MEDIA_CHZZK |
| SOOP | MEDIA_SOOP |
SNS_INSTAGRAM |
|
| TikTok | SNS_TIKTOK |
SNS_FACEBOOK |
|
| EBS | STUDY_EBS |
| λ©κ°μ€ν°λ | STUDY_MEGA |
| μ λΉνΈ | FIN_UPBIT |
| ν€μμ¦κΆ | FIN_KIWOOM |
| Chrome | WEB_CHROME |
| Safari | WEB_SAFARI |
| λ‘€ν μ²΄μ€ | GAME_TFT |
| λ°°νκ·ΈλΌμ΄λ | GAME_PUBG |
| λ€μ΄λ² μΉν° | TOON_NAVER |
| μΉ΄μΉ΄μ€ μΉν° | TOON_KAKAO |
| μ μ± λͺ | μ μ± μ ν | μ€λͺ |
|---|---|---|
| μλ©΄ λͺ¨λ | SCHEDULED |
λ§€μΌ μ§μ ν μλ©΄ μκ° λμ μ± μ¬μ©μ μ νν΄ κ·μΉμ μΈ μνμ λλ μ μ± |
| λ°©ν΄ κΈμ§ λͺ¨λ | ONCE |
μΌμ μκ° λμ μ¦μ μ± μ¬μ©μ μ°¨λ¨ν΄ μ§μ€μ΄ νμν μκ°μ μ§μνλ μ μ± |
| μμ μ§μ€ λͺ¨λ | SCHEDULED |
νμΌ μμ μκ°μ λ§μΆ° μ± μ¬μ©μ μλ μ νν΄ νμ΅ μ§μ€λλ₯Ό λμ΄λ μ μ± |
| μν κΈ°κ° μ§μ€ λͺ¨λ | ONCE |
μν λλΉ κΈ°κ°μ μ₯μκ° μ± μ¬μ©μ μ νν΄ νμ΅ λͺ°μ μ κ°ννλ μ μ± |
μλ©΄ λͺ¨λ
{
"days": ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"],
"startTime": "00:00",
"endTime": "07:00"
}λ°©ν΄ κΈμ§ λͺ¨λ
{
"durationMinutes": 180
}μμ μ§μ€ λͺ¨λ
{
"days": ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY"],
"startTime": "09:00",
"endTime": "14:00"
}μν κΈ°κ° μ§μ€ λͺ¨λ
{
"startTime": "06:00",
"endTime": "23:59"
}.generator-dummyκΈ°μ€ μ΄ μ¬μ©μ 1,000,000λͺ / κ°μ‘± 250,000κ° λ°μ΄ν°λ₯Ό κΈ°μ€μΌλ‘ ꡬμ±- κ°μ‘± ꡬμ±μ 2~8μΈ, μν μ
OWNER / PARENT / CHILD - κ°μ‘± λ°μ΄ν° 곡μ μ μ±
μ
FIFOλλPRIORITY - μ μ± ν νλ¦Ώμ κ΄λ¦¬μ ν νλ¦Ώ λ³΅μ¬ / 컀μ€ν°λ§μ΄μ¦ / μ κ· μμ± λ°©μμΌλ‘ κ°μ‘±μ λ§€ν
- μ€λ°μ΄ν° μ ν©μ±μ μν΄
subscription_keyκΈ°λ° λ³΅νΈν μ¬μ©
- μκ° μ μ± / μ± μ μ± μ λ³λ ν νλ¦ΏμΌλ‘ μ΄μ
- μ μ± νμ± / λΉνμ± μνλ₯Ό κ°μ‘± μ μ© μ‘°νμ μ°κ²°
- μ± μ μ± λΉνμ±ν μ κ°μ‘± μ μ© λ°μ΄ν°κΉμ§ ν¨κ» μ 리
- κ°μ‘± λ¨μ / νμ λ¨μ μ¬μ©λ μ‘°ν μ§μ
- Redis κΈ°λ°μΌλ‘ μ¬μ©λ λ°μ΄ν°λ₯Ό λΉ λ₯΄κ² μ‘°ν
- μ λ¬Ό λ°μ΄ν°μ μκΈμ λ°μ΄ν°λμ ν¨κ» κ³μ°
- κ°μ‘± λͺ©λ‘ / μμ½ / κ²μ μ§μ
- κ°μ‘± μμΈ μ μ΄ μν λ° μ μ± μν μ‘°ν μ§μ
- κ°μ‘± μμ±/μΆκ°/μμ μμ² μΉμΈ νλ¦ μ§μ
- κ°μ‘± ꡬμ±μλ³ μ°¨λ¨/νλ/μ°μ μμ μμ μ§μ
- κ΄λ¦¬μ λ‘κ·ΈμΈμ JWT μΏ ν€ κΈ°λ°μΌλ‘ λμ
- outbox κΈ°λ° νμ μ΄λ²€νΈ λ°ν ꡬ쑰 ν¬ν¨
- μ΄μ νκ²½μμλ μΏ ν€ domain / secure / sameSite / CORS μ ν©μ± μ κ² νμ
./gradlew bootRun./gradlew compileJava --no-daemon./gradlew test --no-daemon/swagger-ui/index.html
μ΄ νλ‘μ νΈλ κ°μ‘± μ΄μ, μ μ± μ΄μ, μ¬μ©λ μ΄μ, κ°μΈμ 보 보νΈλ₯Ό μ€μ μ΄μ κ·μΉμ λ§μΆ° λ¬Άμ΄λΈ κ΄λ¦¬μ λ°±μλμ λλ€.