AI κΈ°λ° μ±μ© κ³΅κ³ μ 리 Β· λͺ¨μ μ§μμ μμ± Β· μμμ λΆμ νλ«νΌ JobDriμ λ°±μλ μλ²μ
λλ€.
"μ±μ© κ³΅κ³ λ₯Ό μ 리νκ³ , μ§μμλ₯Ό λ§λ€κ³ , μμμκΉμ§ λΆμλ°μ."
μ¬μ©μκ° μ λ‘λνκ±°λ μ λ ₯ν μ±μ© κ³΅κ³ λ₯Ό AIκ° κ΅¬μ‘°ννκ³ ,
μ§λ¬΄/νμ¬ κΈ°λ°μ λͺ¨μ κ³΅κ³ μ λ¬Ένμ μμ±νλ©°,
μμ±ν μμμλ₯Ό AI λΆμ + μ€μκ° μν μλ¦ΌμΌλ‘ μ 곡νλ AI μ·¨μ μ§μ νλ«νΌμ λλ€.
Server: https://api.jobdri.site
JobDriλ μ±μ© κ³΅κ³ λΆν° λͺ¨μ μ§μ, μμμ λΆμκΉμ§ μ΄μ΄μ§λ μ§μ μ€λΉ νλ¦μ νλμ μλΉμ€ μμμ μ 곡νλ λ°±μλμ
λλ€.
μ΄ μλ²λ λ€μ νλ¦μ μ€μ¬μΌλ‘ λμν©λλ€.
- μ±μ© κ³΅κ³ μ λ ₯/μ 리 μ¬μ©μκ° ν μ€νΈ λλ μ΄λ―Έμ§ κΈ°λ° κ³΅κ³ λ₯Ό μ λ ₯νλ©΄ AIκ° νμ¬λͺ , μ§λ¬΄λͺ , μ£Όμ μ 무, μ격 μ건, μ°λ μ¬νμ ꡬ쑰νν©λλ€.
- κ³΅κ³ μ μ₯ λ° λͺ¨μ μ§μ μμ±
μ μ₯λ μ€μ κ³΅κ³ λλ AIκ° λ§λ λͺ¨μ κ³΅κ³ λ₯Ό κΈ°λ°μΌλ‘
MockApplyλ₯Ό μμ±νκ³ , μΆμ² λ¬Έν λλ μ§μ μΆκ° λ¬Ένμ μ νν΄ μ§μμλ₯Ό μμ±ν©λλ€. - μμμ λΆμ μμ±ν λ΅λ³μ AIκ° λΉλκΈ°λ‘ λΆμνκ³ , μ μ/νΌλλ°±/λ¬Ένλ³ μ½λ©νΈ/λλ½ ν€μλλ₯Ό μ μ₯ν©λλ€.
- μ€μκ° μν μΆμ
κΈ΄ μμ
μ
RabbitMQ + μΈλΆ FastAPI worker + internal callback API + SSEκ΅¬μ‘°λ‘ μ²λ¦¬νλ©°, μ¬μ©μλ μ§ν μνμ μλ£ μλ¦Όμ μ€μκ°μΌλ‘ νμΈν μ μμ΅λλ€.
POST /api/job-postings/extractν μ€νΈ λλ S3 μ΄λ―Έμ§ object keyλ₯Ό λ°μ AIκ° κ³΅κ³ λ₯Ό μΆμΆν©λλ€.POST /api/job-postings/ingestκ³΅κ³ μΆμΆ β λΆλ₯ ν보 νμ β κ³΅κ³ μ΄μ μμ± β μ μ₯κΉμ§ λΉλκΈ° μμ μΌλ‘ μ μν©λλ€.GET /api/job-postings/ingest/async/{taskId}λΉλκΈ° μνλ₯Ό μ‘°νν©λλ€.GET /api/job-postings/ingest/async/{taskId}/streamSSEλ‘ μν λ³νλ₯Ό ꡬλ ν©λλ€.
- μ μ₯λ μ€μ κ³΅κ³ κΈ°λ°
ACTUALνμ λͺ¨μ μ§μ μμ± - μ μ₯λ κ³΅κ³ κΈ°λ°
MOCKνμ λͺ¨μ μ§μ μμ± - νμ¬/μ§λ¬΄ λΆλ₯ κΈ°λ° κ°μ κ³΅κ³ μμ±κ³Ό μΆμ² μ§λ¬Έ μμ±
- κ°μ κ³΅κ³ κΈ°μ€ νμ°¨(
sequence) κ΄λ¦¬ λ° μ¬λμ μμ±
- λ¬Έν ν보 μ‘°ν, μ§μ λ¬Έν μΆκ°, μ ν λ¬Έν μ μ₯
- λ΅λ³ μ μ₯/μμ
POST /api/mock-applies/{mockApplyId}/analysisμμμ λΆμ λΉλκΈ° μμ μ μGET /api/mock-applies/{mockApplyId}/analysisμ μ₯λ λΆμ κ²°κ³Ό μ‘°νGET /api/mock-applies/{mockApplyId}/analysis/async/{taskId}/streamSSE μν μ€νΈλ¦Ό ꡬλ
| Category | Stack |
|---|---|
| Language | Java 21 |
| Framework | Spring Boot 3.5.14 |
| ORM / DB | Spring Data JPA, PostgreSQL, H2(Test), pgvector |
| Cache / Messaging | Redis, RabbitMQ |
| Auth / Security | Spring Security, JWT, Google OAuth2 |
| AI / Embedding | OpenAI API (gpt-4o-mini default), Cohere Embedding (embed-v4.0) |
| Storage / Infra | AWS S3, Docker, GHCR, EC2, Nginx |
| Payment | Toss Payments |
| Mail / Realtime | JavaMailSender(Gmail SMTP), SSE |
| Docs / Build | SpringDoc Swagger, Gradle, GitHub Actions |
- μ΄λ©μΌ μΈμ¦λ²νΈ λ°μ‘/κ²μ¦ κΈ°λ° νμκ°μ
- JWT Access/Refresh Token λ‘κ·ΈμΈ λ° μ¬λ°κΈ
- Redis κΈ°λ° λ‘κ·Έμμ/ν ν° λ¬΄ν¨ν μ²λ¦¬
- Google OAuth2 λ‘κ·ΈμΈ λ° νλ‘ νΈ λ¦¬λ€μ΄λ νΈ μ°λ
- κ³΅κ³ ν μ€νΈ/μ΄λ―Έμ§μμ ꡬ쑰ν μ 보 μΆμΆ
- S3 presigned PUT URL κΈ°λ° μ΄λ―Έμ§ μ§μ μ λ‘λ
- μ§λ¬΄ μλΆλ₯ μλ λΆλ₯ ν보 μΆμ²
- νμ¬/μ§λ¬΄ κΈ°λ° μ€μ κ³΅κ³ μ΄μ μμ±
- νμ¬/μ§λ¬΄ κΈ°λ° λͺ¨μ κ³΅κ³ λ° μΆμ² μ§λ¬Έ μμ±
- μ€μ κ³΅κ³ κΈ°λ°
ACTUALνμ μ§μ μμ± - μμ± κ³΅κ³ κΈ°λ°
MOCKνμ μ§μ μμ± - μ§λ¬Έ ν보 μ‘°ν λ° μ§μ λ¬Έν μΆκ°
- μ ν λ¬Έν μ μ₯ ν λ΅λ³ μμ± λ¨κ³ μ ν
- κ³΅κ³ λ³ νμ°¨ κ΄λ¦¬ λ° μ¬λμ μ§μ
- λΉλκΈ° λΆμ μμ μ μ λ° μν μ‘°ν
- λΆμ μλ£ μ μ’ ν© μ μ, νλͺ©λ³ μ μ, μ 체 νΌλλ°± μ μ₯
- λ¬Ένλ³ λΆμ κ²°κ³Όμ λλ½ ν€μλ μ 곡
- λΆμ μ€ν μ ν¬λ λ§ 1ν μ°¨κ°, μ€ν¨ μ νλΆ
- ν¬λ λ§ νλ μ‘°ν
- ν μ€νμ΄λ¨ΌμΈ κ²°μ μ€λΉ/μΉμΈ
- μ¬μ©μ ν¬λ λ§ μμ‘/κ±°λ λ΄μ μ‘°ν
- κΈ°λ³Έ νλ
1νκΆ,5νκΆ,10νκΆ
- SSE κΈ°λ° μΈμ± μλ¦Ό μ€νΈλ¦Ό
- λ―Έμ½μ κ°μ μ‘°ν
- κ°λ³/μ 체 μ½μ μ²λ¦¬
- νμ¬ κ΅¬νλ μ£Όμ μλ¦Ό νμ
JOB_POSTING_ASYNC_SUCCEEDED,JOB_POSTING_ASYNC_FAILED,ANALYSIS_ASYNC_SUCCEEDED,ANALYSIS_ASYNC_FAILED
- corpus xlsx μ μ¬ API
- corpus embedding λκΈ°ν API
- λΆμ retrieval preview λλ²κ·Έ API
- λΆνΈμ€νΈλ© κ΄λ¦¬μ μ΄λ©μΌ μΉκ²©
- λΉλκΈ° task timeout sweep μ€μΌμ€λ¬
- κ°μ¬ λ‘κ·Έ AOP (
@AuditLogEvent)
POST /api/auth/email-verificationsPOST /api/auth/email-verifications/confirmationsPOST /api/auth/signupPOST /api/auth/loginPOST /api/auth/reissuePOST /api/auth/logoutGET /oauth2/authorization/googleGET /login/oauth2/code/google
GET /api/classificationsGET /api/classifications/{bigId}/middlesGET /api/classifications/middles/{middleId}/details
POST /api/job-postingsPATCH /api/job-postings/{jobPostingId}GET /api/job-postings/meGET /api/job-postings/me/{jobPostingId}DELETE /api/job-postings/{jobPostingId}POST /api/job-postings/generatePOST /api/job-postings/extractPOST /api/job-postings/ingestGET /api/job-postings/ingest/async/{taskId}GET /api/job-postings/ingest/async/{taskId}/streamPOST /api/job-postings/images/presign-uploadPOST /api/job-postings/extension/ingest
POST /api/job-postings/mock/generateGET /api/job-postings/mock/questionsPOST /api/job-postings/mock/questionsGET /api/mock-applies/mePOST /api/mock-applies/actualPOST /api/mock-applies/mock/from-job-postingPOST /api/mock-applies/mockPOST /api/mock-applies/{mockApplyId}/retryGET /api/mock-applies/{mockApplyId}/job-postingGET /api/mock-applies/{mockApplyId}/sequence
GET /api/mock-applies/{mockApplyId}/questions/candidatesPOST /api/mock-applies/{mockApplyId}/questions/candidatesGET /api/mock-applies/{mockApplyId}/questionsPUT /api/mock-applies/{mockApplyId}/questionsPATCH /api/mock-applies/{mockApplyId}/questions/answersPOST /api/mock-applies/{mockApplyId}/analysisGET /api/mock-applies/{mockApplyId}/analysisGET /api/mock-applies/{mockApplyId}/analysis/async/{taskId}GET /api/mock-applies/{mockApplyId}/analysis/async/{taskId}/streamGET /api/job-postings/{jobPostingId}/analysis
GET /api/payments/plansPOST /api/payments/preparePOST /api/payments/confirmGET /api/payments/credits/meGET /api/payments/credits/me/transactions
GET /api/notificationsGET /api/notifications/unread-countGET /api/notifications/streamPATCH /api/notifications/{notificationId}/readPATCH /api/notifications/read-all
POST /api/admin/corpus/importPOST /api/admin/corpus/import/uploadPOST /api/admin/corpus/embeddings/syncPOST /api/admin/analysis/retrieval-previewPOST /api/internal/worker/job-postings/...POST /api/internal/worker/analysis/...
graph TD
Client[Web Client] -->|JWT / OAuth2 / REST| Spring[Spring Boot API]
Client -->|SSE| Spring
Client -->|Presigned URL Upload| S3[(AWS S3)]
Spring -->|JPA| Postgres[(PostgreSQL)]
Spring -->|Cache / Token / Realtime state| Redis[(Redis)]
Spring -->|AMQP Publish| Rabbit[(RabbitMQ)]
Spring -->|OpenAI Responses API| OpenAI[OpenAI]
Spring -->|Embedding Sync| Cohere[Cohere]
Spring -->|Payment Confirm| Toss[Toss Payments]
Spring -->|SMTP| Gmail[Gmail SMTP]
Rabbit -->|Task Consume| Worker[FastAPI Worker<br/>external deployment]
Worker -->|Internal callback API| Spring
GitHub[GitHub Actions] -->|Build jar / Docker image| GHCR[GHCR]
GHCR -->|Pull latest image| EC2[EC2 + Docker Compose]
EC2 -->|Reverse Proxy| Nginx[Nginx]
Nginx --> Spring
sequenceDiagram
participant U as User
participant C as Client
participant S as Spring Boot API
participant MQ as RabbitMQ
participant W as FastAPI Worker
participant DB as PostgreSQL
participant SSE as SSE Stream
U->>C: μ±μ© κ³΅κ³ λΆμ / μμμ λΆμ μμ²
C->>S: λΉλκΈ° API νΈμΆ
S->>DB: async task μμ±
S->>MQ: μμ
λ©μμ§ publish
S-->>C: taskId λ°ν
C->>SSE: taskId κΈ°μ€ μν μ€νΈλ¦Ό ꡬλ
MQ->>W: μμ
μ λ¬
W->>S: context μ‘°ν (internal API)
W->>S: running / retry / failed / complete callback
S->>DB: μν λ° κ²°κ³Ό μ μ₯
S-->>SSE: μν λ³κ²½ μ΄λ²€νΈ μ μ‘
SSE-->>C: μ§ν μν / μλ£ / μ€ν¨ μμ
μ΄ μλ²μ ν΅μ¬ λΉλκΈ° μμ
μ μ±μ© κ³΅κ³ ingestμ μμμ λΆμ λ μ’
λ₯μ
λλ€.
- μ¬μ©μκ° λΉλκΈ° APIλ₯Ό νΈμΆν©λλ€.
- Spring μλ²κ° task rowλ₯Ό μμ±ν©λλ€.
- Spring μλ²κ° RabbitMQ exchange/queueλ‘ μμ λ©μμ§λ₯Ό publish ν©λλ€.
- μΈλΆ FastAPI workerκ° λ©μμ§λ₯Ό consume ν©λλ€.
- workerλ νμ 컨ν μ€νΈλ₯Ό internal APIλ‘ μ‘°νν©λλ€.
- workerκ° AI μ²λ¦¬ κ²°κ³Όλ₯Ό internal callback APIλ‘ μλ£/μ€ν¨ λ°μν©λλ€.
- Spring μλ²κ° DB μ μ₯, μλ¦Ό μμ±, SSE μν μ νλ₯Ό μνν©λλ€.
com.jobdri.jobdri_api
βββ domain
β βββ analysis # μμμ λΆμ, μ§λ¬Έ μ ν, async task, worker bridge
β βββ auth # νμκ°μ
, λ‘κ·ΈμΈ, μ΄λ©μΌ μΈμ¦, OAuth2
β βββ classification # λ/μ€/μλΆλ₯ μ§λ¬΄ μ²΄κ³ μ‘°ν
β βββ company # νμ¬ μν°ν°
β βββ corpus # corpus μ μ¬, μλ² λ© λκΈ°ν, retrieval
β βββ jobposting # κ³΅κ³ μ μ₯/μμ /μμ±/μΆμΆ/ingest/extension μ°λ
β βββ mockapply # λͺ¨μ μ§μ μμ±, νμ°¨, μ¬λμ
β βββ notification # SSE μλ¦Ό, μ½μ μ²λ¦¬
β βββ payment # ν¬λ λ§, κ²°μ , κ±°λ λ΄μ
β βββ audit # κ°μ¬ λ‘κ·Έ AOP
β βββ user # μ¬μ©μ λλ©μΈ
β βββ skill / experience
βββ global
β βββ apiPayload # κ³΅ν΅ μλ΅/μλ¬ μ½λ
β βββ config # Security, Swagger, RabbitMQ, AWS S3, Async μ€μ
β βββ jwt # JWT νν°/μ νΈ
β βββ mq # RabbitMQ publish support
β βββ scheduling # async timeout sweep scheduler
β βββ security # UserDetails, internal API key validator
β βββ sse # SSE subscription registry
βββ resources
βββ application*.yaml
βββ schema.sqlcp .env.example .env
./gradlew bootRunκΈ°λ³Έ ν¬νΈ:
- API:
http://localhost:8080 - Swagger:
http://localhost:8080/swagger-ui/index.html
./gradlew testν
μ€νΈ νκ²½μ H2 (MODE=PostgreSQL)λ₯Ό μ¬μ©ν©λλ€.
cp .env.example .env
docker compose up --buildν¬ν¨ μλ μλΉμ€:
- Spring Boot API
- PostgreSQL
- Redis
- RabbitMQ
- external FastAPI worker μ°κ²°μ© worker service μ μ
μ£Όμ νκ²½ λ³μ:
DB_URL,DB_USERNAME,DB_PASSWORDREDIS_HOST,REDIS_PORTRABBITMQ_HOST,RABBITMQ_PORT,RABBITMQ_USERNAME,RABBITMQ_PASSWORDJWT_SECRET_KEY,JWT_ACCESS_TOKEN_EXPIRATION,JWT_REFRESH_TOKEN_EXPIRATIONMAIL_HOST,MAIL_USERNAME,MAIL_PASSWORDGOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRETOPENAI_API_KEYCOHERE_API_KEYAWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_REGION,S3_BUCKETTOSS_CLIENT_KEY,TOSS_SECRET_KEYAPP_WORKER_INTERNAL_API_KEY
worker μ°λ κ΄λ ¨ κΈ°λ³Έ queue μ€μ :
APP_WORKER_JOB_POSTING_QUEUE=jobdri.job-posting.ingestAPP_WORKER_ANALYSIS_QUEUE=jobdri.analysis.execute
μ΄ νλ‘μ νΈλ λ¨μ LLM νΈμΆλ§ μ¬μ©νλ κ΅¬μ‘°κ° μλλΌ, corpus κΈ°λ° retrievalλ₯Ό ν¨κ» μ¬μ©ν©λλ€.
- μ±μ© κ³΅κ³ corpus μ μ¬
- μ§λ¬Έ corpus μ μ¬
- Cohere μλ² λ© μμ±
- pgvector μ μ₯
- λͺ¨μ κ³΅κ³ μμ± λ° μμμ λΆμ μ retrieval context μ°Έκ³
- κ΄λ¦¬μ preview APIλ‘ retrieval κ²°κ³Ό μ¬μ μ κ² κ°λ₯
Python μ€ν¬λ¦½νΈλ ν¬ν¨λμ΄ μμ΅λλ€.
scripts/import_corpus.pyscripts/sync_corpus_embeddings.py
GitHub Actions κΈ°μ€ λ°°ν¬ νλ¦:
main,devλΈλμΉ push λλ PR λ°μ- CIμμ JDK 21 κΈ°λ°
./gradlew clean testμ€ν - Docker image build
- Deploy workflowμμ GHCRμ μ΄λ―Έμ§ push
- λ°°ν¬ secretμ΄ μ‘΄μ¬νλ©΄ μ격 μλ²μ SSH μ μ
- μλ²μμ
docker compose -f docker-compose.prod.yml pull api docker compose -f docker-compose.prod.yml up -d api
SpringDoc Swagger UI:
- Local:
http://localhost:8080/swagger-ui/index.html - Production:
https://api.jobdri.site/swagger-ui/index.html
μΆκ° κ³΅κ° κ²½λ‘:
- OpenAPI docs:
/v3/api-docs - Health check:
/actuator/health
- μ΄λ©μΌ μΈμ¦ κΈ°λ° λ‘컬 νμκ°μ /λ‘κ·ΈμΈ
- Google OAuth2 λ‘κ·ΈμΈ
- JWT μΈμ¦/μΈκ° λ° λ‘κ·Έμμ μ²λ¦¬
- μ§λ¬΄ λ/μ€/μλΆλ₯ μ‘°ν
- μ±μ© κ³΅κ³ μμ±/μμ /μ‘°ν/μμ
- μ±μ© κ³΅κ³ μ΄λ―Έμ§ presigned upload
- AI μ±μ© κ³΅κ³ μΆμΆ
- AI μ±μ© κ³΅κ³ μ΄μ μμ±
- AI λͺ¨μ κ³΅κ³ μμ±
- μΆμ² μ§λ¬Έ μΊμ λ° μ‘°ν
- μ€μ /λͺ¨μ κ³΅κ³ κΈ°λ° λͺ¨μ μ§μ μμ±
- μ§λ¬Έ μ ν, μ§μ λ¬Έν μΆκ°, λ΅λ³ μ μ₯
- μμμ λΆμ λΉλκΈ° μ²λ¦¬
- ν¬λ λ§ μ°¨κ°/νλΆ
- ν μ€ κ²°μ μ°λ
- SSE μλ¦Ό
- κ΄λ¦¬μ corpus import / embedding sync
- RabbitMQ κΈ°λ° μΈλΆ worker μ°λμ© internal API
- async timeout sweep scheduler
- κ°μ¬ λ‘κ·Έ μ μ¬