11package com .dmu .debug_visual .config ;
22
3- import com .dmu .debug_visual .collab .domain .entity .Room ;
4- import com .dmu .debug_visual .collab .domain .repository .RoomRepository ;
5- import com .dmu .debug_visual .collab .rest .dto .ParticipantInfo ;
6- import com .dmu .debug_visual .collab .rest .dto .RoomStateUpdate ;
3+ import com .dmu .debug_visual .collab .domain .entity .CodeSession ;
4+ import com .dmu .debug_visual .collab .domain .repository .CodeSessionRepository ;
5+ import com .dmu .debug_visual .collab .service .RoomService ;
76import com .dmu .debug_visual .collab .service .WebSocketRoomService ;
87import com .dmu .debug_visual .security .CustomUserDetails ;
98import lombok .RequiredArgsConstructor ;
109import lombok .extern .slf4j .Slf4j ;
1110import org .springframework .context .event .EventListener ;
12- import org .springframework .messaging .simp .SimpMessageSendingOperations ;
1311import org .springframework .messaging .simp .stomp .StompHeaderAccessor ;
1412import org .springframework .security .core .Authentication ;
1513import org .springframework .stereotype .Component ;
1816import org .springframework .web .socket .messaging .SessionSubscribeEvent ;
1917
2018import java .security .Principal ;
21- import java .util .List ;
2219import java .util .Map ;
2320import java .util .Objects ;
24- import java .util .stream .Collectors ;
2521
2622@ Slf4j
2723@ Component
2824@ RequiredArgsConstructor
2925public class WebSocketEventListener {
3026
31- private final SimpMessageSendingOperations messagingTemplate ;
3227 private final WebSocketRoomService webSocketRoomService ;
33- private final RoomRepository roomRepository ;
28+ private final RoomService roomService ;
29+ private final CodeSessionRepository codeSessionRepository ;
3430
3531 @ EventListener
3632 @ Transactional
@@ -44,19 +40,25 @@ public void handleWebSocketSubscribeListener(SessionSubscribeEvent event) {
4440 try {
4541 String roomId = destination .split ("/" )[3 ];
4642
47- // --- 사용자 정보 가져오기 및 메모리에 사용자 추가 ---
4843 Authentication authentication = (Authentication ) userPrincipal ;
4944 CustomUserDetails userDetails = (CustomUserDetails ) authentication .getPrincipal ();
5045 String userId = userDetails .getUsername ();
46+
5147 webSocketRoomService .addParticipant (roomId , userId );
5248
53- // --- 퇴장 이벤트를 위해 세션에 정보 저장 ---
5449 Map <String , Object > sessionAttributes = Objects .requireNonNull (headerAccessor .getSessionAttributes ());
5550 sessionAttributes .put ("roomId" , roomId );
5651 sessionAttributes .put ("userId" , userId );
5752
58- // --- 방 전체에 최신 상태 브로드캐스팅 ---
59- broadcastRoomState (roomId );
53+ // 만약 구독 주소에 "/session/"이 포함되어 있다면, 세션 참여자로도 등록합니다.
54+ if (destination .contains ("/session/" )) {
55+ String sessionId = destination .split ("/" )[5 ];
56+ sessionAttributes .put ("sessionId" , sessionId ); // 퇴장 시 사용하기 위해 세션 ID 저장
57+ webSocketRoomService .addSessionParticipant (sessionId , userId );
58+ log .info ("User {} joined session {}" , userId , sessionId );
59+ }
60+
61+ roomService .broadcastRoomState (roomId );
6062
6163 } catch (Exception e ) {
6264 log .error ("Error handling subscribe event: " , e );
@@ -73,51 +75,32 @@ public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) {
7375 if (sessionAttributes != null ) {
7476 String roomId = (String ) sessionAttributes .get ("roomId" );
7577 String userId = (String ) sessionAttributes .get ("userId" );
78+ String sessionId = (String ) sessionAttributes .get ("sessionId" );
7679
7780 if (roomId != null && userId != null ) {
7881 log .info ("[퇴장] 사용자: {}, 방: {}" , userId , roomId );
7982
80- // 메모리에서 사용자 제거 (WebSocketRoomService에 removeParticipant 메소드 필요)
8183 webSocketRoomService .removeParticipant (roomId , userId );
8284
83- // --- 방 전체에 최신 상태 브로드캐스팅 ---
84- broadcastRoomState (roomId );
85+ // --- 세션 자동 비활성화 로직 ---
86+ if (sessionId != null ) {
87+ webSocketRoomService .removeSessionParticipant (sessionId , userId );
88+
89+ // 방금 나간 사람이 마지막 참여자였는지 확인
90+ if (webSocketRoomService .isSessionEmpty (sessionId )) {
91+ log .info ("Last user left session {}. Deactivating session." , sessionId );
92+
93+ // DB에서 세션을 찾아 상태를 INACTIVE로 변경
94+ codeSessionRepository .findBySessionId (sessionId ).ifPresent (session -> {
95+ session .updateStatus (CodeSession .SessionStatus .INACTIVE );
96+ log .info ("Session {} status updated to INACTIVE in DB." , sessionId );
97+ });
98+ }
99+ }
100+
101+ // 변경된 방 상태(참여자 감소)를 모두에게 알림
102+ roomService .broadcastRoomState (roomId );
85103 }
86104 }
87105 }
88-
89- /**
90- * 특정 방의 최신 상태(방 이름, 방장, 참여자 목록)를 조회하여
91- * 해당 방의 시스템 채널로 브로드캐스팅하는 헬퍼 메소드
92- */
93- private void broadcastRoomState (String roomId ) {
94- Room dbRoom = roomRepository .findByRoomId (roomId )
95- .orElseThrow (() -> new RuntimeException ("Room not found during state broadcast: " + roomId ));
96-
97- // 1. 방장 정보 DTO 생성
98- ParticipantInfo ownerInfo = ParticipantInfo .builder ()
99- .userId (dbRoom .getOwner ().getUserId ())
100- .userName (dbRoom .getOwner ().getName ())
101- .build ();
102-
103- // 2. 참여자(방장 제외) 목록 DTO 생성
104- List <ParticipantInfo > participantInfos = dbRoom .getParticipants ().stream ()
105- .filter (p -> !p .getUser ().getUserId ().equals (dbRoom .getOwner ().getUserId ()))
106- .map (p -> ParticipantInfo .builder ()
107- .userId (p .getUser ().getUserId ())
108- .userName (p .getUser ().getName ())
109- .build ())
110- .collect (Collectors .toList ());
111-
112- // 3. 최종 업데이트 DTO 생성
113- RoomStateUpdate roomStateUpdate = RoomStateUpdate .builder ()
114- .roomName (dbRoom .getName ())
115- .owner (ownerInfo )
116- .participants (participantInfos )
117- .build ();
118-
119- // 4. 시스템 채널로 브로드캐스팅
120- messagingTemplate .convertAndSend ("/topic/room/" + roomId + "/system" , roomStateUpdate );
121- log .info ("Broadcasted room state update for room: {}" , roomId );
122- }
123106}
0 commit comments