KMA LMS 프로젝트 회고

발행일: 2025년 11월 20일·수정일: 2025년 12월 14일·개발·...
프로젝트 기간: 2024년 2월 - 2024년 8월 (약 6개월)

🎯 서비스 핵심 기능

1. 통합 학습 관리 시스템 (LMS)

  • 이러닝 콘텐츠 관리: 강의 영상, 숏폼 콘텐츠, 오리지널 시리즈 등 다양한 형태의 교육 콘텐츠 제공
  • 학습 진도 추적: 영상 시청 시간 저장 및 이어보기, 90% 이상 시청 시 과정 이수 처리
  • 교육 과정 관리: 개인/기업 맞춤형 교육 과정 등록, 수강, 이수 관리
  • 2. 세분화된 멤버십 시스템

  • KMA 멤버십: 정회원, 이사단 등 등급별 혜택 차등 제공
  • Studio 멤버십: LITE, CORE, PREMIUM, ALL-IN-ONE 4단계 플랜 운영
  • 기업 멤버십: B2B 기업 교육 솔루션 연동
  • 3. 스트리밍 영상 플레이어

  • HLS 스트리밍: 대용량 교육 영상의 안정적인 스트리밍 지원
  • 다중 플레이어 지원: HLS Player, YouTube Player, Iframe 기반 플레이어 통합
  • 학습 로그 기록: 10초 간격 시청 기록 저장, 일시정지/탭 전환 시에도 진도율 보존
  • 4. 보안 및 접근 제어

  • 기기 등록 제한: 계정당 최대 3대 기기로 콘텐츠 접근 제한
  • 세션 토큰 검증: 중복 로그인 감지 및 강제 로그아웃 처리
  • IP/디바이스 기반 인증: 콘텐츠 접근 전 토큰 및 기기 유효성 검증
  • 5. 관리자 시스템 (Admin)

  • 콘텐츠 관리: 교육 콘텐츠 등록, 수정, 삭제
  • 회원 관리: 회원 정보 조회, 권한 관리, 멤버십 상태 관리
  • 결제/환불 관리: NicePay 결제 연동, 결제 내역 및 환불 처리
  • 사이트 운영: 배너, 팝업, FAQ, 약관 등 사이트 운영 요소 관리

  • 🔧 내가 진행한 부분들

    Admin 사이트 (kma-lms-admin)

    퍼블리싱 작업

  • 10개 주요 섹션 UI 구현: 대시보드, 회원관리, 기업관리, 콘텐츠관리, 교육관리, 결제관리, 사이트관리 등
  • 컴포넌트 97개: 재사용 가능한 공통 컴포넌트 및 페이지별 컴포넌트 개발
  • Vite + React 기반 SPA 구조 설계 및 구현
  • 기능 개발

  • HTTP 레이어 설계: 76개 API 모듈 구조화 및 Axios 인스턴스 설정
  • 상태 관리: Zustand 기반 전역 상태 관리 구조 구축
  • 타입 안전성: 34개 타입 정의 파일로 TypeScript 활용 극대화
  • 코드 컨벤션 & 구조 개선

  • 폴더 구조 표준화: pages, components, hooks, http, types 등 명확한 역할 분리
  • 공통 패키지 활용@repo/ui@repo/utils 등 모노레포 패키지 연동
  • 린트/포맷 설정: ESLint, Prettier 공통 설정 적용

  • LMS Web 사이트 (kma-lms-web)

    영상 플레이어 시스템 수정 및 고도화

    // 10초 간격 학습 로그 저장 로직
    const onTimeUpdate = async () => {
      const sec = Math.floor(video.currentTime);
      if (sec - lastLoggedTimeRef.current >= 10) {
        lastLoggedTimeRef.current = sec;
        await saveVideoLog({ eduSeq: cttSeq, watchTm: sec });
        await updateTime({ prvtEnrolCurrPartSeq, enrolTm: sec, enrolYn: isCompleted ? 'Y' : 'N' });
      }
      // 90% 이상 시청 시 완료 처리
      if (!isCompleted && duration > 0 && current / duration >= 0.9) {
        setIsCompleted(true);
        onPartComplete?.(prvtEnrolCurrPartSeq);
      }
    };
    

    기기 제한 및 세션 관리

    // 기기 등록 검증 및 중복 로그인 방지
    const handleCheckDevice = async () => {
      const deviceArr = deviceArrRef.current;
      if (deviceArr.length >= 3) {
        customAlert('등록된 기기가 최대 3대를 초과하여 콘텐츠를 감상할 수 없습니다.');
        return false;
      }
      return true;
    };
    

    멤버십 분기 처리

  • 사용자 등급에 따른 콘텐츠 접근 권한 분기
  • KMA 정회원/이사단, Studio LITE/CORE/PREMIUM 등급별 UI 분기
  • 멤버십 미가입자 제한 화면 및 가입 유도 플로우

  • 💪 어려웠던 점과 해결 방법

    1. 백엔드 영상 스트림 연동

    문제 상황

  • 백엔드에서 HLS 포맷(m3u8)으로 제공되는 영상 스트림 처리
  • 브라우저별 HLS 네이티브 지원 여부가 달라 크로스 브라우저 호환성 확보 필요
  • 인증 토큰을 스트리밍 요청에 포함시켜야 하는 보안 요구사항
  • 해결 방법

    // HLS.js를 활용한 스트리밍 처리
    useEffect(() => {
      if (Hls.isSupported()) {
        const hls = new Hls({
          xhrSetup: (xhr) => {
            xhr.setRequestHeader('Authorization', `Bearer ${session?.accessToken}`);
          },
        });
        hls.loadSource(src);
        hls.attachMedia(video);
      } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
        // Safari 네이티브 HLS 지원
        video.src = src;
      }
    }, [src, session?.accessToken]);
    
  • HLS.js 라이브러리 도입으로 비표준 브라우저 지원
  • xhrSetup 옵션으로 스트리밍 요청에 인증 헤더 자동 주입

  • 2. IP/디바이스별 진행률 클라이언트 관리

    문제 상황

  • 한 사용자가 여러 기기에서 동시 학습 시 진도 동기화 문제
  • 탭 전환, 브라우저 백그라운드 시 학습 데이터 유실 위험
  • 정확한 시청 완료 시점 판단 기준 설정
  • 해결 방법

    // 탭 전환 시에도 진도 보존
    const onVisibility = () => {
      if (document.visibilityState === 'hidden') {
        lastTimeRef.current = video.currentTime;
        if (!video.paused) {
          video.pause();
          isAutoPausedRef.current = true;
        }
      } else if (document.visibilityState === 'visible') {
        if (isAutoPausedRef.current) {
          video.play().catch(console.warn);
          isAutoPausedRef.current = false;
        }
      }
    };
    
  • visibilitychange 이벤트 활용한 탭 전환 감지
  • Ref를 활용한 마지막 재생 시점 저장 및 복원
  • 10초 간격 서버 동기화로 데이터 유실 최소화
  • 90% 시청 기준으로 명확한 완료 처리 로직 구현

  • 3. 세분화된 멤버십별 분기 처리

    문제 상황

  • KMA 멤버십, Studio 멤버십 각각 4개 이상 등급 존재
  • 등급별 콘텐츠 접근 권한, 할인율, UI 표시가 모두 다름
  • 신규 등급 추가 시 확장성 있는 구조 필요
  • 해결 방법

    // Zustand 스토어에서 멤버십 상태 통합 관리
    const isMemberShip =
      kmaMemDiv === '01' ||
      studMemGrd === '11' ||
      studMemGrd === '12' ||
      studMemGrd === '13';
    // 등급별 상수 매핑
    const KMA_MEM_GRD: Record<string, string> = {
      '01': 'KMA 정회원',
      '15': 'KMA 이사단',
      '16': 'KMA 정회원',
    };
    const STUD_MEM_GRD: Record<string, string> = {
      '11': 'LITE',
      '12': 'CORE',
      '13': 'PREMIUM',
      '14': 'ALL-IN-ONE',
    };
    
  • 상수 객체 및 타입 정의로 등급 코드와 표시명 매핑
  • 전역 상태 스토어에서 사용자 멤버십 정보 통합 관리
  • 컴포넌트에서는 isMemberShip 플래그와 등급 코드만으로 분기 처리

  • 📚 배운 점

  • 모노레포 구조의 장점: Turborepo + pnpm workspace를 활용한 코드 공유 및 빌드 최적화 경험
  • 스트리밍 기술 이해: HLS 프로토콜과 적응적 비트레이트 스트리밍에 대한 이해
  • 인증/인가 플로우: 토큰 기반 인증과 기기 제한 등 보안 기능 구현 노하우
  • 대규모 코드베이스 관리: 500개 이상의 컴포넌트와 파일을 체계적으로 구조화하는 방법

  • 🏆 성과

  • Admin 사이트 전체 퍼블리싱 및 기능 구현 완료
  • 영상 플레이어 안정화: 탭 전환, 기기 변경 시에도 학습 데이터 정확성 확보
  • 코드 컨벤션 정립: 팀 전체가 활용 가능한 구조 및 스타일 가이드 확립
  • 6개월간의 대형 외주 프로젝트 성공적 완수