3333import io .f1 .backend .domain .quiz .entity .Quiz ;
3434
3535import lombok .RequiredArgsConstructor ;
36+ import lombok .extern .slf4j .Slf4j ;
3637
38+ import org .hibernate .boot .model .naming .IllegalIdentifierException ;
3739import org .springframework .context .ApplicationEventPublisher ;
3840import org .springframework .stereotype .Service ;
3941
4042import java .util .List ;
4143import java .util .Map ;
4244import java .util .Optional ;
45+ import java .util .concurrent .ConcurrentHashMap ;
4346import java .util .concurrent .atomic .AtomicLong ;
4447
48+ @ Slf4j
4549@ Service
4650@ RequiredArgsConstructor
4751public class RoomService {
@@ -50,6 +54,8 @@ public class RoomService {
5054 private final RoomRepository roomRepository ;
5155 private final AtomicLong roomIdGenerator = new AtomicLong (0 );
5256 private final ApplicationEventPublisher eventPublisher ;
57+ private final Map <Long , Object > roomLocks = new ConcurrentHashMap <>();
58+ private static final String PENDING_SESSION_ID = "PENDING_SESSION_ID" ;
5359
5460 public RoomCreateResponse saveRoom (RoomCreateRequest request ) {
5561
@@ -66,48 +72,62 @@ public RoomCreateResponse saveRoom(RoomCreateRequest request) {
6672
6773 Room room = new Room (newId , roomSetting , gameSetting , host );
6874
75+ room .getUserIdSessionMap ().put (host .id , PENDING_SESSION_ID );
76+
6977 roomRepository .saveRoom (room );
7078
7179 eventPublisher .publishEvent (new RoomCreatedEvent (room , quiz ));
7280
7381 return new RoomCreateResponse (newId );
7482 }
7583
76- public void validateRoom (RoomValidationRequest request ) {
84+ public void enterRoom (RoomValidationRequest request ) {
7785
78- Room room =
79- roomRepository
80- .findRoom (request .roomId ())
81- .orElseThrow (() -> new IllegalArgumentException ("404 존재하지 않는 방입니다.-1" ));
86+ Long roomId = request .roomId ();
8287
83- if (room .getState ().equals (RoomState .PLAYING )) {
84- throw new IllegalArgumentException ("403 게임이 진행중입니다." );
85- }
88+ Object lock = roomLocks .computeIfAbsent (roomId , k -> new Object ());
8689
87- int maxUserCnt = room .getRoomSetting ().maxUserCount ();
88- int currentCnt = room .getPlayerSessionMap ().size ();
89- if (maxUserCnt == currentCnt ) {
90- throw new IllegalArgumentException ("403 정원이 모두 찼습니다." );
91- }
90+ synchronized (lock ) {
91+ Room room = findRoom (request .roomId ());
92+
93+ if (room .getState ().equals (RoomState .PLAYING )) {
94+ throw new IllegalArgumentException ("403 게임이 진행중입니다." );
95+ }
96+
97+ int maxUserCnt = room .getRoomSetting ().maxUserCount ();
98+ int currentCnt = room .getUserIdSessionMap ().size ();
99+ if (maxUserCnt == currentCnt ) {
100+ throw new IllegalArgumentException ("403 정원이 모두 찼습니다." );
101+ }
92102
93- if (room .getRoomSetting ().locked ()
94- && !room .getRoomSetting ().password ().equals (request .password ())) {
95- throw new IllegalArgumentException ("401 비밀번호가 일치하지 않습니다." );
103+ if (room .getRoomSetting ().locked ()
104+ && !room .getRoomSetting ().password ().equals (request .password ())) {
105+ throw new IllegalArgumentException ("401 비밀번호가 일치하지 않습니다." );
106+ }
107+
108+ room .getUserIdSessionMap ().put (getCurrentUserId (), PENDING_SESSION_ID );
96109 }
97110 }
98111
99- public RoomInitialData enterRoom (Long roomId , String sessionId ) {
112+ public RoomInitialData initializeRoomSocket (Long roomId , String sessionId ) {
100113
101- Room room =
102- roomRepository
103- .findRoom (roomId )
104- .orElseThrow (() -> new IllegalArgumentException ("404 존재하지 않는 방입니다." ));
114+ Room room = findRoom (roomId );
105115
106116 Player player = createPlayer ();
107117
108118 Map <String , Player > playerSessionMap = room .getPlayerSessionMap ();
119+ Map <Long , String > userIdSessionMap = room .getUserIdSessionMap ();
120+
121+ if (room .isHost (player .getId ())) {
122+ player .toggleReady ();
123+ }
109124
110125 playerSessionMap .put (sessionId , player );
126+ String existingSession = userIdSessionMap .get (player .getId ());
127+ /* 정상 흐름 or 재연결 */
128+ if (existingSession .equals (PENDING_SESSION_ID ) || !existingSession .equals (sessionId )) {
129+ userIdSessionMap .put (player .getId (), sessionId );
130+ }
111131
112132 RoomSettingResponse roomSettingResponse = toRoomSettingResponse (room );
113133
@@ -130,42 +150,36 @@ public RoomInitialData enterRoom(Long roomId, String sessionId) {
130150 }
131151
132152 public RoomExitData exitRoom (Long roomId , String sessionId ) {
133- Room room =
134- roomRepository
135- .findRoom (roomId )
136- .orElseThrow (() -> new IllegalArgumentException ("404 존재하지 않는 방입니다." ));
137153
138- Map < String , Player > playerSessionMap = room . getPlayerSessionMap ( );
154+ Object lock = roomLocks . computeIfAbsent ( roomId , k -> new Object () );
139155
140- String destination = getDestination (roomId );
156+ synchronized (lock ) {
157+ Room room = findRoom (roomId );
141158
142- if (playerSessionMap .size () == 1 && playerSessionMap .get (sessionId ) != null ) {
143- roomRepository .removeRoom (roomId );
144- return RoomExitData .builder ().destination (destination ).removedRoom (true ).build ();
145- }
159+ String destination = getDestination (roomId );
146160
147- Player removedPlayer = playerSessionMap .remove (sessionId );
148- if (removedPlayer == null ) {
149- throw new IllegalArgumentException ("퇴장 처리 불가 - 404 해당 세션 플레이어는 존재하지않습니다." );
150- }
161+ Player removePlayer = getRemovePlayer (room , sessionId );
151162
152- if (room .getHost ().getId ().equals (removedPlayer .getId ())) {
153- Optional <String > nextHostSessionId = playerSessionMap .keySet ().stream ().findFirst ();
154- Player nextHost =
155- playerSessionMap .get (
156- nextHostSessionId .orElseThrow (
157- () ->
158- new IllegalArgumentException (
159- "방장 교체 불가 - 404 해당 세션 플레이어는 존재하지않습니다." )));
160- room .updateHost (nextHost );
161- }
163+ /* 방 삭제 */
164+ if (isLastPlayer (room , sessionId )) {
165+ return removeRoom (room , destination );
166+ }
162167
163- SystemNoticeResponse systemNoticeResponse =
164- ofPlayerEvent (removedPlayer , RoomEventType .EXIT );
168+ /* 방장 변경 */
169+ if (room .isHost (removePlayer .getId ())) {
170+ changeHost (room , sessionId );
171+ }
165172
166- PlayerListResponse playerListResponse = toPlayerListResponse (room );
173+ /* 플레이어 삭제 */
174+ removePlayer (room , sessionId , removePlayer );
167175
168- return new RoomExitData (destination , playerListResponse , systemNoticeResponse , false );
176+ SystemNoticeResponse systemNoticeResponse =
177+ ofPlayerEvent (removePlayer , RoomEventType .EXIT );
178+
179+ PlayerListResponse playerListResponse = toPlayerListResponse (room );
180+
181+ return new RoomExitData (destination , playerListResponse , systemNoticeResponse , false );
182+ }
169183 }
170184
171185 public RoomListResponse getAllRooms () {
@@ -183,11 +197,63 @@ public RoomListResponse getAllRooms() {
183197 return new RoomListResponse (roomResponses );
184198 }
185199
200+ private Player getRemovePlayer (Room room , String sessionId ) {
201+ Player removePlayer = room .getPlayerSessionMap ().get (sessionId );
202+ if (removePlayer == null ) {
203+ room .removeUserId (getCurrentUserId ());
204+ throw new IllegalIdentifierException ("404 세션 없음 비정상적인 퇴장 요청" );
205+ }
206+ return removePlayer ;
207+ }
208+
186209 private static String getDestination (Long roomId ) {
187210 return "/sub/room/" + roomId ;
188211 }
189212
190- private static Player createPlayer () {
213+ private Player createPlayer () {
191214 return new Player (getCurrentUserId (), getCurrentUserNickname ());
192215 }
216+
217+ private Room findRoom (Long roomId ) {
218+ return roomRepository
219+ .findRoom (roomId )
220+ .orElseThrow (() -> new IllegalArgumentException ("404 존재하지 않는 방입니다." ));
221+ }
222+
223+ private boolean isLastPlayer (Room room , String sessionId ) {
224+ Map <String , Player > playerSessionMap = room .getPlayerSessionMap ();
225+ return playerSessionMap .size () == 1 && playerSessionMap .containsKey (sessionId );
226+ }
227+
228+ private RoomExitData removeRoom (Room room , String destination ) {
229+ Long roomId = room .getId ();
230+ roomRepository .removeRoom (roomId );
231+ roomLocks .remove (roomId );
232+ log .info ("{}번 방 삭제" , roomId );
233+ return RoomExitData .builder ().destination (destination ).removedRoom (true ).build ();
234+ }
235+
236+ private void changeHost (Room room , String hostSessionId ) {
237+ Map <String , Player > playerSessionMap = room .getPlayerSessionMap ();
238+
239+ Optional <String > nextHostSessionId =
240+ playerSessionMap .keySet ().stream ()
241+ .filter (key -> !key .equals (hostSessionId ))
242+ .findFirst ();
243+
244+ Player nextHost =
245+ playerSessionMap .get (
246+ nextHostSessionId .orElseThrow (
247+ () ->
248+ new IllegalArgumentException (
249+ "방장 교체 불가 - 404 해당 세션 플레이어는 존재하지않습니다." )));
250+
251+ room .updateHost (nextHost );
252+ log .info ("user_id:{} 방장 변경 완료 " , nextHost .getId ());
253+ }
254+
255+ private void removePlayer (Room room , String sessionId , Player removePlayer ) {
256+ room .removeUserId (removePlayer .getId ());
257+ room .removeSessionId (sessionId );
258+ }
193259}
0 commit comments