μΈμ’
λνκ΅ λλλ¦Ό λΉκ΅κ³Ό 곡μ§μ νμ¬μΌμ λ°μ΄ν°λ₯Ό μμ§Β·κ°κ³΅νκ³ ,
νκ³ΌΒ·ν€μλΒ·νλ
μ‘°κ±΄μ΄ λ°μλ κ°μΈν .ics ꡬλ
URLμ λ°κΈνλ Spring Boot λ°±μλ μλ²
λλλ¦Όκ³Ό νμ¬μΌμ μ νμν μ λ³΄κ° ν©μ΄μ Έ μκ³ , μ¬μ©μκ° μ§μ μ°Ύμλ³΄μ§ μμΌλ©΄ μ μ²Β·μ΄μ μΌμ μ λμΉκΈ° μ½μ΅λλ€.
| λ¬Έμ | μ€λͺ |
|---|---|
| μ 보 λΆμ° | νμ¬μΌμ κ³Ό λλλ¦Ό 곡μ§κ° μλ‘ λ€λ₯Έ μ±κ²©μΌλ‘ ν©μ΄μ Έ μμ |
| μ§μ νμΈ λΆλ΄ | μ¬μ©μκ° μ£ΌκΈ°μ μΌλ‘ μ¬μ΄νΈλ₯Ό μ§μ λ°©λ¬Έν΄μΌ ν¨ |
| κ°μΈν λΆμ‘± | νκ΅μμ μ 곡νλ κΈ°λ³Έ μΌμ μ κ°μΈ κ΄μ¬μ¬ μ€μ¬ νν°λ§μ΄ μ΄λ €μ |
| μλ λ±λ‘ λ²κ±°λ‘μ | μΊλ¦°λ μ±μ μ§μ μ λ ₯νκ±°λ 볡μ¬ν΄μΌ νλ λΆνΈ |
λͺ©ν: κ³΅μ§ λ°μ΄ν°λ₯Ό μλ μμ§νκ³ , μ¬μ©μ μ‘°κ±΄μ΄ λ°μλ κ°μΈν ICS ꡬλ URLμ λ°κΈν΄ μΊλ¦°λ μ±μμ μλ λκΈ°νλλλ‘ μ§μ
β ν΄λΌμ΄μΈνΈ μμ² νμ¬/λλλ¦Ό κ³΅μ§ μ‘°ν λλ ICS λ°κΈ μμ²
β
β‘ λ°±μλ μ²λ¦¬ νν° κ²μ¦ Β· μ‘°ν Β· ꡬλ
ν ν° μμ±
β
β’ ICS URL λ°κΈ κ³ μ token κΈ°λ° .ics μ£Όμ μμ±
β
β£ μΊλ¦°λ ꡬλ
μ¬μ©μκ° κ΅¬κΈ Β· μ ν Β· μμ룩μ λ±λ‘
β
β€ ICS μ‘°ν μΊλ¦°λ μ±μ΄ /cal/{token}.ics νΈμΆ
β
β₯ μλ λκΈ°ν μΊμλ 곡μ§/νμ¬ λ°μ΄ν°λ₯Ό κΈ°λ°μΌλ‘ μ΅μ μΌμ λ°μ
| κΈ°λ₯ | μ€λͺ |
|---|---|
| π νμ¬μΌμ API | νλ μ λ³΄κ° ν¬ν¨λ νμ¬μΌμ λͺ©λ‘ μ‘°ν |
| π λλλ¦Ό κ³΅μ§ API | λλλ¦Ό OPEN κ³΅μ§ λͺ©λ‘ μ‘°ν |
| π ICS ꡬλ URL λ°κΈ | νμ¬/λλλ¦Ό νν° μ‘°κ±΄μ token κΈ°λ° κ΅¬λ URLλ‘ λ³ν |
| π ICS μΊλ¦°λ μμ± | /cal/{token}.ics μμ² μ λμ ICS λ³Έλ¬Έ λ λλ§ |
| π· ν¬λ‘€λ§ μλν | λλλ¦Ό κ³΅μ§ λ° νμ¬μΌμ μλ μμ§/λκΈ°ν |
| π€ AI λΆλ₯/μμ½ | λλλ¦Ό μμΈ κ³΅μ§λ₯Ό νκ³ΌΒ·ν€μλ κΈ°μ€μΌλ‘ λΆλ₯νκ³ μμ½ μμ± |
| β‘ μΊμ μ΅μ ν | ꡬλ ν ν°/ICS/κ³΅μ§ λͺ©λ‘μ CaffeineμΌλ‘ μΊμ± |
| π ꡬ쑰ν λ‘κ·Έ | JSON λ‘κ·Έ κΈ°λ° μ΄μ λ‘κ·Έ μμ§ λ° λͺ¨λν°λ§ λμ |
λ°±μλ μ΄μ κΈ°λ₯ νΌμΉκΈ°
| κΈ°λ₯ | μ€λͺ |
|---|---|
| π κ΄λ¦¬μ ν¬λ‘€λ§ API | νΉμ μ°λ νμ¬μΌμ λλ λλλ¦Ό ν¬λ‘€λ§ μλ μ€ν |
| π μ€μΌμ€ λ°°μΉ | λλλ¦Ό μ κΈ° ν¬λ‘€λ§, μ 체 λκΈ°ν, νμ¬μΌμ ν¬λ‘€λ§, λ§λ£ μ΄λ²€νΈ μ 리 |
| 𧹠ꡬλ μ 리 | λΉνμ± κ΅¬λ ν ν° cleanup |
| π§ͺ Swagger/OpenAPI | μ΄μ/λ‘컬 μλ² λ¬Έμν λ° ν μ€νΈ μ§μ |
Backend
Infra / Ops
ν¨ν€μ§ ꡬ쑰 νΌμΉκΈ°
src/main/java/com/doogoo/doogoo
βββ academic/ # νμ¬μΌμ μ‘°νΒ·λκΈ°ν
β βββ api/ # νμ¬μΌμ API, μμ²/μλ΅ DTO
β βββ application/ # νμ¬ μ‘°ν/μ°λλ³ λκΈ°ν μλΉμ€
β βββ domain/ # AcademicSchedule μν°ν° λ° DTO
β βββ infrastructure/ # AcademicScheduleRepository
β
βββ calendar/ # ICS μΊλ¦°λ μ‘°ν
β βββ api/ # /cal/{token}.ics μλν¬μΈνΈ
β βββ application/ # ICS λ λλ§, μΊμ, ν ν° μ²λ¦¬
β
βββ dodream/ # λλλ¦Ό κ³΅μ§ μ‘°νΒ·λκΈ°ν
β βββ api/ # λλλ¦Ό API, μμ²/μλ΅ DTO
β βββ application/ # κ³΅μ§ μ‘°ν/μ μ₯/μ
λ°μ΄νΈ/λ§κ° μ²λ¦¬
β βββ domain/ # Event μν°ν°, μν, DTO
β βββ infrastructure/ # EventRepository
β
βββ subscription/ # ꡬλ
ν ν° λ°κΈΒ·μ‘°νΒ·μ 리
β βββ application/ # ꡬλ
λ°κΈ/μ‘°ν/cleanup μλΉμ€
β βββ domain/ # Subscription μν°ν° λ° κ΄λ ¨ enum
β βββ infrastructure/ # SubscriptionRepository
β
βββ lookup/ # νλ
Β·νκ³ΌΒ·ν€μλ μ‘°ν
β βββ api/
β βββ application/
β βββ domain/
β
βββ crawl/ # λλλ¦Ό/νμ¬ ν¬λ‘€λ¬, νμ, μ€μΌμ€λ¬
βββ classify/ # AI λΆλ₯/μμ½ μ°λ
βββ common/ # μλ¬ μ²λ¦¬, λ‘κ·Έ, μ νΈλ¦¬ν°
βββ global/ # κ΄λ¦¬μ API, κ³΅ν΅ μ€μ , μΊμ, CORS, OpenAPI
βββ DoogooApplication.java # Spring Boot μ§μ
μ
src/main/resources
βββ application.yaml # κ³΅ν΅ μ€μ
βββ application-prod.yaml # μ΄μ DB / CORS μ€μ
βββ application-test.yaml # ν
μ€νΈ μ€μ
βββ application-dev.yml # κ°λ° μ€μ
βββ logback-spring.xml # ꡬ쑰ν λ‘κ·Έ μ€μ
βββ prompts/ # AI λΆλ₯/μμ½ ν둬ννΈ
src/test/java/com/doogoo/doogoo
βββ academic/api/ # νμ¬ API ν
μ€νΈ
βββ calendar/api/ # ICS μ‘°ν ν
μ€νΈ
βββ calendar/application/ # ICS μΊμ ν
μ€νΈ
βββ dodream/api/ # λλλ¦Ό API ν
μ€νΈ
βββ crawl/ # ν¬λ‘€λ§ νμ ν
μ€νΈ
βββ subscription/domain/ # ꡬλ
λλ©μΈ ν
μ€νΈ
λ°±μλ μλ²: https://www.sejongdoogoo-api.com
Swagger UI:
- μ΄μ:
https://www.sejongdoogoo-api.com/swagger-ui/index.html - λ‘컬:
http://localhost:8080/swagger-ui/index.html
| Method | Endpoint | μ€λͺ |
|---|---|---|
GET |
/api/grades |
νλ λͺ©λ‘ μ‘°ν |
GET |
/api/colleges |
λ¨κ³ΌλΒ·νκ³Ό λͺ©λ‘ μ‘°ν |
GET |
/api/keywords |
λλλ¦Ό ν€μλ λͺ©λ‘ μ‘°ν |
GET |
/api/academic/notices |
νμ¬μΌμ λͺ©λ‘ μ‘°ν |
GET |
/api/dodream/notices |
λλλ¦Ό κ³΅μ§ λͺ©λ‘ μ‘°ν |
POST |
/api/academic/ics |
νμ¬μΌμ ICS ꡬλ URL λ°κΈ |
POST |
/api/dodream/ics |
λλλ¦Ό ICS ꡬλ URL λ°κΈ |
GET |
/cal/{token}.ics |
ꡬλ ν ν° κΈ°λ° ICS μΊλ¦°λ μ‘°ν |
| Method | Endpoint | μ€λͺ |
|---|---|---|
POST |
/api/admin/crawl/academic/{year} |
νΉμ μ°λ νμ¬μΌμ ν¬λ‘€λ§ |
POST |
/api/admin/crawl/academic/current |
νμ¬ μ°λ νμ¬μΌμ ν¬λ‘€λ§ |
POST |
/api/admin/crawl/academic/current-and-previous |
νμ¬Β·μ΄μ μ°λ νμ¬μΌμ ν¬λ‘€λ§ |
POST |
/api/admin/crawl/dodream |
λλλ¦Ό μ κΈ° ν¬λ‘€λ§ μλ μ€ν |
| Method | Endpoint | μ€λͺ |
|---|---|---|
GET |
/actuator/health |
μλ² μν νμΈ |
| μμ | ν¬λ‘ | μ€λͺ |
|---|---|---|
regularCrawl() |
0 0 0 */2 * * |
λλλ¦Ό μ κΈ° ν¬λ‘€λ§ |
fullSync() |
0 0 3 * * * |
open μ΄λ²€νΈ μμΈ λκΈ°ν |
crawlAcademicSchedule() |
0 0 2 1 1 * |
μ° 1ν νμ¬μΌμ ν¬λ‘€λ§ |
cleanExpiredEvents() |
0 0 4 * * * |
λ§λ£ μ΄λ²€νΈ μ 리 |
deleteSubscription() |
0 0 1 * * MON |
λΉνμ± κ΅¬λ μ 리 |
νλ‘μ νΈ λ£¨νΈμ .env.exampleλ₯Ό μ°Έκ³ ν΄ λ€μ κ°μ μ€λΉν©λλ€.
SEJONG_PORTAL_ID=your_portal_id
SEJONG_PORTAL_PASSWORD=your_portal_password
OPENAI_API_KEY=sk-...
SPRING_PROFILES_ACTIVE=prod
DB_URL=jdbc:postgresql://localhost:5432/doogoo
DB_USERNAME=doogoo_user
DB_PASSWORD=your_db_password
CORS_ALLOWED_ORIGINS=https://yourdomain.comκΈ°λ³Έ νλ‘νμ devμ
λλ€.
./gradlew bootRunν μ€νΈ μ€ν:
./gradlew testJAR λΉλ:
./gradlew bootJar- μ΄μ νλ‘ν:
prod - DB: PostgreSQL
- JPA DDL:
update - CORS:
CORS_ALLOWED_ORIGINSκΈ°λ° - κ³΅κ° μλ² μ 보: OpenAPI μ€μ μ production server λ±λ‘
| νλͺ© | μ€λͺ |
|---|---|
| ν ν° λ°κΈ | Base62 12μ리 ν ν° μμ± ν subscriptions μ μ₯ |
| ICS λ λλ§ | token -> Subscription -> νν° μ‘°κ±΄ -> ICS λ³Έλ¬Έ μμ± |
| μΊμ | icsCache, tokenCache, κ³΅μ§ λͺ©λ‘ μΊμ μ¬μ© |
| λμμ± μ μ΄ | ICS λ λλ§μ© Semaphore(10) μ¬μ© |
| λ‘κ·Έ | logback-spring.xml κΈ°λ° JSON ꡬ쑰ν λ‘κ·Έ |
| μλ¬ μ²λ¦¬ | GlobalExceptionHandlerμμ API/ICS μμ² κ³΅ν΅ μ²λ¦¬ |
νμ¬ ν¬ν¨λ ν μ€νΈ μμ:
AcademicControllerTestDoDreamControllerTestCalendarControllerTestIcsCacheTestAcademicParserTestDodreamParserTestSubscriptionTest
ν΅μ¬ κ²μ¦ λ²μ:
- κ³΅μ§ μ‘°ν API μλ΅
- ICS λ°κΈ λ° μ‘°ν
- ꡬλ ν ν° μ²λ¦¬
- μΊμ λμ
- ν¬λ‘€λ§ νμ± λ‘μ§
- λλλ¦Ό νν°λ₯Ό DB 쑰건μΌλ‘ λ μ§μ λ°μν΄ μ‘°ν λΉμ© μ΅μ ν
- cleanup 쿼리μ μΈλ±μ€ μ λ΅ κ³ λν
- μ΄μ λ‘κ·Έ/Grafana κΈ°λ° λͺ¨λν°λ§ κ°ν
- λ°°μΉ μν λ° ν¬λ‘€λ§ κ²°κ³Ό κ°μμ± κ°μ
- λ°°ν¬/μ΄μ λ¬Έμμ λ°±μλ README μ°λ κ°ν