λΆλ₯
κΈ°μ
Language
Java 17
Framework
Spring Boot 3.2.0
Security
Spring Security + JWT (HttpOnly Cookie)
Database
PostgreSQL 15 (JPA / Hibernate)
Cache
Redis 7 (Lettuce)
Real-time
STOMP WebSocket, SSE
API Docs
Springdoc OpenAPI (Swagger UI)
ID Generation
TSID (hypersistence-utils)
Config Encryption
Jasypt
Monitoring
Micrometer + Prometheus
Build
Gradle 8.5
Container
Docker + Docker Compose
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Cloudflare β
β DNS Β· CDN Β· WAF Β· SSL/TLS β
ββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β HTTPS / WSS
ββββββΌβββββ
β Nginx β :443 β :8080 / :8001
ββββββ¬βββββ
βββββββββββββββΌββββββββββββββ
β β β
ββββββββΌβββββββ β ββββββββΌβββββββ
β BE Server β β β AI Worker β
β Spring Boot ββββββββ β FastAPI β
β :8080 βββββββββββββββ :8001 β
ββββββββ¬βββββββ Callback βββββββββββββββ
β
ββββββββ΄βββββββ
β Data Layer β
β PostgreSQL β
β Redis β
βββββββββββββββ
λλ©μΈ κ΅¬μ± (Clean Architecture)
domain/
βββ auth # μΈμ¦Β·μΈκ° (JWT, ν ν° μ¬λ°κΈ)
βββ exam # μν μΈμ
κ΄λ¦¬
βββ submission # μ½λ μ μΆ Β· Outbox Poller
βββ chat # AI μ±ν
Β· ν ν° μ¬μ©λ
βββ problem # λ¬Έμ CRUD
βββ statistics # ν΅κ³ μ§κ³
βββ admin # κ΄λ¦¬μ Β· λ§μ€ν° μ΄μ
κ° λλ©μΈμ ui β application β domain β infrastructure λ μ΄μ΄λ‘ λΆλ¦¬λλ©°,
UseCase λ¨μλ‘ λΉμ¦λμ€ λ‘μ§μ΄ ꡬμ±λ©λλ€.
src/main/java/com/yd/vibecode/
βββ VibecodeApplication.java
βββ domain/
β βββ admin/ # κ΄λ¦¬μ κ³μ ·보λΒ·λ©νΈλ¦Β·μν κ΄λ¦¬
β βββ auth/ # λ‘κ·ΈμΈΒ·λ‘κ·ΈμμΒ·ν ν° μ¬λ°κΈ
β βββ chat/ # μ±ν
μ μ₯Β·μ‘°νΒ·AI μ½λ°±
β βββ exam/ # μν μνΒ·μ°Έκ°μ μΈμ
β βββ problem/ # λ¬Έμ μ‘°ν
β βββ statistics/ # ν΅κ³
β βββ submission/ # μ μΆΒ·μ€νΈλ¦¬λ°Β·λ΄λΆ μ²λ¦¬
βββ global/
βββ config/ # Web, JPA, Redis, WebSocket, Async
βββ security/ # SecurityConfig, JWT, STOMP Interceptor
βββ interceptor/ # JWT Blacklist, Cookie Handshake
βββ swagger/ # API λͺ
μΈ μΈν°νμ΄μ€
βββ exception/ # μ μ μμΈ μ²λ¦¬
βββ annotation/ # @CurrentUser, @AccessToken λ±
βββ util/ # CookieUtils, SecureRandomGenerator
μ°Έκ°μ μ
μ₯ β Entry Code + μ΄λ¦ + μ νλ²νΈλ‘ JWT λ°κΈ (νμκ°μ
λΆνμ)
κ΄λ¦¬μ λ‘κ·ΈμΈ β ID/PW κΈ°λ° Access Token + Refresh Token λ°κΈ
ν ν° μ μ₯ β HttpOnly μΏ ν€ μ λ¬ (XSS λ°©μ§)
ν ν° λ‘ν
μ΄μ
β Refresh Token μ¬λ°κΈ μ κΈ°μ‘΄ ν ν° μ¦μ 무ν¨ν
λΈλ리μ€νΈ β λ‘κ·Έμμλ Access Tokenμ Redisμ λ±λ‘νμ¬ μ¬μ¬μ© μ°¨λ¨
STOMP μΈμ¦ μ°ν β WebSocketμ HttpOnly μΏ ν€ μ κ·Ό λΆκ° β bodyλ‘ ν ν° μ λ¬ ν StompPrincipalInterceptorμμ κ²μ¦
μν μμ±Β·μμΒ·μ’
λ£ (κ΄λ¦¬μ)
μν μν μ€μκ° μ‘°ν (νμ΄λ¨Έ λκΈ°ν)
μ°Έκ°μ μΈμ
μ 보 μ‘°ν
μ½λ λλννΈ μλ μ μ₯ (PUT /api/exams/{id}/code-draft)
π€ μ½λ μ μΆ Β· AI μ±μ
μ μΆ μ μ ν 202 Accepted μ¦μ μλ΅ (λΉλκΈ° μ²λ¦¬)
Outbox Poller λ‘ Redis μ΄λ²€νΈλ₯Ό AI Workerμ μ λ¬
AI μ±μ μλ£ μ Callback μμ (POST /api/callbacks/ai/**)
μ±μ κ²°κ³Όλ₯Ό SSE μ€νΈλ¦Ό μΌλ‘ κ΄λ¦¬μμκ² μ€μκ° μ μ‘
STOMP WebSocket (/ws) β μν μν λ³κ²½ λΈλ‘λμΊμ€νΈ
SSE (/api/admin/submissions/{id}/stream) β μ±μ μ§ν μ€νΈλ¦¬λ°
μ°Έκ°μ μ±ν
λ©μμ§ μ μ₯ β AI Workerλ‘ μ λ¬
λν μ΄λ ₯ μ‘°ν
ν ν° μ¬μ©λ μΆμ λ° μ ν
π§βπΌ κ΄λ¦¬μ / λ§μ€ν°
κ΄λ¦¬μ κ³μ CRUD, μ
μ₯ μ½λ μμ±Β·κ΄λ¦¬
μ€μκ° μ°Έκ°μ νν© λ³΄λ
μ μΆ μμΈ μ‘°ν (루λΈλ¦Β·μ½λ ν¬ν¨)
μμ€ν
λ©νΈλ¦, νλ λ‘κ·Έ
νλ«νΌ μ μ μ€μ (μν μκ°, ν ν° μ ν, λ°μ΄ν° λ³΄κ΄ μ μ±
)
Method
Path
μ€λͺ
κΆν
POST
/api/auth/enter
μ°Έκ°μ μ
μ₯ (JWT λ°κΈ)
Public
POST
/api/auth/admin/login
κ΄λ¦¬μ λ‘κ·ΈμΈ
Public
POST
/api/auth/admin/logout
λ‘κ·Έμμ
ADMIN
POST
/api/auth/admin/reissue
ν ν° μ¬λ°κΈ
Cookie
GET
/api/auth/me
λ΄ μ 보 μ‘°ν
Any
GET
/api/exams/{id}/state
μν μν μ‘°ν
USER
GET
/api/exams/{id}/participants/me
μ°Έκ°μ μΈμ
μ‘°ν
USER
POST
/api/exams/{id}/submissions
μ½λ μ μΆ (202)
USER
GET/PUT
/api/exams/{id}/code-draft
μ½λ λλννΈ
USER
GET
/api/submissions/{id}
μ μΆ μμΈ (λ³ΈμΈ)
USER
POST
/api/chat/messages
μ±ν
λ©μμ§ μ μ₯
USER
GET
/api/chat/history
μ±ν
μ΄λ ₯ μ‘°ν
Any
GET
/api/problems
λ¬Έμ λͺ©λ‘ μ‘°ν
USER
GET
/api/admin/**
κ΄λ¦¬μ μ μ© API
ADMIN/MASTER
POST
/api/callbacks/ai/**
AI μ±μ κ²°κ³Ό μμ
Internal
GET
/api/admin/submissions/{id}/stream
μ±μ κ²°κ³Ό SSE
ADMIN
Swagger UI β http://localhost:8080/swagger-ui/index.html
μ°Έκ°μ μλ²
β β
ββ POST /api/auth/enter βββββββΊβ
β { entryCode, name, phone } β
ββββββββββββββββββββββββββββββββ€ Set-Cookie: accessToken (HttpOnly)
β { accessToken } β (body ν¬ν¨: STOMP μΈμ¦μ©)
β β
β STOMP Connect β
ββ connectHeaders: { β
β Authorization: Bearer ... β
β } βββββββββββββββββββββββββββΊβ StompPrincipalInterceptor κ²μ¦
β β
κ΄λ¦¬μ
β
ββ POST /api/auth/admin/login ββΊβ
ββββββββββββββββββββββββββββββββ€ Set-Cookie: accessToken, refreshToken (HttpOnly)
β β
ββ POST /api/auth/admin/reissueβΊβ refreshToken κ²μ¦ + λ‘ν
μ΄μ
ββββββββββββββββββββββββββββββββ€ μ accessToken, refreshToken λ°κΈ
ws://host/ws
SUBSCRIBE /topic/exams/{examId}/state
β μν μμΒ·μ’
λ£Β·μν λ³κ²½ λΈλ‘λμΊμ€νΈ
SUBSCRIBE /topic/exams/{examId}/submissions
β μ κ· μ μΆ μλ¦Ό (κ΄λ¦¬μ 보λ)
GET /api/admin/submissions/{submissionId}/stream
β data: {"status":"JUDGING","score":null}
β data: {"status":"COMPLETED","score":85}
β event: close
Java 17+
Docker & Docker Compose
# 1. μΈνλΌ κΈ°λ (PostgreSQL + Redis)
docker compose up -d
# 2. μ ν리μΌμ΄μ
μ€ν
./gradlew bootRun
# μλ²: http://localhost:8080
# Swagger: http://localhost:8080/swagger-ui/index.html
docker build -t be-vibecodeeval .
docker run -p 8080:8080 be-vibecodeeval
docker compose -f docker-compose.prod.yml up -d
λ³μ
μ€λͺ
μμ
SPRING_DATASOURCE_URL
PostgreSQL JDBC URL
jdbc:postgresql://localhost:5432/ai_vibe_coding_test
SPRING_DATASOURCE_USERNAME
DB μ¬μ©μλͺ
postgres
SPRING_DATASOURCE_PASSWORD
DB λΉλ°λ²νΈ
password
SPRING_REDIS_HOST
Redis νΈμ€νΈ
localhost
SPRING_REDIS_PORT
Redis ν¬νΈ
6379
JWT_SECRET
JWT μλͺ
ν€ (256bit+)
your-secret-key
JWT_ACCESS_EXPIRATION
Access Token λ§λ£ (ms)
3600000
JWT_REFRESH_EXPIRATION
Refresh Token λ§λ£ (ms)
604800000
JASYPT_ENCRYPTOR_PASSWORD
μ€μ νμΌ μνΈν ν€
jasypt-password
AI_SERVER_URL
AI Worker μ£Όμ
http://localhost:8001
π κ΄λ ¨ λ ν¬μ§ν 리