Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import io.f1.backend.domain.game.model.Room;
import io.f1.backend.domain.game.websocket.MessageSender;
import io.f1.backend.domain.question.entity.Question;
import io.f1.backend.domain.user.dto.UserPrincipal;

import lombok.RequiredArgsConstructor;

Expand All @@ -24,7 +25,7 @@ public class ChatService {
private final ApplicationEventPublisher eventPublisher;

// todo 동시성적용
public void chat(Long roomId, String sessionId, ChatMessage chatMessage) {
public void chat(Long roomId, UserPrincipal userPrincipal, ChatMessage chatMessage) {

Room room = roomService.findRoom(roomId);

Expand All @@ -42,7 +43,8 @@ public void chat(Long roomId, String sessionId, ChatMessage chatMessage) {

if (answer.equals(chatMessage.message())) {
eventPublisher.publishEvent(
new GameCorrectAnswerEvent(room, sessionId, chatMessage, answer));
new GameCorrectAnswerEvent(
room, userPrincipal.getUserId(), chatMessage, answer));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,13 @@ public void onCorrectAnswer(GameCorrectAnswerEvent event) {

Room room = event.room();
log.debug(room.getId() + "번 방 채팅으로 정답! 현재 라운드 : " + room.getCurrentRound());
String sessionId = event.sessionId();
Long userId = event.userId();
ChatMessage chatMessage = event.chatMessage();
String answer = event.answer();

String destination = getDestination(room.getId());

room.increasePlayerCorrectCount(sessionId);
room.increasePlayerCorrectCount(userId);

messageSender.sendBroadcast(
destination,
Expand Down Expand Up @@ -168,13 +168,13 @@ public void gameEnd(Room room) {
Long roomId = room.getId();
String destination = getDestination(roomId);

Map<String, Player> playerSessionMap = room.getPlayerSessionMap();
Map<Long, Player> playerMap = room.getPlayerMap();

// TODO : 랭킹 정보 업데이트
messageSender.sendBroadcast(
destination,
MessageType.GAME_RESULT,
toGameResultListResponse(playerSessionMap, room.getGameSetting().getRound()));
toGameResultListResponse(playerMap, room.getGameSetting().getRound()));

room.initializeRound();
room.initializePlayers();
Expand All @@ -201,11 +201,11 @@ public void gameEnd(Room room) {
}

@DistributedLock(prefix = "room", key = "#roomId")
public void handlePlayerReady(Long roomId, String sessionId) {
public void handlePlayerReady(Long roomId, UserPrincipal userPrincipal) {

Room room = findRoom(roomId);

Player player = room.getPlayerBySessionId(sessionId);
Player player = room.getPlayerByUserId(userPrincipal.getUserId());

toggleReadyIfPossible(room, player);

Expand Down
166 changes: 109 additions & 57 deletions backend/src/main/java/io/f1/backend/domain/game/app/RoomService.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import static io.f1.backend.domain.quiz.mapper.QuizMapper.toGameStartResponse;
import static io.f1.backend.global.util.SecurityUtils.getCurrentUserId;
import static io.f1.backend.global.util.SecurityUtils.getCurrentUserNickname;
import static io.f1.backend.global.util.SecurityUtils.getCurrentUserPrincipal;

import io.f1.backend.domain.game.dto.MessageType;
import io.f1.backend.domain.game.dto.RoomEventType;
Expand All @@ -34,13 +35,16 @@
import io.f1.backend.domain.game.model.RoomSetting;
import io.f1.backend.domain.game.model.RoomState;
import io.f1.backend.domain.game.store.RoomRepository;
import io.f1.backend.domain.game.store.UserRoomRepository;
import io.f1.backend.domain.game.websocket.DisconnectTaskManager;
import io.f1.backend.domain.game.websocket.MessageSender;
import io.f1.backend.domain.quiz.app.QuizService;
import io.f1.backend.domain.quiz.dto.QuizMinData;
import io.f1.backend.domain.quiz.entity.Quiz;
import io.f1.backend.domain.user.dto.UserPrincipal;
import io.f1.backend.global.exception.CustomException;
import io.f1.backend.global.exception.errorcode.RoomErrorCode;
import io.f1.backend.global.exception.errorcode.UserErrorCode;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -61,9 +65,11 @@ public class RoomService {

private final QuizService quizService;
private final RoomRepository roomRepository;
private final UserRoomRepository userRoomRepository;
private final AtomicLong roomIdGenerator = new AtomicLong(0);
private final ApplicationEventPublisher eventPublisher;
private final Map<Long, Object> roomLocks = new ConcurrentHashMap<>();
private final DisconnectTaskManager disconnectTasks;

private final MessageSender messageSender;

Expand All @@ -85,10 +91,13 @@ public RoomCreateResponse saveRoom(RoomCreateRequest request) {

Room room = new Room(newId, roomSetting, gameSetting, host);

room.addValidatedUserId(getCurrentUserId());
room.addPlayer(host);

roomRepository.saveRoom(room);

/* 다른 방 접속 시 기존 방은 exit 처리 - 탭 동시 로그인 시 (disconnected 리스너 작동x) */
exitIfInAnotherRoom(room, host.getId());

eventPublisher.publishEvent(new RoomCreatedEvent(room, quiz));

return new RoomCreateResponse(newId);
Expand All @@ -103,6 +112,16 @@ public void enterRoom(RoomValidationRequest request) {
synchronized (lock) {
Room room = findRoom(request.roomId());

Long userId = getCurrentUserId();

/* 다른 방 접속 시 기존 방은 exit 처리 - 탭 동시 로그인 시 (disconnected 리스너 작동x) */
exitIfInAnotherRoom(room, userId);

/* reconnect */
if (room.hasPlayer(userId)) {
return;
}

if (room.getState().equals(RoomState.PLAYING)) {
Copy link
Collaborator

@jiwon1217 jiwon1217 Jul 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

room.isPlaying() 메서드로 대체할 수 있을 것 같습니다 !

throw new CustomException(RoomErrorCode.ROOM_GAME_IN_PROGRESS);
}
Expand All @@ -118,17 +137,42 @@ public void enterRoom(RoomValidationRequest request) {
throw new CustomException(RoomErrorCode.WRONG_PASSWORD);
}

room.addValidatedUserId(getCurrentUserId());
room.addPlayer(createPlayer());
}
}

private void exitIfInAnotherRoom(Room room, Long userId) {

Long joinedRoomId = userRoomRepository.getRoomId(userId);

if (joinedRoomId != null && !room.getId().equals(joinedRoomId)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[L4-변경제안]
Objects.equals()사용하면 의도가 명확해보여서 좋을 것 같습니다!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

room 내부의 메서드로 분리하면 좋을 것 같습니다 !


if (room.isPlaying()) {
changeConnectedStatus(userId, ConnectionState.DISCONNECTED);
} else {
exitRoom(joinedRoomId, getCurrentUserPrincipal());
}
}
}

public void initializeRoomSocket(Long roomId, String sessionId, UserPrincipal principal) {
public void initializeRoomSocket(Long roomId, UserPrincipal principal) {

Room room = findRoom(roomId);
Long userId = principal.getUserId();

Player player = createPlayer(principal);
if (!room.hasPlayer(userId)) {
throw new CustomException(RoomErrorCode.ROOM_ENTER_REQUIRED);
}

/* 재연결 */
if (room.getPlayerState(userId).equals(ConnectionState.DISCONNECTED)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

room의 메서드로 분리하는게 좋을 것 같습니다 !

changeConnectedStatus(userId, ConnectionState.CONNECTED);
cancelTask(userId);
reconnectSendResponse(roomId, principal);
return;
}

room.addPlayer(sessionId, player);
Player player = createPlayer(principal);

RoomSettingResponse roomSettingResponse = toRoomSettingResponse(room);

Expand All @@ -145,6 +189,8 @@ public void initializeRoomSocket(Long roomId, String sessionId, UserPrincipal pr

String destination = getDestination(roomId);

userRoomRepository.addUser(player, room);

messageSender.sendPersonal(
getUserDestination(), MessageType.GAME_SETTING, gameSettingResponse, principal);

Expand All @@ -153,25 +199,29 @@ public void initializeRoomSocket(Long roomId, String sessionId, UserPrincipal pr
messageSender.sendBroadcast(destination, MessageType.SYSTEM_NOTICE, systemNoticeResponse);
}

public void exitRoom(Long roomId, String sessionId, UserPrincipal principal) {
public void exitRoom(Long roomId, UserPrincipal principal) {

Object lock = roomLocks.computeIfAbsent(roomId, k -> new Object());

synchronized (lock) {
Room room = findRoom(roomId);

Player removePlayer = getRemovePlayer(room, sessionId, principal);
if (!room.hasPlayer(principal.getUserId())) {
throw new CustomException(UserErrorCode.USER_NOT_FOUND);
}

Player removePlayer = createPlayer(principal);

String destination = getDestination(roomId);

cleanRoom(room, removePlayer);

messageSender.sendPersonal(
getUserDestination(),
MessageType.EXIT_SUCCESS,
new ExitSuccessResponse(true),
principal);

cleanRoom(room, sessionId, removePlayer);

SystemNoticeResponse systemNoticeResponse =
ofPlayerEvent(removePlayer.nickname, RoomEventType.EXIT);

Expand All @@ -198,10 +248,8 @@ public RoomListResponse getAllRooms() {
return new RoomListResponse(roomResponses);
}

public void reconnectSession(
Long roomId, String oldSessionId, String newSessionId, UserPrincipal principal) {
public void reconnectSendResponse(Long roomId, UserPrincipal principal) {
Room room = findRoom(roomId);
room.reconnectSession(oldSessionId, newSessionId);

String destination = getDestination(roomId);
String userDestination = getUserDestination();
Expand Down Expand Up @@ -249,30 +297,26 @@ public void reconnectSession(
}
}

public void changeConnectedStatus(Long roomId, String sessionId, ConnectionState newState) {
public Long changeConnectedStatus(Long userId, ConnectionState newState) {
Long roomId = userRoomRepository.getRoomId(userId);
Room room = findRoom(roomId);
room.updatePlayerConnectionState(sessionId, newState);
}

public boolean isExit(String sessionId, Long roomId) {
Room room = findRoom(roomId);
return room.isExit(sessionId);
room.updatePlayerConnectionState(userId, newState);

return roomId;
}

public void exitIfNotPlaying(Long roomId, String sessionId, UserPrincipal principal) {
Room room = findRoom(roomId);
if (!room.isPlaying()) {
exitRoom(roomId, sessionId, principal);
}
public void cancelTask(Long userId) {
disconnectTasks.cancelDisconnectTask(userId);
}

private Player getRemovePlayer(Room room, String sessionId, UserPrincipal principal) {
Player removePlayer = room.getPlayerSessionMap().get(sessionId);
if (removePlayer == null) {
room.removeValidatedUserId(principal.getUserId());
throw new CustomException(RoomErrorCode.SOCKET_SESSION_NOT_FOUND);
public void exitIfNotPlaying(Long roomId, UserPrincipal principal) {
Room room = findRoom(roomId);
if (room.isPlaying()) {
removeUserRepository(principal.getUserId(), roomId);
} else {
exitRoom(roomId, principal);
}
return removePlayer;
}

private Player createPlayer(UserPrincipal principal) {
Expand All @@ -296,43 +340,33 @@ private void removeRoom(Room room) {
log.info("{}번 방 삭제", roomId);
}

private void changeHost(Room room, String hostSessionId) {
Map<String, Player> playerSessionMap = room.getPlayerSessionMap();
private void changeHost(Room room, Player host) {
Map<Long, Player> playerMap = room.getPlayerMap();

Optional<String> nextHostSessionId =
playerSessionMap.entrySet().stream()
.filter(entry -> !entry.getKey().equals(hostSessionId))
Optional<Player> nextHost =
playerMap.entrySet().stream()
.filter(entry -> !entry.getKey().equals(host.getId()))
.filter(entry -> entry.getValue().getState() == ConnectionState.CONNECTED)
.map(Map.Entry::getKey)
.map(Map.Entry::getValue)
.findFirst();

Player nextHost =
playerSessionMap.get(
nextHostSessionId.orElseThrow(
() -> new CustomException(RoomErrorCode.SOCKET_SESSION_NOT_FOUND)));

room.updateHost(nextHost);
log.info("user_id:{} 방장 변경 완료 ", nextHost.getId());
}

private void removePlayer(Room room, String sessionId, Player removePlayer) {
room.removeSessionId(sessionId);
room.removeValidatedUserId(removePlayer.getId());
room.updateHost(
nextHost.orElseThrow(() -> new CustomException(RoomErrorCode.PLAYER_NOT_FOUND)));
}

private String getUserDestination() {
return "/queue";
}

public void exitRoomForDisconnectedPlayer(Long roomId, Player player, String sessionId) {
public void exitRoomForDisconnectedPlayer(Long roomId, Player player) {

Object lock = roomLocks.computeIfAbsent(roomId, k -> new Object());

synchronized (lock) {
// 연결 끊긴 플레이어 exit 로직 타게 해주기
Room room = findRoom(roomId);

cleanRoom(room, sessionId, player);
cleanRoom(room, player);

String destination = getDestination(roomId);

Expand All @@ -346,28 +380,46 @@ public void exitRoomForDisconnectedPlayer(Long roomId, Player player, String ses
}
}

private void cleanRoom(Room room, String sessionId, Player player) {
private void cleanRoom(Room room, Player player) {

Long roomId = room.getId();
Long userId = player.getId();

/* user-room mapping 정보 삭제 */
removeUserRepository(userId, roomId);

/* 방 삭제 */
if (room.isLastPlayer(sessionId)) {
if (room.isLastPlayer(player)) {
removeRoom(room);
Long roomId = room.getId();
eventPublisher.publishEvent(new RoomDeletedEvent(roomId));
return;
}

/* 방장 변경 */
if (room.isHost(player.getId())) {
changeHost(room, sessionId);
if (room.isHost(userId)) {
changeHost(room, player);
}

/* 플레이어 삭제 */
removePlayer(room, sessionId, player);
room.removePlayer(player);
}

public void handleDisconnectedPlayers(Room room, List<Player> disconnectedPlayers) {
for (Player player : disconnectedPlayers) {
String sessionId = room.getSessionIdByUserId(player.getId());
exitRoomForDisconnectedPlayer(room.getId(), player, sessionId);
exitRoomForDisconnectedPlayer(room.getId(), player);
}
}

public ConnectionState getPlayerState(Long userId, Long roomId) {
Room room = findRoom(roomId);
return room.getPlayerState(userId);
}

public void removeUserRepository(Long userId, Long roomId) {
userRoomRepository.removeUser(userId, roomId);
}

public boolean isUserInAnyRoom(Long userId) {
return userRoomRepository.isUserInAnyRoom(userId);
}
}
Loading