📔 Moodiary — Backend

일기 + AI 무드 캘린더 플랫폼

호서대학교 컴퓨터공학부 졸업프로젝트 · Backend 파트

Spring Boot 4.0.6 Java 25 AWS EC2 + RDS Docker JWT + Refresh Rotation @Async AI

01 무엇을 만들었나

사용자가 일기를 작성하면 AI 가 공감 메시지 + 그 날의 기분 이모지를 생성한다. 그 이모지는 월별 캘린더 화면에 매핑되어 한 달치 감정을 한눈에 보여준다.

🚀운영 서버

EC2 (Amazon Linux 2023) + Elastic IP. Docker 컨테이너로 배포 중.

→ Swagger UI 열기

🎤발표 슬라이드

5섹션 약 28장 · 페이지마다 발표 대본 포함.

→ 슬라이드 발표 (브라우저)

💻GitHub 레포

모든 PR / 트러블슈팅 / 결정 기록 공개. 63 PRs 머지.

→ goospel/hoseo-moodiary

📚문서 사이트

로드맵 · API 명세 · 보안 · 트러블슈팅 · 학습 노트 12 항목 · 운영 Runbook 까지 한 페이지에 검색 가능 (MkDocs Material).

→ 문서 사이트 열기

📒학습 노트

모르고 물어봐서 배운 기술 개념 12 항목. 일반화된 공개판은 goospel.github.io 에 별도 운영.

→ 학습 노트 (문서 사이트 안)

02 기술 스택

Java 25 (Amazon Corretto)
Spring Boot 4.0.6
Spring Security 6
Spring Data JPA + Hibernate 7.2
QueryDSL 5.0
JWT (jjwt 0.12.6, HS256, access 1h)
Refresh Token (2w, rotation, SHA-256)
@EnableAsync + DB 상태머신
BCrypt 해시
MySQL 9.x (AWS RDS, utf8mb4)
SpringDoc OpenAPI 3.0.3
Docker + AWS EC2 + Elastic IP
GitHub Actions (OIDC) + AWS SSM
JUnit 5 + Mockito (80+ tests, 0 failures)

03 핵심 구현

🏛️ 아키텍처 — 레이어 + 흐름

Controller → Service → Repository → Entity. 컨트롤러에 @AuthenticationPrincipal UUID 자동 주입, 서비스에 소유권 검증 + @Transactional, 엔티티에 명시 메서드 패턴 (setter X).

비동기 AI 응답 흐름 (PR 4-pre 운영 반영): POST /post 가 같은 트랜잭션에 AiResponse(PENDING) row 저장 → controller 가 commit 후 triggerAsync 호출 → @Async 스레드가 Stub 호출 → DONE/FAILED 전이. 클라이언트는 GET /post/{id}/ai-response 폴링.

🗄️ 데이터베이스 — 4 테이블 + 6 정책

UUID v4 PK

@UuidGenerator. 컬럼명 <table>_id. 분산 환경 가정 + ID 추측 방지.

BaseEntity Auditing

모든 엔티티 created_at / updated_at 자동. JpaAuditingConfig 분리 (슬라이스 테스트 호환).

utf8mb4 강제

이모지 (😊) 그대로 저장. 변환 로직 X. 운영 RDS 머지 전 사전 검증.

1:1 UNIQUE

Post ↔ AiResponseuk_ai_response_post_id UNIQUE. 운영 머지 후 SHOW INDEX 사후 검증 필수.

ddl-auto: update (현재)

부팅 시 누락 컬럼/테이블 자동 추가. 인덱스/UNIQUE 보장 X → PR 6 Flyway 도입 시 validate.

명시 메서드 패턴

setter 없음. Post.update(...) / AiResponse.markDone(...) → 트랜잭션 dirty-checking.

🚢 인프라 — Runtime

   [FE: React+Vite]              [BE: Spring Boot 4]        [AI: 별도 EC2]
          |  build                       |  build                    ^
          v  aws s3 sync                 v  Docker Hub               |
   +----------------+            +-----------------+                 |
   |  AWS S3        |            |  AWS EC2        |  <-- SSM --- GitHub
   |  ap-northeast-1| -- HTTP -->|  Amazon Linux   |     send       Actions
   |  (도쿄)        |            |  ap-northeast-2 |     -command   (main push)
   +----------------+            +-----------------+                 |
                                          |  JDBC                    |
                                          v                          |
                                 +-----------------+                 |
                                 |  AWS RDS MySQL  |                 |
                                 |  utf8mb4        |                 |
                                 +-----------------+                 |

CI: PR → dev → ./gradlew build. CD: push → main → Docker Hub (latest + :SHA) → AWS SSM 으로 EC2 에서 docker-compose pull && up -dwait command-executed + health check (swagger 200 polling). SHA 태그로 응급 롤백 가능.

🔐 보안 — 인증 / 인가 / CORS / 시크릿

BCrypt + JWT HS256

비밀번호 BCrypt strength 10. Access token 1시간, Claims sub=UUID.

Refresh Token Rotation

2주 수명. SHA-256 hex 만 DB 저장 — raw 노출 시에도 복원 불가. 사용 시 즉시 revoke + 새 발급 (탈취 노출 창 최소화).

소유권 격리

@AuthenticationPrincipal UUID 자동 주입 + Post.isOwnedBy() 검증 → 본인 200 / 타인 403.

CORS 명시 allowlist

app.cors.allowed-origins env 외부화. 와일드카드 * 금지. compose ${VAR:-default} 패턴.

열거 공격 방지

로그인 실패 / refresh 실패 모두 같은 401 메시지. 이메일 존재 여부 노출 X.

application.yaml 추적

모든 값 ${ENV_VAR:기본값} 플레이스홀더. 평문 비밀값 금지 (T-013 사고 이후 정책).

04 핵심 트러블슈팅

27 항목 누적 (T-001 ~ T-027). 작업 중 막힌 지점이 다음 사람의 시작점이 되도록 기록. 가장 큰 사건 셋:

T-019 · 운영 부팅 폭발 대사건 — 5층 결함 동시 노출

표면: CORS preflight 401. 실제: 컨테이너 restart loop. 원인: SSM agent 죽음 + CD silent fail + springdoc 2.8.3 ↔ Spring Boot 4 비호환 + Post→User FK orphan + @SpringBootTest @Disabled 안전망 부재. 교훈: 표면 증상에서 멈추지 말고 docker ps + docker logs + docker inspect .Created 부터.

T-023 · 옛 CD 들의 silent fail 진실

워크플로우의 docker compose (스페이스) 가 EC2 에서 unknown sub-command. PR #45 의 wait/verify 가 처음으로 노출 — 옛 CD 들의 자동 deploy 는 한 번도 진짜로 작동한 적이 없었다. SSM 의 send-command silent fail 이 그것을 가렸을 뿐.

T-015 → T-018 → T-020 · 머지된 PR 추적 못 함 (3회 재발)

사용자가 GitHub UI 에서 머지하는 시점과 Claude 가 인지하는 시점의 갭. 매번 새로운 회색 지대로 재발: PR 생성 → 추가 push → 메타데이터 변경. 규칙을 회색 지대 한정 열거가 아닌 "gh pr 으로 시작하는 거의 모든 명령" 으로 일반화.

→ 전체 트러블슈팅 로그 (T-001 ~ T-027)

05 앞으로