Skip to content

da-bom/dabom-api-core

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

487 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Dabom API Core

๊ฐ€์กฑ ๋‹จ์œ„ ๋””์ง€ํ„ธ ์‚ฌ์šฉ๋Ÿ‰ ๊ด€๋ฆฌ ์„œ๋น„์Šค๋ฅผ ์œ„ํ•œ Spring Boot ๋ฐฑ์—”๋“œ์ž…๋‹ˆ๋‹ค. ๊ณ ๊ฐ/๊ด€๋ฆฌ์ž ์ธ์ฆ, ๊ฐ€์กฑ ๋ฐ ์ •์ฑ… ๊ด€๋ฆฌ, ๋ฏธ์…˜-๋ณด์ƒ, ์ด์˜์ œ๊ธฐ, ์‚ฌ์šฉ๋Ÿ‰ ์ง‘๊ณ„, ์›”๊ฐ„ ๋ฆฌ์บก, ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ, ์•Œ๋ฆผ ์ด๋ฒคํŠธ ๋ฐœํ–‰์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค.

Overview ๐Ÿš€

  • Language: Java 21
  • Framework: Spring Boot 3.4
  • DB: MySQL, Redis
  • Messaging: Kafka
  • Storage: Cloudflare R2(S3 ํ˜ธํ™˜)
  • Docs: Springdoc OpenAPI, Swagger UI
  • Observability: Actuator, Prometheus, OTLP(OpenTelemetry)

ํŒจํ‚ค์ง€ ๊ตฌ์กฐ ๐Ÿ—‚๏ธ

src/main/java/com/project ๊ธฐ์ค€ ์ฃผ์š” ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค.

com.project
โ”œโ”€ Application.java
โ”œโ”€ common
โ”‚  โ”œโ”€ api.response        # ๊ณตํ†ต API ์‘๋‹ต ํฌ๋งท
โ”‚  โ”œโ”€ auth                # JWT, ์ธํ„ฐ์…‰ํ„ฐ, ์ธ์ฆ ์ปจํ…์ŠคํŠธ, ๊ถŒํ•œ AOP
โ”‚  โ”œโ”€ config              # Web, Redis, JPA, Swagger, R2, ์‹œ๊ฐ„ ์„ค์ •
โ”‚  โ”œโ”€ exception           # ๊ณตํ†ต ์˜ˆ์™ธ, ์—๋Ÿฌ ์ฝ”๋“œ, ์ „์—ญ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ
โ”‚  โ””โ”€ util                # ๊ณตํ†ต ์œ ํ‹ธ๋ฆฌํ‹ฐ
โ””โ”€ domain
   โ”œโ”€ admin              # ๊ด€๋ฆฌ์ž ์ธ์ฆ, ๋Œ€์‹œ๋ณด๋“œ
   โ”œโ”€ appeal             # ์ •์ฑ… ์ด์˜์ œ๊ธฐ, ๋Œ“๊ธ€, ๊ธด๊ธ‰ ์ฟผํ„ฐ
   โ”œโ”€ customer           # ๊ณ ๊ฐ ์ธ์ฆ, ๋งˆ์ดํŽ˜์ด์ง€, ๋‚ด ์ •๋ณด
   โ”œโ”€ eventoutbox        # ์•Œ๋ฆผ ์ด๋ฒคํŠธ outbox ์ €์žฅ ๋ฐ Kafka ๋ฐœํ–‰
   โ”œโ”€ family             # ๊ฐ€์กฑ ์ •๋ณด, ๊ตฌ์„ฑ์›, ๊ด€๋ฆฌ์ž์šฉ ๊ฐ€์กฑ ๊ด€๋ฆฌ
   โ”œโ”€ mission            # ๋ฏธ์…˜ ์ƒ์„ฑ/์กฐํšŒ/์š”์ฒญ/๋กœ๊ทธ/์ด๋ ฅ
   โ”œโ”€ policy             # ์ •์ฑ… ํ…œํ”Œ๋ฆฟ, ๊ฐ€์กฑ ์ •์ฑ… ์กฐํšŒ/์ˆ˜์ •
   โ”œโ”€ recap              # ์›”๊ฐ„ ๊ฐ€์กฑ ๋ฆฌ์บก ์กฐํšŒ
   โ”œโ”€ reward             # ๋ณด์ƒ ํ…œํ”Œ๋ฆฟ, ๋ณด์ƒ ์Šน์ธ/์ง€๊ธ‰/์ˆ˜๋ น ์ด๋ ฅ
   โ”œโ”€ upload             # ๊ด€๋ฆฌ์ž ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ
   โ””โ”€ usagerecord        # ๊ฐ€์กฑ/๊ตฌ์„ฑ์› ์‚ฌ์šฉ๋Ÿ‰ ์ง‘๊ณ„

๋„๋ฉ”์ธ ํŒจํ‚ค์ง€๋Š” ๋Œ€์ฒด๋กœ ์•„๋ž˜ ๋ ˆ์ด์–ด๋ฅผ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค.

  • controller: HTTP API ์ง„์ž…์ 
  • service: ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง
  • repository: DB ์กฐํšŒ/์ €์žฅ
  • entity: JPA ์—”ํ‹ฐํ‹ฐ
  • dto: ์š”์ฒญ/์‘๋‹ต DTO
  • model: ์„œ๋น„์Šค ๋ฐ˜ํ™˜ ๋ชจ๋ธ
  • enums: ๋„๋ฉ”์ธ ์ƒํƒœ๊ฐ’

ํ•ต์‹ฌ ๊ธฐ๋Šฅ โš™๏ธ

1. ๊ณ ๊ฐ/๊ด€๋ฆฌ์ž ์ธ์ฆ ๐Ÿ”

  • ๊ณ ๊ฐ: ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ, ํ† ํฐ ์žฌ๋ฐœ๊ธ‰, ๋‚ด ์ •๋ณด, ๋งˆ์ดํŽ˜์ด์ง€ ์กฐํšŒ
  • ๊ด€๋ฆฌ์ž: ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ, ํ† ํฐ ์žฌ๋ฐœ๊ธ‰, ๋‚ด ์ •๋ณด, ๋Œ€์‹œ๋ณด๋“œ ์กฐํšŒ
  • JWT ๊ธฐ๋ฐ˜ ์ธ์ฆ์ด๋ฉฐ ๋Œ€๋ถ€๋ถ„์˜ API๋Š” LoginInterceptor์—์„œ ํ† ํฐ์„ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
  • ๊ด€๋ฆฌ์ž ์ „์šฉ API๋Š” @AdminOnly, ๋ณดํ˜ธ์ž ์ „์šฉ API๋Š” @OwnerOnly๋กœ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.
flowchart TD
    A[Client] --> B[Controller]
    B --> C[Service]
    C --> D{์‚ฌ์šฉ์ž ์กด์žฌ ๋ฐ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ผ์น˜}
    D -- No --> E[ApplicationException]
    D -- Yes --> F[์—ญํ•  ์กฐํšŒ]
    F --> G[Access/Refresh Token ๋ฐœ๊ธ‰]
    G --> H[ApiResponse.success]
Loading

2. ๊ฐ€์กฑ ๋ฐ ์ •์ฑ… ๊ด€๋ฆฌ ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ

  • ๋ณดํ˜ธ์ž๋Š” ๊ฐ€์กฑ ์ด๋ฆ„์„ ๋ณ€๊ฒฝํ•˜๊ณ  ๊ฐ€์กฑ ๊ตฌ์„ฑ์›์„ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋ณดํ˜ธ์ž๋Š” ๊ฐ€์กฑ ๊ตฌ์„ฑ์›๋ณ„ ์ •์ฑ…์„ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ •์ฑ… ๋ณ€๊ฒฝ ์‹œ DB ๋ฐ˜์˜๋ฟ ์•„๋‹ˆ๋ผ Redis ์ •์ฑ… ๋™๊ธฐํ™”์™€ ์•Œ๋ฆผ ๋ฐœํ–‰์ด ํ•จ๊ป˜ ์ˆ˜ํ–‰๋ฉ๋‹ˆ๋‹ค.
  • ์›”๊ฐ„ ์ œํ•œ ์ •์ฑ…์€ CustomerQuota๋ฅผ ํ•จ๊ป˜ ๊ฐฑ์‹ ํ•˜๊ณ , ์ˆ˜๋™ ์ฐจ๋‹จ ์ •์ฑ…์€ ์ฐจ๋‹จ ์ƒํƒœ๊นŒ์ง€ ๋ฐ˜์˜ํ•ฉ๋‹ˆ๋‹ค.
sequenceDiagram
    participant Owner as Owner
    participant Controller as FamilyPolicyController
    participant Service as FamilyPolicyService
    participant Assignment as PolicyAssignmentRepository
    participant Redis as PolicyRedisService
    participant Quota as CustomerQuotaRepository
    participant Outbox as NotificationOutboxPublisher

    Owner->>Controller: PATCH /families/policies
    Controller->>Service: updateMemberPolicy(...)
    Service->>Service: OWNER ๊ถŒํ•œ ๊ฒ€์ฆ
    Service->>Assignment: ์ •์ฑ… ํ• ๋‹น ์กฐํšŒ
    Assignment-->>Service: PolicyAssignment
    Service->>Assignment: ์ •์ฑ… ๋ณ€๊ฒฝ ์ €์žฅ
    Service->>Redis: Redis ์ •์ฑ… ๋™๊ธฐํ™”

    alt MONTHLY_LIMIT
        Service->>Quota: ์›”๊ฐ„ ํ•œ๋„ ๊ฐฑ์‹ 
    else MANUAL_BLOCK
        Service->>Quota: ์ฐจ๋‹จ/ํ•ด์ œ ์ƒํƒœ ๋ฐ˜์˜
    else TIME_BLOCK / APP_BLOCK
        Service->>Service: ์•Œ๋ฆผ ๋ฉ”์‹œ์ง€ ์ค€๋น„
    end

    Service->>Outbox: ์•Œ๋ฆผ ์ด๋ฒคํŠธ ์ ์žฌ
    Outbox-->>Controller: ์™„๋ฃŒ
    Controller-->>Owner: ApiResponse.success
Loading

3. ๋ฏธ์…˜ ์ƒ์„ฑ๊ณผ ๋ณด์ƒ ์Šน์ธ ๐ŸŽฏ

  • ๋ณดํ˜ธ์ž๋Š” ๋ฏธ์…˜์„ ์ƒ์„ฑํ•˜๊ณ  ์ทจ์†Œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋ฉค๋ฒ„๋Š” ์ž์‹ ์—๊ฒŒ ํ• ๋‹น๋œ ๋ฏธ์…˜์— ๋Œ€ํ•ด ์™„๋ฃŒ ์š”์ฒญ์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋ณดํ˜ธ์ž๋Š” ์š”์ฒญ์„ ์Šน์ธ/๊ฑฐ์ ˆํ•  ์ˆ˜ ์žˆ๊ณ , ์Šน์ธ ์‹œ RewardGrant๊ฐ€ ๋ฐœ๊ธ‰๋ฉ๋‹ˆ๋‹ค.
  • ๋ฏธ์…˜/๋ณด์ƒ ํ๋ฆ„์€ ๋กœ๊ทธ ์ €์žฅ๊ณผ ์•Œ๋ฆผ ๋ฐœํ–‰์„ ํ•จ๊ป˜ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
sequenceDiagram
    participant Owner as Owner
    participant Member as Member
    participant Mission as MissionService
    participant Reward as RewardService
    participant MissionRepo as MissionItemRepository
    participant RequestRepo as MissionRequestRepository
    participant GrantRepo as RewardGrantRepository
    participant LogRepo as MissionLogRepository
    participant Outbox as NotificationOutboxPublisher

    Owner->>Mission: ๋ฏธ์…˜ ์ƒ์„ฑ ์š”์ฒญ
    Mission->>Mission: ๋Œ€์ƒ ๋ฉค๋ฒ„/๊ฐ€์กฑ ๊ฒ€์ฆ
    Mission->>MissionRepo: MissionItem ์ €์žฅ
    Mission->>LogRepo: MISSION_CREATED ๋กœ๊ทธ ์ €์žฅ
    Mission->>Outbox: ๋ฏธ์…˜ ์ƒ์„ฑ ์•Œ๋ฆผ ์ ์žฌ

    Member->>Mission: ๋ฏธ์…˜ ์™„๋ฃŒ ์š”์ฒญ
    Mission->>Mission: ๋Œ€์ƒ์ž/์ƒํƒœ/์ค‘๋ณต ์š”์ฒญ ๊ฒ€์ฆ
    Mission->>RequestRepo: MissionRequest ์ €์žฅ
    Mission->>LogRepo: MISSION_REQUESTED ๋กœ๊ทธ ์ €์žฅ
    Mission->>Outbox: ๋ณดํ˜ธ์ž ์•Œ๋ฆผ ์ ์žฌ

    Owner->>Reward: ๋ณด์ƒ ์Šน์ธ/๊ฑฐ์ ˆ
    Reward->>RequestRepo: MissionRequest ์ž ๊ธˆ ์กฐํšŒ
    Reward->>MissionRepo: MissionItem ์ž ๊ธˆ ์กฐํšŒ

    alt ์Šน์ธ
        Reward->>GrantRepo: RewardGrant ๋ฐœ๊ธ‰
        Reward->>LogRepo: MISSION_COMPLETED ๋กœ๊ทธ ์ €์žฅ
        Reward->>Outbox: ์Šน์ธ ์•Œ๋ฆผ ์ ์žฌ
    else ๊ฑฐ์ ˆ
        Reward->>RequestRepo: rejectReason ์ €์žฅ
        Reward->>Outbox: ๊ฑฐ์ ˆ ์•Œ๋ฆผ ์ ์žฌ
    end
Loading

4. ์ด์˜์ œ๊ธฐ์™€ ๊ธด๊ธ‰ ์ฟผํ„ฐ ๐Ÿ“

  • ๋ฉค๋ฒ„๋Š” ๋ณธ์ธ์—๊ฒŒ ์ ์šฉ๋œ ์ •์ฑ…์— ๋Œ€ํ•ด ์ด์˜์ œ๊ธฐ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋ณดํ˜ธ์ž๋Š” ์ด์˜์ œ๊ธฐ๋ฅผ ์Šน์ธ/๊ฑฐ์ ˆํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ์Šน์ธ ์‹œ ์ •์ฑ… ์ ์šฉ๊ฐ’์ด ์ฆ‰์‹œ ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค.
  • ๋ฉค๋ฒ„๋Š” ์›” 1ํšŒ ๊ธด๊ธ‰ ์ฟผํ„ฐ๋ฅผ ์š”์ฒญํ•  ์ˆ˜ ์žˆ๊ณ , ์Šน์ธ ์ฆ‰์‹œ ๊ฐœ์ธ ์›”๊ฐ„ ์ œํ•œ๋Ÿ‰์ด ์ฆ๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
sequenceDiagram
    participant Member as Member
    participant Owner as Owner
    participant Controller as AppealController
    participant Service as AppealService
    participant AppealRepo as PolicyAppealRepository
    participant AssignmentRepo as PolicyAssignmentRepository
    participant QuotaRepo as CustomerQuotaRepository
    participant Outbox as NotificationOutboxPublisher

    Member->>Controller: ์ด์˜์ œ๊ธฐ ์ƒ์„ฑ
    Controller->>Service: createAppeal(...)
    Service->>Service: MEMBER ๊ถŒํ•œ ๋ฐ ์†Œ์† ๊ฒ€์ฆ
    Service->>AssignmentRepo: PolicyAssignment ์กฐํšŒ
    Service->>AppealRepo: ์ค‘๋ณต PENDING ๊ฒ€์‚ฌ
    Service->>AppealRepo: PolicyAppeal ์ €์žฅ
    Service->>Outbox: ๋ณดํ˜ธ์ž ์•Œ๋ฆผ ์ ์žฌ

    Owner->>Controller: ์ด์˜์ œ๊ธฐ ์‘๋‹ต
    Controller->>Service: respondAppeal(...)
    Service->>AppealRepo: Appeal ์กฐํšŒ

    alt ์Šน์ธ
        Service->>AssignmentRepo: ์ •์ฑ… rules/isActive ๋ฐ˜์˜
        Service->>Outbox: ์Šน์ธ ์•Œ๋ฆผ ์ ์žฌ
    else ๊ฑฐ์ ˆ
        Service->>AppealRepo: rejectReason ์ €์žฅ
        Service->>Outbox: ๊ฑฐ์ ˆ ์•Œ๋ฆผ ์ ์žฌ
    end

    Member->>Controller: ๊ธด๊ธ‰ ์ฟผํ„ฐ ์š”์ฒญ
    Controller->>Service: requestEmergencyQuota(...)
    Service->>QuotaRepo: ์ด๋ฒˆ ๋‹ฌ CustomerQuota ์กฐํšŒ
    Service->>AppealRepo: EMERGENCY appeal ์ €์žฅ
    Service->>QuotaRepo: ์›”๊ฐ„ ํ•œ๋„ ์ฆ๊ฐ€
    Service->>AssignmentRepo: MONTHLY_LIMIT ์ •์ฑ… ๋™๊ธฐํ™”
    Service->>Outbox: ๋ณดํ˜ธ์ž ์•Œ๋ฆผ ์ ์žฌ
Loading

5. ์‚ฌ์šฉ๋Ÿ‰/๋ฆฌ์บก ์กฐํšŒ ๐Ÿ“Š

  • ๊ฐ€์กฑ ํ˜„์žฌ ์ด ์‚ฌ์šฉ๋Ÿ‰, ๊ฐ€์กฑ ๊ตฌ์„ฑ์›๋ณ„ ์›”๊ฐ„ ์‚ฌ์šฉ๋Ÿ‰ ์š”์•ฝ, ๋Œ€์‹œ๋ณด๋“œ์šฉ ์ƒ์„ธ ๋ถ„ํฌ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
  • ์›”๊ฐ„ ๋ฆฌ์บก์€ DB์— ์ €์žฅ๋œ ์Šค๋ƒ…์ƒท JSON์„ ์—ญ์ง๋ ฌํ™”ํ•ด ์‘๋‹ตํ•ฉ๋‹ˆ๋‹ค.
flowchart TD
    A[Client] --> B{์กฐํšŒ ๋Œ€์ƒ}
    B -- ๊ฐ€์กฑ ์‚ฌ์šฉ๋Ÿ‰ --> C[FamilyQuota/๊ตฌ์„ฑ์› ์‚ฌ์šฉ๋Ÿ‰ ์กฐํšŒ]
    B -- ์›”๊ฐ„ ๋ฆฌ์บก --> D[FamilyRecapMonthly ์กฐํšŒ]
    D --> E[JSON Snapshot ์—ญ์ง๋ ฌํ™”]
    C --> F[์‘๋‹ต ๋ชจ๋ธ ์กฐํ•ฉ]
    E --> F
    F --> G[ApiResponse.success]
Loading

6. ์•Œ๋ฆผ ์ด๋ฒคํŠธ Outbox ๐Ÿ“ฌ

  • ๋ฏธ์…˜ ์ƒ์„ฑ, ๋ณด์ƒ ์š”์ฒญ/์Šน์ธ, ์ •์ฑ… ๋ณ€๊ฒฝ, ์ด์˜์ œ๊ธฐ ์ƒ์„ฑ/์‘๋‹ต ๋“ฑ ์ฃผ์š” ์ด๋ฒคํŠธ๋Š” ๋ฐ”๋กœ Kafka๋กœ๋งŒ ๋ณด๋‚ด์ง€ ์•Š๊ณ  Outbox ํ…Œ์ด๋ธ”์— ๋จผ์ € ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
  • ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹ ์ดํ›„ PUBLISH_PENDING ์ƒํƒœ์˜ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœํ–‰ํ•˜๊ณ , ์„ฑ๊ณต ์‹œ SENT๋กœ ๋งˆํ‚นํ•ฉ๋‹ˆ๋‹ค.
  • ๋ฐœํ–‰ ์‹คํŒจ ์‹œ row๋ฅผ ์œ ์ง€ํ•˜๋ฏ€๋กœ ์žฌ์‹œ๋„ ์ „๋žต์„ ๋ถ™์ด๊ธฐ ์ข‹์€ ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค.
sequenceDiagram
    participant Domain as Domain Service
    participant Publisher as NotificationOutboxPublisher
    participant OutboxRepo as EventOutboxRepository
    participant Kafka as Kafka
    participant Status as EventOutboxStatusService

    Domain->>Publisher: enqueueAndPublishAfterCommit(payload)
    Publisher->>OutboxRepo: outbox insert(PUBLISH_PENDING)

    alt ํŠธ๋žœ์žญ์…˜ ๋‚ด๋ถ€
        Publisher->>Publisher: afterCommit ํ›… ๋“ฑ๋ก
        Publisher->>Kafka: ์ปค๋ฐ‹ ํ›„ ๋ฐœํ–‰ ์‹œ๋„
    else ํŠธ๋žœ์žญ์…˜ ์™ธ๋ถ€
        Publisher->>Kafka: ์ฆ‰์‹œ ๋ฐœํ–‰ ์‹œ๋„
    end

    alt ๋ฐœํ–‰ ์„ฑ๊ณต
        Publisher->>Status: markSent(outboxId)
    else ๋ฐœํ–‰ ์‹คํŒจ
        Publisher->>Publisher: PUBLISH_PENDING ์œ ์ง€
    end
Loading

์ด ๋ ˆํฌ์—์„œ ๋ˆˆ์—ฌ๊ฒจ๋ณผ ๊ตฌ์กฐ ๐Ÿ‘€

์ •์ฑ… ๋ณ€๊ฒฝ์ด Redis + DB + ์•Œ๋ฆผ์œผ๋กœ ์ด์–ด์ง€๋Š” ์ƒํƒœ ๋™๊ธฐํ™” ๊ตฌ์กฐ ๐Ÿ”„

  • ์ •์ฑ… ์ˆ˜์ •์€ ๋‹จ์ˆœํžˆ policy_assignment๋งŒ ๋ฐ”๊พธ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • FamilyPolicyServiceImpl์—์„œ DB ์ €์žฅ ํ›„ Redis ์ •์ฑ…์„ ๋™๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค.
  • ์ •์ฑ… ํƒ€์ž…์— ๋”ฐ๋ผ customer_quota๊นŒ์ง€ ํ•จ๊ป˜ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.
  • ๋งˆ์ง€๋ง‰์œผ๋กœ ์‚ฌ์šฉ์ž์—๊ฒŒ ์•Œ๋ฆผ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœํ–‰ํ•ฉ๋‹ˆ๋‹ค.
flowchart LR
    A[์ •์ฑ… ์ˆ˜์ • ์š”์ฒญ] --> B[DB ์ €์žฅ]
    B --> C[Redis ์ •์ฑ… ๋™๊ธฐํ™”]
    C --> D[Quota ์ƒํƒœ ๋ฐ˜์˜]
    D --> E[์•Œ๋ฆผ Outbox ์ ์žฌ]
Loading

๋ฏธ์…˜/๋ณด์ƒ/์ด์˜์ œ๊ธฐ์—์„œ cursor ๊ธฐ๋ฐ˜ ๋ชฉ๋ก ์กฐํšŒ ์‚ฌ์šฉ โ†”๏ธ

  • missions, missions/logs, missions/history, appeals, rewards/received ๋“ฑ์€ cursor ๊ธฐ๋ฐ˜ ํŽ˜์ด์ง€๋„ค์ด์…˜์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ๋ณดํ†ต size + 1๊ฐœ๋ฅผ ์กฐํšŒํ•œ ๋’ค hasNext์™€ nextCursor๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
  • ์ปค์„œ ํŒŒ์‹ฑ ์‹คํŒจ ์‹œ ๋„๋ฉ”์ธ๋ณ„ ์˜ˆ์™ธ ์ฝ”๋“œ๋กœ ์‘๋‹ตํ•ฉ๋‹ˆ๋‹ค.
flowchart TD
    A[Request cursor,size] --> B[์ปค์„œ ํŒŒ์‹ฑ]
    B --> C[size + 1 ์กฐํšŒ]
    C --> D{pageSize ์ดˆ๊ณผ ์—ฌ๋ถ€}
    D -- Yes --> E[nextCursor ๊ณ„์‚ฐ]
    D -- No --> F[nextCursor null]
    E --> G[hasNext=true]
    F --> H[hasNext=false]
Loading

์•Œ๋ฆผ ์ด๋ฒคํŠธ๋ฅผ ์œ„ํ•œ Outbox ํŒจํ„ด ์‚ฌ์šฉ ๐Ÿ“ฆ

  • ๋„๋ฉ”์ธ ํŠธ๋žœ์žญ์…˜๊ณผ ๋ฉ”์‹œ์ง€ ๋ฐœํ–‰์˜ ์›์ž์„ฑ์„ ๋А์Šจํ•˜๊ฒŒ ๋งž์ถ”๊ธฐ ์œ„ํ•œ ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค.
  • ๋จผ์ € Outbox์— ์ ์žฌํ•˜๊ณ  ์ปค๋ฐ‹ ์ดํ›„ ๋ฐœํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  • ์žฌ์‹œ๋„ ๊ฐ€๋Šฅ์„ฑ์„ ์œ„ํ•ด ์‹คํŒจ ์‹œ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.

๋ฆฌ์บก ์‘๋‹ต์— JSON ์Šค๋ƒ…์ƒท ์—ญ์ง๋ ฌํ™” ์‚ฌ์šฉ ๐Ÿงฉ

  • FamilyRecapMonthly๋Š” ์š”์ผ๋ณ„ ์‚ฌ์šฉ๋Ÿ‰, ํ”ผํฌ ์‚ฌ์šฉ ์‹œ๊ฐ„, ๋ฏธ์…˜/์ด์˜์ œ๊ธฐ ์š”์•ฝ ๋“ฑ์˜ JSON ์Šค๋ƒ…์ƒท์„ ๋ฌธ์ž์—ด๋กœ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
  • RecapServiceImpl์—์„œ ์š”์ฒญ ์‹œ์ ์— ์—ญ์ง๋ ฌํ™”ํ•ด ์‘๋‹ต ๋ชจ๋ธ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
flowchart LR
    A[FamilyRecapMonthly] --> B[JSON ๋ฌธ์ž์—ด ์ปฌ๋Ÿผ]
    B --> C[ObjectMapper.readValue]
    C --> D[MonthlyRecap ์‘๋‹ต ๋ชจ๋ธ]
Loading

๊ด€๋ฆฌ์ž API์™€ ๊ณ ๊ฐ API์˜ ์ธ์ฆ/๊ถŒํ•œ ๊ฒฝ๋กœ๊ฐ€ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ์Œ ๐Ÿ›ก๏ธ

  • ๊ณ ๊ฐ API๋Š” /customers, /families, /missions, /appeals, /rewards, /recaps ๋“ฑ์— ๋ถ„์‚ฐ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๊ด€๋ฆฌ์ž API๋Š” /admin/** ๊ฒฝ๋กœ ์•„๋ž˜๋กœ ๋ณ„๋„ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๊ถŒํ•œ ์ฒดํฌ๋„ @AdminOnly์™€ @OwnerOnly๋กœ ๋ช…ํ™•ํžˆ ๋ถ„๋ฆฌ๋ฉ๋‹ˆ๋‹ค.
flowchart TD
    A[Request] --> B{"๊ฒฝ๋กœ/๊ถŒํ•œ"}
    B -- "/admin/**" --> C["@AdminOnly"]
    B -- "๋ณดํ˜ธ์ž ์ „์šฉ ๊ธฐ๋Šฅ" --> D["@OwnerOnly"]
    B -- "์ผ๋ฐ˜ ๊ณ ๊ฐ ๊ธฐ๋Šฅ" --> E["JWT + CustomerId Resolver"]
Loading

API ์‘๋‹ต ๊ทœ์•ฝ ๐Ÿ“˜

๋ชจ๋“  ์‘๋‹ต์€ ApiResponse<T>๋กœ ๊ฐ์‹ธ์ง‘๋‹ˆ๋‹ค.

์„ฑ๊ณต ์˜ˆ์‹œ:

{
  "success": true,
  "data": {
    "id": 1
  },
  "timestamp": "2026-03-19T00:00:00Z"
}

์‹คํŒจ ์˜ˆ์‹œ:

{
  "success": false,
  "error": {
    "code": "MISSION_006",
    "message": "์ด๋ฏธ ์ฒ˜๋ฆฌ ๋Œ€๊ธฐ ์ค‘์ธ ์š”์ฒญ์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.",
    "details": null
  },
  "timestamp": "2026-03-19T00:00:00Z"
}

์š”์ฒญ/์‘๋‹ต DTO ์˜ˆ์‹œ ๐Ÿงพ

1. ๊ณ ๊ฐ ๋กœ๊ทธ์ธ ๐Ÿ”‘

Request:

{
  "phoneNumber": "01012345678",
  "password": "1234"
}

Response:

{
  "success": true,
  "data": {
    "accessToken": "eyJhbGciOi...",
    "refreshToken": "eyJhbGciOi...",
    "role": "MEMBER"
  },
  "timestamp": "2026-03-19T00:00:00Z"
}

2. ๋ฏธ์…˜ ๋ชฉ๋ก ์กฐํšŒ ๐Ÿ“‹

Request:

GET /missions?cursor=120&size=20

Response:

{
  "success": true,
  "data": {
    "missions": [
      {
        "missionItemId": 101,
        "requestId": 55,
        "missionText": "์ˆ™์ œ 30๋ถ„ ํ•˜๊ธฐ",
        "requestStatus": "PENDING",
        "target": {
          "customerId": 7,
          "name": "๋ฏผ์ˆ˜"
        },
        "createdBy": {
          "customerId": 1,
          "name": "์—„๋งˆ"
        },
        "reward": {
          "rewardId": 3,
          "name": "์ ค๋ฆฌ 1๊ฐœ",
          "category": "SNACK",
          "thumbnailUrl": "https://cdn.example.com/reward/jelly.png",
          "templateId": 12
        },
        "createdAt": "2026-03-19T09:30:00"
      }
    ],
    "nextCursor": "101",
    "hasNext": true
  },
  "timestamp": "2026-03-19T00:00:00Z"
}

3. ๊ฐ€์กฑ ์ •์ฑ… ์ˆ˜์ • ๐Ÿ› ๏ธ

Request:

{
  "updateInfo": {
    "customerId": 7,
    "type": "MONTHLY_LIMIT",
    "rules": {
      "limitBytes": 3221225472
    },
    "isActive": true
  }
}

Response:

{
  "success": true,
  "data": {
    "customerId": 7,
    "type": "MONTHLY_LIMIT",
    "updated": true
  },
  "timestamp": "2026-03-19T00:00:00Z"
}

4. ์ด์˜์ œ๊ธฐ ์ƒ์„ฑ โœ๏ธ

Request:

{
  "policyAssignmentId": 99,
  "requestReason": "์ˆ™์ œ ์ œ์ถœ ๋•Œ๋ฌธ์— ์˜ค๋Š˜๋งŒ ์ œํ•œ์„ ์™„ํ™”ํ•ด์ฃผ์„ธ์š”.",
  "desiredRules": {
    "startTime": "23:00",
    "endTime": "07:00"
  },
  "policyActive": true
}

Response:

{
  "success": true,
  "data": {
    "appealId": 201,
    "policyAssignmentId": 99,
    "status": "PENDING",
    "policyActive": true,
    "desiredRules": {
      "startTime": "23:00",
      "endTime": "07:00"
    },
    "createdAt": "2026-03-19T10:15:00"
  },
  "timestamp": "2026-03-19T00:00:00Z"
}

์—”๋“œํฌ์ธํŠธ ๋ชฉ๋ก ๐ŸŒ

์ธ์ฆ/์‚ฌ์šฉ์ž ๐Ÿ‘ค

Method Path ์„ค๋ช… ๊ถŒํ•œ
POST /customers/login ๊ณ ๊ฐ ๋กœ๊ทธ์ธ Public
POST /customers/signup ๊ณ ๊ฐ ํšŒ์›๊ฐ€์ž… Public
POST /customers/refresh ๊ณ ๊ฐ ํ† ํฐ ์žฌ๋ฐœ๊ธ‰ Public
POST /customers/logout ๊ณ ๊ฐ ๋กœ๊ทธ์•„์›ƒ Public
GET /customers/me ๊ณ ๊ฐ ๊ธฐ๋ณธ ์ •๋ณด ์กฐํšŒ Customer
GET /customers/mypage ๋งˆ์ดํŽ˜์ด์ง€ ์กฐํšŒ Customer
POST /admin/login ๊ด€๋ฆฌ์ž ๋กœ๊ทธ์ธ Public
POST /admin/signup ๊ด€๋ฆฌ์ž ํšŒ์›๊ฐ€์ž… Public
POST /admin/refresh ๊ด€๋ฆฌ์ž ํ† ํฐ ์žฌ๋ฐœ๊ธ‰ Public
POST /admin/logout ๊ด€๋ฆฌ์ž ๋กœ๊ทธ์•„์›ƒ Public
GET /admin/me ๊ด€๋ฆฌ์ž ๋‚ด ์ •๋ณด Admin
GET /admin/dashboard ๊ด€๋ฆฌ์ž ๋Œ€์‹œ๋ณด๋“œ Admin

๊ฐ€์กฑ/์‚ฌ์šฉ๋Ÿ‰ ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ

Method Path ์„ค๋ช… ๊ถŒํ•œ
PUT /families ๊ฐ€์กฑ ์ด๋ฆ„ ์ˆ˜์ • Owner
GET /families/members ๊ฐ€์กฑ ๊ตฌ์„ฑ์› ์กฐํšŒ Owner
GET /families/usage/current ํ˜„์žฌ ๊ฐ€์กฑ ์ด ์‚ฌ์šฉ๋Ÿ‰ ์กฐํšŒ Customer
GET /families/usage/customers ์›”๋ณ„ ๊ตฌ์„ฑ์› ์‚ฌ์šฉ๋Ÿ‰ ์š”์•ฝ Customer
GET /families/usage/dashboard ์›”๋ณ„ ๊ตฌ์„ฑ์› ์‚ฌ์šฉ๋Ÿ‰ ์ƒ์„ธ Customer
POST /admin/families ๊ฐ€์กฑ ๊ฒ€์ƒ‰ Admin
GET /admin/families/{familyId} ๊ฐ€์กฑ ์ƒ์„ธ ์กฐํšŒ Admin
PATCH /admin/families/{familyId} ๊ฐ€์กฑ ๊ตฌ์„ฑ์› ์—ญํ• /ํ•œ๋„ ์ˆ˜์ • Admin

์ •์ฑ… ๐Ÿ“

Method Path ์„ค๋ช… ๊ถŒํ•œ
GET /policies/{policyId} ์ •์ฑ… ํ…œํ”Œ๋ฆฟ ์ƒ์„ธ ์กฐํšŒ Admin
GET /policies ์ •์ฑ… ํ…œํ”Œ๋ฆฟ ๋ชฉ๋ก ์กฐํšŒ Admin
POST /policies ์ •์ฑ… ํ…œํ”Œ๋ฆฟ ์ƒ์„ฑ Admin
PUT /policies/{policyId} ์ •์ฑ… ํ…œํ”Œ๋ฆฟ ์ˆ˜์ • Admin
DELETE /policies/{policyId} ์ •์ฑ… ํ…œํ”Œ๋ฆฟ ์‚ญ์ œ Admin
GET /families/policies ๊ฐ€์กฑ ๊ตฌ์„ฑ์›๋ณ„ ์ •์ฑ… ์กฐํšŒ Customer
PATCH /families/policies ํŠน์ • ๊ตฌ์„ฑ์› ์ •์ฑ… ์ˆ˜์ • Owner

๋ฏธ์…˜/๋ณด์ƒ ๐ŸŽ

Method Path ์„ค๋ช… ๊ถŒํ•œ
GET /missions ๋ฏธ์…˜ ๋ชฉ๋ก ์กฐํšŒ Customer
GET /missions/logs ๋ฏธ์…˜ ๋กœ๊ทธ ์กฐํšŒ Customer
GET /missions/history ๋ฏธ์…˜ ์š”์ฒญ ์ด๋ ฅ ์กฐํšŒ Customer
POST /missions ๋ฏธ์…˜ ์ƒ์„ฑ Owner
DELETE /missions/{missionId} ๋ฏธ์…˜ ์ทจ์†Œ Owner
POST /missions/{missionId}/request ๋ฏธ์…˜ ์™„๋ฃŒ ์š”์ฒญ Customer
GET /rewards/templates ๋ณด์ƒ ํ…œํ”Œ๋ฆฟ ๋ชฉ๋ก ์กฐํšŒ Owner
PUT /rewards/requests/{requestId}/respond ๋ณด์ƒ ์š”์ฒญ ์Šน์ธ/๊ฑฐ์ ˆ Owner
GET /rewards/received ๋‚ด ์ˆ˜๋ น ๋ณด์ƒ ์ด๋ ฅ ์กฐํšŒ Customer
GET /admin/rewards/templates ๋ณด์ƒ ํ…œํ”Œ๋ฆฟ ๋ชฉ๋ก ์กฐํšŒ Admin
GET /admin/rewards/templates/{id} ๋ณด์ƒ ํ…œํ”Œ๋ฆฟ ์ƒ์„ธ ์กฐํšŒ Admin
POST /admin/rewards/templates ๋ณด์ƒ ํ…œํ”Œ๋ฆฟ ์ƒ์„ฑ Admin
PUT /admin/rewards/templates/{id} ๋ณด์ƒ ํ…œํ”Œ๋ฆฟ ์ˆ˜์ • Admin
DELETE /admin/rewards/templates/{id} ๋ณด์ƒ ํ…œํ”Œ๋ฆฟ ์‚ญ์ œ Admin
GET /admin/rewards/grants ๋ณด์ƒ ์ง€๊ธ‰ ์ด๋ ฅ ์กฐํšŒ Admin

์ด์˜์ œ๊ธฐ/๋ฆฌ์บก/์—…๋กœ๋“œ ๐Ÿ“Ž

Method Path ์„ค๋ช… ๊ถŒํ•œ
GET /appeals/policies ์ด์˜์ œ๊ธฐ ๊ฐ€๋Šฅ ์ •์ฑ… ๋ชฉ๋ก ์กฐํšŒ Customer
GET /appeals ์ด์˜์ œ๊ธฐ ๋ชฉ๋ก ์กฐํšŒ Customer
GET /appeals/{appealId} ์ด์˜์ œ๊ธฐ ์ƒ์„ธ ์กฐํšŒ Customer
POST /appeals ์ด์˜์ œ๊ธฐ ์ƒ์„ฑ Customer
PATCH /appeals/{appealId}/respond ์ด์˜์ œ๊ธฐ ์Šน์ธ/๊ฑฐ์ ˆ Owner
POST /appeals/{appealId}/comments ์ด์˜์ œ๊ธฐ ๋Œ“๊ธ€ ์ž‘์„ฑ Customer
PATCH /appeals/{appealId}/cancel ์ด์˜์ œ๊ธฐ ์ทจ์†Œ Customer
POST /appeals/emergency ๊ธด๊ธ‰ ์ฟผํ„ฐ ์š”์ฒญ Customer
GET /recaps/monthly ์›”๊ฐ„ ๊ฐ€์กฑ ๋ฆฌ์บก ์กฐํšŒ Customer
POST /uploads/images ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ Admin

์ธ์ฆ๊ณผ ๊ถŒํ•œ ์ฒ˜๋ฆฌ ๐Ÿ”’

์ธ์ฆ ํ๋ฆ„ ๐Ÿ”

  • LoginInterceptor๊ฐ€ ๋Œ€๋ถ€๋ถ„์˜ ์š”์ฒญ์—์„œ Authorization ํ—ค๋”๋ฅผ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค.
  • JwtTokenUtil์ด ํ† ํฐ ์œ ํšจ์„ฑ, ๋งŒ๋ฃŒ, ํƒ€์ž…์„ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
  • CustomerArgumentResolver, AdminArgumentResolver๊ฐ€ ์ปจํŠธ๋กค๋Ÿฌ ํŒŒ๋ผ๋ฏธํ„ฐ์— ์‚ฌ์šฉ์ž ์‹๋ณ„์ž๋ฅผ ์ฃผ์ž…ํ•ฉ๋‹ˆ๋‹ค.

๊ถŒํ•œ ์ œ์–ด ๐Ÿšง

  • @AdminOnly: ๊ด€๋ฆฌ์ž ํ† ํฐ๋งŒ ํ—ˆ์šฉ
  • @OwnerOnly: ๊ฐ€์กฑ ๋‚ด ๋ณดํ˜ธ์ž ์—ญํ• ๋งŒ ํ—ˆ์šฉ
  • ์„œ๋น„์Šค ๋ ˆ์ด์–ด์—์„œ๋„ AuthContext์™€ ๊ฐ€์กฑ ์†Œ์†์„ ๋‹ค์‹œ ๊ฒ€์ฆํ•ด ๋„๋ฉ”์ธ ๋ฌด๊ฒฐ์„ฑ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

์—๋Ÿฌ ์ฒ˜๋ฆฌ ๐Ÿšจ

์ „์—ญ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋Š” ExceptionAdvice์—์„œ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค.

  • ๋„๋ฉ”์ธ ์˜ˆ์™ธ๋Š” ApplicationException extends BaseException์œผ๋กœ ๋˜์ง‘๋‹ˆ๋‹ค.
  • ๊ฐ ๋„๋ฉ”์ธ์€ BaseErrorCode ๊ตฌํ˜„ enum์„ ํ†ตํ•ด HTTP ์ƒํƒœ, ์ปค์Šคํ…€ ์ฝ”๋“œ, ๋ฉ”์‹œ์ง€๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
  • ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์€ ์˜ˆ์™ธ๋Š” GLOBAL_001๋กœ ํ†ต์ผํ•ด ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
flowchart TD
    A[Controller/Service] --> B{์˜ˆ์™ธ ๋ฐœ์ƒ}
    B -- ApplicationException --> C[ExceptionAdvice.handleBaseException]
    B -- ๊ธฐํƒ€ Exception --> D[ExceptionAdvice.handleUnhandledException]
    C --> E[BaseErrorCode์—์„œ status/code/message ์ถ”์ถœ]
    D --> F[GLOBAL_001 INTERNAL_SERVER_ERROR]
    E --> G[ApiResponse.fail]
    F --> G
Loading

๋Œ€ํ‘œ ์—๋Ÿฌ ์ฝ”๋“œ ์˜ˆ์‹œ:

๋„๋ฉ”์ธ ์ฝ”๋“œ HTTP Status ์˜๋ฏธ
Global GLOBAL_001 500 ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์€ ์„œ๋ฒ„ ์˜ค๋ฅ˜
Global GLOBAL_002 400 ์ž˜๋ชป๋œ ์ž…๋ ฅ๊ฐ’
Global GLOBAL_003 401 ์œ ํšจํ•˜์ง€ ์•Š์€ ํ† ํฐ
Mission MISSION_006 409 ์ค‘๋ณต ๋ฏธ์…˜ ์š”์ฒญ
Mission MISSION_010 400 ์ž˜๋ชป๋œ ์ปค์„œ
Mission MISSION_012 400 ๋ณด์ƒ ๊ฑฐ์ ˆ ์‚ฌ์œ  ๋ˆ„๋ฝ
Appeal APPEAL_003 403 ์ด์˜์ œ๊ธฐ ์ ‘๊ทผ ๊ถŒํ•œ ์—†์Œ
Appeal APPEAL_011 409 ์ค‘๋ณต PENDING ์ด์˜์ œ๊ธฐ
Upload UPLOAD_002 400 ํ—ˆ์šฉ๋˜์ง€ ์•Š์€ MIME ํƒ€์ž…
Upload UPLOAD_004 500 ์—…๋กœ๋“œ ์‹คํŒจ

ERD ์š”์•ฝ ๐Ÿงฑ

์ƒ์„ธ ERD ๋ฌธ์„œ๋Š” src/main/resources/db/migration/docs/ERD.md์— ์žˆ๊ณ , README์—๋Š” ๋„๋ฉ”์ธ ๊ด€์ ์—์„œ ํ•ต์‹ฌ ๊ด€๊ณ„๋งŒ ์š”์•ฝํ•ฉ๋‹ˆ๋‹ค.

ํ•ต์‹ฌ ์—”ํ‹ฐํ‹ฐ ๋ฌถ์Œ ๐Ÿ“š

  • ์‚ฌ์šฉ์ž/๊ฐ€์กฑ: customer, admin, family, family_member
  • ์ •์ฑ…/์ฟผํ„ฐ: policy, policy_assignment, customer_quota, family_quota
  • ๋ฏธ์…˜/๋ณด์ƒ: mission_item, mission_request, mission_log, reward, reward_template, reward_grant
  • ์ด์˜์ œ๊ธฐ: policy_appeal, policy_appeal_comment
  • ์ง‘๊ณ„/๋ฆฌ์บก: usage_record, family_recap_monthly, family_recap_weekly
  • ์ด๋ฒคํŠธ: event_outbox ์„ฑ๊ฒฉ์˜ usage_event_outbox์™€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ notification outbox ํ…Œ์ด๋ธ”

๊ด€๊ณ„ ์š”์•ฝ ๐Ÿ”—

erDiagram
    FAMILY ||--o{ FAMILY_MEMBER : has
    CUSTOMER ||--o{ FAMILY_MEMBER : joins
    POLICY ||--o{ POLICY_ASSIGNMENT : template
    CUSTOMER ||--o{ POLICY_ASSIGNMENT : target
    FAMILY ||--o{ POLICY_ASSIGNMENT : scope
    CUSTOMER ||--o{ CUSTOMER_QUOTA : owns
    FAMILY ||--o{ FAMILY_QUOTA : owns
    CUSTOMER ||--o{ MISSION_ITEM : target_or_creator
    FAMILY ||--o{ MISSION_ITEM : scope
    MISSION_ITEM ||--o{ MISSION_REQUEST : has
    MISSION_ITEM ||--o{ MISSION_LOG : has
    REWARD_TEMPLATE ||--o{ REWARD : snapshot_of
    REWARD ||--|| MISSION_ITEM : rewards
    REWARD ||--o{ REWARD_GRANT : issued_as
    POLICY_ASSIGNMENT ||--o{ POLICY_APPEAL : appealed
    POLICY_APPEAL ||--o{ POLICY_APPEAL_COMMENT : comments
    FAMILY ||--o{ FAMILY_RECAP_MONTHLY : monthly
Loading

๋„๋ฉ”์ธ๋ณ„ ์ €์žฅ ํฌ์ธํŠธ ๐Ÿ—ƒ๏ธ

๋„๋ฉ”์ธ ์ฃผ์š” ํ…Œ์ด๋ธ” ์„ค๋ช…
์ธ์ฆ/์‚ฌ์šฉ์ž customer, admin, family_member ๋กœ๊ทธ์ธ ์ฃผ์ฒด์™€ ๊ฐ€์กฑ ๋‚ด ์—ญํ•  ๊ด€๋ฆฌ
์ •์ฑ… policy, policy_assignment, customer_quota ํ…œํ”Œ๋ฆฟ๊ณผ ์‹ค์ œ ์ ์šฉ ์ƒํƒœ๋ฅผ ๋ถ„๋ฆฌ
๋ฏธ์…˜ mission_item, mission_request, mission_log ํ• ๋‹น, ์š”์ฒญ, ๊ฐ์‚ฌ ๋กœ๊ทธ๋ฅผ ๋ถ„๋ฆฌ
๋ณด์ƒ reward_template, reward, reward_grant ํ…œํ”Œ๋ฆฟ๊ณผ ๋ฐœ๊ธ‰ ๋ณด์ƒ ์Šค๋ƒ…์ƒท ๋ถ„๋ฆฌ
์ด์˜์ œ๊ธฐ policy_appeal, policy_appeal_comment ๋ณธ๋ฌธ๊ณผ ๋Œ“๊ธ€์„ ๋ถ„๋ฆฌ
๋ฆฌ์บก family_recap_monthly ์›”๊ฐ„ ์Šค๋ƒ…์ƒท์„ JSON ํฌํ•จ ํ˜•ํƒœ๋กœ ์ €์žฅ

์„ค์ • ํฌ์ธํŠธ โšก

src/main/resources/application.yml ๊ธฐ์ค€ ์ฃผ์š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜์ž…๋‹ˆ๋‹ค.

  • DATABASE_URL, DATABASE_USER, DATABASE_PASSWORD
  • JWT_SECRET_KEY, JWT_ACCESS_TOKEN_EXPIRES_IN, JWT_REFRESH_TOKEN_EXPIRES_IN
  • REDIS_HOST, REDIS_PORT, REDIS_PASSWORD
  • KAFKA_BOOTSTRAP_SERVERS
  • R2_ENDPOINT, R2_ACCESS_KEY, R2_SECRET_KEY, R2_BUCKET, R2_CDN_BASE_URL
  • FRONTEND_URL
  • OTEL_EXPORTER_OTLP_ENDPOINT

๋กœ์ปฌ ์‹คํ–‰ โ–ถ๏ธ

./gradlew bootRun

Windows:

.\gradlew.bat bootRun

ํ…Œ์ŠคํŠธ:

./gradlew test

Swagger UI:

  • http://localhost:8080/swagger-ui.html

OpenAPI:

  • http://localhost:8080/v3/api-docs

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๐Ÿ› ๏ธ

  • Flyway๋ฅผ ์‚ฌ์šฉํ•ด ์Šคํ‚ค๋งˆ๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  • ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ์€ src/main/resources/db/migration์— ์žˆ์Šต๋‹ˆ๋‹ค.
  • ERD ๋ฌธ์„œ๋Š” src/main/resources/db/migration/docs/ERD.md์— ์žˆ์Šต๋‹ˆ๋‹ค.

ํ…Œ์ŠคํŠธ ํ˜„ํ™ฉ ๐Ÿงช

  • ์„œ๋น„์Šค ๋‹จ์œ„ ํ…Œ์ŠคํŠธ์™€ ์ผ๋ถ€ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๊ฐ€ src/test/java์— ๊ตฌ์„ฑ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋Œ€ํ‘œ์ ์œผ๋กœ mission, reward, appeal, family, policy, recap, upload ๊ด€๋ จ ํ…Œ์ŠคํŠธ๊ฐ€ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

About

๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ ๊ฐ€์กฑ ๋‹จ์œ„ ๋””์ง€ํ„ธ ์‚ฌ์šฉ๋Ÿ‰ ๊ด€๋ฆฌ API ์„œ๋ฒ„

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages