실시간 학습 신호, 강의 요약, 대시보드 집계를 담당하는 iKnow 백엔드입니다.
학생 클라이언트에서 발생한 학습 신호와 강의 텍스트를 저장하고, 강사 대시보드에 실시간 알림과 수업 단위 분석 데이터를 제공합니다.
| 구분 | 내용 |
|---|---|
| Language | Java 21 |
| Framework | Spring Boot 4.0.5 |
| Build | Gradle |
| Persistence | Spring Data JPA |
| Database | MySQL 8 |
| Realtime | Spring WebSocket, STOMP, SockJS |
| Timezone | Asia/Seoul |
flowchart LR
student[학생 클라이언트<br/>React] -->|학습 신호 전송| spring[Spring Boot Backend]
student -->|STT/강의 텍스트 전달| spring
student -->|이해도 점수 전송| spring
teacher[강사 대시보드<br/>React] -->|REST 조회| spring
teacher <-->|STOMP alert topic| spring
fastapi[FastAPI AI Server<br/>FER / MediaPipe / GPT] -.직접 연동 가능.- student
spring --> mysql[(MySQL)]
sequenceDiagram
participant S as Student Client
participant B as Spring Boot
participant DB as MySQL
participant T as Teacher Dashboard
S->>B: POST /api/confused-events
B->>DB: LearningSignalEvent 저장
B-->>T: WebSocket Push (/topic/alert/{sessionId})
sequenceDiagram
participant C as Client
participant B as Spring Boot
participant DB as MySQL
C->>B: POST /api/lecture-chunk
B->>DB: Alert 생성
C->>B: POST /api/lecture-summary
B->>DB: Alert 갱신 + AlertKeyword 재생성
stateDiagram-v2
[*] --> ACTIVE: POST /api/sessions
ACTIVE --> ACTIVE: 참가자 join/leave
ACTIVE --> ENDED: PATCH /api/sessions/{sessionId}/end
ACTIVE --> ENDED: 24시간 경과 시 lazy expire
ACTIVE --> [*]: POST /api/sessions/{sessionId}/terminate\n(ENDED 처리 후 DB 삭제)
ENDED --> [*]
erDiagram
curriculums {
BIGINT id PK
VARCHAR name UK
DATETIME createdAt
}
sessions {
BIGINT id PK
VARCHAR sessionId UK
VARCHAR classId
INT thresholdPct
TEXT curriculum
DATETIME startedAt
DATETIME endedAt
STRING status
}
session_participants {
BIGINT id PK
VARCHAR sessionId
VARCHAR classId
VARCHAR curriculum
VARCHAR studentId
VARCHAR studentName
DATETIME joinedAt
DATETIME leftAt
BOOLEAN active
}
learning_signal_events {
BIGINT id PK
VARCHAR sessionId
VARCHAR classId
VARCHAR curriculum
VARCHAR studentId
VARCHAR studentName
VARCHAR signalType
VARCHAR signalSubtype
DOUBLE score
DATETIME capturedAt
DATETIME createdAt
}
alerts {
BIGINT id PK
VARCHAR sessionId
VARCHAR classId
VARCHAR curriculum
VARCHAR studentId
VARCHAR studentName
INT studentCount
INT totalStudentCount
DATETIME capturedAt
DOUBLE confusedScore
TEXT reason
TEXT unclearTopic
TEXT lectureText
TEXT lectureSummary
DATETIME createdAt
}
alert_keywords {
BIGINT id PK
BIGINT alertId FK
VARCHAR keyword
DATETIME createdAt
}
lecture_topics {
BIGINT id PK
VARCHAR sessionId
VARCHAR classId
TEXT topicText
DATETIME capturedAt
DATETIME createdAt
}
understanding_difficulty_trends {
BIGINT id PK
VARCHAR sessionId
VARCHAR classId
VARCHAR curriculum
DOUBLE difficultyScore
DATETIME capturedAt
DATETIME createdAt
}
sessions ||--o{ session_participants : sessionId
sessions ||--o{ learning_signal_events : sessionId
sessions ||--o{ alerts : sessionId
sessions ||--o{ lecture_topics : sessionId
sessions ||--o{ understanding_difficulty_trends : sessionId
alerts ||--o{ alert_keywords : alertId
src/main/java/com/iknow
├─ config
│ ├─ CorsConfig.java
│ └─ WebSocketConfig.java
├─ controller
│ ├─ AlertController.java
│ ├─ ConfusedEventController.java
│ ├─ CurriculumController.java
│ ├─ DashboardController.java
│ ├─ LectureChunkController.java
│ ├─ LectureSummaryController.java
│ ├─ SessionController.java
│ ├─ SessionParticipantController.java
│ └─ UnderstandingDifficultyTrendController.java
├─ dto
│ ├─ request
│ └─ response
├─ entity
├─ repository
└─ service
- Java 21+
- MySQL 8
./gradlew bootRunsrc/main/resources/application.yml- 기본 서버 포트:
8080 - Jackson / Hibernate timezone:
Asia/Seoul
기준 Base URL: http://localhost:8080
| Method | Path | 설명 |
|---|---|---|
| POST | /api/sessions |
세션 생성 |
| GET | /api/sessions/{sessionId} |
세션 조회 |
| PATCH | /api/sessions/{sessionId}/end |
세션 종료 |
| POST | /api/sessions/{sessionId}/terminate |
세션 종료 후 DB 삭제 |
요청 필드
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
classId |
String | O | 반 ID |
curriculum |
String | O | 등록된 커리큘럼 이름 |
thresholdPct |
Integer | X | 기본값 50 |
응답 주요 필드
| 필드 | 설명 |
|---|---|
sessionId |
8자리 영문 대문자+숫자 |
status |
ACTIVE 또는 ENDED |
activeParticipantCount |
현재 활성 참가자 수 |
예시
{
"sessionId": "AB12CD34",
"classId": "A반",
"thresholdPct": 50,
"curriculum": "자격증반",
"status": "ACTIVE",
"startedAt": "2026-04-13T13:00:00",
"endedAt": null,
"activeParticipantCount": 0
}| Method | Path | 설명 |
|---|---|---|
| POST | /api/sessions/{sessionId}/participants/join |
참가자 입장 |
| POST | /api/sessions/{sessionId}/participants/leave |
참가자 퇴장 |
요청 예시
{
"studentId": "student-001",
"studentName": "홍길동"
}| Method | Path | 설명 |
|---|---|---|
| POST | /api/confused-events |
학습 신호 저장 + WebSocket 알림 |
| GET | /api/sessions/{sessionId}/confused-events |
세션별 학습 신호 목록 |
| GET | /api/sessions/{sessionId}/alerts |
세션별 알림 목록 |
| DELETE | /api/alerts/{alertId} |
알림 삭제 |
요청 필드
| 필드 | 타입 | 설명 |
|---|---|---|
sessionId |
String | 세션 ID |
studentId |
String | 학생 ID |
studentName |
String | 학생 이름 |
studentCount |
Integer | 혼란 학생 수 |
totalStudentCount |
Integer | 전체 학생 수 |
capturedAt |
LocalDateTime | 수집 시각 |
confusedScore |
Double | 혼란 점수 |
reason |
String | 혼란 사유 |
signalType |
String | 예: GAZE_AWAY, MANUAL_HELP, FACIAL_INSTABILITY |
signalSubtype |
String | 세부 유형 |
| Method | Path | 설명 |
|---|---|---|
| POST | /api/lecture-chunk |
강의 텍스트 기반 Alert 생성 |
| POST | /api/lecture-summary |
요약, 추천 개념, 키워드 저장 |
| GET | /api/alerts/{alertId}/summary |
요약 조회 |
요청 예시
{
"alertId": 1,
"summary": "학생들이 분수 통분 개념에서 분모를 맞추는 흐름을 어려워함",
"recommendedConcept": "분수 통분 개념 재설명 필요",
"keywords": ["분수", "통분", "분모 맞추기", "약분", "공통분모"]
}키워드 저장 규칙
null, 빈 문자열 제거- 공백 정규화
- 각 키워드는 최대 3단어까지 사용
- 중복 제거
- 최대 5개까지 저장
| Method | Path | 설명 |
|---|---|---|
| GET | /api/dashboard/classes?date=YYYY-MM-DD |
날짜별 반 대시보드 집계 |
| GET | /api/dashboard/keyword-report?date=...&keyword=... |
키워드 리포트 |
| POST | /api/dashboard/ai-coaching-data |
AI 코칭 화면용 집계 |
응답 주요 필드
| 필드 | 설명 |
|---|---|
alertCount |
알림 개수 |
participantCount |
고유 참가자 수 |
avgConfusedScore |
평균 혼란도 |
signalBreakdown |
신호 유형별 집계 |
difficultyTrend |
시간대별 평균 난이도 추이 |
topTopics |
자주 등장한 난해 토픽 |
recentAlerts |
최근 알림 목록 |
difficultyTrend 예시
[
{
"time": "10:00",
"avgDifficultyScore": 62.5,
"sampleCount": 4
},
{
"time": "11:00",
"avgDifficultyScore": 71.3,
"sampleCount": 3
}
]| Method | Path | 설명 |
|---|---|---|
| POST | /api/understanding-difficulty-trends |
이해도 난이도 추이 저장 |
요청 예시
{
"sessionId": "AB12CD34",
"difficultyScore": 68.4,
"capturedAt": "2026-04-13T18:30:00"
}응답 예시
{
"id": 1,
"sessionId": "AB12CD34",
"classId": "A반",
"curriculum": "자격증반",
"difficultyScore": 68.4,
"capturedAt": "2026-04-13T18:30:00",
"createdAt": "2026-04-13T18:30:01"
}주의 사항
- 활성 세션만 저장할 수 있습니다.
difficultyScore는0.0 ~ 100.0범위로 보정됩니다.- 대시보드에서는 시간 단위로 평균 집계되어
difficultyTrend로 노출됩니다.
| Method | Path | 설명 |
|---|---|---|
| GET | /api/curriculums |
커리큘럼 목록 조회 |
| POST | /api/curriculums |
커리큘럼 생성 |
| DELETE | /api/curriculums/{curriculumId} |
커리큘럼 삭제 |
- Endpoint:
ws://localhost:8080/ws - Protocol:
STOMP over SockJS - Topic:
/topic/alert/{sessionId}
{
"sessionId": "AB12CD34",
"classId": "A반",
"studentCount": 3,
"totalStudentCount": 20,
"confusedScore": 0.75,
"reason": "분수 개념 이해 부족",
"capturedAt": "2026-04-13T18:20:00"
}- 세션 ID는
A-Z,0-9조합의 8자리 문자열이며 충돌 시 재생성합니다. - 세션 조회 시 24시간이 지난 활성 세션은 lazy 방식으로
ENDED처리됩니다. - 세션 강제 종료 시 활성 참가자는 모두 퇴장 처리한 뒤 세션 레코드를 삭제합니다.
- 대시보드 집계는
curriculum + classId기준으로 묶습니다. Alert는 강의 요약 결과까지 포함한 분석 레코드이고,LearningSignalEvent는 실시간 원시 신호 이벤트입니다.