-
Notifications
You must be signed in to change notification settings - Fork 3
[feat] 재연결 로직 구현 #102
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[feat] 재연결 로직 구현 #102
Changes from 18 commits
d32fa2c
4a87b5b
2ebc45b
22e2dce
211a772
fb25877
7be49b7
51efbed
5ba8a13
07da462
6ce1897
527a6c5
bde0694
bdc1700
4abe282
849ef71
bc3fd2b
435d8f0
05b3c60
781c283
fd9baf5
fd9aff1
7bd4453
e3dcb00
15bfbab
700c734
4af5a2c
147ab05
31d1f76
1b54ded
357443d
6061c87
cb1f75d
3914981
e8616f4
f1dd3a2
ba3e466
a568032
e25f2f2
cce37c4
3aef18f
6a5ad37
c506712
5a1b865
6424848
0e644b7
b408205
cbb9a72
fc37e91
f9606ba
83544c5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,4 +5,5 @@ public enum RoomEventType { | |
| EXIT, | ||
| START, | ||
| END, | ||
| RECONNECT, | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -92,6 +92,8 @@ public static SystemNoticeResponse ofPlayerEvent(String nickname, RoomEventType | |
| message = " 님이 입장하셨습니다"; | ||
| } else if (roomEventType == RoomEventType.EXIT) { | ||
| message = " 님이 퇴장하셨습니다"; | ||
| } else if (roomEventType == RoomEventType.RECONNECT) { | ||
| message = " 님이 재연결 되었습니다."; | ||
|
||
| } | ||
| return new SystemNoticeResponse(nickname + message, Instant.now()); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,18 +1,24 @@ | ||
| package io.f1.backend.domain.game.model; | ||
|
|
||
| import io.f1.backend.domain.question.entity.Question; | ||
| import io.f1.backend.global.exception.CustomException; | ||
| import io.f1.backend.global.exception.errorcode.RoomErrorCode; | ||
|
|
||
| import lombok.Getter; | ||
|
|
||
| import java.time.LocalDateTime; | ||
| import java.util.ArrayList; | ||
| import java.util.HashSet; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Set; | ||
| import java.util.concurrent.ConcurrentHashMap; | ||
|
|
||
| @Getter | ||
| public class Room { | ||
|
|
||
| private static final String PENDING_SESSION_ID = "PENDING_SESSION_ID"; | ||
|
|
||
| private final Long id; | ||
|
|
||
| private final RoomSetting roomSetting; | ||
|
|
@@ -27,7 +33,7 @@ public class Room { | |
|
|
||
| private Map<String, Player> playerSessionMap = new ConcurrentHashMap<>(); | ||
|
|
||
| private Map<Long, String> userIdSessionMap = new ConcurrentHashMap<>(); | ||
| private final Set<Long> validatedUserIds = new HashSet<>(); | ||
|
|
||
| private final LocalDateTime createdAt = LocalDateTime.now(); | ||
|
|
||
|
|
@@ -40,6 +46,26 @@ public Room(Long id, RoomSetting roomSetting, GameSetting gameSetting, Player ho | |
| this.host = host; | ||
| } | ||
|
|
||
| public boolean addValidatedUserIds(Long userId) { | ||
|
||
| return validatedUserIds.add(userId); | ||
| } | ||
|
|
||
| public int getCurrentUserCnt() { | ||
| return validatedUserIds.size(); | ||
| } | ||
|
|
||
| public void addPlayer(Long userId, String sessionId, Player player) { | ||
|
|
||
| if (!validatedUserIds.contains(userId)) { | ||
| throw new CustomException(RoomErrorCode.ROOM_ENTER_REQUIRED); | ||
| } | ||
|
|
||
| if (isHost(player.getId())) { | ||
| player.toggleReady(); | ||
| } | ||
| playerSessionMap.put(sessionId, player); | ||
| } | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 해당 로직을 room 내부로 옮기니까 서비스 코드가 간결해지고, 가독성이 좋아진 것 같습니다 ! 👍 |
||
|
|
||
| public boolean isHost(Long id) { | ||
| return this.host.getId().equals(id); | ||
| } | ||
|
|
@@ -56,14 +82,14 @@ public void updateRoomState(RoomState newState) { | |
| this.state = newState; | ||
| } | ||
|
|
||
| public void removeUserId(Long id) { | ||
| this.userIdSessionMap.remove(id); | ||
| } | ||
|
|
||
| public void removeSessionId(String sessionId) { | ||
| this.playerSessionMap.remove(sessionId); | ||
| } | ||
|
|
||
| public void removeUserId(Long userId) { | ||
| validatedUserIds.remove(userId); | ||
| } | ||
|
|
||
| public void increasePlayerCorrectCount(String sessionId) { | ||
| this.playerSessionMap.get(sessionId).increaseCorrectCount(); | ||
| } | ||
|
|
@@ -79,4 +105,22 @@ public Boolean isPlaying() { | |
| public void increaseCorrectCount() { | ||
| currentRound++; | ||
| } | ||
|
|
||
| public void reconnectSession(String oldSessionId, String newSessionId) { | ||
| Player player = playerSessionMap.get(oldSessionId); | ||
| removeSessionId(oldSessionId); | ||
| playerSessionMap.put(newSessionId, player); | ||
| } | ||
|
|
||
| public void updatePlayerConnectionState(String sessionId, ConnectionState newState) { | ||
| playerSessionMap.get(sessionId).updateState(newState); | ||
| } | ||
|
|
||
| public boolean isReconnectTarget(String sessionId) { | ||
|
||
| return playerSessionMap.get(sessionId) != null; | ||
| } | ||
|
|
||
| public boolean isExit(String sessionId) { | ||
| return playerSessionMap.get(sessionId) == null; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| package io.f1.backend.domain.game.websocket.Service; | ||
|
||
|
|
||
| import io.f1.backend.domain.game.app.RoomService; | ||
| import io.f1.backend.domain.game.model.ConnectionState; | ||
| import io.f1.backend.domain.user.dto.UserPrincipal; | ||
|
|
||
| import lombok.RequiredArgsConstructor; | ||
|
|
||
| import org.springframework.stereotype.Service; | ||
|
|
||
| import java.util.Map; | ||
| import java.util.concurrent.ConcurrentHashMap; | ||
| import java.util.concurrent.Executors; | ||
| import java.util.concurrent.ScheduledExecutorService; | ||
| import java.util.concurrent.TimeUnit; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class SessionService { | ||
|
|
||
| private final RoomService roomService; | ||
| private final Map<String, Long> sessionIdUser = new ConcurrentHashMap<>(); | ||
| private final Map<String, Long> sessionIdRoom = new ConcurrentHashMap<>(); | ||
| private final Map<Long, String> userIdSession = new ConcurrentHashMap<>(); | ||
| private final Map<Long, String> userIdLatestSession = new ConcurrentHashMap<>(); | ||
| private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); | ||
|
|
||
| public void addSession(String sessionId, Long userId) { | ||
| sessionIdUser.put(sessionId, userId); | ||
| userIdSession.put(userId, sessionId); | ||
| } | ||
|
|
||
| public void addRoomId(Long roomId, String sessionId) { | ||
| sessionIdRoom.put(sessionId, roomId); | ||
| } | ||
|
|
||
| public void handleUserReconnect(Long roomId, String newSessionId, Long userId) { | ||
|
|
||
| String oldSessionId = userIdLatestSession.get(userId); | ||
| /* room 재연결 대상인지 아닌지 판별 */ | ||
| if (roomService.isReconnectTarget(roomId, oldSessionId)) { | ||
| roomService.reconnectSession(roomId, oldSessionId, newSessionId); | ||
| } | ||
| } | ||
|
|
||
| public void handleUserDisconnect(String sessionId, UserPrincipal principal) { | ||
|
|
||
| Long roomId = sessionIdRoom.get(sessionId); | ||
|
|
||
| /* 정상 동작*/ | ||
| if (roomService.isExit(sessionId, roomId)) { | ||
| removeSession(sessionId, roomId); | ||
dlsrks1021 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return; | ||
| } | ||
|
|
||
| Long userId = principal.getUserId(); | ||
|
|
||
| roomService.changeConnectedStatus(roomId, sessionId, ConnectionState.DISCONNECTED); | ||
|
|
||
| // 5초 뒤 실행 | ||
| scheduler.schedule( | ||
| () -> { | ||
| if (userIdSession.get(userId).equals(sessionId)) { | ||
| roomService.exitIfNotPlaying(roomId, sessionId, principal); | ||
| } else { | ||
| roomService.notifyIfReconnected(roomId, principal); | ||
dlsrks1021 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| userIdLatestSession.remove(principal.getUserId()); | ||
| removeSession(sessionId, roomId); | ||
dlsrks1021 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| }, | ||
| 5, | ||
| TimeUnit.SECONDS); | ||
| } | ||
|
|
||
| public void removeSession(String sessionId, Long userId) { | ||
| sessionIdUser.remove(sessionId); | ||
| sessionIdRoom.remove(sessionId); | ||
| userIdSession.remove(userId); | ||
|
|
||
| userIdLatestSession.put(userId, sessionId); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
API 명세서에 /auth/me response를 수정했습니다. 해당 PR이 merge가 되면 재연결 로직이 구현됨에 따라 명세서가 수정되었다고 말씀드리면 될 것 같습니다 !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이부분은 재연결때문에 추가한건 아니였고, 추후에 1:1 응답에서 추가될 예정이라서 그부분 구현하고나서 제가 말씀드리겠습니다!
감사합니다.. 💪