κ³΅μ° μλ§€(λκΈ°μ΄/μ’μ μ μ /μΆμ²¨Β·μ μ² μλ§€)μ ν°μΌ κ±°λ(κ΅ν/μλ), κ²°μ νλ¦μ μ§μνλ Spring Boot κΈ°λ° λ°±μλ μλ²
- π― νλ‘μ νΈ κ°μ
- π₯ ν ꡬμ±
- π λ§ν¬
- βοΈ ν΅μ¬ κΈ°λ₯
- π οΈ κΈ°μ μ€ν
- ποΈ μμ€ν μν€ν μ²
- ποΈ ERD
- ποΈ νλ‘μ νΈ κ΅¬μ‘°
- π λͺ¨λν°λ§ ꡬμ±
- π§© νμ κ·μΉ
TT(Ticket & Trade) λ κ³΅μ° ν°μΌ μλ§€ λ° 2μ°¨ κ±°λ(κ΅ν/μλ) νλ«νΌμ λ°±μλ μλ²μ λλ€.
| λλ©μΈ | μ€λͺ |
|---|---|
| λκΈ°μ΄ | Redis κΈ°λ° νΈλν½ λΆμ°, μμ°¨ μ κ·Ό μ μ΄ |
| μ’μ μλ§€ | μ’μ μ μ (HOLD β SOLD), λ§λ£ μλ 볡ꡬ |
| μΆμ²¨ μλ§€ | λ±κΈλ³ μλͺ¨, 곡μ μΆμ²¨, λΉμ²¨ μλ¦Ό |
| μ¬μ μ μ² μλ§€ | μ€ν/λ§κ° μ μ± κΈ°λ° μ μ² μ²λ¦¬ |
| κ±°λ | ν°μΌ μμ κΆ κ²μ¦, κ΅νΒ·μλ νλ¦ |
| κ²°μ | λλ©μΈλ³ κ²°μ νλ¦ λΆλ¦¬, μν κ΄λ¦¬ |
| μΈμ¦ | JWT + Refresh Token Rotation, μΉ΄μΉ΄μ€ OIDC |
| μ΄λ¦ | μν | GitHub |
|---|---|---|
| κΉμ±ν | Backend (PO) | |
| κΉμ±ν | Backend | |
| λ Έλ―Έκ²½ | Backend | |
| λ°λ―Όν | Backend (νμ₯) | |
| μ΄μλ¦Ό | Backend |
| κ΅¬λΆ | λ§ν¬ |
|---|---|
| π λ°°ν¬ | https://doncrytt.vercel.app |
| π» Frontend | WEB7_9_B2ST_FE |
| π§ Backend | WEB7_9_B2ST_BE |
| π API λ¬Έμ | Swagger UI |
| κΈ°λ₯ | ꡬν μμΈ |
|---|---|
| JWT μΈμ¦ | Access Token(30λΆ) + Refresh Token(7μΌ, Redis μ μ₯) |
| Token Rotation | μ¬λ°κΈ μ Family/Generation κΈ°λ° νμ·¨ κ°μ§, μ΄μ ν ν° μ¬μ© μ μ 체 μΈμ 무ν¨ν |
| μΉ΄μΉ΄μ€ OIDC | ID Token RSA μλͺ κ²μ¦(JWKS 24μκ° μΊμ±), nonce 1νμ± κ²μ¦, μλ κ³μ μ°λ |
| λ‘κ·ΈμΈ λ³΄μ | 5ν μ€ν¨ μ 10λΆ μ κΈ(Redis TTL), Lua Script μμμ μΉ΄μ΄ν |
| μν νμ§ | Credential Stuffing(IPλΉ 10+ κ³μ ), Brute Force(IPλΉ 50+ μ€ν¨) νμ§ β Slack μλ¦Ό |
| κΈ°λ₯ | ꡬν μμΈ |
|---|---|
| νμκ°μ | BCrypt μνΈν, IPλ³ Rate Limiting(μκ°λΉ 3ν, Lua Script) |
| μ΄λ©μΌ μΈμ¦ | SecureRandom 6μ리 μ½λ, Redis TTL 5λΆ, μλ νμ μ ν(5ν) |
| νν΄/볡ꡬ | Soft Delete + 30μΌ λ³΅κ΅¬ μ μ, 볡ꡬ ν ν°(UUID, 24μκ° TTL) |
| κ°μ¬ λ‘κ·Έ | λ‘κ·ΈμΈ/κ°μ μ΄λ²€νΈ λΉλκΈ° μ μ₯(@Async + REQUIRES_NEW) |
| κΈ°λ₯ | ꡬν μμΈ |
|---|---|
| λκΈ°μ΄ μ§μ | Redis Sorted Set κΈ°λ°, νμμ€ν¬ν μ€μ½μ΄λ‘ μμ 보μ₯ |
| μλ² μ‘°ν | ZRANK λͺ λ ΉμΌλ‘ μ€μκ° λκΈ° μλ² λ°ν |
| μ μ₯ μ²λ¦¬ | μμ°¨μ μ μ₯ ν ν° λ°κΈ, μ ν¨ μκ° μ ν |
| μν κ΄λ¦¬ | WAITING β PROCESSING β COMPLETED μν μ μ΄ |
| νκ²½ μ€μ | λκΈ°μ΄ on/off μ€μ κ°λ₯, νΈλν½ μν©μ λ°λΌ μ μ°νκ² μ μ© |
| κΈ°λ₯ | ꡬν μμΈ |
|---|---|
| μ’μ μ μ | AVAILABLE β HOLD μν μ μ΄, 5λΆ TTL μ€μ |
| μ€λ³΅ λ°©μ§ | λμΌ μ’μ λμ μ μ μλ μ λκ΄μ λ½μΌλ‘ μΆ©λ κ°μ§ |
| μλ§€ νμ | κ²°μ μλ£ μ HOLD β SOLD μν μ μ΄ |
| μ μ λ§λ£ | μ€μΌμ€λ¬ κΈ°λ° TTL λ§λ£ μ’μ μλ 볡ꡬ (HOLD β AVAILABLE) |
| μλ§€ μ·¨μ | μλ§€ μ·¨μ μ μ’μ μν μ볡, νλΆ μ²λ¦¬ μ°λ |
| κΈ°λ₯ | ꡬν μμΈ |
|---|---|
| μλͺ¨ λ±λ‘ | νμ°¨/λ±κΈλ³ μλͺ¨, μ€λ³΅ μλͺ¨ κ²μ¦ |
| μλͺ¨ μ ν | 1μΈλΉ λ±κΈλ³ μ΅λ μλͺ¨ μλ μ ν |
| μΆμ²¨ μ²λ¦¬ | SecureRandom κΈ°λ° κ³΅μ μΆμ²¨ μκ³ λ¦¬μ¦ |
| λΉμ²¨ μ²λ¦¬ | λΉμ²¨μ μ’μ μλ λ°°μ , κ²°μ κΈ°ν μ€μ |
| κ²°κ³Ό μλ¦Ό | λΉμ²¨/λ첨 μ΄λ©μΌ λΉλκΈ° λ°μ‘ |
| λ―Έκ²°μ μ²λ¦¬ | κ²°μ κΈ°ν μ΄κ³Ό μ μλ λΉμ²¨ μ·¨μ, μ’μ λ°ν |
| κΈ°λ₯ | ꡬν μμΈ |
|---|---|
| μ μ² κΈ°κ° | μ€ν/λ§κ° μΌμ κΈ°λ° μ μ² κ°λ₯ κΈ°κ° κ²μ¦ |
| μ μ² λ±λ‘ | νμ°¨/λ±κΈλ³ μ¬μ μ μ², μλ μ§μ |
| μ μ² νμ | μ μ² β κ²°μ λκΈ° β κ²°μ μλ£ νλ¦, μμΈ: CANCELLED, EXPIRED |
| λ§λ£ μ²λ¦¬ | κ²°μ κΈ°ν μ΄κ³Ό μ μ μ² μλ λ§λ£, κ²°μ μλ μ°¨λ¨ |
| μ μ² μ·¨μ | μ¬μ©μ μμ²μ μν μ μ² μ·¨μ μ²λ¦¬ |
| κΈ°λ₯ | ꡬν μμΈ |
|---|---|
| λλ©μΈλ³ λΆλ¦¬ | μ’μμλ§€/μΆμ²¨/μ¬μ μ μ²/κ±°λλ³ κ²°μ μμ±/κ²μ¦ λ‘μ§ λ 립 |
| μν κ΄λ¦¬ | PENDING β PROCESSING β COMPLETED/FAILED/CANCELLED (μ μ΄ κ·μΉ λͺ μ) |
| κΈ°λ₯ | ꡬν μμΈ |
|---|---|
| κ±°λ λ±λ‘ | ν°μΌ μμ κΆ κ²μ¦, μ€λ³΅ λ±λ‘ λ°©μ§ |
| κ±°λ μμ² | ꡬ맀μ κ±°λ μμ², νλ§€μ μΉμΈ λκΈ° |
| μμ μ μ | μΉμΈ μ redisμ ν°μΌ μμ μ μ λ±λ‘, κ²°μ κΈ°νκΉμ§ μ¬κ±°λ/μ¬μμ½ μμ² μ¦μ μ°¨λ¨(μ΅μ’ νμ μ DBλ‘ κ²μ¦) |
| κ±°λ μΉμΈ | νλ§€μ μΉμΈ μ κ²°μ νλ‘μΈμ€ μ§μ |
| μμ κΆ μ΄μ | κ²°μ μλ£ μ ν°μΌ μμ κΆ κ΅¬λ§€μλ‘ λ³κ²½ |
| κΈ°λ₯ | ꡬν μμΈ |
|---|---|
| νμ κ΄λ¦¬ | κ²μ/νν°λ§/νμ΄μ§, λμ보λ ν΅κ³ |
| μΈμ¦ κ΄λ¦¬ | λ‘κ·ΈμΈ/κ°μ λ‘κ·Έ μ‘°ν, κ³μ μ κΈ ν΄μ |
| κΆν λΆλ¦¬ | /api/admin/** URL λ 벨 + @PreAuthorize λ©μλ λ 벨 |
νΌμ³λ³΄κΈ°
src/main/java/com/back/b2st/
βββ domain/
β βββ auth/ # JWT, OAuth, λ‘κ·ΈμΈ λ³΄μ, ν ν° κ΄λ¦¬
β βββ member/ # νμ CRUD, νν΄/볡ꡬ, Rate Limiting
β βββ email/ # μ΄λ©μΌ μΈμ¦, λΉλκΈ° λ°μ‘
β βββ performance/ # κ³΅μ° κ΄λ¦¬
β βββ performanceschedule/# κ³΅μ° νμ°¨
β βββ seat/ # μ’μ κ΄λ¦¬
β βββ scheduleseat/ # νμ°¨λ³ μ’μ μν
β βββ queue/ # λκΈ°μ΄ μμ€ν
β βββ reservation/ # μ’μ μλ§€
β βββ prereservation/ # μ¬μ μ μ²
β βββ lottery/ # μΆμ²¨ μλ§€
β βββ payment/ # κ²°μ
β βββ ticket/ # ν°μΌ κ΄λ¦¬
β βββ trade/ # κ±°λ (κ΅ν/μλ)
β βββ venue/ # 곡μ°μ₯
β βββ bank/ # μν μ½λ Enum
β
βββ global/
β βββ alert/ # SlackAlertService (Webhook μ°λ)
β βββ config/ # Redis, S3, Redisson, Alert μ€μ
β βββ error/ # GlobalExceptionHandler, ErrorCode
β βββ jwt/ # JwtTokenProvider, JwtAuthenticationFilter
β βββ jpa/ # BaseEntity, QueryDslConfig, AuditorAware
β βββ s3/ # S3Service, PresignedUrl
β βββ util/ # MaskingUtil, CookieUtils, SecurityUtils
β βββ metrics/ # MetricsConfig
β
βββ security/ # Spring Security μ€μ
βββ SecurityConfig.java
βββ CustomUserDetails.java
βββ CustomUserDetailsService.java
βββ JwtAuthenticationEntryPoint.java
βββ JwtAccessDeniedHandler.java
docker/
βββ docker-compose.yml # μ 체 μ€ν (App, DB, Redis Cluster, Monitoring)
βββ monitoring/
β βββ prometheus/
β β βββ prometheus.yml
β β βββ rules/auth-alerts.yml
β βββ grafana/
β β βββ provisioning/
β β βββ dashboards/
β βββ alertmanager/
β βββ alertmanager.yml
βββ init-*.sh # Redis Cluster μ΄κΈ°ν μ€ν¬λ¦½νΈ
Grafana λμ보λ
| κ³μΈ΅ | λμ보λ | μ£Όμ λ©νΈλ¦ |
|---|---|---|
| Service | tt-service-overview | μμ² μ, μλ¬μ¨, μλ΅ μκ° λΆν¬ |
| Domain | tt-auth-dashboard | auth_login_total, auth_account_locked_total, auth_token_reissue_total |
| tt-email-dashboard | email_sent_total, email_verification_total |
|
| tt-queue-dashboard | λκΈ°μ΄ μν, μ²λ¦¬λ | |
| tt-reservation-dashboard | μλ§€ μμ±, μ’μ μ μ /μ·¨μ | |
| tt-lottery-dashboard | μλͺ¨ μ, λΉμ²¨ μ²λ¦¬ | |
| tt-payment-dashboard | κ²°μ μμ²/μλ£/μ€ν¨ | |
| tt-trade-dashboard | κ±°λ λ±λ‘/μλ£ | |
| Infra | tt-jvm-dashboard | ν λ©λͺ¨λ¦¬, GC, μ€λ λ ν |
| tt-database-dashboard | 컀λ₯μ ν, 쿼리 μ±λ₯ | |
| tt-redis-dashboard | λ©λͺ¨λ¦¬, 컀맨λ/sec, ν€ μν |
Alertmanager κ·μΉ
// μμ
groups:
- name: auth-alerts
rules:
- alert: HighLoginFailureRate
expr: rate(auth_login_total{result="failure"}[5m]) > 0.1
for: 2m
labels:
severity: warning
annotations:
summary: "λ‘κ·ΈμΈ μ€ν¨μ¨ μ¦κ°"
- alert: AccountLockDetected
expr: increase(auth_account_locked_total[5m]) > 0
labels:
severity: critical
annotations:
summary: "κ³μ μ κΈ λ°μ"νΌμ³λ³΄κΈ°
- Naver Java Convention κΈ°λ°
- IntelliJ IDEA μλ μμ μ€μ
main: νλ‘λμ develop: κ°λ° ν΅ν©feature/*: κΈ°λ₯ κ°λ°- λ¨Έμ§ μ‘°κ±΄: μ΅μ 1λͺ 리뷰 μΉμΈ
νΌμ³λ³΄κΈ°
- μ λͺ© κ·μΉ:
[νμ ] μμ λ΄μ©- μμ:
[feat] λ‘κ·ΈμΈ κΈ°λ₯ μΆκ°
- μμ:
- λ³Έλ¬Έ: ν ν νλ¦Ώμ λ§μΆ° μμ±
- μ λͺ© κ·μΉ:
[νμ ] μμ λ΄μ©- μμ:
[feat] λ‘κ·ΈμΈ κΈ°λ₯ μΆκ°
- μμ:
- λ³Έλ¬Έ: ν ν νλ¦Ώμ λ§μΆ° μμ±
- λΈλμΉ λ³΄νΈ κ·μΉ:
main,developμ λ³΄νΈ λΈλμΉλ‘ μ΅μ 1λͺ 리뷰 μΉμΈ νμλ§ λ¨Έμ§
- μμ± κΈ°μ€:
developλΈλμΉμμ μμ± - λͺ
λͺ
κ·μΉ:
νμ /μμ λ΄μ©- μμ:
feat/μ‘°ν-κΈ°λ₯-κ°λ°
- μμ:
- νμ:
νμ : μμ λ΄μ©- μμ:
feat: λ‘κ·ΈμΈ κΈ°λ₯ μΆκ°
- μμ:
| νμ | μλ―Έ |
|---|---|
feat |
μλ‘μ΄ κΈ°λ₯ μΆκ° |
fix |
λ²κ·Έ μμ |
docs |
λ¬Έμ μμ (README, μ£Όμ λ±) |
refactor |
μ½λ 리ν©ν λ§(λμ λ³ν μμ) |
test |
ν μ€νΈ μ½λ μΆκ°/μμ |