출석 모니터링 시스템 (egatten)
실시간 출석 모니터링 및 학습 관리 통합 플랫폼
📋 프로젝트 개요
egatten은 메타버스(Gather Town, ZEP.US) 기반 온라인 교육 환경에서 학생 출석, 학습 활동, 과제 관리를 실시간으로 모니터링하고 강사 도구를 통합 제어하는 웹 기반 통합 플랫폼입니다.
시스템의 중심: egatten
┌─────────────────────────────────────────┐
│ egatten (메인 시스템) ⭐ │
├─────────────────────────────────────────┤
│ 프론트엔드: 웹 대시보드 │
│ ├─ 실시간 출석 현황 │
│ ├─ PBL 과제 관리 │
│ ├─ 채팅 로그 분석 │
│ └─ 강사 도구 제어 │
│ │
│ 백엔드: 실시간 처리 엔진 │
│ ├─ Flask + Socket.IO (실시간 통신) │
│ ├─ Redis (캐시 + 큐 + Pub/Sub) │
│ └─ Worker (비동기 데이터 처리) │
└─────────────────────────────────────────┘
↕ (데이터 수집)
┌─────────────────────────────────────────┐
│ 서브 시스템들 (데이터 소스) │
├─────────────────────────────────────────┤
│ [[출석-모니터링-백엔드|Google Sheets 출석 수집]] │
│ [[출석-모니터링-LMS-웹훅|LMS 이벤트 웹훅]] │
│ [[출석-모니터링-ZEP-위젯|ZEP.US 위젯]] │
│ [[ZEP-원격제어-Extension|Chrome Extension]] │
└─────────────────────────────────────────┘
🎯 해결한 문제
온라인 교육의 현실적 과제
-
실시간성 부재
- 폴링 방식의 비효율성
- 접속 상태 변경 즉시 반영 불가
- 강사-학생 간 소통 지연
-
플랫폼 파편화
- Gather Town, ZEP, LMS로 데이터 분산
- 각 플랫폼을 개별 확인해야 하는 번거로움
- 통합된 view 부재
-
수업 진행 불편
- 학생 기기(마이크/카메라) 제어 어려움
- 출석 체크 수동 작업
- PBL 과제 채점 비효율
-
데이터 분석 한계
- 채팅 로그 수동 확인
- 학습 활동 데이터 미활용
- 성적 산출 자동화 부족
egatten 솔루션
- 실시간 대시보드: Socket.IO로 즉시 반영
- 통합 플랫폼: 모든 데이터를 한 곳에서 확인
- 강사 도구 통합: StreamDeck, Chrome Extension 제어
- 비동기 처리: Redis Queue + Worker로 대용량 처리
- 자동화: 출석, 채점, 통계 생성 자동화
🛠 기술 스택
Backend (Python)
- Framework: Flask 3.0.0
- Real-time: Flask-SocketIO (WebSocket)
- Async Worker: eventlet
- Queue: Redis Queue (RQ)
Database
- MySQL: 핵심 정형 데이터
- 사용자, PBL 과제, 출결, 설정
- MongoDB: 대용량 로그
- Gather Town 접속 기록, 채팅 로그
- Redis: 실시간 데이터
- 세션, 캐시, 메시지 브로커, 작업 큐
Frontend
- Template: Jinja2 (Server-side rendering)
- Library: jQuery, Socket.IO Client
- CSS: Custom CSS
Data Processing
- pandas: 복잡한 데이터 처리
- openpyxl: Excel 생성/파싱
Infrastructure
- Container: Docker, Docker Compose
- WSGI: Gunicorn (eventlet worker)
💡 주요 구현 내용
1. Event-Driven Architecture
[클라이언트들]
↓ (Socket.IO / HTTP)
[Nginx Proxy]
↓
[Flask App]
├─ REST API
└─ Socket.IO Server
↓
[Redis]
├─ Pub/Sub (실시간 이벤트)
├─ Cache (세션, 상태)
└─ Queue (비동기 작업)
↓
[Chat Worker]
├─ 배치 처리 (50개씩)
├─ Bulk Insert
└─ 통계 생성
↓
[MySQL / MongoDB]
핵심 패턴:
- 실시간: Socket.IO로 즉시 전파
- 무거운 작업: Redis Queue → Worker 처리
- 캐싱: Redis로 빠른 응답
2. 실시간 모니터링 대시보드
웹 대시보드 (templates/)
# app.py - 메인 대시보드
@app.route('/dashboard/<sk_id>')
def dashboard(sk_id):
# 실시간 접속자 현황
students = get_students(sk_id)
# PBL 과제 제출 현황
pbl_status = get_pbl_submissions(sk_id)
# 메모 시스템
memos = get_student_memos(sk_id)
return render_template('dashboard.html',
students=students,
pbl_status=pbl_status,
memos=memos
)Socket.IO 실시간 업데이트
// 클라이언트 (dashboard.html)
const socket = io();
// 학생 접속 상태 실시간 업데이트
socket.on('student_status_update', (data) => {
updateStudentCard(data.student_id, data.status);
});
// 메모 추가 실시간 반영
socket.on('memo_added', (data) => {
appendMemoToList(data.memo);
});
// PBL 제출 현황 업데이트
socket.on('pbl_submission', (data) => {
updatePBLProgress(data);
});3. PBL 과제 관리 시스템
과제 제출 현황 조회
# pbl_api.py
@app.route('/api/pbl/submissions')
def get_pbl_submissions():
"""
학생별 PBL 과제 제출 현황 조회
- 문제별 제출 여부
- 채점 상태
- 점수
"""
query = """
SELECT
s.name,
p.problem_title,
sub.submitted_at,
sub.score,
sub.graded
FROM students s
LEFT JOIN pbl_submissions sub
ON s.id = sub.student_id
LEFT JOIN pbl_problems p
ON sub.problem_id = p.id
WHERE s.sk_id = %s
ORDER BY s.name, p.unit, p.chapter
"""
results = execute_query(query, [sk_id])
# pandas로 피벗 테이블 생성
df = pd.DataFrame(results)
pivot = df.pivot_table(
index='name',
columns='problem_title',
values='score',
aggfunc='first'
)
return jsonify(pivot.to_dict())Excel 일괄 채점
# pbl_api.py
@app.route('/api/pbl/grade/upload', methods=['POST'])
def upload_grade_excel():
"""
Excel 파일로 일괄 채점
- 양식: 학생명 | 문제1 | 문제2 | ...
- pandas로 파싱 후 Bulk Update
"""
file = request.files['file']
df = pd.read_excel(file)
grades = []
for _, row in df.iterrows():
student_name = row['학생명']
for problem_col in df.columns[1:]:
score = row[problem_col]
if pd.notna(score):
grades.append({
'student_name': student_name,
'problem': problem_col,
'score': score
})
# Bulk Update
bulk_update_grades(grades)
# Socket.IO로 실시간 반영
socketio.emit('pbl_grades_updated',
{'sk_id': sk_id},
room='dashboard')
return jsonify({'success': True,
'updated': len(grades)})4. 강사 도구 통합 제어
ZEP 강사 제어 (Socket.IO)
# socketio_events/zep_events.py
@socketio.on('zep_connect')
def handle_zep_connect(data):
"""
Chrome Extension 연결
"""
client_id = data['client_id']
instructor_name = data['instructor_name']
# Redis에 강사 상태 저장
redis_client.hset(
f'zep:instructor:{client_id}',
mapping={
'name': instructor_name,
'connected_at': datetime.now().isoformat(),
'status': 'active'
}
)
# 관리자 대시보드에 알림
emit('instructor_connected',
{'name': instructor_name},
room='admin_room')
@socketio.on('streamdeck_command')
def handle_streamdeck_command(data):
"""
StreamDeck 버튼 → Extension으로 명령 전달
"""
action = data['action'] # 'mic_on', 'camera_off' 등
instructor_name = data['instructor_name']
# 해당 강사의 Extension에 명령 전송
client_id = get_instructor_client_id(instructor_name)
emit('zep_command',
{'action': action},
room=client_id)통합 흐름:
[StreamDeck 버튼 클릭]
↓ (Socket.IO)
[Flask Server]
↓ (명령 라우팅)
[Chrome Extension]
↓ (DOM 조작)
[ZEP.US 마이크/카메라 제어]
5. 비동기 데이터 처리 (Worker)
Chat Worker 구조
# chat_worker.py
import redis
from rq import Queue
redis_conn = redis.Redis()
queue = Queue('chat_queue', connection=redis_conn)
def process_chat_batch():
"""
배치 단위로 채팅 로그 처리
- Redis 큐에서 50개씩 가져오기
- Bulk Insert로 MySQL 저장
- 실패 시 DLQ로 이동
"""
while True:
# blpop: 대기하다가 메시지 도착 시 처리
messages = get_batch_messages(batch_size=50)
if messages:
try:
# Bulk Insert (한 번의 트랜잭션)
bulk_insert_chats(messages)
logger.info(f'✅ {len(messages)}개 메시지 저장')
except Exception as e:
logger.error(f'❌ 배치 처리 실패: {e}')
# DLQ로 이동
for msg in messages:
redis_conn.rpush('chat_dlq',
json.dumps(msg))
def bulk_insert_chats(messages):
"""
executemany로 배치 처리
"""
query = """
INSERT INTO chat_logs
(student_id, message, timestamp, map_id)
VALUES (%s, %s, %s, %s)
"""
data = [(m['student_id'], m['message'],
m['timestamp'], m['map_id'])
for m in messages]
cursor.executemany(query, data)
conn.commit()장점:
- 메인 서버 부하 분산
- 대량 데이터 효율적 처리
- 실패 시 데이터 유실 방지 (DLQ)
6. 메모 시스템 (실시간 협업)
# memo_api.py
@app.route('/api/memos', methods=['POST'])
def create_memo():
"""
학생 메모 작성
- 여러 관리자가 동시 작성 가능
- Socket.IO로 실시간 동기화
"""
data = request.json
memo_id = insert_memo(
sk_id=data['sk_id'],
student_name=data['student_name'],
content=data['content'],
author=data['author']
)
# 모든 관리자에게 실시간 전파
socketio.emit('memo_added',
{
'memo_id': memo_id,
'student_name': data['student_name'],
'content': data['content'],
'author': data['author'],
'timestamp': datetime.now().isoformat()
},
room=f'dashboard_{data["sk_id"]}'
)
return jsonify({'success': True,
'memo_id': memo_id})🏗 시스템 아키텍처
전체 아키텍처
┌──────────────────────────────────────┐
│ 클라이언트 Layer │
│ ├─ 웹 브라우저 (관리자 대시보드) │
│ ├─ Chrome Extension (강사) │
│ └─ StreamDeck (물리 버튼) │
└──────────────────────────────────────┘
↕ (HTTP / WebSocket)
┌──────────────────────────────────────┐
│ Application Layer │
│ ├─ Flask App (메인 서버) │
│ │ ├─ REST API │
│ │ └─ Socket.IO Server │
│ └─ Chat Worker (비동기 처리) │
└──────────────────────────────────────┘
↕
┌──────────────────────────────────────┐
│ Data Layer │
│ ├─ Redis (캐시/큐/Pub-Sub) │
│ ├─ MySQL (핵심 데이터) │
│ └─ MongoDB (로그) │
└──────────────────────────────────────┘
↕ (데이터 수집)
┌──────────────────────────────────────┐
│ Data Source Layer │
│ ├─ Google Sheets (출석) │
│ ├─ LMS API (학습 데이터) │
│ ├─ ZEP.US (메타버스 상태) │
│ └─ Gather Town (레거시) │
└──────────────────────────────────────┘
모듈 구조
eg_atten_ver2/
├── app.py # 메인 진입점
├── chat_worker.py # 비동기 Worker
├── pws_func.py # DB 쿼리 라이브러리
│
├── API 모듈 (도메인별 분리)
│ ├── memo_api.py # 학생 메모
│ ├── pbl_api.py # PBL 과제
│ ├── vod_api.py # VOD 진도율
│ └── statistics_api.py # 통계 산출
│
├── Socket.IO 이벤트
│ ├── zep_events.py # ZEP 제어
│ ├── streamdeck_events.py# StreamDeck 연동
│ └── zep_dm_events.py # DM 처리
│
├── templates/ # Jinja2 템플릿
│ ├── dashboard.html # 메인 대시보드
│ ├── pbl_grading.html # 과제 채점
│ └── statistics.html # 통계 페이지
│
└── static/ # CSS, JS, Images
📊 주요 기능
관리자 기능
| 기능 | 설명 | 구현 |
|---|---|---|
| 실시간 대시보드 | 접속 현황 실시간 확인 | Socket.IO |
| PBL 과제 관리 | 제출 현황, 채점 | pandas + Excel |
| 메모 시스템 | 학생별 메모 작성/공유 | 실시간 동기화 |
| 채팅 로그 | 대화 내용 검색/분석 | MongoDB + Full-text |
| 통계 생성 | 출석률, 과제 통계 | Worker 비동기 처리 |
강사 기능
| 기능 | 설명 | 구현 |
|---|---|---|
| StreamDeck 제어 | 물리 버튼으로 기기 제어 | Socket.IO 중계 |
| Chrome Extension | 브라우저에서 원격 제어 | WebSocket 연결 |
| 실시간 모니터링 | 학생 상태 즉시 확인 | Socket.IO Rooms |
API 엔드포인트
PBL 관련:
GET /api/pbl/problems/<sk_id>- 문제 목록GET /api/pbl/submissions- 제출 현황POST /api/pbl/grade- 일괄 채점GET /api/excel/pbl_submissions- Excel 다운로드
메모 관련:
GET /api/memos/<sk_id>/<name>- 학생 메모 조회POST /api/memos- 메모 작성PUT /api/memos/<id>- 메모 수정DELETE /api/memos/<id>- 메모 삭제
Socket.IO 이벤트:
zep_connect- Extension 연결zep_command- 원격 명령streamdeck_command- StreamDeck 버튼memo_added- 메모 추가 알림
📈 성과
운영 효율화
- 출석 체크: 수동 30분 → 자동 0분
- 과제 채점: Excel 일괄 처리로 70% 시간 단축
- 실시간 모니터링: 평균 응답 시간 < 1초
기술적 성과
- 동시 접속: 최대 500명 안정적 처리
- 메시지 처리: Worker로 초당 100개 처리 가능
- 데이터 처리: Bulk Insert로 DB 부하 90% 감소
사용자 만족도
- 관리자: 통합 대시보드로 업무 편의성 증대
- 강사: StreamDeck 제어로 수업 집중도 향상
- 학생: 실시간 피드백으로 학습 참여도 증가
🔗 서브 시스템 연동
데이터 수집 파이프라인
[Google Sheets 출석 수집]
↓ (REST API)
[egatten Backend]
↓ (저장)
[MySQL]
[LMS 웹훅]
↓ (WebSocket)
[egatten Backend]
↓ (Redis Queue)
[Chat Worker]
↓ (Bulk Insert)
[MySQL / MongoDB]
[ZEP.US 위젯]
↓ (API 호출)
[egatten Backend]
↓ (실시간 업데이트)
[Socket.IO → Dashboard]
[Chrome Extension]
↓ (WebSocket)
[egatten Backend]
↓ (명령 라우팅)
[다른 Extension들]
관련 프로젝트
-
데이터 수집
- Google Sheets 출석 수집 - 출석 데이터 자동 수집
- LMS 웹훅 - 학습 데이터 실시간 수집
-
사용자 도구
- ZEP.US 위젯 - 메타버스 내부 관리 도구
- Chrome Extension - 원격 제어 도구
📝 배운 점
대규모 실시간 시스템 설계
-
Event-Driven Architecture
- Socket.IO로 실시간 양방향 통신
- Redis Pub/Sub로 이벤트 전파
- 느슨한 결합으로 확장성 확보
-
비동기 처리 패턴
- Redis Queue로 작업 분산
- Worker 프로세스로 무거운 작업 처리
- Batch Processing으로 DB 부하 감소
-
데이터베이스 전략
- MySQL: 정형 데이터 (ACID 보장)
- MongoDB: 비정형 로그 (유연한 스키마)
- Redis: 실시간 데이터 (빠른 읽기/쓰기)
Flask 고급 활용
-
모듈화 설계
- Blueprint 패턴으로 API 분리
pws_func.py로 공통 로직 중앙화- 도메인별 파일 분리 (
*_api.py)
-
Socket.IO 통합
- Flask-SocketIO로 WebSocket 지원
- Room 기반 메시지 타겟팅
- 세션 관리 및 인증
-
성능 최적화
- pandas로 복잡한 데이터 처리
- Bulk Operation으로 DB 최적화
- Redis 캐싱으로 응답 속도 개선
운영 경험
- 모니터링: Worker 상태 실시간 추적
- 에러 핸들링: DLQ로 데이터 유실 방지
- 확장성: 모듈화로 기능 추가 용이
🔮 향후 계획
단기 (완료됨)
- 기본 대시보드 구축
- PBL 과제 관리
- 실시간 모니터링
- Worker 비동기 처리
중기
- API 문서화 (Swagger/OpenAPI)
- Flask Application Factory 패턴 도입
- 타입 힌팅 전면 적용
- 단위 테스트 작성
장기
- AI 분석 (학습 성향 리포트)
- 알림 센터 (Push Notification)
- 모바일 앱 (React Native)
- 머신러닝 기반 이탈 예측
프로젝트 기간: 2023.12 - 2025.12 (2년) 현재 상태: 완료됨 (운영 중) 역할: 메인 통합 플랫폼 최종 업데이트: 2025-12-21