출석 모니터링 시스템 (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]]      │
└─────────────────────────────────────────┘

🎯 해결한 문제

온라인 교육의 현실적 과제

  1. 실시간성 부재

    • 폴링 방식의 비효율성
    • 접속 상태 변경 즉시 반영 불가
    • 강사-학생 간 소통 지연
  2. 플랫폼 파편화

    • Gather Town, ZEP, LMS로 데이터 분산
    • 각 플랫폼을 개별 확인해야 하는 번거로움
    • 통합된 view 부재
  3. 수업 진행 불편

    • 학생 기기(마이크/카메라) 제어 어려움
    • 출석 체크 수동 작업
    • PBL 과제 채점 비효율
  4. 데이터 분석 한계

    • 채팅 로그 수동 확인
    • 학습 활동 데이터 미활용
    • 성적 산출 자동화 부족

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들]

관련 프로젝트

📝 배운 점

대규모 실시간 시스템 설계

  1. Event-Driven Architecture

    • Socket.IO로 실시간 양방향 통신
    • Redis Pub/Sub로 이벤트 전파
    • 느슨한 결합으로 확장성 확보
  2. 비동기 처리 패턴

    • Redis Queue로 작업 분산
    • Worker 프로세스로 무거운 작업 처리
    • Batch Processing으로 DB 부하 감소
  3. 데이터베이스 전략

    • MySQL: 정형 데이터 (ACID 보장)
    • MongoDB: 비정형 로그 (유연한 스키마)
    • Redis: 실시간 데이터 (빠른 읽기/쓰기)

Flask 고급 활용

  1. 모듈화 설계

    • Blueprint 패턴으로 API 분리
    • pws_func.py로 공통 로직 중앙화
    • 도메인별 파일 분리 (*_api.py)
  2. Socket.IO 통합

    • Flask-SocketIO로 WebSocket 지원
    • Room 기반 메시지 타겟팅
    • 세션 관리 및 인증
  3. 성능 최적화

    • 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