Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
157 commits
Select commit Hold shift + click to select a range
3cf0d46
Create README.md
kon28289 Dec 22, 2025
e502120
Update README.md
kon28289 Dec 22, 2025
920748f
feat : repository 테스트 컨테이너 truncate 설정
Juhye0k Jan 3, 2026
994e023
feat : testconfig 설정
Juhye0k Jan 3, 2026
6451a82
feat : test-yml 설정
Juhye0k Jan 5, 2026
8e900c5
refactor : 하트비트 로직 삭제
Juhye0k Jan 5, 2026
9ea084c
refactor : RedisConfig 주입 테스트코드를 위해 수정
Juhye0k Jan 5, 2026
ef12a36
refactor : 랭킹 산정 sql 수정
Juhye0k Jan 5, 2026
ddea8b3
refactor : testContainer 라이브러리 추가
Juhye0k Jan 5, 2026
4260c84
refactor : 필요없는 코드 제거
Juhye0k Jan 5, 2026
2300268
refactor : 통합테스트 truncate 설정
Juhye0k Jan 5, 2026
11cd261
feat : 랭킹, 공부시간 단위테스트 작성
Juhye0k Jan 5, 2026
d8bf11a
feat : 랭킹, 공부시간 통합테스트 작성
Juhye0k Jan 5, 2026
df56697
feat : 테스트코드 설정
Juhye0k Jan 5, 2026
4557dc9
feat : 테스트코드 설정
Juhye0k Jan 5, 2026
7e88e91
feat : 컨테이너 설정 충돌 해결
Juhye0k Jan 5, 2026
7f3d02b
chore : 코드래빗 오류 해결
Juhye0k Jan 5, 2026
febe868
chore : 컨테이너 라이프사이클 오류
Juhye0k Jan 5, 2026
78117c3
chore : 데이터 정리 트랜잭션 적용
Juhye0k Jan 5, 2026
c791688
chore : test 설정
Juhye0k Jan 5, 2026
124c7ae
chore : github ci/cd 환경 컨테이너 오류 수정
Juhye0k Jan 6, 2026
01be812
chore : Ryuk 컨테이너 생성 오류
Juhye0k Jan 6, 2026
646f4d7
chore : 컨테이너 생성 오류 해결 시도 1
Juhye0k Jan 6, 2026
e6bcc17
Revise CI workflow for testing and Docker setup
Juhye0k Jan 6, 2026
eff72ac
chore : 컨테이너 생성 오류 해결 시도 2
Juhye0k Jan 7, 2026
e32139f
chore : 컨테이너 생성 오류 해결 시도 2
Juhye0k Jan 7, 2026
1efb329
chore : 컨테이너 생성 오류 해결 시도 3
Juhye0k Jan 7, 2026
6be60d4
chore : 컨테이너 생성 오류 해결 시도 3
Juhye0k Jan 7, 2026
c627e6d
chore : 통합테스트 Controller 로직 추가
Juhye0k Jan 7, 2026
ad8f2c6
chore : testcontainer 에러 해결 4
Juhye0k Jan 7, 2026
6988dbd
chore : testcontainer 에러 해결 시도 5
Juhye0k Jan 7, 2026
199f3bf
chore : testcontainer 에러 해결 시도 6
Juhye0k Jan 7, 2026
70e71d7
chore : testcontainer 에러 해결 시도 7
Juhye0k Jan 7, 2026
9bd5566
chore : testcontainer 에러 해결 시도 8
Juhye0k Jan 7, 2026
1b653e4
chore : testcontainer 에러 해결 시도 9
Juhye0k Jan 7, 2026
c6915ec
chore : 메소드 한글로 통일
Juhye0k Jan 7, 2026
456cb03
chore : 코드래빗 반영
Juhye0k Jan 7, 2026
1f7a55e
Update .github/workflows/dev-ci.yml
Juhye0k Jan 7, 2026
60718c6
chore : 필요없는 테스트 (12시간 공부 시간 측정) 삭제
Juhye0k Jan 8, 2026
3287637
Merge branch 'testcode' of https://github.com/Geumpumta/backend into …
Juhye0k Jan 8, 2026
9909d3b
Merge pull request #63 from Geumpumta/testcode
Juhye0k Jan 8, 2026
28dbc4f
refactor : 네트워크 검증 Redis -> 로컬 캐시 전환
Juhye0k Jan 13, 2026
24c9471
refactor : 공부 세션 1개 유지
Juhye0k Jan 13, 2026
08eb38b
chore : 테스트 메소드 올바르게 설정
Juhye0k Jan 13, 2026
b23cd2a
Merge pull request #64 from Geumpumta/testcode
Juhye0k Jan 13, 2026
4b47e91
refactor : 공부 종료시간 시작시간보다 작지 않게 예외 설정
Juhye0k Jan 13, 2026
a73d897
chore : 병합충돌 해결
Juhye0k Jan 13, 2026
21814a7
refactor : 통계 코드 분리
kon28289 Jan 16, 2026
44dae60
chore: 시간 단위를 ms로 변경하여 dto 명 변경
kon28289 Jan 16, 2026
23cc938
chore: 의존하는 repository 변경
kon28289 Jan 16, 2026
97bf643
Merge pull request #65 from Geumpumta/chore/statistics
kon28289 Jan 16, 2026
18b2c66
test: userService 단위 테스트 작성
kon28289 Jan 17, 2026
8257e55
chore: 이메일 및 학번 중복 시 409 예외를 반환하도록 수정
kon28289 Jan 17, 2026
3d7bedc
Merge pull request #66 from Geumpumta/chore/user
kon28289 Jan 17, 2026
9a6e2d4
feat : Spring Retry 라이브러리 추가
Juhye0k Jan 18, 2026
f672b0b
feat : 시즌 랭킹 설계
Juhye0k Jan 18, 2026
d8847fb
refactor : 공부 시간 조회 시 활성화된 세션 여부 데이터 추가
Juhye0k Jan 18, 2026
eadd25f
refactor : batch를 위한 db url 수정
Juhye0k Jan 18, 2026
b594db1
refactor : 예외 타입 추가
Juhye0k Jan 18, 2026
21f0e0e
refactor : 테스트 환경 스케줄러 비활성화
Juhye0k Jan 18, 2026
53f7e88
Update src/main/java/com/gpt/geumpumtabackend/rank/scheduler/SeasonTr…
Juhye0k Jan 19, 2026
d69b884
refactor : ranking null 전달 시 빈리스트로 방지
Juhye0k Jan 19, 2026
9969bb3
refactor : jpql limit 미지원으로 인한 native query 사용
Juhye0k Jan 19, 2026
ab06d43
faet: : CacheConfig 설정
Juhye0k Jan 19, 2026
367b324
Merge branch 'ranking' of https://github.com/Geumpumta/backend into r…
Juhye0k Jan 19, 2026
4b653af
Merge pull request #67 from Geumpumta/ranking
Juhye0k Jan 19, 2026
b9c0972
feat : claude.md 문서화
Juhye0k Feb 2, 2026
c9e5c52
perf: 월 범위 세션 선필터링으로 월간 통계 쿼리 성능 개선
kon28289 Feb 4, 2026
861480c
Merge pull request #68 from Geumpumta/perf/statistics
kon28289 Feb 7, 2026
502e465
feat : 최대 공부 집중시간 설계
Juhye0k Feb 15, 2026
4a8b052
feat : FCM 라이브러리
Juhye0k Feb 15, 2026
a8153e0
chore : CLAUDE 문서화 업데이트
Juhye0k Feb 15, 2026
26e3895
feat :FCM 설정
Juhye0k Feb 15, 2026
eebb7c6
chore : 클로드 문서
Juhye0k Feb 15, 2026
70c3e9b
feat : 최대 집중시간 Swagger 작성
Juhye0k Feb 15, 2026
bac0fd8
refactor : 시즌 학과랭킹 수정
Juhye0k Feb 16, 2026
de5672b
chore: CLAUDE.md 수정
Juhye0k Feb 16, 2026
9f3848a
feat : 시즌 랭킹 response
Juhye0k Feb 16, 2026
17d95ba
Merge branch 'dev' of https://github.com/Geumpumta/backend into ranking
Juhye0k Feb 16, 2026
0a6274d
refactor : fcm 추가
Juhye0k Feb 16, 2026
640a369
refactor : fcm test 설정
Juhye0k Feb 16, 2026
0a95f2c
fix : 통합테스트 CI 실패 에러 해결 시도
Juhye0k Feb 16, 2026
9592ce3
fix : 통합테스트 CI 실패 에러 해결 시도 (우분투 버전)
Juhye0k Feb 16, 2026
3c13a41
fix : 통합테스트 CI 실패 에러 해결 시도 - docker api 버전
Juhye0k Feb 16, 2026
e4eea91
fix : 통합테스트 CI 실패 에러 해결 시도 - build gradle
Juhye0k Feb 16, 2026
aa3ec28
fix : 통합테스트 CI 실패 에러 해결 시도 - build gradle
Juhye0k Feb 16, 2026
fc393ff
refactor: fcm 토큰 설정
Juhye0k Feb 16, 2026
84e6a56
refactor: 최대집중시간 시작시간 보정
Juhye0k Feb 16, 2026
1d3271c
refactor: FCM 재시도 로직
Juhye0k Feb 17, 2026
fa677d8
feat : 서브모듈 업데이트
Juhye0k Feb 17, 2026
f64c1be
feat : fcm yml 추가
Juhye0k Feb 17, 2026
9e86c71
fix : BOM 에러 수정
Juhye0k Feb 18, 2026
2f01fb1
refactor : 트랜잭션 외부에서 fcm 메세지 전송
Juhye0k Feb 18, 2026
1683d75
refactor : FCM api 엔드포인트 변경
Juhye0k Feb 18, 2026
417a831
Merge pull request #69 from Geumpumta/ranking
Juhye0k Feb 18, 2026
d828f6d
refactor : PersonalRankingTemp 생성자 1개로 수정
Juhye0k Feb 19, 2026
e761777
Merge pull request #70 from Geumpumta/ranking
Juhye0k Feb 19, 2026
f02dbec
refactor : 학과 랭킹 0일때도 랭킹 조회 반영
Juhye0k Feb 19, 2026
785eba5
feat: 배지 관련 예외 추가
kon28289 Feb 19, 2026
551e77c
feat: 배지 관련 예외 추가
kon28289 Feb 20, 2026
9365d77
feat: 배지 컨트롤러 추가
kon28289 Feb 20, 2026
1dbad2c
feat: 배지 도메인 추가
kon28289 Feb 20, 2026
a8c0fff
feat: 배지 dto 추가
kon28289 Feb 20, 2026
d075a0f
feat: 학습 세션 dto 추가
kon28289 Feb 20, 2026
96b8f34
feat: 회원가입 완료 시 웰컴 배지 정보도 함께 제공
kon28289 Feb 20, 2026
284043f
docs: 배지 스웨거 문서화 추가
kon28289 Feb 20, 2026
20f8d9f
test: 통계 통합 테스트 수정
kon28289 Feb 20, 2026
70948f1
test: 베지 단위 테스트 추가
kon28289 Feb 20, 2026
2e3cd4d
test: 유저 통합 테스트 수정
kon28289 Feb 20, 2026
a9bc32d
feat: 회원가입 완료 후 전송하는 dto 변경
kon28289 Feb 20, 2026
0b11773
feat: 대표배지 상태 변경 메서드 추가
kon28289 Feb 20, 2026
836345c
docs: 유저 스웨거 문서화
kon28289 Feb 20, 2026
a7e8e8e
docs: 공부 세션 스웨거 문서화 수정
kon28289 Feb 20, 2026
10b4f83
feat: 공부 세션 종료 시 업적을 달성한 배지 제공
kon28289 Feb 20, 2026
f3b4e79
feat: 전체 공부 시간 조회하는 메서드 추가
kon28289 Feb 20, 2026
e77ea63
feat: 연속 공부 일수를 측정하는 메서드 추가
kon28289 Feb 20, 2026
656cb8f
feat: 시즌 랭킹 스냅샷 1·2·3등 조회 메서드 추가
kon28289 Feb 20, 2026
4792c44
feat: 시즌 종료 시 랭킹 배지 지급 및 로그 추가
kon28289 Feb 20, 2026
7ff994f
chore: 최대집중시간 테스트용 3분
Juhye0k Feb 20, 2026
061ef7a
feat: 배지 레파지토리 추가
kon28289 Feb 20, 2026
6c3e380
feat: 배지 서비스 추가
kon28289 Feb 20, 2026
ab04ae5
Merge pull request #71 from Geumpumta/ranking
Juhye0k Feb 20, 2026
e5bef8d
chore: userBadge 도메인 접근 제어자 추가
kon28289 Feb 21, 2026
82e577b
chore: Spring Data JPA 컨벤션에 맞게 단일 조회 시 반환 타입을 Optional로 변경
kon28289 Feb 21, 2026
2a5234c
chore: 예외 타입 오타 수정 및 접두사 충돌 제거
kon28289 Feb 21, 2026
4a9adbc
fix: 공부 세션 종료 트랜잭션과 배지 지급 트랜잭션 분리
kon28289 Feb 21, 2026
1abe266
refactor: 연속 공부일수 계산 쿼리를 일별 집계+윈도우 함수 기반으로 최적화
kon28289 Feb 21, 2026
a14b0f1
fix: 시즌 전환 시 랭킹 배지 지급 실패를 try-catch로 처리
kon28289 Feb 21, 2026
61b97b3
refactor: end API를 세션 종료 전용으로 단순화하고 배지 지급을 AFTER_COMMIT 이벤트로 처리
kon28289 Feb 21, 2026
df00557
test: 공부 세션 테스트 수정
kon28289 Feb 21, 2026
25872ce
docs: 공부 세션 스웨거 문서화
kon28289 Feb 21, 2026
043fbcb
docs: 공부 세션 스웨거 문서화
kon28289 Feb 21, 2026
efd99ec
Merge pull request #72 from Geumpumta/feat/badge
kon28289 Feb 22, 2026
0f79d19
refactor : 서브모듈 업데이트
Juhye0k Feb 23, 2026
6b0284c
Merge pull request #73 from Geumpumta/ranking
Juhye0k Feb 23, 2026
c6a4c3d
Disable SQL logging in production configuration
Juhye0k Feb 23, 2026
ae90359
Update application-dev.yml
Juhye0k Feb 23, 2026
4166720
Change logging level for Spring and Hibernate
Juhye0k Feb 23, 2026
ff41563
refactor : fcm 서브모듈 업데이트
Juhye0k Feb 23, 2026
927eb5b
Merge pull request #74 from Geumpumta/ranking
Juhye0k Feb 23, 2026
87abd5d
feat: Maintenance 필터 추가
kon28289 Mar 2, 2026
0c752c9
feat: Maintenance 관련 예외 추가
kon28289 Mar 2, 2026
39b39e5
feat: Maintenance 도메인 추가
kon28289 Mar 2, 2026
c805535
feat: Maintenance dto 추가
kon28289 Mar 2, 2026
9d70db0
feat: Maintenance 도메인 상태 관리 로직 추가
kon28289 Mar 2, 2026
b967345
test: Maintenance 테스트 코드 추가
kon28289 Mar 2, 2026
6095515
docs: 스웨거 문서화
kon28289 Mar 2, 2026
6b3e190
fix: getRequestURI를 getServletPath로 변경
kon28289 Mar 2, 2026
b4c1d07
Merge pull request #75 from Geumpumta/feat/maintenance
kon28289 Mar 2, 2026
0fcc759
fix: 회원가입 완료 시 웰컴 배지를 지급하지 않도록 수정
kon28289 Mar 4, 2026
33fb120
Merge pull request #76 from Geumpumta/fix/user
kon28289 Mar 4, 2026
ece890b
fix: 웰컴 배지 지급 시 notifiedAt을 Null로 설정
kon28289 Mar 4, 2026
8a2f1fb
Merge pull request #77 from Geumpumta/fix/user
Juhye0k Mar 4, 2026
942b13a
feat: 동일 계정 중복 로그인 차단 정책 추가
kon28289 Mar 8, 2026
0cc29d8
test: 중복 로그인 차단 정책 테스트 주가
kon28289 Mar 8, 2026
7013fd7
Merge pull request #78 from Geumpumta/feat/block-multi-device-login
kon28289 Mar 8, 2026
e44764e
Merge pull request #79 from Geumpumta/main
kon28289 Mar 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 250 additions & 0 deletions .ai/ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
# ARCHITECTURE.md

Geumpumta 백엔드 시스템 아키텍처 문서.

---

## 1. 시스템 개요

```
┌─────────────────────────────────────────────────────────────────┐
│ 클라이언트 (모바일 앱) │
└────────────────────────────┬────────────────────────────────────┘
│ HTTPS
┌─────────────────────────────────────────────────────────────────┐
│ Security Filter Chain │
│ CORS → OAuth2Login → JwtAuthenticationFilter → @PreAuthorize │
├─────────────────────────────────────────────────────────────────┤
│ Controller Layer (@AssignUserId AOP → userId 자동 주입) │
├─────────────────────────────────────────────────────────────────┤
│ Service Layer │
│ study │ rank │ statistics │ user │ token │ board │ fcm │ wifi │
├─────────────────────────────────────────────────────────────────┤
│ Repository Layer (JPA │ Native Query │ JDBC Batch │ Redis) │
├─────────────────────────────────────────────────────────────────┤
│ Scheduler Layer │
│ RankingScheduler │ SeasonTransition │ MaxFocus │ TokenCleanup │
└────────┬──────────┬──────────┬──────────┬───────────────────────┘
│ │ │ │
┌─────▼───┐ ┌───▼────┐ ┌──▼───┐ ┌───▼──────┐
│ MySQL 8 │ │ Redis │ │ FCM │ │Cloudinary│
└─────────┘ └────────┘ └──────┘ └──────────┘
```

---

## 2. 엔티티 관계도

```
┌─────────────┐
│ User │
│ role │ GUEST → USER → ADMIN
│ department │ Enum (25개 학과)
│ provider │ KAKAO, GOOGLE, APPLE
│ fcmToken │
└──────┬──────┘
┌──────────────┼──────────────┐
│ 1:N (FK) │ 1:N (FK) │ 1:N (FK 없음)
▼ ▼ ▼
┌─────────────┐ ┌───────────┐ ┌─────────────┐
│StudySession │ │UserRanking│ │RefreshToken │
│ startTime │ │ rank │ │ userId │
│ endTime │ │ totalMillis│ │ refreshToken│
│ totalMillis │ │ rankingType│ │ expiredAt │
│ status │ │calculatedAt│ └─────────────┘
└─────────────┘ └───────────┘

┌──────────────────┐ ┌───────────────────────┐ ┌────────┐
│DepartmentRanking │ │SeasonRankingSnapshot │ │ Season │
│ department (Enum)│ │ seasonId (FK없음) │ │ type │
│ rank, totalMillis│ │ userId (FK없음) │ │ status │
│ rankingType │ │ rankType, finalRank │ │ start │
│ calculatedAt │ │ department (nullable) │ │ end │
└──────────────────┘ └───────────────────────┘ └────────┘
```

**설계 결정:**
- `SeasonRankingSnapshot`에 FK 없음 → 시즌/유저 삭제 후에도 이력 보존
- `RefreshToken`에 FK 없음 → 유저 soft-delete와 독립적으로 토큰 정리
- `User` soft-delete 시 `deleted_` prefix → unique 제약 유지하면서 재가입 허용

---

## 3. 인증 플로우

```
[OAuth2 로그인]
앱 → /oauth2/authorization/{provider}?redirect_uri=...
→ CustomAuthorizationRequestResolver (redirect_uri를 state에 인코딩)
→ OAuth2 Provider 인증
→ CustomOAuth2UserService.loadUser() → User 조회/생성 (role=GUEST)
→ SuccessHandler → JWT 발급 → redirect_uri?accessToken=...&refreshToken=...

[회원가입 완료]
POST /email/request-code → Redis에 인증코드 (TTL 5분)
POST /email/verify-code → 코드 검증
POST /user/complete-registration → GUEST→USER 승격, 새 JWT 발급

[API 요청]
Authorization: Bearer {token}
→ JwtAuthenticationFilter → parseToken (JJWT, HMAC-SHA256)
→ withdrawn=true이면 /restore 외 차단
→ @PreAuthorize → @AssignUserId AOP → Controller
```

---

## 4. 랭킹 시스템

### 이중 랭킹 구조

```
date 파라미터 유무로 분기:

date 없음 (현재 기간) date 있음 (과거 기간)
│ │
▼ ▼
실시간 랭킹 확정 랭킹
StudySession Native Query로 UserRanking / DepartmentRanking
직접 계산 (진행중 세션 포함) 테이블에서 조회 (스케줄러가 저장)
```

### 시즌 랭킹 계산

```
현재 시즌 랭킹 = ① + ② + ③ 합산 후 순위 부여

① 확정 월간 합산 (시즌 시작 ~ 전월 말)
→ UserRankingRepository JPQL
② 현재 월 일간 합산 (이번 달 1일 ~ 어제)
→ UserRankingRepository JPQL
③ 오늘 실시간 데이터
→ StudySessionRepository Native Query

종료된 시즌 → SeasonRankingSnapshot 불변 스냅샷 조회 (계산 없음)
```

### 시즌 전환 (매일 00:05)

```
SeasonTransitionScheduler
→ 캐시 우회 DB 조회 → today ≥ endDate+1 ?
→ Yes: activeSeason 캐시 clear
→ transitionToNextSeason (현재=ENDED, 다음=ACTIVE)
→ createSeasonSnapshot (@Retryable 3회, JDBC 배치 2000건)
→ No: return
```

### 학과 랭킹

학과별 상위 30명의 공부 시간 합산. Native Query + CTE로 25개 학과 처리.
`ROW_NUMBER() PARTITION BY department` → 상위 30 필터 → `SUM GROUP BY` → `RANK()`.

---

## 5. 학습 세션 흐름

```
[시작] POST /study/start {gatewayIp, clientIp}
→ WiFi 검증 (@Cacheable) → 중복 STARTED 확인 → 세션 생성 (서버 시간)

[종료] POST /study/end {studySessionId}
→ 세션 조회 → endTime=서버시간, totalMillis=Duration 계산 → FINISHED

[자동종료] 매 10분 스케줄러
→ STARTED + 3시간 초과 세션 → 자동 종료 + FCM 알림
```

---

## 6. 크로스 도메인 의존성

### 서비스 의존 그래프

```
StudySessionService ──→ CampusWiFiValidationService, FcmService
PersonalRankService ──→ StudySessionRepository, UserRankingRepository
DepartmentRankService → StudySessionRepository, DepartmentRankingRepository
SeasonRankService ────→ SeasonService(@Cacheable), UserRankingRepo, StudySessionRepo
SeasonSnapshotService → UserRankingRepo, SeasonSnapshotBatchService(JDBC)
StatisticsService ────→ StudySessionRepository (12개 CTE)
UserService ──────────→ JwtHandler, RefreshTokenRepo, FcmService
TokenService ─────────→ JwtHandler, RefreshTokenRepo
```

### StudySessionRepository — 쿼리 허브

3개 도메인(study, rank, statistics)이 공유. 수정 시 전체 영향.

| 쿼리 | 도메인 | 용도 |
|------|--------|------|
| `calculateCurrentPeriodRanking` | rank | 실시간 개인 랭킹 |
| `calculateCurrentDepartmentRanking` | rank | 실시간 학과 랭킹 |
| `calculateFinalizedPeriodRanking` | rank | 확정 개인 랭킹 배치 |
| `calculateFinalizedDepartmentRanking` | rank | 확정 학과 랭킹 배치 |
| `getTwoHourSlotStats` | statistics | 일간 2시간 슬롯 |
| `getWeeklyStatistics` | statistics | 주간 통계 |
| `getMonthlyStatistics` | statistics | 월간 통계 |
| `getGrassStatistics` | statistics | 잔디 차트 (NTILE) |
| `sumCompletedStudySessionByUserId` | study | 오늘 총 공부 시간 |

---

## 7. 캐싱 전략

| 캐시 | 저장소 | 키 | TTL | 무효화 |
|------|--------|-----|-----|--------|
| `wifiValidation` | Caffeine | `gatewayIp:clientIp` | 10분 | 자동 만료 |
| `activeSeason` | Caffeine | 단일 엔트리 | 10분 | 시즌 전환 시 수동 clear |
| 이메일 인증코드 | Redis | `{userId}email:{email}` | 5분 | 자동 만료 |

---

## 8. 스케줄러 타임라인

```
매일:
00:00:00 RefreshTokenDelete 만료 토큰 삭제
00:00:05 DailyRanking 전일 개인/학과 랭킹 확정
00:05:00 SeasonTransition 시즌 종료 확인 → 전환/스냅샷
★ MonthlyRanking(00:02) 이후 실행 (데이터 의존)
월요일: 00:01 WeeklyRanking
1일: 00:02 MonthlyRanking
매 10분: MaxFocusStudy 3시간 초과 세션 자동 종료 + FCM
```

---

## 9. 예외 처리 경로

```
경로 1: Service 예외
throw BusinessException(ExceptionType) → GlobalExceptionHandler
→ {"success":false, "code":"ST002", "msg":"..."}

경로 2: 인증 예외
JwtAuthenticationFilter catch → HttpServletResponse 직접 JSON 작성
→ {"success":false, "code":"S004", "msg":"..."}

경로 3: Validation 예외
@Valid MethodArgumentNotValidException → GlobalExceptionHandler
→ {"success":false, "code":"C002", "msg":"커스텀 메시지"}
```

```
예외 계층:
RuntimeException
├── BusinessException (ExceptionType: code + message + HttpStatus)
└── JwtAuthenticationException
├── JwtTokenExpiredException (S004, 401)
├── JwtTokenInvalidException (S005, 401)
├── JwtNotExistException (S006, 401)
└── JwtAccessDeniedException (S003, 403)

응답 구조:
ResponseBody<T> (sealed)
├── SuccessResponseBody<T> → {"success":true, "data":{...}}
└── FailedResponseBody → {"success":false, "code":"...", "msg":"..."}
```
Loading
Loading