-
Notifications
You must be signed in to change notification settings - Fork 2
[BE] 프로젝트 코드 읽기
JIN edited this page Jan 14, 2025
·
2 revisions
⚙️ Web BE |
---|
김진 |
2025.01.06. 작성 |
Note
server 디렉터리 하위에 있는 코드만 읽었습니다.
-
TypeScript
-
Node.js
-
NestJS
-
Redis
-
Socket.io
-
Prettier
-
ESLint
-
pnpm
REDIS_HOST=''
REDIS_PORT=''
- env 파일은 정말 단순히
Redis
이용을 위해서 사용 - 밑에 파일들 훑고 와서 알게된 점. clova 관련 누락되어있음!
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir: __dirname,
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: ['plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'prettier/prettier': ['error', { endOfLine: 'auto' }],
},
};
- 인터페이스 이름 접두사 강제성 off
- 함수 반환 타입 명시적으로 지정 off
- 모듈 경계 타입 명시적으로 지정 off
- any 타입 사용 금지 off
- 얘는 왜 off 인지 아직은 잘 모르겠음. 따로 외부 API 등을 사용하지 않는 이상 any 타입을 쓸 일이 없을 거 같은데..?
- 밑에서 확인했는데 Redis에서 일단 any 타입 많이 씀
-
chat.gateway.ts
-
socket.io
사용 -
handleConnection
- roomId, playerId 확인해서 경우에 따라 에러 처리
-
handleSendMessage
- 클라이언트의 데이터가 중간에 변할 수도 있으므로 메시지 처리 전에 한번 더 연결 확인
- 채팅 메시지 전송
-
chat.gateway.spec.ts
- gateway가 정의되었는지만 확인하는 단순한 테스트코드
-
-
chat.repository.ts
-
getPlayer
- 완전히 동일한 코드가
game.repository.ts
에 존재 → util 등으로 빼는 건?
- 완전히 동일한 코드가
-
existsRoom
existsPlayer
- redis에 방/플레이어의 정보를 저장하고 관리
- redis에서 roomId, playerId가 존재하는지를 확인해서 boolean 타입으로 반환
- redis에서 exists는 0 또는 1을 반환하기 때문에 boolean 타입으로 반환을 위해
return exists === 1
로 작성 - 밑에 redis 디렉터리 부분에 작성하긴 했는데, 애초에
redis.service.ts
에서 exists 함수를 boolean 타입 반환으로 바꾸는게 더 깔끔해보이긴함(완전 주관적인 내 생각)
- redis에 방/플레이어의 정보를 저장하고 관리
-
-
chat.service.ts
-
sendMessage
- 플레이어 id, 닉네임, 메시지, 메시지 쓴 시간 전달
-
existsRoom
existsPlayer
- 방/플레이어 정보 확인용
-
chat.service.spec.ts
- 테스트 코드가 다 이런 식으로만 작성이 되어있나요? gateway랑 똑같이 service 모듈이 잘 되어있는지만 확인하는 코드
-
- enums 디렉터리
-
game.status.enum.ts
-
PlayerStatus
왜 필요한거지? 플레이어가 게임 중인지 아닌지는 친구 기능이 있지 않는 이상 방 상태로 확인하는 편이 더 낫지 않았을까 하는 생각이 드네요 -
RoomStatus
여기에 PLAYING이 있는게 훨씬 자연스러울 거 같은데..
-
-
- services 디렉터리 /
timer.service.ts
- 게임할 때 쓰는 타이머!
- types 디렉터리
- player, room, roomsettings에 대한 interface
-
clova-client.ts
-
.env.example
에clova_api_key
랑clova_gateway_key
누락되어있네
-
-
drawing.gateway.ts
-
handleConnection
- 얘는
chat.gateway
에 있는 거랑 똑같음
- 얘는
-
handleDraw
- 얘는 그림 그린 사람이랑 그림 데이터 보내주는 역할!
-
drawing.gateway.spec.ts
- 얘도… 그냥 잘 정의됐는지만 확인… test 코드가 작성이 아예 안된 거로 봐도 되겠다
-
-
drawing.repository.ts
- 여기에 들어있는 함수 두개 다
chat.repository
에서 똑같이 쓰는 건데 이런거 공통으로 분리해도 되려나?
- 여기에 들어있는 함수 두개 다
-
drawing.service.ts
- 여기도
chat.service
에서 쓰는거 똑같이 쓰는데 이런거 공통 레포지, 서비스로 나누는게 낫지 않을까 싶음 - 둘이 공통된게 찾아보니까 똑같이 gateway에서만 단 한번 쓰는건데… 공통 레포지, 서비스 만들어서 게이트웨이에서 그 공통 서비스 불러와서 이용하는 편이 나을듯
- 여기도
-
game.exception.ts
- 특정 상황에서 발생하는 예외 정리한 커스텀 예외 클래스
-
ws-exception.filter.ts
-
socket.io
에서 발생한 에러 캐치하는 filter
-
-
game.controller.ts
- 방 생성하는 것만 있다!
-
game.controller.spec.ts
- 얘도 똑같다.. 그냥 toBeDefined()다..
-
game.gateway.ts
-
handleJoinRoom
- 단순.. 방 참가
-
handleReconnect
- 재연결 로직
-
handleSettings
- 세팅 바뀌면 바뀌었다고 다 알려줘! 다같이 바꿔!
-
handle
- 점수 같은거 바뀔 때마다 업데이트 해줘야 하니까
- 근데 얜 왜 이름이 그냥 handle일까 handlePlayer 이런 것도 아니고..
-
handleGameStart
- 게임 시작~
-
startNewRound
- 새로운 라운드를 시작한다는게 첫 라운드를 시작한다는게 아니라 라운드 + 1해서 쭉쭉 올라가는 그런 새로운 라운드
- 여기 시점에서 역할 분배는 끝
- 그래서 라운드 만약에 끝났으면 게임 종료니까 게임 성공적으로 종료할 수 있게 하고 return
- 그런거 아니면 그 라운드 시작하면서 자기 역할 같은거 알려주기
- 타이머도 동작 시작하고
- 그림 그리는 시간 끝나면 끝난대로 상태 업데이트하고, 맞추는 시간 진행. 얘도 끝나면 끝난대로 점수 체크하고… 새로운 라운드 시작!
- 새로운 라운드를 시작한다는게 첫 라운드를 시작한다는게 아니라 라운드 + 1해서 쭉쭉 올라가는 그런 새로운 라운드
-
notifyPlayerRoundStart
- 여기서 현재 라운드랑, 배정 받은 역할이랑, 그림 그리는 시간이랑 이런거 관리
- 그림꾼/방해꾼이면 단어 이제 보여주고
- 추측하는 사람들은 멍때리면서 바라보기
-
runTimer
- 타이머
-
handleCheckAnswer
- 제한시간 내 지속적으로 답안체크. 답 맞으면 이제 라운드 종료하고 새로운 라운드 시작할 수 있게!
- 근데 여기저기서 다 startNewRound를 해도 되는 것인가?? 다음 라운드 시작하는 로직을 하나로 합쳐도 될 거 같은데…
- 정답 맞췄을 경우 startNewRound 호출
- 정답 못맞췄을 때 타이머 다 끝나고 난 이후에 startNewRound 호출 이런 식으로
-
handleDisconnect
- 일정 시간 동안 재접속 안하면 완전히 연결을 끊는 로직
-
game.gateway.spec.ts
- 얘도 잘 주입하고 있는지..
-
-
game.repository.ts
-
createRoom
- 트랜잭션 이용해서 해시 데이터 순차적으로 실행
- 근데 지금 객체를 레디스에 저장하려고 하는거니까 JSON.stringify 써서 직렬화해도 괜찮게 저장하는 건 어떨까?
- 실패한 경우가 처리가 안되어있음
- 지금 코드 그대로 활용하려면, 명령 실행하고 실패한 경우에는 생성된거 다 지워주고 에러 던지기 (화면에서 실패했습니다! 다시 생성해주세요! 이런거 띄워줘야하니까)
- redis에서는 해당 키가 없을 때 del 한다고 에러가 뜨진 않으니까 그냥 hset 한거 둘 다 del 해서 지우면 될듯
-
getRoom
- 게임 방에 대한 정보를 싹 가져오기
- as 문법 안쓰는게 좋다고 어디서 들은거 같은데 아시는 거 있나요?
-
updateRoom
- hostId나 room status같은거 변경 일어나면 업데이트
-
deleteRoom
- 방 삭제 시 룸에 대한 정보 다 지우기
- 얘도 실패했을 경우 처리해줘야 함
- 그러면 실패했을 땐 다시 hset해서 지정하고 실패했다고 에러 던져줘야겠지?
-
getRoomStatus
- 방 status 대해서만 꺼내올 때 쓰는거
- 근데 안쓰는 건 왜 만들어두신거지.. 지우고 싶게 ㅠㅠ 그냥 밑에거랑 세트 메뉴라 그런거겠죠..
-
getRoomSettings
- room setting 가져오는 거
-
getRoomPlayers
- 방에 참가한 플레이어들 정보 가져오는거
- 마지막에 filter(boolean) 해서 null이나 undefined 없이 유효한 사람들 정보만 남김
-
addPlayerToRoom
- 게임 방에 유저 추가
- 얘도 실패햇을 때 처리
-
getPlayer
- 얘가 위에
chat.repository
완전 똑같은거 있는 그거 - 근데 이제보니 이 코드가 존재하는데 왜 위에 getRoomPlayers에서 얘 재활용안하고 그냥 썼지?
- 얘가 위에
-
updatePlayer
- 플레이어 상태 등 업데이트할 때 쓰는거
-
removePlayerFromRoom
- 게임방에서 플레이어가 나갔을 때 처리
- 얘도 실패했을 경우 처리
-
-
game.service.ts
-
createRoom
- 방 생성
-
joinRoom
- 방 참가
- 게임 진행 중일 땐 참가 불가
- 나중에 관전 기능 같은 거 넣어도 재밌겟당
- 처음 들어온 사람이면 이 사람이 자동으로 host
- host로 업데이트 해주기
- 호스트 + 다른 사람들 모두 방에 입장한 사람들이니까 addPlayerToRoom 도 해주고
- 이거 근데 updatedPlayers를 reverse해서 쓸 거면 lpush말고 rpush하면 reverse 안해도 되지 않나?
- 찾아보니까 lpush, rpush 둘 다 리스트의 시작/끝 부분에 새로운 요소를 추가하는 작업이니까 시간 복잡도는 O(1)
- 근데 reverse하는 과정의 시간 복잡도는 O(n)이니까 lpush하고 reverse하는게 성능면에서 불리
- 이런 걸 CS적(시간 복잡도 최적화)으로 개선했다!! 하면서 내세워도 되지 않을까 하는 생각 ㅎㅎ
-
generateNickname
- 뭐 그냥 닉네임 만드는거
- 얘도 클로바 시켜서 형용사/부사랑 명사 랜덤으로 뽑게 하는건 안되나??
-
reconnect
- 연결 끊겼을 때 재연결하는 건가봥
-
leaveRoom
- 방 퇴장할 때
- 호스트면 다른 사람한테 호스트 넘기고 퇴장
- 여기 코드도 왜 length - 1인 사람한테 호스트 넘기나 했는데 lpush 해서 두번째로 들어온 사람이 맨 뒤에 있어서 그런거였ㄷㅏ… 그냥 rpush해서 관리하면 되지 않을까?
-
intializeGame
- 이름을 intializeGame이라고 해서 게임 시작 전 준비 이런거라 생각했는데 아니었네용.. 그냥 게임 룸을 초기화하는건가봄 초기 상태로. 게임이 완전히 끝났을 때 되돌리는? 그런 거인듯
-
updateSettings
- 게임 세팅 업데이트
- 호스트 아니면 못하게 하고
-
updatePlayer
- 얘도 위에랑 동일!
- 점수 같은거 업데이트할 때 사용하나봄
-
startGame
- 룸 세팅 기반해서 게임 시작
-
fetchWords
- 10번 시도해서 클로바로부터 단어 받아오기
- 10번했는데도 개수 안맞거나 그러면 default 단어 라운드 수만큼 잘라서 가져오기
-
setupRound
- 라운드 + 1하면서 방 상태 drawig 으로 바꾸고 진행하고.. 만약에 최종라운드까지 끝났으면 게임 종료되게
- 라운드 새로 시작되면 그림꾼, 방해꾼 이런 역할들 새로 뽑고..
-
distributeRoles
- 플레이어들 섞어서
detremineRole
함수로 역할 분배
- 플레이어들 섞어서
-
detremineRole
- 섞인거 기반으로 그림꾼, 방해꾼, 맞추는 사람 결정할 수 있게 하는 코드
-
categorizePlayerRoles
- 역할 나뉘었으면 그 역할 분류하기(방 설정 업데이트 할 때 사용)
-
handleDrawingTimeout
- 그림 그리는 시간 끝나면 키워드 맞추는 시간으로 넘겨줘야 하니까
-
checkAnswer
- 그림꾼, 방해꾼 제외 키워드 맞추기
- 정답 아닌거 들어오면 바로바로 오답이라고 리턴 받을 수 있게 early return
- 정답 맞춘 사람/그림꾼은 각각 결과에 따른 점수 얻을 수 있게
-
calculateScores
- 그림꾼, 일반 플레이어들 점수 계산하는 로직
-
handleGuessingTimeout
- 추측 시간 끝나면 방해꾼이 승리한거니까 방해꾼 점수 계산 로직
- 여기까지 보니까 sort하는 로직 자체는 겹치니까 하나로 합쳐도 되지 않을까 하는 생각…
-
game.service.spec.ts
- 얘도 toBeDefined()
-
-
redis.service.ts
-
hset
hget
hgetall
- Redis hash 자료구조
-
hset
으로 hash 자료구조 생성-
field
로는status
와hostId
를 갖고 있음 - 게임 방 상태에 따라
status
변경 - 게임 방 생성 및 호스트가 게임방을 나갔을 경우에만
hostId
업데이트
-
-
hget
으로field
에 해당하는value
값을 꺼내옴- 이제보니 여기서 any가 잔뜩 쓰였네….
-
hgetall
으로 hash 자료구조의 key가 가지고 있는 모든 field-value 값을 배열 형태로 반환
-
del
- key에 해당하는 값 삭제
-
lpush
lrange
lrangeAll
lrem
- Redis List 자료구조
-
lpush
-
key
에 해당하는 List 처음에value
삽입 -
rpush
라는 친구도 있는데 얘는 List 마지막에 삽입
-
-
lrange
-
key
에 해당하는 List의 start ~ end 범위까지의 데이트 반환 - 일반적으로 생각하는 거랑 똑같음! 왼쪽부터 할 거면 0, 1, 2, … 이 순서
- 오른쪽부터는 음수인 -1, -2, … 이 순서!
-
-
lrangeAll
- 얘는 전체 조회를 위해 따로 만든 함수로 보임 good
-
lrem
- 삭제만을 위해 사용하면 return 값 없어도 될듯
- return 한다면 삭제된 항목의 개수를 반환해줌
-
count
양수-
key
에 해당하는 List에서 입력받은value
를 리스트의 왼쪽부터 삭제
-
-
count
음수-
key
에 해당하는 List에서 입력받은value
를 리스트의 오른쪽부터 삭제
-
-
count
0-
key
에 해당하는 List에서 입력받은value
전부 삭제
-
- 삭제만을 위해 사용하면 return 값 없어도 될듯
-
exists
- 존재 여부를 0 또는 1(number 타입)로 반환
- 여기를
boolean
반환으로 바꾸어서 위에existsRoom
이나existsPlayer
부분 코드를 줄이는 것은 어떨지 - 불필요한
await
가 사용된 것은 아닐까?
-
multi
- redis 트랜잭션을 시작하는 커맨드
-
multi
커맨드로 트랜잭션을 시작하면 Redis는 이후에 입력되는 커맨드를 바로 실행하지 않고 Queue에 쌓음
-