11package com .back .global .websocket .controller ;
22
33import com .back .global .exception .CustomException ;
4- import com .back .global .websocket . dto . HeartbeatMessage ;
4+ import com .back .global .security . user . CustomUserDetails ;
55import com .back .global .websocket .service .WebSocketSessionManager ;
66import com .back .global .websocket .util .WebSocketErrorHelper ;
77import lombok .RequiredArgsConstructor ;
88import lombok .extern .slf4j .Slf4j ;
9+ import org .springframework .messaging .handler .annotation .MessageExceptionHandler ;
910import org .springframework .messaging .handler .annotation .MessageMapping ;
10- import org .springframework .messaging .handler .annotation .DestinationVariable ;
11- import org .springframework .messaging .handler .annotation .Payload ;
1211import org .springframework .messaging .simp .SimpMessageHeaderAccessor ;
12+ import org .springframework .security .core .Authentication ;
1313import org .springframework .stereotype .Controller ;
1414
15+ import java .security .Principal ;
16+
1517@ Slf4j
1618@ Controller
1719@ RequiredArgsConstructor
@@ -22,106 +24,41 @@ public class WebSocketMessageController {
2224
2325 // Heartbeat 처리
2426 @ MessageMapping ("/heartbeat" )
25- public void handleHeartbeat (@ Payload HeartbeatMessage message ,
26- SimpMessageHeaderAccessor headerAccessor ) {
27- try {
28- if (message .userId () != null ) {
29- // TTL 10분으로 연장
30- sessionManager .updateLastActivity (message .userId ());
31- log .debug ("Heartbeat 처리 완료 - 사용자: {}" , message .userId ());
32- } else {
33- log .warn ("유효하지 않은 Heartbeat 메시지 수신: userId가 null" );
34- errorHelper .sendInvalidRequestError (headerAccessor .getSessionId (), "사용자 ID가 필요합니다" );
35- }
36- } catch (CustomException e ) {
37- log .error ("Heartbeat 처리 실패: {}" , e .getMessage ());
38- errorHelper .sendCustomExceptionToUser (headerAccessor .getSessionId (), e );
39- } catch (Exception e ) {
40- log .error ("Heartbeat 처리 중 예상치 못한 오류" , e );
41- errorHelper .sendGenericErrorToUser (headerAccessor .getSessionId (), e , "Heartbeat 처리 중 오류가 발생했습니다" );
27+ public void handleHeartbeat (Principal principal , SimpMessageHeaderAccessor headerAccessor ) {
28+ if (principal instanceof Authentication auth && auth .getPrincipal () instanceof CustomUserDetails userDetails ) {
29+ Long userId = userDetails .getUserId ();
30+ sessionManager .updateLastActivity (userId );
31+ log .debug ("Heartbeat 처리 완료 - 사용자: {}" , userId );
32+ } else {
33+ log .warn ("인증되지 않은 Heartbeat 요청: {}" , headerAccessor .getSessionId ());
34+ errorHelper .sendUnauthorizedError (headerAccessor .getSessionId ());
4235 }
4336 }
4437
45- /**
46- * 방 입장 처리
47- *
48- * @deprecated 이 STOMP 엔드포인트는 REST API로 대체되었습니다.
49- * 대신 POST /api/rooms/{roomId}/join을 사용하세요.
50- *
51- * 참고: REST API 호출 시 자동으로 Redis에 입장 처리되며 WebSocket 알림도 전송됩니다.
52- * 이 엔드포인트는 하위 호환성을 위해 유지되지만 사용을 권장하지 않습니다.
53- */
54- @ Deprecated
55- @ MessageMapping ("/rooms/{roomId}/join" )
56- public void handleJoinRoom (@ DestinationVariable Long roomId ,
57- @ Payload HeartbeatMessage message ,
58- SimpMessageHeaderAccessor headerAccessor ) {
59- try {
60- if (message .userId () != null ) {
61- sessionManager .joinRoom (message .userId (), roomId );
62- log .info ("STOMP 방 입장 처리 완료 - 사용자: {}, 방: {}" , message .userId (), roomId );
63- } else {
64- log .warn ("유효하지 않은 방 입장 요청: userId가 null" );
65- errorHelper .sendInvalidRequestError (headerAccessor .getSessionId (), "사용자 ID가 필요합니다" );
66- }
67- } catch (CustomException e ) {
68- log .error ("방 입장 처리 실패 - 방: {}, 에러: {}" , roomId , e .getMessage ());
69- errorHelper .sendCustomExceptionToUser (headerAccessor .getSessionId (), e );
70- } catch (Exception e ) {
71- log .error ("방 입장 처리 중 예상치 못한 오류 - 방: {}" , roomId , e );
72- errorHelper .sendGenericErrorToUser (headerAccessor .getSessionId (), e , "방 입장 중 오류가 발생했습니다" );
38+ // 사용자 활동 신호 처리
39+ @ MessageMapping ("/activity" )
40+ public void handleActivity (Principal principal , SimpMessageHeaderAccessor headerAccessor ) {
41+ if (principal instanceof Authentication auth && auth .getPrincipal () instanceof CustomUserDetails userDetails ) {
42+ Long userId = userDetails .getUserId ();
43+ sessionManager .updateLastActivity (userId );
44+ log .debug ("사용자 활동 신호 처리 완료 - 사용자: {}" , userId );
45+ } else {
46+ log .warn ("유효하지 않은 활동 신호: 인증 정보 없음" );
47+ errorHelper .sendInvalidRequestError (headerAccessor .getSessionId (), "사용자 ID가 필요합니다" );
7348 }
7449 }
7550
76- /**
77- * 방 퇴장 처리
78- *
79- * @deprecated 이 STOMP 엔드포인트는 REST API로 대체되었습니다.
80- * 대신 POST /api/rooms/{roomId}/leave를 사용하세요.
81- *
82- * 참고: REST API 호출 시 자동으로 Redis에서 퇴장 처리되며 WebSocket 알림도 전송됩니다.
83- * 이 엔드포인트는 하위 호환성을 위해 유지되지만 사용을 권장하지 않습니다.
84- */
85- @ Deprecated
86- @ MessageMapping ("/rooms/{roomId}/leave" )
87- public void handleLeaveRoom (@ DestinationVariable Long roomId ,
88- @ Payload HeartbeatMessage message ,
89- SimpMessageHeaderAccessor headerAccessor ) {
90- try {
91- if (message .userId () != null ) {
92- sessionManager .leaveRoom (message .userId (), roomId );
93- log .info ("STOMP 방 퇴장 처리 완료 - 사용자: {}, 방: {}" , message .userId (), roomId );
94- } else {
95- log .warn ("유효하지 않은 방 퇴장 요청: userId가 null" );
96- errorHelper .sendInvalidRequestError (headerAccessor .getSessionId (), "사용자 ID가 필요합니다" );
97- }
98- } catch (CustomException e ) {
99- log .error ("방 퇴장 처리 실패 - 방: {}, 에러: {}" , roomId , e .getMessage ());
100- errorHelper .sendCustomExceptionToUser (headerAccessor .getSessionId (), e );
101- } catch (Exception e ) {
102- log .error ("방 퇴장 처리 중 예상치 못한 오류 - 방: {}" , roomId , e );
103- errorHelper .sendGenericErrorToUser (headerAccessor .getSessionId (), e , "방 퇴장 중 오류가 발생했습니다" );
104- }
51+ // WebSocket 메시지 처리 중 발생하는 CustomException 처리
52+ @ MessageExceptionHandler (CustomException .class )
53+ public void handleCustomException (CustomException e , SimpMessageHeaderAccessor headerAccessor ) {
54+ log .error ("WebSocket 처리 중 CustomException 발생: {}" , e .getMessage ());
55+ errorHelper .sendCustomExceptionToUser (headerAccessor .getSessionId (), e );
10556 }
10657
107- // 사용자 활동 신호 처리
108- @ MessageMapping ("/activity" )
109- public void handleActivity (@ Payload HeartbeatMessage message ,
110- SimpMessageHeaderAccessor headerAccessor ) {
111- try {
112- if (message .userId () != null ) {
113- sessionManager .updateLastActivity (message .userId ());
114- log .debug ("사용자 활동 신호 처리 완료 - 사용자: {}" , message .userId ());
115- } else {
116- log .warn ("유효하지 않은 활동 신호: userId가 null" );
117- errorHelper .sendInvalidRequestError (headerAccessor .getSessionId (), "사용자 ID가 필요합니다" );
118- }
119- } catch (CustomException e ) {
120- log .error ("활동 신호 처리 실패: {}" , e .getMessage ());
121- errorHelper .sendCustomExceptionToUser (headerAccessor .getSessionId (), e );
122- } catch (Exception e ) {
123- log .error ("활동 신호 처리 중 예상치 못한 오류" , e );
124- errorHelper .sendGenericErrorToUser (headerAccessor .getSessionId (), e , "활동 신호 처리 중 오류가 발생했습니다" );
125- }
58+ // 예상치 못한 모든 Exception 처리
59+ @ MessageExceptionHandler (Exception .class )
60+ public void handleGeneralException (Exception e , SimpMessageHeaderAccessor headerAccessor ) {
61+ log .error ("WebSocket 처리 중 예상치 못한 오류 발생" , e );
62+ errorHelper .sendGenericErrorToUser (headerAccessor .getSessionId (), e , "요청 처리 중 서버 오류가 발생했습니다." );
12663 }
12764}
0 commit comments