목록으로
GitHub - Mineru98/MiniFlix
Service2025.04.27

GitHub - Mineru98/MiniFlix

요약

MiniFlix는 넷플릭스의 핵심 기능을 간소화하여 구현한 웹 기반 스트리밍 서비스로, 콘텐츠 탐색, 재생, 회원 관리 및 찜 목록 기능을 제공합니다.
이 시스템은 사용자, 콘텐츠, 장르, 찜 목록, 시청 기록 등을 관리하는 ERD와 함께, 회원가입, 로그인, 콘텐츠 재생 등의 핵심 시나리오를 Client, API, DB 간의 상호작용으로 상세하게 설계하였습니다.
️ 기술 스택으로는 프론트엔드에 Next.js와 React를, 백엔드에 Golang과 Gin 프레임워크를 활용했으며, 데이터베이스는 MySQL을 사용하고 Docker Compose를 통해 쉽게 배포할 수 있습니다.

상세 내용

이 문서는 웹 기반 스트리밍 서비스인 "MiniFlix"의 시스템 설계 및 구현에 대한 기획을 설명합니다. 넷플릭스의 핵심 기능을 간소화하여 구현한 프로젝트입니다.

1. 시스템 설계 기획

* 화면 흐름도 (Flowchart):
MiniFlix는 랜딩 페이지에서 시작하여 로그인 페이지 또는 회원가입 페이지로 이동할 수 있습니다. 성공적인 인증 후에는 홈 화면으로 이동합니다. 홈 화면에서는 검색 결과 화면, 장르별 필터링 화면, 콘텐츠 상세 페이지, 마이페이지로 접근할 수 있습니다. 검색 결과 화면장르별 필터링 화면콘텐츠 상세 페이지로 연결되며, 콘텐츠 상세 페이지에서는 비디오 플레이어로 이동하거나 찜하기/취소 기능을 수행할 수 있습니다. 마이페이지찜 목록계정 정보 관리 페이지로 나뉘며, 찜 목록에서는 다시 콘텐츠 상세 페이지로 이동 가능합니다. 모든 하위 페이지 (검색, 장르, 상세, 플레이어, 찜 목록, 계정)에서 홈 화면으로 돌아갈 수 있는 구조입니다. 각 페이지는 기능별로 색상(landingPage: #3E2723, authPage: #0D47A1, mainPage: #B71C1C, contentPage: #2E7D32, playerPage: #4A148C, userPage: #E65100)으로 구분되어 시각적으로 명확하게 표현됩니다.

* ERD (개체-관계 다이어그램):
시스템의 데이터 모델은 다음 여섯 가지 주요 엔티티로 구성됩니다.
* Users: 사용자 정보를 저장합니다.
* id (bigint PK): 사용자 고유 식별자.
* email (varchar(100)): 사용자 이메일 (로그인 ID).
* password_hash (varchar(255)): 암호화된 비밀번호.
* name (varchar(50)): 사용자 이름.
* created_at (timestamp): 가입 일시.
* updated_at (timestamp): 정보 수정 일시.
* is_active (bit(1)): 계정 활성화 상태.
* Contents: 콘텐츠 정보를 저장합니다.
* id (bigint PK): 콘텐츠 고유 식별자.
* title (varchar(200)): 콘텐츠 제목.
* description (text): 콘텐츠 설명.
* thumbnail_url (varchar(255)): 썸네일 이미지 경로.
* video_url (varchar(255)): 비디오 파일 경로.
* duration (int): 영상 길이(초).
* release_year (int): 출시 연도.
* created_at (timestamp): 등록 일시.
* updated_at (timestamp): 수정 일시.
* Genres: 장르 정보를 저장합니다.
* id (bigint PK): 장르 고유 식별자.
* name (varchar(50)): 장르명.
* description (varchar(200)): 장르 설명.
* ContentGenres: 콘텐츠와 장르 간의 다대다 관계를 매핑합니다.
* id (bigint PK): 매핑 고유 식별자.
* content_id (bigint FK): 콘텐츠 ID.
* genre_id (bigint FK): 장르 ID.
* Wishlists: 사용자의 찜 목록을 저장합니다.
* id (bigint PK): 찜 목록 고유 식별자.
* user_id (bigint FK): 사용자 ID (Users 테이블 참조).
* content_id (bigint FK): 콘텐츠 ID (Contents 테이블 참조).
* created_at (timestamp): 찜한 일시.
* ViewingHistories: 사용자의 시청 기록을 저장합니다.
* id (bigint PK): 시청 기록 고유 식별자.
* user_id (bigint FK): 사용자 ID (Users 테이블 참조).
* content_id (bigint FK): 콘텐츠 ID (Contents 테이블 참조).
* watch_duration (int): 총 시청 시간(초).
* last_position (int): 마지막 시청 위치(초).
* watched_at (timestamp): 시청 일시.
* is_completed (bit(1)): 시청 완료 여부.
관계는 UsersWishlistsViewingHistories를 "저장"하고 "시청"하며, ContentsWishlistsViewingHistories에 "저장됨" 또는 "시청됨"으로 연결되고, ContentsGenresContentGenres를 통해 "분류"됩니다.

2. 핵심 기능별 시나리오 (Sequence Diagrams)

각 기능은 클라이언트 (브라우저), 백엔드 API 서버, 데이터베이스 (DB), 그리고 미디어 스토리지 간의 상호작용을 상세히 보여줍니다.

* 회원가입 시나리오:
1. 클라이언트가 이메일, 비밀번호, 이름을 포함한 회원가입 요청을 API 서버로 전송합니다.
2. API 서버는 요청 데이터의 유효성을 검증(이메일 형식, 비밀번호 강도)하고, DB에 이메일 중복 확인 쿼리를 보냅니다.
3. 이메일이 중복되면 409 Conflict 응답을, 유효성 검증 실패 시 400 Bad Request 응답을 클라이언트에 반환합니다.
4. 가입 가능한 경우, API 서버는 비밀번호를 해싱한 후, Users 테이블에 사용자 정보를 저장합니다.
5. 저장 성공 시, API 서버는 201 Created 응답과 함께 회원가입 성공 메시지를 반환하고, 클라이언트는 로그인 페이지로 리다이렉트됩니다.

* 로그인 시나리오:
1. 클라이언트가 이메일과 비밀번호로 로그인 요청을 API 서버에 전송합니다.
2. API 서버는 Users 테이블에서 해당 이메일로 사용자 정보를 조회합니다.
3. 사용자가 없으면 401 Unauthorized를, 계정이 비활성화 상태면 403 Forbidden을 반환합니다.
4. 사용자가 존재하면, API 서버는 저장된 password_hash와 입력된 비밀번호를 비교하여 검증합니다.
5. 비밀번호가 일치하지 않으면 401 Unauthorized를 반환합니다.
6. 로그인 성공 시, API 서버는 사용자 ID, 이름 등의 정보를 포함하는 JWT(JSON Web Token)를 생성하여 200 OK 응답과 함께 클라이언트에 반환합니다.
7. 클라이언트는 토큰을 localStorage 또는 쿠키에 저장하고 홈 화면으로 리다이렉트됩니다.

* 홈 화면 콘텐츠 로드 시나리오:
1. 클라이언트가 홈 화면에 접근합니다.
2. 로그인 상태인 경우: JWT 토큰을 포함하여 인증된 콘텐츠 목록을 요청합니다. API 서버는 토큰을 검증하고 유효한 경우, Contents 테이블에서 기본 정보를 조회합니다. 추가적으로 Wishlists 테이블에서 사용자의 찜 목록을 조회하여 콘텐츠에 찜 상태 정보를 추가한 후 200 OK 응답으로 클라이언트에 반환합니다. 토큰이 무효하면 401 Unauthorized를 반환하고 로그인 페이지로 리다이렉트됩니다.
3. 비로그인 상태인 경우: 비인증 콘텐츠 목록을 요청합니다. API 서버는 Contents 테이블에서 콘텐츠 기본 정보만 조회하여 찜 상태 없이 200 OK 응답으로 클라이언트에 반환합니다.
4. 클라이언트는 수신된 콘텐츠를 그리드 형태로 표시합니다.

* 콘텐츠 검색 시나리오:
1. 클라이언트가 검색어를 입력하고 API 서버에 검색 요청(검색어, 선택적으로 JWT 토큰)을 전송합니다.
2. API 서버는 로그인 상태인 경우 JWT 토큰을 검증합니다.
3. API 서버는 Contents 테이블에서 title LIKE '%검색어%' 쿼리를 사용하여 제목 기반으로 콘텐츠를 검색합니다.
4. 로그인 상태인 경우, Wishlists 테이블에서 사용자의 찜 목록을 조회하여 검색 결과에 찜 상태 정보를 추가합니다.
5. 검색 결과가 있으면 200 OK (검색 결과 콘텐츠 목록)를, 없으면 200 OK (빈 배열)를 반환합니다.
6. 클라이언트는 검색 결과를 표시합니다.

* 장르별 필터링 시나리오:
1. 클라이언트가 API 서버에 장르 목록을 요청하고, API 서버는 Genres 테이블에서 장르 정보를 조회하여 반환합니다. 클라이언트는 이를 장르 필터 UI로 표시합니다.
2. 클라이언트가 특정 장르를 선택하고 API 서버에 해당 장르의 콘텐츠를 요청(genre\_id, 선택적으로 JWT 토큰)합니다.
3. API 서버는 로그인 상태인 경우 JWT 토큰을 검증합니다.
4. API 서버는 Contents 테이블과 ContentGenres 테이블을 JOIN하여 선택된 genre_id에 해당하는 콘텐츠를 조회합니다.
5. 로그인 상태인 경우, Wishlists 테이블에서 사용자의 찜 목록을 조회하여 필터링된 콘텐츠에 찜 상태 정보를 추가합니다.
6. API 서버는 200 OK 응답과 함께 필터링된 콘텐츠 목록을 반환하고, 클라이언트는 이를 표시합니다.

* 콘텐츠 상세 정보 조회 시나리오:
1. 클라이언트가 콘텐츠 썸네일을 클릭하여 API 서버에 콘텐츠 상세 정보 요청(content\_id, 선택적으로 JWT 토큰)을 전송합니다.
2. API 서버는 로그인 상태인 경우 JWT 토큰을 검증합니다.
3. API 서버는 Contents, ContentGenres, Genres 테이블을 JOIN하여 콘텐츠의 상세 정보(제목, 설명, URL, 길이, 출시 연도, 장르 목록 등)를 조회합니다.
4. 로그인 상태인 경우, Wishlists 테이블에서 찜 여부를, ViewingHistories 테이블에서 최근 시청 정보(last_position)를 추가로 조회합니다.
5. API 서버는 200 OK 응답과 함께 콘텐츠 상세 정보, 찜 상태, 시청 위치(선택적)를 반환합니다.
6. 클라이언트는 콘텐츠 상세 페이지를 렌더링(찜하기 버튼, 재생 버튼 등 포함)합니다.

* 콘텐츠 재생 시나리오:
1. 클라이언트가 재생 버튼을 클릭합니다.
2. 비로그인 상태인 경우: 클라이언트는 로그인 페이지로 리다이렉트되고 시청 불가 알림을 받습니다.
3. 로그인 상태인 경우: 클라이언트가 content_id와 JWT 토큰을 포함한 재생 요청을 API 서버에 보냅니다.
4. API 서버는 JWT 토큰을 검증하고, 유효한 경우 Contents 테이블에서 video_url을, ViewingHistories 테이블에서 last_position을 조회합니다.
5. API 서버는 200 OK 응답과 함께 스트리밍 URL과 마지막 시청 위치를 클라이언트에 반환합니다.
6. 클라이언트는 미디어 스토리지에 비디오 스트림을 요청하여 수신하고, 비디오 플레이어를 마지막 시청 위치부터 초기화하여 재생합니다.
7. 클라이언트는 약 30초마다 현재 재생 위치를 API 서버에 업데이트 요청(content_id, current_position, JWT 토큰)합니다. API 서버는 ViewingHistories 테이블의 last_positionwatch_duration을 업데이트/생성합니다.
8. 재생 종료 시(일시정지, 창 닫기 등), 클라이언트는 최종 재생 위치(final_position), 총 시청 시간(watch_duration), 시청 완료 여부(is_completed)를 포함하여 최종 업데이트 요청을 API 서버에 보냅니다. API 서버는 ViewingHistories 테이블을 업데이트합니다.

* 콘텐츠 찜하기/취소 시나리오:
1. 클라이언트가 찜하기 버튼을 클릭합니다.
2. 비로그인 상태인 경우: 클라이언트는 로그인 페이지로 리다이렉트됩니다.
3. 로그인 상태인 경우: 클라이언트가 content_id와 JWT 토큰을 포함한 찜하기 토글 요청을 API 서버에 전송합니다.
4. API 서버는 JWT 토큰을 검증하고, Wishlists 테이블에서 현재 찜 상태를 확인합니다.
5. 이미 찜한 상태인 경우, Wishlists 테이블에서 해당 항목을 삭제하고 200 OK (찜 취소 성공, 새로운 상태: false)를 반환합니다.
6. 찜하지 않은 상태인 경우, Wishlists 테이블에 새 항목을 추가하고 201 Created (찜하기 성공, 새로운 상태: true)를 반환합니다.
7. 클라이언트는 UI(찜하기 버튼 상태)를 업데이트합니다.

* 찜 목록 조회 시나리오:
1. 클라이언트가 마이페이지에서 찜 목록을 클릭하고, JWT 토큰을 포함한 찜 목록 요청을 API 서버에 보냅니다.
2. API 서버는 JWT 토큰을 검증하고, Wishlists 테이블과 Contents 테이블을 JOIN하여 사용자가 찜한 콘텐츠 목록을 조회합니다.
3. 찜 목록이 있으면 200 OK (찜한 콘텐츠 목록)를, 없으면 200 OK (빈 배열)를 반환합니다.
4. 클라이언트는 찜 목록 화면을 렌더링합니다.

* 계정 정보 조회 시나리오:
1. 클라이언트가 마이페이지에서 계정 정보를 클릭하고, JWT 토큰을 포함한 계정 정보 요청을 API 서버에 보냅니다.
2. API 서버는 JWT 토큰을 검증하고, Users 테이블에서 사용자 정보(이메일, 이름, 가입일시)를 조회합니다.
3. API 서버는 200 OK 응답과 함께 사용자 정보를 반환하고, 클라이언트는 계정 정보 화면을 렌더링합니다.

* 계정 정보 수정 시나리오:
1. 클라이언트가 계정 정보 수정 폼(이름 또는 비밀번호)을 작성하고, 변경할 정보, 현재 비밀번호, JWT 토큰을 포함한 수정 요청을 API 서버에 보냅니다.
2. API 서버는 JWT 토큰을 검증하고, DB에서 현재 사용자의 password_hash를 조회하여 입력된 현재 비밀번호와 비교합니다.
3. 현재 비밀번호가 불일치하면 403 Forbidden을 반환하고 클라이언트는 오류 메시지를 표시합니다.
4. 비밀번호가 일치하면, API 서버는 새 정보의 유효성을 검증하고, 새 비밀번호 변경 요청이 포함된 경우 새 비밀번호를 해싱합니다.
5. API 서버는 Users 테이블을 업데이트(name, password_hash(선택적), updated_at)하고, 200 OK (수정 성공 메시지)를 반환합니다.
6. 클라이언트는 성공 알림을 표시하고 업데이트된 정보를 반영합니다.

3. 주요 기능

* 콘텐츠 스트리밍: 영화 및 드라마 클립을 웹에서 시청할 수 있습니다.
* 콘텐츠 탐색: 홈 화면에서 콘텐츠 목록 확인, 검색 및 장르별 필터링을 제공합니다.
* 회원 기능: 회원가입, 로그인, 마이페이지 기능을 포함합니다.
* 찜 목록: 관심 있는 콘텐츠를 저장하고 관리할 수 있습니다.
* 반응형 디자인: PC와 모바일 환경 모두에서 사용 가능한 UI를 제공합니다.

4. 기술 스택

* 프론트엔드:
* 언어: TypeScript
* 프레임워크/라이브러리: React, Next.js
* 상태 관리: Zustand
* 서버 상태 관리: React Query
* 스타일링: TailwindCSS
* HTTP 클라이언트: Axios
* 백엔드:
* 언어: Golang
* 웹 프레임워크: Gin
* 데이터베이스: MySQL
* 인증: JWT (JSON Web Token)
* API 문서: Swagger

5. 실행 방법

* Docker Compose: docker-compose up 또는 docker-compose up -d 명령어를 통해 전체 서비스를 실행할 수 있으며, docker-compose down으로 중지합니다.
* 개발 환경 설정:
* 프론트엔드: cd frontend, yarn, yarn dev 명령어로 개발 서버를 실행합니다.
* 백엔드: cd backend, go mod tidy, ./swag init && go run main.go 명령어로 개발 서버를 실행합니다.
* 접속 정보:
* 프론트엔드: http://localhost:3000
* 백엔드 API: http://localhost:8080
* Swagger API 문서: http://localhost:8080/swagger/index.html

원본 보기
GitHub
Shared by Anonymous