11package cmf .commitField .global .websocket ;
22
3+ import cmf .commitField .domain .chat .chatMessage .controller .request .ChatMsgRequest ;
4+ import cmf .commitField .domain .chat .chatMessage .controller .response .ChatMsgResponse ;
5+ import cmf .commitField .domain .chat .chatMessage .service .ChatMessageService ;
6+ import cmf .commitField .domain .user .entity .User ;
7+ import cmf .commitField .domain .user .repository .UserRepository ;
38import cmf .commitField .global .error .ErrorCode ;
49import cmf .commitField .global .exception .CustomException ;
10+ import com .fasterxml .jackson .databind .JsonNode ;
11+ import com .fasterxml .jackson .databind .ObjectMapper ;
12+ import lombok .RequiredArgsConstructor ;
513import lombok .extern .slf4j .Slf4j ;
614import org .springframework .stereotype .Component ;
715import org .springframework .web .socket .*;
816
917import java .io .IOException ;
18+ import java .time .LocalDateTime ;
1019import java .util .*;
1120
1221@ Component
1322@ Slf4j
23+ @ RequiredArgsConstructor
1424public class ChatWebSocketHandler implements WebSocketHandler {
1525
1626 private final Map <Long , List <WebSocketSession >> chatRooms = new HashMap <>();
17- // ๋ฐฉ์ ํค๊ฐ
18-
27+ private final ObjectMapper objectMapper = new ObjectMapper ();
28+ private final ChatMessageService chatMessageService ;
29+ private final UserRepository userRepository ;
1930
2031 // ์ฐ๊ฒฐ์ด ๋์์ ๋
2132 @ Override
2233 public void afterConnectionEstablished (WebSocketSession session )
2334 throws Exception {
24- // list.add(session);
25- Long roomId = extractRoomId (session );
26- // roomId ๊ฐ ์์ ๊ฒฝ์ฐ, session list (new ArrayList)
27- List <WebSocketSession > roomSessions = chatRooms .getOrDefault (roomId , new ArrayList <>());
28- // ์ธ์
์ถ๊ฐ
29- roomSessions .add (session );
30- // ํด๋น ๋ฐฉ์ ํค๊ฐ์ session list ์ถ๊ฐ
31- chatRooms .put (roomId , roomSessions );
32- log .info (session + "์ ํด๋ผ์ด์ธํธ ์ ์" );
35+ log .info ("ํด๋ผ์ด์ธํธ ์ ์: {}" , session .getId ());
36+
37+ // ์ฐ๊ฒฐ ์ฑ๊ณต ๋ฉ์์ง ์ ์ก
38+ Map <String , Object > connectMessage = new HashMap <>();
39+ connectMessage .put ("type" , "SYSTEM" );
40+ connectMessage .put ("message" , "์ฑํ
์๋ฒ์ ์ฐ๊ฒฐ๋์์ต๋๋ค." );
41+ connectMessage .put ("timestamp" , LocalDateTime .now ().toString ());
42+
43+ try {
44+ session .sendMessage (new TextMessage (objectMapper .writeValueAsString (connectMessage )));
45+ } catch (Exception e ) {
46+ log .error ("์ฐ๊ฒฐ ๋ฉ์์ง ์ ์ก ์คํจ: {}" , e .getMessage ());
47+ }
3348 }
3449
3550 // ํด๋ผ์ด์ธํธ๋ก๋ถํฐ ๋ฐ์ ๋ฉ์์ง๋ฅผ ์ฒ๋ฆฌํ๋ ๋ก์ง
3651 @ Override
3752 public void handleMessage (WebSocketSession session , WebSocketMessage <?> message )
3853 throws Exception {
39- // ๋ฉ์์ง ์ฒ๋ฆฌ ๋ก์ง
40- Long roomId = extractRoomId (session );
54+ String payload = message .getPayload ().toString ();
55+ log .info ("๋ฉ์์ง ์์ : {}" , payload );
56+
57+ try {
58+ JsonNode jsonNode = objectMapper .readTree (payload );
59+ String messageType = jsonNode .has ("type" ) ? jsonNode .get ("type" ).asText () : "UNKNOWN" ;
60+
61+ switch (messageType ) {
62+ case "SUBSCRIBE" :
63+ handleSubscribe (session , jsonNode );
64+ break ;
65+ case "UNSUBSCRIBE" :
66+ handleUnsubscribe (session , jsonNode );
67+ break ;
68+ case "CHAT" :
69+ handleChatMessage (session , jsonNode );
70+ break ;
71+ default :
72+ log .warn ("์ ์ ์๋ ๋ฉ์์ง ํ์
: {}" , messageType );
73+ sendErrorMessage (session , "์ง์ํ์ง ์๋ ๋ฉ์์ง ํ์
์
๋๋ค: " + messageType );
74+ }
75+ } catch (Exception e ) {
76+ log .error ("๋ฉ์์ง ์ฒ๋ฆฌ ์ค ์ค๋ฅ ๋ฐ์: {}" , e .getMessage (), e );
77+ // ์ค๋ฅ ๋ฉ์์ง ์ ์ก
78+ sendErrorMessage (session , "๋ฉ์์ง ์ฒ๋ฆฌ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: " + e .getMessage ());
79+ }
80+ }
81+
82+ // ๊ตฌ๋
๋ฉ์์ง ์ฒ๋ฆฌ
83+ private void handleSubscribe (WebSocketSession session , JsonNode jsonNode ) {
84+ try {
85+ if (!jsonNode .has ("roomId" )) {
86+ sendErrorMessage (session , "roomId ํ๋๊ฐ ๋๋ฝ๋์์ต๋๋ค." );
87+ return ;
88+ }
89+
90+ Long roomId = jsonNode .get ("roomId" ).asLong ();
91+ log .info ("์ฑํ
๋ฐฉ ๊ตฌ๋
์์ฒญ: roomId={}, sessionId={}" , roomId , session .getId ());
92+
93+ // ํด๋น ๋ฃธ์ ์ธ์
๋ชฉ๋ก์ ์ถ๊ฐ
94+ List <WebSocketSession > roomSessions = chatRooms .getOrDefault (roomId , new ArrayList <>());
95+
96+ // ์ด๋ฏธ ๋ฑ๋ก๋ ์ธ์
์ธ์ง ํ์ธํ์ฌ ์ค๋ณต ๋ฑ๋ก ๋ฐฉ์ง
97+ boolean alreadyRegistered = roomSessions .stream ()
98+ .anyMatch (existingSession -> existingSession .getId ().equals (session .getId ()));
99+
100+ if (!alreadyRegistered ) {
101+ roomSessions .add (session );
102+ chatRooms .put (roomId , roomSessions );
103+ log .info ("ํด๋ผ์ด์ธํธ ์ธ์
{}๊ฐ ๋ฃธ {}์ ๊ตฌ๋
๋จ" , session .getId (), roomId );
104+
105+ // ๊ตฌ๋
ํ์ธ ๋ฉ์์ง ์ ์ก
106+ Map <String , Object > subscribeResponse = new HashMap <>();
107+ subscribeResponse .put ("type" , "SUBSCRIBE_ACK" );
108+ subscribeResponse .put ("roomId" , roomId );
109+ subscribeResponse .put ("timestamp" , LocalDateTime .now ().toString ());
110+ subscribeResponse .put ("message" , "์ฑํ
๋ฐฉ์ ์ฐ๊ฒฐ๋์์ต๋๋ค." );
111+
112+ session .sendMessage (new TextMessage (objectMapper .writeValueAsString (subscribeResponse )));
113+ } else {
114+ log .info ("์ด๋ฏธ ๊ตฌ๋
์ค์ธ ์ธ์
: sessionId={}, roomId={}" , session .getId (), roomId );
115+ }
116+ } catch (Exception e ) {
117+ log .error ("๊ตฌ๋
์ฒ๋ฆฌ ์ค ์ค๋ฅ: {}" , e .getMessage (), e );
118+ try {
119+ sendErrorMessage (session , "๊ตฌ๋
์ฒ๋ฆฌ ์ค ์ค๋ฅ: " + e .getMessage ());
120+ } catch (IOException ex ) {
121+ log .error ("์ค๋ฅ ๋ฉ์์ง ์ ์ก ์คํจ: {}" , ex .getMessage ());
122+ }
123+ }
124+ }
125+
126+ // ๊ตฌ๋
ํด์ ๋ฉ์์ง ์ฒ๋ฆฌ
127+ private void handleUnsubscribe (WebSocketSession session , JsonNode jsonNode ) {
128+ try {
129+ if (!jsonNode .has ("roomId" )) {
130+ sendErrorMessage (session , "roomId ํ๋๊ฐ ๋๋ฝ๋์์ต๋๋ค." );
131+ return ;
132+ }
133+
134+ Long roomId = jsonNode .get ("roomId" ).asLong ();
135+
136+ List <WebSocketSession > roomSessions = chatRooms .get (roomId );
137+ if (roomSessions != null ) {
138+ roomSessions .removeIf (existingSession -> existingSession .getId ().equals (session .getId ()));
139+ log .info ("ํด๋ผ์ด์ธํธ ์ธ์
{}๊ฐ ๋ฃธ {}์์ ๊ตฌ๋
ํด์ ๋จ" , session .getId (), roomId );
140+
141+ // ๊ตฌ๋
ํด์ ๊ฐ ์ฑ๊ณต์ ์ผ๋ก ์ฒ๋ฆฌ๋์์์ ์๋ฆฌ๋ ๋ฉ์์ง ์ ์ก
142+ Map <String , Object > unsubscribeResponse = new HashMap <>();
143+ unsubscribeResponse .put ("type" , "UNSUBSCRIBE_ACK" );
144+ unsubscribeResponse .put ("roomId" , roomId );
145+ unsubscribeResponse .put ("timestamp" , LocalDateTime .now ().toString ());
146+ unsubscribeResponse .put ("message" , "์ฑํ
๋ฐฉ์์ ์ฐ๊ฒฐ์ด ํด์ ๋์์ต๋๋ค." );
147+
148+ session .sendMessage (new TextMessage (objectMapper .writeValueAsString (unsubscribeResponse )));
149+ } else {
150+ log .warn ("์กด์ฌํ์ง ์๋ ์ฑํ
๋ฐฉ ๊ตฌ๋
ํด์ ์๋: roomId={}" , roomId );
151+ sendErrorMessage (session , "์กด์ฌํ์ง ์๋ ์ฑํ
๋ฐฉ์
๋๋ค: " + roomId );
152+ }
153+ } catch (Exception e ) {
154+ log .error ("๊ตฌ๋
ํด์ ์ฒ๋ฆฌ ์ค ์ค๋ฅ: {}" , e .getMessage (), e );
155+ try {
156+ sendErrorMessage (session , "๊ตฌ๋
ํด์ ์ฒ๋ฆฌ ์ค ์ค๋ฅ: " + e .getMessage ());
157+ } catch (IOException ex ) {
158+ log .error ("์ค๋ฅ ๋ฉ์์ง ์ ์ก ์คํจ: {}" , ex .getMessage ());
159+ }
160+ }
161+ }
162+
163+ // ์ฑํ
๋ฉ์์ง ์ฒ๋ฆฌ
164+ private void handleChatMessage (WebSocketSession session , JsonNode jsonNode ) {
165+ try {
166+ // ํ์ ํ๋ ๊ฒ์ฆ
167+ if (!jsonNode .has ("roomId" ) || !jsonNode .has ("message" ) || !jsonNode .has ("userId" )) {
168+ sendErrorMessage (session , "ํ์ ํ๋๊ฐ ๋๋ฝ๋์์ต๋๋ค. (roomId, message, userId ํ์)" );
169+ return ;
170+ }
171+
172+ Long roomId = jsonNode .get ("roomId" ).asLong ();
173+ Long userId = jsonNode .get ("userId" ).asLong ();
174+ String message = jsonNode .get ("message" ).asText ();
175+
176+ if (message == null || message .trim ().isEmpty ()) {
177+ sendErrorMessage (session , "๋ฉ์์ง ๋ด์ฉ์ด ๋น์ด์์ต๋๋ค." );
178+ return ;
179+ }
180+
181+ log .info ("์ฑํ
๋ฉ์์ง: roomId={}, userId={}, message={}" , roomId , userId , message );
182+
183+ // ์ฌ์ฉ์ ์ ๋ณด ๊ฒ์ฆ
184+ User user = userRepository .findById (userId ).orElse (null );
185+ if (user == null ) {
186+ log .warn ("์กด์ฌํ์ง ์๋ ์ฌ์ฉ์: userId={}" , userId );
187+ sendErrorMessage (session , "์กด์ฌํ์ง ์๋ ์ฌ์ฉ์์
๋๋ค." );
188+ return ;
189+ }
190+
191+ // ๋ฉ์์ง ์ ์ฅ ๋ฐ ์ฒ๋ฆฌ
192+ try {
193+ ChatMsgRequest chatMsgRequest = new ChatMsgRequest (message );
194+ ChatMsgResponse response = chatMessageService .sendMessage (chatMsgRequest , userId , roomId );
195+
196+ // ๋ฉ์์ง ํฌ๋งท ๋ณํํ์ฌ ์ ์ก
197+ String messageJson = objectMapper .writeValueAsString (response );
198+
199+ // ํด๋น ์ฑํ
๋ฐฉ์ ๋ชจ๋ ์ธ์
์ ๋ฉ์์ง ๋ธ๋ก๋์บ์คํธ
200+ broadcastMessageToRoom (roomId , messageJson );
201+ } catch (Exception e ) {
202+ log .error ("๋ฉ์์ง ์ ์ฅ ์ฒ๋ฆฌ ์ค ์ค๋ฅ: {}" , e .getMessage (), e );
203+ sendErrorMessage (session , "๋ฉ์์ง ์ ์ก ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: " + e .getMessage ());
204+ }
205+
206+ } catch (Exception e ) {
207+ log .error ("์ฑํ
๋ฉ์์ง ์ฒ๋ฆฌ ์ค ์ค๋ฅ: {}" , e .getMessage (), e );
208+ try {
209+ sendErrorMessage (session , "๋ฉ์์ง ์ ์ก ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: " + e .getMessage ());
210+ } catch (IOException ex ) {
211+ log .error ("์ค๋ฅ ๋ฉ์์ง ์ ์ก ์คํจ: {}" , ex .getMessage ());
212+ }
213+ }
214+ }
215+
216+ // ํน์ ์ฑํ
๋ฐฉ์ ๋ฉ์์ง ๋ธ๋ก๋์บ์คํธ
217+ private void broadcastMessageToRoom (Long roomId , String message ) {
41218 List <WebSocketSession > roomSessions = chatRooms .get (roomId );
42219 if (roomSessions != null ) {
43- String payload = message .getPayload ().toString ();
44- log .info ("์ ์ก ๋ฉ์์ง: " + payload );
220+ List <WebSocketSession > failedSessions = new ArrayList <>();
45221
46- for (WebSocketSession msg : roomSessions ) {
222+ for (WebSocketSession session : roomSessions ) {
47223 try {
48- msg .sendMessage (message );
224+ if (session .isOpen ()) {
225+ session .sendMessage (new TextMessage (message ));
226+ } else {
227+ failedSessions .add (session );
228+ }
49229 } catch (IOException e ) {
50- throw new CustomException (ErrorCode .CHAT_ERROR );
230+ log .error ("๋ฉ์์ง ๋ธ๋ก๋์บ์คํธ ์ค ์ค๋ฅ: {}" , e .getMessage ());
231+ failedSessions .add (session );
51232 }
52233 }
234+
235+ // ์คํจํ ์ธ์
์ ๋ฆฌ
236+ if (!failedSessions .isEmpty ()) {
237+ log .info ("๋ซํ ์ธ์
์ ๋ฆฌ: {} ๊ฐ์ ์ธ์
์ ๊ฑฐ" , failedSessions .size ());
238+ roomSessions .removeAll (failedSessions );
239+ }
53240 } else {
54- log .info ("ํด๋น ์ฑํ
๋ฐฉ์ ํด๋ผ์ด์ธํธ๊ฐ ์์ต๋๋ค." );
55- throw new CustomException (ErrorCode .NOT_EXIST_CLIENT );
241+ log .warn ("์กด์ฌํ์ง ์๋ ์ฑํ
๋ฐฉ์ ๋ฉ์์ง ์ ์ก ์๋: roomId={}" , roomId );
56242 }
57243 }
58244
59- //์ค๋ฅ ์ฒ๋ฆฌ ๋ก์ง์ ๊ตฌํ (๋คํธ์ํฌ ์ค๋ฅ, ํ๋กํ ์ฝ ์ค๋ฅ, ์ฒ๋ฆฌ ์ค๋ฅ... ์๊ฐ ์ค)
245+ // ์ค๋ฅ ๋ฉ์์ง ์ ์ก
246+ private void sendErrorMessage (WebSocketSession session , String errorMessage ) throws IOException {
247+ Map <String , Object > errorResponse = new HashMap <>();
248+ errorResponse .put ("type" , "ERROR" );
249+ errorResponse .put ("message" , errorMessage );
250+ errorResponse .put ("timestamp" , LocalDateTime .now ().toString ());
251+
252+ session .sendMessage (new TextMessage (objectMapper .writeValueAsString (errorResponse )));
253+ }
254+
255+ // ์ค๋ฅ ์ฒ๋ฆฌ ๋ก์ง
60256 @ Override
61257 public void handleTransportError (WebSocketSession session , Throwable exception )
62258 throws Exception {
63- log .error (exception .getMessage ());
259+ log .error ("WebSocket ํต์ ์ค๋ฅ: {}" , exception .getMessage (), exception );
64260 }
65261
66262 // ์ฐ๊ฒฐ ์ข
๋ฃ๋์์ ๋
67263 @ Override
68264 public void afterConnectionClosed (WebSocketSession session , CloseStatus closeStatus )
69265 throws Exception {
70- Long roomId = extractRoomId ( session ); // ํด๋ผ์ด์ธํธ๊ฐ ์ํ ์ฑํ
๋ฐฉ ID๋ฅผ ์ถ์ถ
266+ log . info ( "ํด๋ผ์ด์ธํธ ์ ์ ํด์ : {}, ์ํ์ฝ๋: {}" , session . getId (), closeStatus );
71267
72- List <WebSocketSession > roomSessions = chatRooms .get (roomId );
73- if (roomSessions != null ) {
74- roomSessions .remove (session );
268+ // ๋ชจ๋ ์ฑํ
๋ฐฉ์์ ์ธ์
์ ๊ฑฐ
269+ for (Map .Entry <Long , List <WebSocketSession >> entry : chatRooms .entrySet ()) {
270+ Long roomId = entry .getKey ();
271+ List <WebSocketSession > roomSessions = entry .getValue ();
272+
273+ boolean removed = roomSessions .removeIf (existingSession ->
274+ existingSession .getId ().equals (session .getId ()));
275+
276+ if (removed ) {
277+ log .info ("์ธ์
์ด ์ฑํ
๋ฐฉ {}์์ ์ ๊ฑฐ๋จ: {}" , roomId , session .getId ());
278+ }
75279 }
76- log .info (session + "์ ํด๋ผ์ด์ธํธ ์ ์ ํด์ " );
77280 }
78281
79- //๋ถ๋ถ ๋ฉ์์ง๋ฅผ ์ง์ํ๋์ง ์ฌ๋ถ๋ฅผ ๋ฐํ (์์ง๊น์ง๋ ํ์ ์์ผ๋ false)
80- //๋์ฉ๋(์ฌ์ง์ด๋ ๋์์ ๋ฑ)์ด ํ์ํ ๊ฒฝ์ฐ์๋ ๋ฐ๋ก ๊ตฌํํ ํ์๊ฐ ์์.
282+ // ๋ถ๋ถ ๋ฉ์์ง ์ง์ ์ฌ๋ถ
81283 @ Override
82284 public boolean supportsPartialMessages () {
83285 return false ;
84286 }
85-
86- private Long extractRoomId (WebSocketSession session ) {
87- Long roomId = null ;
88- String uri = Objects .requireNonNull (session .getUri ()).toString ();
89- String [] uriParts = uri .split ("/" );
90- // EX_URL) /chat/room/{roomId} ์ผ ๋ roomId ์ถ์ถ
91- // ๋์ด๋๋ค๋ฉด ์ ๋ณ๊ฒฝํด์ฃผ๋ฉด.. (์ผ๋จ ์์๋ก ์ค์ )
92- // if (uriParts.length >= 3 && uriParts[2].equals("room")) {
93- // roomId = Long.valueOf(uriParts[3]);
94- if (uriParts .length >= 4 && uriParts [2 ].equals ("msg" )) {
95- return Long .valueOf (uriParts [3 ]);
96- }
97- // /chat/room/join/{roomId}, /chat/room/out/{roomId}, /chat/room/delete/{roomId} ์ผ ๋ roomId ์ถ์ถ
98- if (uriParts .length >= 5 && uriParts [2 ].equals ("room" ) &&
99- (uriParts [3 ].equals ("join" ) || uriParts [3 ].equals ("out" ) || uriParts [3 ].equals ("delete" ))) {
100- roomId = Long .valueOf (uriParts [4 ]);
101- }
102- return roomId ;
103- }
104- //๋ฉ์ธ์ง ์ ์ก
105- public void sendMessage (String payload ) throws Exception {
106- for (List <WebSocketSession > sessions : chatRooms .values ()) {
107- for (WebSocketSession session : sessions ) {
108- TextMessage msg = new TextMessage (payload );
109- session .sendMessage (msg );
110- }
111- }
112- }
113287}
0 commit comments