ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [팀 프로젝트] LMS 웹 사이트 만들기 (Learn Hub)
    Project 2024. 8. 28. 21:30

    https://github.com/jngsngjn/Learn-Hub

     

    GitHub - jngsngjn/Learn-Hub: LMS 웹 사이트입니다.

    LMS 웹 사이트입니다. Contribute to jngsngjn/Learn-Hub development by creating an account on GitHub.

    github.com


    개요

    네이버 클라우드 캠프에서 진행한 세 번째 프로젝트이자 나의 세 번째 프로젝트이다. 프론트엔드는 리액트, 백엔드는 스프링부트로 구성하여 웹 프로젝트를 진행했다. 6명의 팀원들과 함께 했으며 내가 팀장이 되어 팀을 이끌었다. 프로젝트는 약 6주 동안 진행되었다.

    주제

    "특정 IT 학원의 LMS 웹 사이트"

    교육기관에서 교육을 받으면서 "학생, 강사, 매니저에게 하나의 일관된 웹 서비스를 제공해보자"라는 생각에서 시작한 프로젝트이다.

    사용한 주요 기술

    • Spring Boot
    • Spring Security + JWT
    • Spring Data JPA + Querydsl
    • MySQL
    • Redis
    • React

    내가 기여한 부분

    (1) JPA 엔티티 개발

    • 프로젝트에서 사용한 33개의 테이블을 JPA 엔티티로 개발하고 그들의 연관 관계를 맺어주는 역할을 맡았다.
    • 생성일시, 수정일시와 같은 자주 사용되는 컬럼은 BaseEntity로 정의하여 필요한 엔티티에서 상속 받아 사용하도록 했다.
    • 요구사항에 따라 서비스 사용자인 학생, 강사, 매니저가 하나의 사이트 안에서 고유한 로그인 아이디를 사용해야 했다. 이들이 독립적인 엔티티로 존재할 경우, 아이디 중복 확인을 하려면 3개의 테이블에 대해 조회 쿼리가 실행되어야 했다. 따라서 User라는 추상 클래스를 정의하고 학생, 강사, 매니저 엔티티가 이를 상속 받게 했으며 user_type으로 이들을 구분하여 관리하는 전략을 사용했다.
    • 다대일 관계에 모두 지연 로딩을 적용하여 불필요한 조회 쿼리를 방지하고자 노력했으며 필요한 경우에만 fetch 조인을 사용하여 연관된 엔티티를 함께 조회하도록 했다.

    (2) JWT 인증/인가

    • JWT 이중 토큰 방식을 사용하여 인증/인가를 구현했다.
    • 사용자가 로그인에 성공하면 Access 토큰을 응답 헤더에, Refresh 토큰을 쿠키에 담아 클라이언트 측으로 전송하여 Access 토큰은 로컬 스토리지, Refresh 토큰은 쿠키에 저장하도록 했다.
    • Refresh 토큰을 Redis에 저장하고, Refresh 토큰 Rotate 전략을 사용하여 Access 토큰을 재발급할 때 기존 Refresh 토큰을 무효화하도록 했다.
    • 로그아웃 시 프론트엔드 측에서는 로컬 스토리지에 존재하는 Access 토큰을 삭제하고 서버 측으로 Refresh 토큰을 전송하도록 하였고, 백엔드 측에서는 Refresh 토큰을 받아 데이터베이스에서 해당 토큰을 삭제하도록 하였으며 쿠키를 초기화하는 작업까지 해주었다.

    (3) 과제 유사도 검사

    • 강사가 학생이 제출한 과제를 확인할 때 과제의 유사도를 확인하는 기능을 구현하였다.
    • 원활한 유사도 검사를 위해 강사가 과제를 등록할 때 학생이 제출할 파일의 확장자를 지정하게 하였다.
    • 파일 확장자에 따라 유사도 검사를 하는 내부 동작은 달라질 수 있지만 해당 메서드의 반환 타입과 매개변수는 동일하다는 점을 파악하고 전략 패턴을 적용해 보았다.
    // 전략 패턴
    public interface FileSimilarityStrategy {
    
        // 유사도로 그룹핑된 학생 리스트 반환
        List<List<String>> similarityCheck(List<StudentHomework> studentHomeworks);
    }
    • 두 텍스트 간의 코사인 유사도를 구하여 유사도 검사 기능을 구현하였고, 계산된 유사도가 90% 이상인 경우 해당 학생들을 같은 그룹으로 묶도록 했다.
    • PDF 파일은 apache.pdfbox 라이브러리를 사용하여 이미지를 제외하고 텍스트만 읽어 유사도 검사를 진행하게 하였으며, zip 파일은 압축을 해제하고 내부 파일을 탐색하며 특정 확장자의 파일만을 처리하도록 했다.

    (4) 비밀번호 찾기

    • 아이디 찾기 기능은 단순히 이메일을 받아서 해당하는 아이디의 뒷자리 3개를 *로 표시하여 제공했다. 하지만 비밀번호 찾기 기능은 조금 더 엄격하게 처리해야겠다고 생각했다.
    • 비밀번호를 찾기 전 가입한 이메일을 입력 받고, 해당 이메일로 숫자와 문자로 조합된 6자리의 인증 코드를 전송한다. 그리고 이메일과 인증 코드를 레디스에 5분 동안 저장했다.
    • 사용자가 입력한 인증 코드가 일치하면 비밀번호를 재설정하는 작업을 시작한다. 이때 만약 사용자가 해당 화면에서 오랜 시간 자리를 비웠을 경우 누군가가 비밀번호를 바꾸는 것을 방지하고자, 인증 코드가 일치한 경우 해당 사용자를 5분 동안 인증된 사용자로 레디스에 저장했다.
    • 또한 악의적인 요청이나 자동화된 봇의 요청을 방지하고자 구글의 reCAPTCHA v2 방식을 적용하여 정상적인 요청이 맞는지 확인했다.

    (5) 설문조사

    • 매니저가 특정 교육과정에 대해 만족도 설문조사를 실시하고 마감하며 통계 정보를 볼 수 있는 기능을 만들었다.
    • 설문의 기본 정보를 저장하는 Survey 테이블, 설문조사 문항을 저장하는 SurveyContent 테이블, 설문조사 응답 정보를 저장하는 SurveyAnswer 테이블을 사용했다. 테이블을 설계하는 일이 가장 어려웠다.

    (6) 투표 참여

    • 강사가 투표를 등록하면 학생이 해당 투표를 참여하는 기능을 만들었다.
    • 투표를 참여할 때 생각보다 검증해야 할 요소가 있어서 까다로웠다.
      • 현재 시간이 해당 투표의 마감 시간을 넘기지 않았는지 확인
      • trueCount가 1개 이상인지 확인
      • 단일 투표일 때 trueCount가 1개인지 확인
      • 이미 투표에 참여했었는지 확인 (첫 참여 시에만)

    (7) 배지

    • 학생이 특정 조건을 달성했을 때 배지를 지급하는 기능을 만들었다.
    • 총 11개의 배지를 제작했는지 배지를 생각해내고 디자인하는 것이 가장 어려웠다.
    • 배지 지급이 필요한 곳곳에 로직을 추가하는 일은 재미있었다.

    (8) 오브젝트 스토리지

    • 아마존 S3를 사용했다. 스토리지 폴더 구조를 세분화하기 위해 노력했다. 매니저가 교육과정을 생성할 때 그에 맞도록 이미 정해진 스토리지 폴더가 생성되게 하고 싶었다.
    curriculum/
    ├── ncp/
    │   ├── 1/ (기수)
    │   │   ├── freeboard/ (학생이 자유게시판에 올리는 이미지)
    │   │   ├── subject/ (강사가 과목 게시판에 올리는 첨부파일, 강사가 설정한 과목 이미지)
    │   │   ├── homework/ (강사가 게시한 과제의 첨부파일, 학생이 게시한 과제의 첨부파일)
    │   │   └── profile/ (학생 또는 강사의 프로필 이미지)
    │   ├── 2/
    │   │   ├── freeboard/
    │   │   ├── subject/
    │   │   ├── homework/
    │   │   └── profile/
    │   └── 3/
    │       ├── freeboard/
    │       ├── subject/
    │       ├── homework/
    │       └── profile/
    • 즉, 스토리지 폴더가 동적으로 생성되어야 했다. 매니저가 교육과정을 생성하는 로직에 폴더 추가 로직을 추가했다. ObjectMetadata의 contentLength를 0으로 설정하면 폴더가 생성되는 것을 알아 내어서 어렵지 않게 구현할 수 있었다.

    느낀 점

    프로젝트 기간이 약 6주였기 때문에 신체적으로 정신적으로 매우 힘들었다.

    아쉬웠던 부분은..
    대용량 트래픽 대응해보는 거.. 쿼리 최적화하는 거.. 등등

    그래도 배운 것도 많다.
    REST API 통신.. JPA 엔티티 설계 능력, 복잡한 쿼리를 Querydsl로 풀어내기.. 동시성 문제.. JWT 인증/인가.. 등등

    확실한 건 앞으로 무엇을 배워야 할지 방향성이 잡혔다는 거다.
    1. CS 공부 - 오늘 [이것이 컴퓨터 과학이다] 라는 도서를 구입했다. 당장 읽도록 한다.
    2. 리액트 공부 - 언제 시작할지 아직 미정
    3. 리눅스 공부 - 리눅스 마스터 2급 자격증 취득 예정 (11월 시험 접수)