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 ;
3+ import com .back .domain .chat .room .dto .RoomChatMessageRequest ;
4+ import com .back .domain .chat .room .dto .RoomChatMessageResponse ;
55import com .back .domain .studyroom .entity .RoomChatMessage ;
6- import com .back .domain .chat .room .dto .RoomChatMessageDto ;
76import com .back .global .exception .CustomException ;
7+ import com .back .global .exception .ErrorCode ;
88import com .back .global .security .user .CustomUserDetails ;
99import com .back .domain .chat .room .service .RoomChatService ;
1010import com .back .global .websocket .util .WebSocketAuthHelper ;
1111import com .back .global .websocket .util .WebSocketErrorHelper ;
12- import io .swagger .v3 .oas .annotations .tags .Tag ;
1312import lombok .RequiredArgsConstructor ;
1413import lombok .extern .slf4j .Slf4j ;
1514import org .springframework .messaging .handler .annotation .DestinationVariable ;
15+ import org .springframework .messaging .handler .annotation .MessageExceptionHandler ;
1616import org .springframework .messaging .handler .annotation .MessageMapping ;
1717import org .springframework .messaging .handler .annotation .Payload ;
1818import org .springframework .messaging .simp .SimpMessageHeaderAccessor ;
1919import org .springframework .messaging .simp .SimpMessagingTemplate ;
20- import org .springframework .security .core .Authentication ;
2120import org .springframework .stereotype .Controller ;
2221
2322import java .security .Principal ;
2423
2524@ Slf4j
2625@ Controller
2726@ RequiredArgsConstructor
28- @ Tag (name = "RoomChat WebSocket" , description = "STOMP를 이용한 실시간 채팅 WebSocket 컨트롤러 (Swagger에서 직접 테스트 불가)" )
2927public class RoomChatWebSocketController {
3028
3129 private final RoomChatService roomChatService ;
3230 private final SimpMessagingTemplate messagingTemplate ;
33- private final WebSocketAuthHelper authHelper ;
3431 private final WebSocketErrorHelper errorHelper ;
3532
3633 /**
@@ -39,117 +36,30 @@ public class RoomChatWebSocketController {
3936 */
4037 @ MessageMapping ("/chat/room/{roomId}" )
4138 public void handleRoomChat (@ DestinationVariable Long roomId ,
42- RoomChatMessageDto chatMessage ,
43- SimpMessageHeaderAccessor headerAccessor ,
39+ @ Payload RoomChatMessageRequest request ,
4440 Principal principal ) {
4541
46- try {
47- // WebSocket에서 인증된 사용자 정보 추출
48- CustomUserDetails userDetails = authHelper .extractUserDetails (principal );
49-
50- if (userDetails == null ) {
51- errorHelper .sendUnauthorizedError (headerAccessor .getSessionId ());
52- return ;
53- }
54-
55- Long currentUserId = userDetails .getUserId ();
56- String currentUserNickname = userDetails .getUsername ();
57-
58- // 메시지 정보 보완
59- RoomChatMessageDto enrichedMessage = chatMessage
60- .withRoomId (roomId )
61- .withUserId (currentUserId )
62- .withNickname (currentUserNickname );
63-
64- // DB에 메시지 저장
65- RoomChatMessage savedMessage = roomChatService .saveRoomChatMessage (enrichedMessage );
66-
67- // 저장된 메시지 정보로 응답 DTO 생성
68- RoomChatMessageDto responseMessage = RoomChatMessageDto .createResponse (
69- savedMessage .getId (),
70- roomId ,
71- savedMessage .getUser ().getId (),
72- savedMessage .getUser ().getNickname (),
73- savedMessage .getUser ().getProfileImageUrl (),
74- savedMessage .getContent (),
75- chatMessage .messageType (),
76- null , // 텍스트 채팅에서는 null
77- savedMessage .getCreatedAt ()
78- );
79-
80- // 해당 방의 모든 구독자에게 브로드캐스트
81- messagingTemplate .convertAndSend ("/topic/room/" + roomId , responseMessage );
82-
83- } catch (CustomException e ) {
84- log .warn ("채팅 메시지 처리 실패 - roomId: {}, error: {}" , roomId , e .getMessage ());
85- errorHelper .sendCustomExceptionToUser (headerAccessor .getSessionId (), e );
86-
87- } catch (Exception e ) {
88- log .error ("채팅 메시지 처리 중 예상치 못한 오류 발생 - roomId: {}" , roomId , e );
89- errorHelper .sendGenericErrorToUser (headerAccessor .getSessionId (), e , "메시지 전송 중 오류가 발생했습니다" );
42+ CustomUserDetails userDetails = WebSocketAuthHelper .extractUserDetails (principal );
43+ if (userDetails == null ) {
44+ // 인증 정보가 없는 경우 예외 처리
45+ throw new CustomException (ErrorCode .UNAUTHORIZED );
9046 }
91- }
92-
93- /**
94- * 스터디룸 채팅 일괄 삭제 처리
95- * 클라이언트가 /app/chat/room/{roomId}/clear로 삭제 요청 시 호출
96- */
97- @ MessageMapping ("/chat/room/{roomId}/clear" )
98- public void clearRoomChat (@ DestinationVariable Long roomId ,
99- @ Payload ChatClearRequest request ,
100- SimpMessageHeaderAccessor headerAccessor ,
101- Principal principal ) {
10247
103- try {
104- log .info ("WebSocket 채팅 일괄 삭제 요청 - roomId: {}" , roomId );
48+ RoomChatMessage savedMessage = roomChatService .saveRoomChatMessage (
49+ roomId ,
50+ userDetails .getUserId (),
51+ request
52+ );
10553
106- // 사용자 인증 확인
107- CustomUserDetails userDetails = authHelper .extractUserDetails (principal );
108-
109- if (userDetails == null ) {
110- errorHelper .sendUnauthorizedError (headerAccessor .getSessionId ());
111- return ;
112- }
113-
114- // 삭제 확인 메시지 검증
115- if (!request .isValidConfirmMessage ()) {
116- errorHelper .sendErrorToUser (headerAccessor .getSessionId (), "WS_011" ,
117- "삭제 확인 메시지가 일치하지 않습니다" );
118- return ;
119- }
120-
121- Long currentUserId = userDetails .getUserId ();
122-
123- // 삭제 전에 메시지 수 먼저 조회 (삭제 후에는 0이 되므로)
124- int deletedCount = roomChatService .getRoomChatCount (roomId );
125-
126- // 채팅 일괄 삭제 실행
127- ChatClearedNotification .ClearedByDto clearedByInfo = roomChatService .clearRoomChat (roomId , currentUserId );
128-
129- // 알림 생성
130- ChatClearedNotification notification = ChatClearedNotification .create (
131- roomId ,
132- deletedCount , // 삭제 전에 조회한 수 사용
133- clearedByInfo .userId (),
134- clearedByInfo .nickname (),
135- clearedByInfo .profileImageUrl (),
136- clearedByInfo .role ()
137- );
138-
139- // 해당 방의 모든 구독자에게 브로드캐스트
140- messagingTemplate .convertAndSend ("/topic/room/" + roomId + "/chat-cleared" , notification );
141-
142- log .info ("WebSocket 채팅 일괄 삭제 완료 - roomId: {}, deletedCount: {}, userId: {}" ,
143- roomId , deletedCount , currentUserId );
144-
145- } catch (CustomException e ) {
146- log .warn ("채팅 삭제 실패 - roomId: {}, error: {}" , roomId , e .getMessage ());
147- errorHelper .sendCustomExceptionToUser (headerAccessor .getSessionId (), e );
148-
149- } catch (Exception e ) {
150- log .error ("채팅 일괄 삭제 중 예상치 못한 오류 발생 - roomId: {}" , roomId , e );
151- errorHelper .sendGenericErrorToUser (headerAccessor .getSessionId (), e , "채팅 삭제 중 오류가 발생했습니다" );
152- }
54+ // 응답 DTO 생성 및 브로드캐스트
55+ RoomChatMessageResponse responseMessage = RoomChatMessageResponse .from (savedMessage );
56+ messagingTemplate .convertAndSend ("/topic/room/" + roomId , responseMessage );
15357 }
15458
59+ // 채팅 메시지 처리 중 발생하는 예외 중앙 처리
60+ @ MessageExceptionHandler (CustomException .class )
61+ public void handleChatException (CustomException e , SimpMessageHeaderAccessor headerAccessor ) {
62+ log .warn ("채팅 메시지 처리 실패 - SessionId: {}, Error: {}" , headerAccessor .getSessionId (), e .getMessage ());
63+ errorHelper .sendCustomExceptionToUser (headerAccessor .getSessionId (), e );
64+ }
15565}
0 commit comments