로스트아크 직업 추천 프로젝트
팀명: 내 Null을 바라봐
팀원: 황호영 김영조 오우제 문민혁 정흥수 안다겸
목차

1

1. 프로젝트 개요

2

2. 팀 구성 및 역할

3

3. 사용 기술 및 개발 환경

4

4. 설계

5

5. 주요기능

6

6. 설계의 주안점

7

7. 배포 환경

8

8. 인프라 아키텍처
1. 프로젝트 개요
로스트아크 직업 추천 웹 프로젝트입니다.
10가지 질문을 통해 사용자의 성향을 분석하고, 그에 맞는 직업을 추천해줍니다.
2. 팀 구성 및 역할
3. 사용 기술 및 개발 환경
Language
  • Java 17
  • Javascript
  • TypeScript
Framework
  • Spring Boot
  • React
  • Tailwind CSS
Library
  • Lombok
  • JPA
  • Spring Boot Data Redis
  • json-simple
  • JUnit 5
Tool
  • IntelliJ
  • Visual Studio Code
  • Postman
  • Docker
  • GitHub Actions (CI/CD)
WAS
  • Apache Tomcat
  • Nginx
DB
  • AWS RDS (MySQL)
  • Redis
협업 프로그램
  • Git
  • GitHub
  • Discord
  • Notion
운영체제
  • Window 10
  • AWS EC2 (Ubuntu)
Try me
4. 설계 - 요구사항 명세서
4. 설계 - 액터, 유스케이스 추출
4. 설계 - UseCase & Sequence Diagram
UseCase Diagram
Sequence Diagram
4. 설계 - 데이터베이스
5. 설계의 주안점
1
프론트엔드와 백엔드간의 협업 경험
2
AWS를 이용한 서비스의 배포
3
사용자 모집 후 문제 탐색 및 해결 과정 경험
6. 주요 기능
맞춤 직업 추천
10가지 질문을 통해 사용자의 성향을 분석하고 최적의 로스트아크 직업을 추천합니다.
직업 통계
실시간 테스트 통계와와 직업 분포도를 시각화하여 제공합니다.
댓글 시스템
사용자 간 정보 교류를 위한 실시간 댓글 기능을 구현했습니다.
테스트 질문 및 응답(1)
담당자 : 문민혁
- 테스트 질문을 설계 및 배치했습니다.
- 질문 응답(선택)시 노란색 바가 늘어나며 진행상황을 보여줍니다.
-클라이언트가 항목을 선택할 시 선택 항목을 배열 형태로 저장, 다음 테스트 문장을 제공합니다.
테스트 질문 및 응답(2)
사용자의 최종 답변을 저장
- 사용자 데이터, 질문들을 구성하고 답변을 숫자로 반환합니다. 데이터 값을 알아보기 쉽게 저장할 수 있으며, 데이터를 유연하게 사용 할 수 있습니다.
- 모든 응답이 끝나면 결과 페이지로 갈 수 있도록 버튼을 출력합니다.
구현 초기엔 answers useState로 관리하려 했으나 값이 변할 때마다 리렌더링 되어 빈 객체로 초기화되는 문제가 발생 했습니다.
useRef로 변경하여 상태 업데이트로 인한 리렌더링 없이 값을 즉시 업데이트할 수 있도록 수정했습니다.
테스트 질문 및 응답(3)
테스트 질문
- 총 10개로 나눠져있으며 각 고유의 ID를 가지고 있습니다.
- 질문의 ID값을 인덱스로 사용하여, 고유하게 식별할 수 있게 했습니다. 이를 통해 문제 데이터를 쉽게 관리 할 수 있습니다.
테스트 질문 및 응답(4)
사용자 응답 데이터 저장
/api/users POST
- 각 질문에 대한 답변들을 UserDto로 받습니다.
- 사용자가 테스트를 마쳤을 때, 고유 ID값을 생성합니다. UUID로 ID를 생성하여 고유성과 보안을 강화 했습니다.

- 각 질문에 대한 답변을 UserDto로 받아user 객체에 매핑하여 DB에 저장합니다.
- 이후, 생성된 user 객체의 ID값을 반환합니다.

주요 기능- 테스트 결과
담당자 : 정흥수
  • /api/results/{id} [POST] :
    테스트가 끝나면 성향에 맞는 직업을 계산한 뒤,, 결과를 등록합니다.
  • /api/results/{id} [GET] :
    userID를 기준으로 테스트 결과를 조회합니다.
  • User에서 저장한 테스트 결과값을 기반으로 각 성향별 평균 점수 산출합니다.
  • 이후 미리 저장된 직업들의 성향값을 모두 곱한후 각각 더해줍니다.
  • 테스트 결과로 나온 각 직업별 점수중 1위부터 5위까지의 직업을 선별후 DB 에 저장합니다
  • 테스트 결과 모아보기 기능을 위해
    1위 직업만 DB에 카운팅 해 줍니다.

주요 기능 - 댓글
1. 화면 구현
댓글 작성
이전 결과 데이터를 참조하여 댓글을 작성합니다.
댓글 수정 및 삭제
비밀번호를 입력하여 댓글을 수정하거나 삭제합니다.
검색 기능
작성된 댓글을 '직업' 테그를 검색하여 순차적으로 보여줍니다.
페이지네이션
댓글창을 구현하여, 댓글들을 나타내고, 페이지네이션을 구현하여 댓글을 순차적으로 보여줍니다.
댓글 작성
완성된 user테이블의 userId를 참조하여 댓글을 작성합니다.
'닉네임', '비밀번호', '한마디 남기기'칸에 적절한 정보를 넣습니다.
'댓글 작성' 버튼을 누르면 comment테이블에 데이터가 저장됩니다.
댓글 수정
댓글 수정 버튼을 구현하였습니다.
댓글 수정 버튼을 누르면 댓글 작성창이 댓글 수정기능으로 활성됩니다.
올바른 비밀번호를 입력하면 댓글이 수정됩니다.
틀린 비밀번호를 입력하면 오류알람이 뜹니다.
댓글 삭제
댓글 삭제 버튼을 누르면 삭제에 필요한 비밀번호를 입력할 알람창이 나옵니다.
댓글이 삭제되면 성공알람이 나옵니다.
페이지네이션
많은 댓글창을 분리하여 가독성을 높였습니다.
버튼을 누르면 다음에 만들어진 댓글들을 순차적으로 나타냅니다.
검색 기능
작성된 댓글을 '직업' 태그를 검색하여 순차적으로 보여줍니다.
검색창에 '소울'을 입력하면 댓글창이 직업 중
'소울'단어가 포함된 모든 댓글을 찾습니다.
주요 기능 - 댓글
2. 기능 구현
SQL 테이블
1.Users테이블 참조
댓글을 작성하기 위해, 댓글의 작성자의 정보를 가져와야합니다.
작성자의 정보는 기본키인 userId를 외래키로서 가져옵니다.
2.Results테이블 공유
Results테이블에서 댓글에 필요한 데이터를 가져옵니다.
top_factor1은 기본키이거나 유일성이 보장되지 않기 때문에 외래키로 사용할 수 없습니다.
3.Comments테이블 생성
다른 테이블에서 가져온 데이터를 기반으로 테이블을 작성합니다.
댓글 작성
Repository를 통해 Entity에 JSON형식의 데이터를 전달 하고 저장합니다.
userId는 CommentEntity에서 외래키(ManyToOne)로 지정되어 있기 때문에, CommentRepository통해 데이터를 찾아올 수 있습니다.
top_factor1는 외래키로 지정되어 있지 않기 때문에, 따로 userId와 ResultRepository를 통해서 데이터를 찾아옵니다.
댓글 삭제
URL경로로 가져온 UserId,CommentId와 입력창으로부터 JSON의 형태로 가져온 String password를 가지고 기존 데이터와 비교하여 데이터를 삭제합니다.
댓글 수정
URL경로로 가져온 UserId,CommentId와 입력창으로부터 JSON의 형태로 가져온 String password를 가지고 기존 데이터와 비교하여 데이터를 수정합니다.
password가 기존 데이터와 같다면, 댓글(content 속성)을 수정하고 저장합니다.
페이지네이션
페이지네이션 어노테이션을 활용하여 페이지데이터(크기,기준속성,방향)와 URL을 통한 페이지번호를 전달 받습니다.
댓글창에 구현할 데이터를 DTO와 Repository(Page)를 통해 찾아옵니다.
구현할 데이터를 활용하여 페이지기능을 구현할 변수를 만듭니다.
페이지네이션은 검색 기능과 공유하기 때문에 검색 기능이 비 활성상태와 상태 기능을 구분 하여야합니다. if문을 활용하여 구분하였습니다.
검색기능
URL(RequestParam)을 통해 받은 데이터를 Repository를 통해 Comments테이블에서 찾습니다.(대소문자무시,일부포함기능 추가하였습니다.)
찾은 데이터를 페이지네이션 기능과 공유하기 위해 DTO을 매핑하여, 데이터를 구현합니다.
마지막으로 구현할 데이터를 활용하여 페이지기능을 구현할 변수를 만듭니다.
Router
Route를 사용하면 URL주소에 따른 페이지를 렌더링 할 수 있습니다.
"/results/:id" 경로에서 :id는 동적 경로 매개변수로, id를 활용하여 URL을 유동적으로 랜더링 할 수 있습니다.
Component
React컴포넌트는 react나 router의 훅을 사용합니다.
HTTP 요청을 처리하는 함수들을 가져옵니다. 각 함수들을 사용하여 서버와 통신합니다.
각 조회, 추가, 수정, 삭제기능을 수행하는 함수입니다.
Http
async를 사용하여 비동기 함수로 선언합니다.
fetch 함수로 HTTP요청을 보내고 서버의 응답을 받아옵나다.
method:로 함수의 기능을 설정합니다.POST,GET,DELETE,PUT을 사용하였습니다.
요청 헤더와 바디는 백엔드에서 JSON형식으로 받기 때문에 JSON형식을 사용합니다.
Hook
먼저 Comment 인터페이스와 각 필드를 만듭니다.
Comment 인터페이스는 각 필드는 데이터 타입을 저장합니다.
useState 훅을 사용하여 Comment 인터페이스에 동적 변화를 줍니다.
setCommentData를 통해 commentData의 상태를 업데이트 합니다.
formattedComments변수를 이용하여 서버를 통해 받은 데이터(comments 테이블)를 화면에 표시되기 위한 필요한 형식(배열)으로 변환합니다.
상태 업데이트 함수인setCommentData formattedComments 배열을 commentData 상태에 저장합니다.
업데이트 저장된 commentData에 map메서드를 통해 댓글 화면에 랜더링합니다.
코드 리뷰(Comments)
백엔드
  • 프로젝트의 주요 CRUD를 JSON의 프론트 중심으로 만들었습니다.
  • Spring boot의 Entity와 Repository를 활용하여 테이블을 구성 하고, 활용하였습니다.
  • Dto클래스를 만들어, 원하는 데이터만 가져오도록 설정해 보았습니다.
  • 다양한 어노테이션을 통해 JSON과 URL로 부터 값을 가져와 활용하였습니다. 최종적으로 SQL에 데이터를 작성,조회,수정,삭제기능을 구현하였습니다.
  • Pagenation과 Repository의 Containing등 다양한 기능을 활용해 보았습니다.
프론트 엔드
  • React를 활용하여 UI를 구현하였습니다.
  • Component,import,export 등을 활용하여 가독성, 유지보수성 등 기능성을 높였습니다.
  • async를 활용하여 비동기 방식으로 서버(백엔드)와 연동하여 응답성을 향상키고, 서버의 성능을 향상시켰습니다.
  • useState와 같은 훅을 사용하여, 변수의 데이터를 변경하여 동적인 움직임을 구현하였습니다.
기능 개선점
백엔드
  • 로그인 기능과 CSRF 비밀번호 해싱 기능 설정.
  • 예외 처리 표준화.
  • 재사용 가능한 기능을 Helper클래스로 분리후 정리.
  • 일부 중복 코드 제거.
프론트엔드
  • Loader 기능 활용.
  • 비동기 작업과 관련된 함수를 서비스 레이어나 헬퍼 함수로 분리.
  • 일부 중복 코드 제거
  • 상태 초기화 로직 개선
  • UseCallback함수로 메모이제이션.

주요 기능- 직업 분포도
담당자 : 황호영
  • [GET] /api/statistics/alluser :
로스트아크 직업 분포도를 조회합니다.
  • [PUT] /api/statistics/alluser :
외부 API로부터 직업 분포도를 받아와 데이터를 갱신합니다.
직업 분포도(1)
DB Table
직업별로 컬럼을 지정하고,
직업 인구 수를 저장하도록 설계하였습니다.
직업 분포도(2)
create()
로스트아크 정보 제공 사이트​로부터 데이터를 받아와 가공하여 DB에 저장합니다.

1. JSON 데이터로 파싱합니다. (String 타입으로 받아온 데이터를 JSON 데이터로 변환)
2. 필요한 데이터만 JSON Array로 변환합니다.
3. 미전직 데이터를 삭제합니다.
직업 분포도(3)
4. 가공한 데이터를 Entity에 넣고 DB에 저장합니다.
직업 분포도(4)
  • update()
    매일 자정마다 create() 메서드를 실행하여 데이터 갱신합니다.
직업 분포도 (5)
read()
1. DB에서 통계 데이터를 불러옵니다.
2. 직업별 데이터를 OuterDataDto로 이루어진 List에 하나씩 담습니다.
3. 직업 비율을 기준으로 내림차순 정렬 후 반환합니다.
직업 분포도(6)
데이터 갱신 기능 요약
  • /api/statistics/alluser [PUT] :
로스트아크 정보 제공 사이트로부터​ 통계 데이터를 받아와 DB에 저장.
매일 00시에 데이터를 주기적으로 갱신.

1. 외부 데이터를 받아옴
{"className": "도화가","classTotal": "19095"},{"className": "바드","classTotal": "16336"},…
3. 데이터 저장

1

2

3

2. 데이터 가공
직업 분포도 (7)
frontend
1. AllUserStatistics가CharacterRankingTable 에게 fetchOuterData 함수를 넘겨줍니다.
2. useEffect를 사용하여 넘겨받은 함수로부터 data를 받아옵니다.
3. 받아온 array 데이터를 map 메서드로 반환문 형식에 맞게 출력합니다.
직업 분포도 개선점(1)
직업별 고정 데이터를 LoskArk.json 파일에 작성하여 관리하였습니다.

또한 Method 클래스와 invoke 메서드를 통해 메서드 명을 동적으로 적용하여 실행하였습니다.
이를 통해 불필요하게 긴 switch 문을 줄였습니다.

1. json 파일의 데이터를 읽어옵니다.
직업 분포도 개선점(2)
2. outerData Entity의 직업별 set 메서드명을 동적으로 생성하여 실행합니다.

3. 이후 해당 Entity를 DB에 저장합니다.

classTotalArr : 직업명들을 저장한 String 타입 리스트
{"className": "도화가","classTotal": "19095"},{"className": "바드","classTotal": "16336"},…
methodName = setArtist;
method.invoke(outerData,classTotal) // = outerData.setArtist(19095);
직업 분포도 개선점(3)
데이터를 조회하는 read() 메서드 또한 같은 방식으로 수정하였습니다.

주요 기능- 테스트 통계
담당자 : 안다겸
  • /api/statistics/data[GET] :
테스트 통계를 조회합니다.
반응형, 적응형 화면
기기의 화면 크기별로 창의 크기와 위치가 조절되도록 구현하였습니다.
데스크탑 : 너비를 450px 고정합니다.

태블릿 : 반응형
너비를 화면의 45%만큼 차지합니다.

모바일 : 적응형
댓글, 결과 창의 display를 block으로 설정하여 두 창을 위 아래로 정렬합니다.
캐싱 기능
댓글 조회 성능 / 응답 속도 테스트 결과
87 → 26,562 TPS
초당 처리량 300배 개선
80ms → 8ms
응답속도 10배 개선
성능 테스트 세팅:
  • 테스트 환경: 12th Gen Intel(R) Core(TM) i5-12400 2.50 GHz / RAM 16GB / SSD 512GB
  • 테스트 시나리오: 1000명의 동시 사용자가 1분 동안 반복 조회
  • 테스트 목적: 댓글 데이터 조회 성능 확인
캐싱 기능을 사용한 이유?
테스트 결과창에서 댓글 데이터가 유독 느리게 나타나는 문제가 발생했습니다.
브라우저 캐시에 저장된 이후에는 문제가 없었지만, 자주 조회되는 데이터이다보니 조회 성능을 개선시키고 싶었습니다.
이에 대한 해결책으로 Redis 캐싱 기능을 선택했습니다.
캐싱 전략
캐싱 전략으로는 Look Aside와 Write Around 전략을 사용하였습니다.
  • Look Aside
  • Write Around
댓글 조회 기능에 Cacheable 어노테이션을 달아주어 캐싱 기능을 적용했습니다.
문제점
댓글 데이터가 변경되었을 때, Redis에는 반영되지 않는 문제점이 생겼습니다.
해결방안
댓글 데이터 변동 시 @CacheEvict로 캐시를 모두 삭제해 조회 시 다시 갱신하는 방식을 사용했습니다.
이를 통해 데이터베이스 접근을 최소화하고 빠른 응답 시간을 확보할 수 있었습니다.
추후 개선점
하지만 이러한 방법은 댓글 데이터의 변동이 빈번하게 일어난다면 (실제로 빈번히 일어날 것임), @CacheEvict 기능은 성능 저하를 초래할 수 있습니다. 또한 서비스 초기에 대량의 cache miss가 발생하여 데이터베이스 부하가 급증할 수 있습니다.
Write-back
Write-through
프로젝트에 반영된 내용은 아니지만, 추후에는 Write-Back 또는 Write-Through 전략과 캐시워밍을 사용하여 캐시와 DB 간의 일관성을 유지하면서 성능을 개선할 수 있습니다.
GitHub Actions (CI/CD)
back
front
내용:
1. EC2에 접속합니다. (EC2 접속 정보는 secrets 변수로 관리)
2. git pull을 통해 변경사항을 적용합니다.
3. 갱신한 프로젝트 파일을 바탕으로 빌드 및 배포를 수행합니다.

EC2 접속 정보와 application.properties 같은 민감한 내용은 secrets 변수로 관리하였습니다.
7. 배포 환경

1

서버 구성
AWS EC2 인스턴스에 Ubuntu OS 서버를 구축하고 Docker Compose로 Spring Boot와 Redis를 함께 구동하였습니다.

2

DNS
AWS Route53를 이용해 도메인을 등록하였습니다.

3

로드 밸런싱
AWS LoadBalancer를 통해 SSL 인증으로 보안을 강화했습니다.

4

리버스 프록시
Nginx로 EC2 80포트에 접근하는 요청 중
/api 경로는 8080포트(Spring Boot)로,
나머지 요청은 3000포트(React)로 분산하였습니다.
8. 인프라 아키텍처
Made with Gamma