Skip to content

🔖 Feat: 1차 MVP 모델 기능 구현#30

Merged
Bumnote merged 60 commits intomainfrom
dev
Apr 29, 2025
Merged

🔖 Feat: 1차 MVP 모델 기능 구현#30
Bumnote merged 60 commits intomainfrom
dev

Conversation

@Bumnote
Copy link
Copy Markdown
Member

@Bumnote Bumnote commented Apr 29, 2025

#️⃣ 연관된 이슈

#29

📝 작업 내용

📋 기본 환경 세팅

  • Mysql 연동
  • GlobalExceptionHandler 구현
  • CORS 문제 해결 및 Config 설정

🧩 인증 및 회원 관리

  • 카카오 OIDC 로그인 방식 구현
  • 회원 가입/로그인 기능 구현
  • JWT 토큰 발급 (AccessToken, RefreshToken) 및 인증/인가 처리
  • AccessToken 만료 시 RefreshToken 재발급 및 재로그인 유도 기능
  • 회원, 비회원 구분 및 결과/랭킹 데이터 반환 처리
  • 회원정보 입력 및 닉네임 관리
  • 닉네임 규칙 검사 (기호, 길이 제한 등)
  • 랜덤 닉네임 생성 지원
  • 비회원 Guest 처리 (AnonymousAuthenticationToken 활용)
  • 비회원일 경우 임시 ID (UUID 랜덤 문자열)로 접근
  • 랭킹 등록 불가, 기본 Guest 닉네임 표출

📈 타자 연습 기능

  • 문장 제공 및 관리
  • 저장된 문장 중 랜덤 20개 문장 조회 기능
  • 문장 ID 기준 랜덤 조회
  • 타자 결과 기록 처리
  • 타수(CPM), 최대 타수(MAX_CPM), 분당 타수(WPM), 정확도(ACC) 저장
  • 타자 결과 메시지 및 긍정 문구 랜덤 제공

🏆 랭킹 관리 기능

  • 실시간 랭킹 조회 기능
  • 공식을 활용하여 도출된 점수 기준으로 정렬
  • CPM > 순간 최고 CPM > 정확도 순 정렬 우선순위 적용
  • 월별 랭킹 조회 기능
  • 매월 1일 00:00 기준 초기화
  • 회원만 랭킹 등록 가능
  • 비회원은 랭킹 미표출 및 등록 불가

🧪 테스트 및 유지보수

  • 테스트 코드 수정 및 구현

💬 리뷰 요구사항

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요


Bumnote and others added 30 commits March 10, 2025 00:35
- Mysql typing DB와 연동합니다.
issue #1
- PR & Issue 템플릿을 생성합니다.
- JPA Auditing 기술을 활용해서 Entity 요소들의 생성일, 수정일을 저장하기 위해 Config 파일을 작성했습니다.
- CreatedDate, ModifiedDate 속성을 모든 Entity에 부여하기 위해서 BaseEntity 추상 메소드를 작성했습니다.
issue #3
- Local 영역과 Test 영역을 분리하기 위해서 Profile 설정하였습니다.
- Local 영역은 Mysql DB 사용하여 실제 데이터들을 담도록 하였습니다.
- Test 영역은 H2 인모메리 DB 사용하여 가볍게 테스트용 데이터를 저장 및 롤백하도록 하였습니다.
issue #3
- build.gradle 파일을 구조적으로 파악하기 쉽도록 기술 모듈별로 정리하였습니다.
issue #3
- 타이핑 문장을 의미하는 Phrase Entity 구현하였습니다.
- DB에서 문장들을 불러올 수 있도록 JpaRepository 활용한 PhraseRepository를 구현하였습니다.
- DB에서 최대 20개의 문장들을 랜덤하게 가져올 수 있도록 JPQL 활용하여 쿼리문을 작성했습니다.
issue #3
- Entity id를 가져오기 위해서 Phrase 어노테이션 Getter 추가
- Junit 활용하여 문장 랜덤 조회 기능 테스트 구현
issue #3
✨ Feat: 문장 최대 20개 랜덤 조회 기능 구현
- RestController 전역으로 관리하기 위한 GlobalExceptionHandler 클래스를 구현합니다.
- 예외 처리를 함에 있어서 예외 코드들을 커스텀하기 위해 enum 클래스를 활용해 구현합니다.
- API 호출 성공 Response, 예외 발생 Response 응답 객체인 ApiResponse 클래스를 구현합니다.
issue #5
- API 호출 성공 시, 응답 객체가 잘 생성되는지 테스트합니다.
- 예외가 발생했을 때, 응답 객체가 잘 생성되는지 테스트합니다.
issue #5
- 존재하지 않은 문장에 대한 예외 코드를 추가하였습니다.
- HttpStatus code와 기존 예외 코드의 code가 같아 혼란을 야기하여 수정하였습니다.
issue #7
- PostMapping, RequestBody 활용하여 타이핑 결과 정보를 프론트 단에서부터 전달받습니다.
- Typing N:1 Phrase 연관 관계를 구현합니다.
- Typing N:1 Member 연관 관계를 구현합니다.
- 각 계층의 의존성을 줄이기 위하여 Controller Request와 Service Request를 분리했습니다.
- 존재하지 않은 문장에 대하여 예외 처리를 하였습니다.
- Member 객체와 행운의 메시지 및 순위 응답 객체를 임시 데이터로 구현하였습니다.
- 타이핑 결과 정보를 DB에 저장합니다.
issue #7
- 특정 문장에 대한 타자 정보를 저장하는 로직 테스트를 진행하였습니다.
- 존재하지 않는 문장에 대한 예외 처리 테스트를 진행하였습니다.
- 타자 정보 생성 후, 행운의 메시지와 순위를 반환하는 응답 로직 테스트를 진행하였습니다.
issue #7
✨ Feat: 타이핑 결과 저장 및 행운의 메시지, 순위 응답 구현
- SQL 실행 쿼리 로그를 확인하기 위해서 application.yml 파일 설정을 추가하였습니다.
- SQL 쿼리에서 바인딩된 실제 값을 출력하기 위해서 TRACE 설정을 추가하였습니다.
issue #9
- 도메인 별로 묶기 위해서 파일 이동을 하였습니다.
- 타자 정보가 담긴 Typing 관련 파일들을 typing 폴더를 만들어 이동하였습니다.
issue #9
- 타자 결과 정보의 생성일과 수정일을 함께 저장하기 위해서 BaseEntity 추상 클래스 상속
issue #9
- API 호출 및 조회한 랭킹 정보를 반환하는 RankingController를 구현했습니다.
- DB에서 전달받은 데이터를 Controller 측으로 전달해주는 RankingService를 구현했습니다.
- 상위 최대 50등까지 타자 정보를 가져올 수 있는 JPA native query를 TypingRepository에 구현했습니다.
- JPA native query의 결과물을 직접 받을 수 있는 RankingResponse 객체를 구현했습니다.
issue #9
- CSRF 공격을 막기 위한 보호 기능을 비활성화했습니다.
- HTTP 기본 인증 방식을 비활성화했습니다.
- Form Login 설정을 비활성화했습니다.
- Logout 설정을 비활성화했습니다.
- 테스트를 진행하기 위해서 모든 요청에 대한 접근을 허용하였습니다.
- 추후 JWT 인증 방식을 도입하기 위해서 Session 설정을 비활성화했습니다.
issue #9
- 약 60개의 데이터를 미리 DB에 넣어 테스트하기 위해서 ranking.sql 파일을 만들어 저장하도록 구현했습니다.
- TypingRepository에 작성한 JPA native query가 정상적으로 작동하는지 단위 테스트를 진행했습니다.
- Repository에서 가져온 데이터가 RankingResponse DTO 객체에 잘 담겨있는지 RankingService 단위 테스트를 진행했습니다.
- Controller -> Service -> Repository -> DB -> Repository -> Service -> Controller 통합 테스트를 진행했습니다.
issue #9
✨ Feat: 실시간 랭킹 조회 기능 구현
- JUnit5의 "Less is more" 모토를 적용하기 위해서 public 접근 제한자를 제거했습니다.
- 가독성을 높이고, 테스트 코드에만 집중할 수 있는 효과를 얻었습니다.
issue #10
- Member 객체의 데이터를 접근하기 위해서 Lombok의 Getter 어노테이션을 추가했습니다.
- JPA Auditing 기술을 적용하여 생성일, 수정일을 저장하기 위해서 BaseEntity 추상 클래스를 상속받습니다.
issue #10
- 날짜 정보를 가져오기 위한 RankingResponse DTO 클래스에 생성일 변수를 추가하였습니다.
- native query를 통해서 받은 Timestamp 타입을 불변 객체를 활용하기 위한 LocalDateTime 타입으로 변환하여 저장합니다.
- 현재 날짜의 연월에 대한 첫번째 날짜의 시작 시간과 마지막 날짜의 끝 시간 사이에 존재하는 모든 데이터들을 토대로 순위를 계산합니다.
- 해당 데이터들을 Service에서 받아 Controller로 넘깁니다.
- Controller에서 받은 월별 랭킹 데이터를 요청한 Client 측으로 전달합니다.
- 날짜를 받기 위해서 native query select 문에 "createDate"를 추가하였습니다.
issue #10
- 약 60개의 데이터를 미리 DB에 넣어 테스트하기 위해서 ranking.sql 파일을 만들어 저장하도록 구현했습니다.
- 월별 랭킹 데이터를 DB에서 잘 가져오는지 Repository 단위 테스트를 진행했습니다.
- DB에서 가져온 월별 랭킹 데이터의 순위가 오름차순이 맞는지, 점수가 내림차순이 맞는지 테스트를 진행했습니다.
- Repository -> Service -> Controller 순서대로 데이터를 가져 오는지 RankingController 통합 테스트를 진행했습니다.
issue #10
✨ Feat: 월별 랭킹 조회 기능 구현
- Kakao Social Login 기능 구현에 필요한 provider, registration 환경 변수 요소들을 추가하였습니다.
- Kakao Login 동의 항목에 해당하는 scope 요소들을 작성하였습니다.
- OIDC 로그인 방식을 구현하기 위해서 scope 항목에 openid 요소를 추가하였습니다.

issue #13
- OAuth2 Client 기능을 활성화합니다.
- OAuth2 로그인 과정 중, 사용자의 정보를 가져오기 위한 작업을 customOAuth2UserService 파일에서 수행하도록 연결해주었습니다.
- OAuth2 로그인 성공 시, 어떤 동작을 해야할 지 지정해주기 위해서 successHandler 기능을 설정해주었습니다.
- 마찬가지로, 성공했을 때의 동작은 oAuth2AuthenticationSuccessHandler 파일에서 수행하도록 연결해주었습니다.

issue #13
Bumnote and others added 26 commits April 22, 2025 22:10
- 유저 회원가입 시, 동의한 약관 내용을 저장하기 위해서 새로운 Consent, MemberConsent Entity를 구현했습니다.
- 하나의 유저는 여러 개의 약관에 동의할 수 있고, 하나의 약관은 여러 명의 유저가 동의합니다.
- N:M 관계이므로 이를 MemberConsent 라는 중간 Table을 만들어 1:N, M:1 관계로 풀어서 구현했습니다.
- Consent 데이터는 정적이고, MemberConsent 데이터는 Member 정보에 의존합니다.
- Member Entity 데이터의 저장으로 MemberConsent Entity 측 데이터도 함께 저장하기 위해서 단방향 연관관계 매핑을 활용했습니다.
- ManyToOne 어노테이션의 기본값이 EAGER 이기 때문에, 필요한 정보만 가져오기 위해서 LAZY로 설정하였습니다.

issue #16
- Token 관련 예외, Member 관련 예외, Nickname 관련 Custom Exception 정보를 추가했습니다.
- 유저의 약관 동의 내용 및 설정한 닉네임을 받는 DTO 객체를 구현했습니다.
- Controller <-> Service Layer 분리를 위해서 Controller DTO, Service DTO 객체를 분리했습니다.
- 단순 형용사, 명사 하드코딩으로 랜덤 닉네임 생성 기능을 구현했습니다.
- 닉네임 검증을 위하여 request DTO, validate Method, regex validate 정규식 검증을 구현했습니다.
- 회원 로그인 시 또는 비회원 회원가입 완료 시, Access JWT 토큰을 Header에 담아 프론트엔드 단으로 넘깁니다.

issue #16
- 유일한 유저 생성을 위한 nickname, kakaoId Unique 설정 테스트를 진행했습니다.
- 랜덤 닉네임 생성 테스트를 진행했습니다.
- 코드 변경으로 인한 ApiResponse 테스트를 수정했습니다.

issue #16
✨ Feat: 회원 로그인 및 회원가입 기능 구현
- 프론트엔드 개발자와 변수 네이밍 전략을 일치시키기 위해서 SNAKE_CASE 전략으로 변경했습니다.
- @JsonProperty 어노테이션을 활용하기 보다는 전역으로 관리하기 위해서 yml 설정 파일에 반영했습니다.

issue #19
- Phrase Entity 필드 변수 이름을 조금 수정했습니다. (복수형 -> 단수형)
- PhraseRepository 쿼리 메소드의 이름을 수정했습니다. (find -> get)
- 공통 응답 메소드를 오버로딩하여 보다 유연한 응답 반환을 할 수 있도록 하였습니다. (ApiResponse<Map<String, T>>)
- Repository 쿼리 메소드를 통해서 임의의 20개 문장들을 Entity List 형태로 가져옵니다.
- Entity List 형태를 DTO List 형태로 가공하여 Controller 측으로 반환합니다.
- Controller 측에서는 ApiResponse<Map<String, T>> 형식으로 응답을 반환합니다.

issue #19
- Phrase Entity 관련 변경 내용을 반영하여 재테스트를 진행했습니다.
- 행운의 메시지를 랜덤으로 반환하기 위해서 많은 행운의 메시지들을 String 배열에 담아 관리합니다.
- Random 라이브러리를 활용하여 String 배열 길이까지의 숫자를 무작위로 받아 해당 인덱스에 해당하는 문장을 찾아 반환합니다.

issue #19
- 행운의 메시지 랜덤 반환 기능을 테스트합니다.
- 가장 쉽게 접근할 수 있는 랜덤으로 생성한 두 행운의 메시지가 서로 다를 것을 가정하고 테스트합니다.

issue #19
✨ Feat: 저장되어있는 문장 중 임의의 20개 문장 조회 기능 구현
✨ Feat: 행운의 메시지 랜덤 반환 기능 구현
- 타자 결과에 대한 점수 공식을 도메인 측에 설계하여 보다 더 응집력을 높이고, 책임과 역할을 명확하게 구현했습니다.

issue #19
- 기획 내용을 반영하여 score -> maxCpm -> acc -> createdDate -> id 순서대로 순위를 정합니다.
- 해당 순위를 Repository 쿼리 메소드를 통해서 데이터를 불러옵니다.
- 테스트를 위해서 사용한 RankingResponse 필드 변수는 JsonIgnore 어노테이션을 통해서 응답에서 제외합니다.

issue #19
- 타자 결과에 대해서 적용한 점수 공식이 정확한 값을 반환하는지 테스트합니다.
- 다양한 매개변수에 대한 테스트를 진행하기 위해서 @ParameterizedTest 어노테이션을 활용했습니다.

issue #19
✨ Feat: 타자 결과에 대한 점수 공식 적용 및 순위 조회 기능 구현
- 요청으로 들어온 문장 id 값을 통해서 문장의 정보를 불러옵니다.
- 요청이 들어올 때, 설정해둔 Filter를 통해서 Authentication 객체에 Role 정보를 저장합니다.
- TypingService 비즈니스 로직에서 Authentication 객체의 Role 정보를 통해서 회원, 비회원을 구분합니다.
- 회원이라면, Role 값과 해당 타자 결과 정보에 대한 순위, 닉네임, 행운의 메시지를 함께 응답 객체에 담아서 반환합니다.
- 비회원이라면, Role 값과 순위(null), 닉네임(GUEST), 행운의 메시지를 함께 응답 객체에 담아서 반환합니다.
- 예외를 반환하기 위해서 Custom Exception 예외 정보를 추가했습니다.(Code: 3003)

issue #19
- 타자 결과 점수 공식 적용을 위하여 ranking.sql 파일을 수정하였습니다.
- ranking.sql 파일을 테스트 메소드 각각에 적용하기 위해서 @DirtiesContext 어노테이션을 활용했습니다.
- 타자 결과 정보에 대한 개별 순위를 정확하게 반환하는지에 대한 테스트를 진행했습니다.

issue #19
✨ Feat: 회원, 비회원에 따른 타자 결과 정보 저장 로직 및 개별 순위 반환 기능 구현
- JwtAuthenticationFilter -> MemberController -> MemberService
- 회원 AccessToken 유효시간이 지나 프론트 단에서 reissue 엔드포인트를 호출합니다.
- 만료된 AccessToken 및 회원 자격 증명을 Controller 단으로 넘기기 위해서 Filter 내에서 분기시킵니다.
- USER 자격만 허용하는 reissue 엔드포인트 Controller에서 회원의 카카오 Id를 Service로 넘깁니다.
- Service에서 refreshToken이 Redis DB 내에 존재하는지 체크합니다.
- 유효한 refreshToken이 존재하는 경우 -> 새로운 회원 AccessToken과 RefreshToken을 발급하여 Update합니다.
- 유효하지 않은 refreshToken인 경우 -> 예외를 반환하고, 재로그인을 유도합니다.

issue #20
- AccessToken 및 RefreshToken 예외를 위해서 validate 메소드를 분리했습니다.(validateAccessToken, validateRefreshToken)
- Subject 값을 UUID 랜덤 문자열로 저장해줌으로써 refreshToken이 동일하게 생성되는 이슈를 해결했습니다.
- luckyMessageService 내의 luckyMessages 문자열 배열에 접근하기 위해서 Getter 어노테이션을 추가했습니다.
- 테스트를 원활하게 진행하기 위해서 TypingController 단에서 authentication을 Service로 넘기도록 변경했습니다.

issue #20
- refreshToken 활용하여 회원 AccessToken 재발급하는 테스트를 진행했습니다.
- refreshToken이 만료된 경우, EXPIRED_REFRESH_TOKEN 예외가 발생합니다.
- refreshToken이 유효하지 않은 경우, INVALID_REFRESH_TOKEN 예외가 발생합니다.
- 모든 상황을 만족하는 경우, 새로운 AccessToken과 refreshToken을 발급하여 AccessToken은 반환, refreshToken은 redis에 업데이트합니다.

issue #20
✨ Feat: 회원 AccessToken 만료 시, RefreshToken 활용한 재발급 및 재로그인 유도 기능 구현
- 구현
- TypingServiceTest
-> Entity 필드 변수가 변경되어 수정 사항을 반영했습니다.
-> 회원, 비회원일 경우 타자 결과 응답으로 나타내는 요소들을 테스트합니다. (role, nickname, rank, luckyMessage)

- 수정
- RankingControllerTest
-> 공통 응답 객체 코드 변경(Array -> Map)에 따른 결과 타입을 변경하였습니다.
-> sql 파일을 모든 메소드에 적용하기 위해서 @DirtiesContext 어노테이션을 활용했습니다.
- RankingServiceTest, TypingRepositoryTest
-> sql 파일을 모든 메소드에 적용하기 위해서 @DirtiesContext 어노테이션을 활용했습니다.
-> 각 메소드가 실행되면서 DB에 저장된 데이터를 모두 지우기 위해서 tearDown 메소드를 사용했습니다.

issue #20
- API 통신할 때, 발생하는 CORS 문제 해결을 위한 Config 설정을 구현했습니다.
- frontServer 내용을 감추기 위해서 @value 어노테이션을 활용하여 env 파일에서 불러오도록 구현했습니다.

issue #21
✅ Test: 일부 코드 수정으로 인한 테스트 코드 수정 및 구현
🔧 Feat: CORS 문제 해결 Config 구현
@Bumnote Bumnote requested a review from kyubumjang April 29, 2025 07:36
@Bumnote Bumnote self-assigned this Apr 29, 2025
@Bumnote Bumnote linked an issue Apr 29, 2025 that may be closed by this pull request
2 tasks
@Bumnote Bumnote merged commit 1156fc2 into main Apr 29, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] 1차 MVP 모델 기능 구현 완료 및 main 브랜치 push (dev -> main)

1 participant