11package com .back .domain .chat .room .controller ;
22
3+ import com .back .domain .chat .room .dto .ChatClearRequest ;
4+ import com .back .domain .chat .room .dto .ChatClearedNotification ;
35import com .back .domain .studyroom .entity .RoomChatMessage ;
46import com .back .domain .chat .room .dto .RoomChatMessageDto ;
7+ import com .back .global .exception .CustomException ;
58import com .back .global .security .user .CustomUserDetails ;
6- import com .back .global .websocket .dto .WebSocketErrorResponse ;
79import com .back .domain .chat .room .service .RoomChatService ;
10+ import com .back .global .websocket .util .WebSocketErrorHelper ;
811import io .swagger .v3 .oas .annotations .tags .Tag ;
912import lombok .RequiredArgsConstructor ;
13+ import lombok .extern .slf4j .Slf4j ;
1014import org .springframework .messaging .handler .annotation .DestinationVariable ;
1115import org .springframework .messaging .handler .annotation .MessageMapping ;
16+ import org .springframework .messaging .handler .annotation .Payload ;
1217import org .springframework .messaging .simp .SimpMessageHeaderAccessor ;
1318import org .springframework .messaging .simp .SimpMessagingTemplate ;
1419import org .springframework .security .core .Authentication ;
1520import org .springframework .stereotype .Controller ;
1621
1722import java .security .Principal ;
1823
24+ @ Slf4j
1925@ Controller
2026@ RequiredArgsConstructor
2127@ Tag (name = "RoomChat WebSocket" , description = "STOMP를 이용한 실시간 채팅 WebSocket 컨트롤러 (Swagger에서 직접 테스트 불가)" )
2228public class RoomChatWebSocketController {
2329
2430 private final RoomChatService roomChatService ;
2531 private final SimpMessagingTemplate messagingTemplate ;
32+ private final WebSocketErrorHelper errorHelper ;
2633
2734 /**
2835 * 방 채팅 메시지 처리
2936 * 클라이언트가 /app/chat/room/{roomId}로 메시지 전송 시 호출
30- *
31- * @param roomId 스터디룸 ID
32- * @param chatMessage 채팅 메시지 (content, messageType, attachmentId)
33- * @param headerAccessor WebSocket 헤더 정보
34- * @param principal 인증된 사용자 정보
3537 */
3638 @ MessageMapping ("/chat/room/{roomId}" )
3739 public void handleRoomChat (@ DestinationVariable Long roomId ,
@@ -43,7 +45,7 @@ public void handleRoomChat(@DestinationVariable Long roomId,
4345 // WebSocket에서 인증된 사용자 정보 추출
4446 CustomUserDetails userDetails = extractUserDetails (principal );
4547 if (userDetails == null ) {
46- sendErrorToUser (headerAccessor .getSessionId (), "WS_UNAUTHORIZED" , "인증이 필요합니다" );
48+ errorHelper . sendUnauthorizedError (headerAccessor .getSessionId ());
4749 return ;
4850 }
4951
@@ -75,16 +77,74 @@ public void handleRoomChat(@DestinationVariable Long roomId,
7577 // 해당 방의 모든 구독자에게 브로드캐스트
7678 messagingTemplate .convertAndSend ("/topic/room/" + roomId , responseMessage );
7779
80+ } catch (CustomException e ) {
81+ log .warn ("채팅 메시지 처리 실패 - roomId: {}, error: {}" , roomId , e .getMessage ());
82+ errorHelper .sendCustomExceptionToUser (headerAccessor .getSessionId (), e );
83+
7884 } catch (Exception e ) {
79- // 에러 응답을 해당 사용자에게만 전송
80- WebSocketErrorResponse errorResponse = WebSocketErrorResponse .create (
81- "WS_ROOM_NOT_FOUND" ,
82- "존재하지 않는 방입니다"
85+ log .error ("채팅 메시지 처리 중 예상치 못한 오류 발생 - roomId: {}" , roomId , e );
86+ errorHelper .sendGenericErrorToUser (headerAccessor .getSessionId (), e , "메시지 전송 중 오류가 발생했습니다" );
87+ }
88+ }
89+
90+ /**
91+ * 스터디룸 채팅 일괄 삭제 처리
92+ * 클라이언트가 /app/chat/room/{roomId}/clear로 삭제 요청 시 호출
93+ */
94+ @ MessageMapping ("/chat/room/{roomId}/clear" )
95+ public void clearRoomChat (@ DestinationVariable Long roomId ,
96+ @ Payload ChatClearRequest request ,
97+ SimpMessageHeaderAccessor headerAccessor ,
98+ Principal principal ) {
99+
100+ try {
101+ log .info ("WebSocket 채팅 일괄 삭제 요청 - roomId: {}" , roomId );
102+
103+ // 사용자 인증 확인
104+ CustomUserDetails userDetails = extractUserDetails (principal );
105+ if (userDetails == null ) {
106+ errorHelper .sendUnauthorizedError (headerAccessor .getSessionId ());
107+ return ;
108+ }
109+
110+ // 삭제 확인 메시지 검증
111+ if (!request .isValidConfirmMessage ()) {
112+ errorHelper .sendErrorToUser (headerAccessor .getSessionId (), "WS_011" ,
113+ "삭제 확인 메시지가 일치하지 않습니다" );
114+ return ;
115+ }
116+
117+ Long currentUserId = userDetails .getUserId ();
118+
119+ // 삭제 전에 메시지 수 먼저 조회 (삭제 후에는 0이 되므로)
120+ int deletedCount = roomChatService .getRoomChatCount (roomId );
121+
122+ // 채팅 일괄 삭제 실행
123+ ChatClearedNotification .ClearedByDto clearedByInfo = roomChatService .clearRoomChat (roomId , currentUserId );
124+
125+ // 알림 생성
126+ ChatClearedNotification notification = ChatClearedNotification .create (
127+ roomId ,
128+ deletedCount , // 삭제 전에 조회한 수 사용
129+ clearedByInfo .userId (),
130+ clearedByInfo .nickname (),
131+ clearedByInfo .profileImageUrl (),
132+ clearedByInfo .role ()
83133 );
84134
85- // 에러를 발생시킨 사용자에게만 전송
86- String sessionId = headerAccessor .getSessionId ();
87- messagingTemplate .convertAndSendToUser (sessionId , "/queue/errors" , errorResponse );
135+ // 해당 방의 모든 구독자에게 브로드캐스트
136+ messagingTemplate .convertAndSend ("/topic/room/" + roomId + "/chat-cleared" , notification );
137+
138+ log .info ("WebSocket 채팅 일괄 삭제 완료 - roomId: {}, deletedCount: {}, userId: {}" ,
139+ roomId , deletedCount , currentUserId );
140+
141+ } catch (CustomException e ) {
142+ log .warn ("채팅 삭제 실패 - roomId: {}, error: {}" , roomId , e .getMessage ());
143+ errorHelper .sendCustomExceptionToUser (headerAccessor .getSessionId (), e );
144+
145+ } catch (Exception e ) {
146+ log .error ("채팅 일괄 삭제 중 예상치 못한 오류 발생 - roomId: {}" , roomId , e );
147+ errorHelper .sendGenericErrorToUser (headerAccessor .getSessionId (), e , "채팅 삭제 중 오류가 발생했습니다" );
88148 }
89149 }
90150
@@ -99,10 +159,4 @@ private CustomUserDetails extractUserDetails(Principal principal) {
99159 return null ;
100160 }
101161
102- // 특정 사용자에게 에러 메시지 전송
103- private void sendErrorToUser (String sessionId , String errorCode , String errorMessage ) {
104- WebSocketErrorResponse errorResponse = WebSocketErrorResponse .create (errorCode , errorMessage );
105- messagingTemplate .convertAndSendToUser (sessionId , "/queue/errors" , errorResponse );
106- }
107-
108162}
0 commit comments