diff --git a/build.gradle b/build.gradle index 68fd60f..8b1deda 100644 --- a/build.gradle +++ b/build.gradle @@ -37,10 +37,13 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-validation' - /** ๐Ÿงฉ WebSocket & Messaging **/ + /** ๐Ÿงฉ WebSocket & Messaging & RabbitMQ **/ implementation 'org.springframework.boot:spring-boot-starter-websocket' implementation 'org.springframework.security:spring-security-messaging' +// implementation 'org.springframework.boot:spring-boot-starter-amqp' +// implementation 'org.springframework.boot:spring-boot-starter-reactor-netty' + /** ๐Ÿ›ข Database **/ // runtimeOnly 'com.mysql:mysql-connector-j' // PostgreSQL + pgvector (์ถ”์ฒœ ์‹œ์Šคํ…œ์šฉ) diff --git a/src/main/java/com/goteego/chat/controller/ChatController.java b/src/main/java/com/goteego/chat/controller/ChatController.java index f7c6109..c173bae 100644 --- a/src/main/java/com/goteego/chat/controller/ChatController.java +++ b/src/main/java/com/goteego/chat/controller/ChatController.java @@ -10,11 +10,53 @@ import org.springframework.messaging.handler.annotation.DestinationVariable; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.messaging.simp.SimpMessageHeaderAccessor; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.stereotype.Controller; import org.springframework.transaction.annotation.Transactional; import java.security.Principal; +import java.util.Map; + +//@Controller +//@RequiredArgsConstructor +//@Slf4j +//public class ChatController { +// +// private final ChatService chatService; +// +// // 1:1 ์ฑ„ํŒ… +// @MessageMapping("/chat.direct.send/{roomId}") +// public void sendDirectMessage(@Payload DirectMessageRequest directMessageRequest, +// @DestinationVariable(value = "roomId") String roomId, +// Principal principal) { +// +// log.info("โœ…โœ…โœ… [ChatController] /chat.direct.send/{roomId} ๋ฉ”์„œ๋“œ ์ง„์ž…! โœ…โœ…โœ…"); +// User user = (User) ((UsernamePasswordAuthenticationToken) principal).getPrincipal(); +// chatService.sendDirectMessage(roomId, directMessageRequest, user.getId()); +// +// Long userId = user.getId(); +// log.warn("๋ฉ”์‹œ์ง€ ๋ฐœ์‹ ์ž ID: {}", userId); +// +// chatService.sendDirectMessage(roomId, directMessageRequest, userId); +// } +// +// // ๊ทธ๋ฃน ์ฑ„ํŒ… +// @MessageMapping("/chat.group.send/{roomId}") +// public void sendGroupMessage(@Payload GroupMessageRequest groupMessageRequest, +// @DestinationVariable(value = "roomId") String roomId, +// Principal principal) { +// User user = (User) ((UsernamePasswordAuthenticationToken) principal).getPrincipal(); +// chatService.sendGroupMessage(roomId, groupMessageRequest, user.getId()); +// Long userId = user.getId(); +// +// chatService.sendGroupMessage(roomId, groupMessageRequest, userId); +// +// } +// +//} + + @Controller @RequiredArgsConstructor @@ -23,20 +65,43 @@ public class ChatController { private final ChatService chatService; - // 1:1 ์ฑ„ํŒ… @MessageMapping("/chat.direct.send/{roomId}") - public void sendDirectMessage(@Payload DirectMessageRequest directMessageRequest, @DestinationVariable(value = "roomId") String roomId, Principal principal) { + public void sendDirectMessage( + @Payload DirectMessageRequest directMessageRequest, + @DestinationVariable(value = "roomId") String roomId, + SimpMessageHeaderAccessor headerAccessor + ) { + Map sessionAttributes = headerAccessor.getSessionAttributes(); + Object userIdObj = sessionAttributes.get("userId"); + + if (userIdObj == null) { + log.error("!!!!!!!!!! [ChatController] Session attributes์—์„œ userId๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. !!!!!!!!!!!"); + return; + } + + Long userId = (Long) userIdObj; + log.warn("๋ฉ”์‹œ์ง€ ๋ฐœ์‹ ์ž ID: {}", userId); - log.info("โœ…โœ…โœ… [ChatController] /chat.direct.send/{roomId} ๋ฉ”์„œ๋“œ ์ง„์ž…! โœ…โœ…โœ…"); - User user = (User) ((UsernamePasswordAuthenticationToken) principal).getPrincipal(); - chatService.sendDirectMessage(roomId, directMessageRequest, user.getId()); + chatService.sendDirectMessage(roomId, directMessageRequest, userId); } - // ๊ทธ๋ฃน ์ฑ„ํŒ… @MessageMapping("/chat.group.send/{roomId}") - public void sendGroupMessage(@Payload GroupMessageRequest groupMessageRequest, @DestinationVariable(value = "roomId") String roomId, Principal principal) { - User user = (User) ((UsernamePasswordAuthenticationToken) principal).getPrincipal(); - chatService.sendGroupMessage(roomId, groupMessageRequest, user.getId()); - } + public void sendGroupMessage( + @Payload GroupMessageRequest groupMessageRequest, + @DestinationVariable(value = "roomId") String roomId, + SimpMessageHeaderAccessor headerAccessor + ) { + Map sessionAttributes = headerAccessor.getSessionAttributes(); + Object userIdObj = sessionAttributes.get("userId"); + + if (userIdObj == null) { + log.error("!!!!!!!!!! [ChatController] Session attributes์—์„œ userId๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. !!!!!!!!!!!"); + return; + } + Long userId = (Long) userIdObj; + log.warn("๊ทธ๋ฃน ๋ฉ”์‹œ์ง€ ๋ฐœ์‹ ์ž ID: {}", userId); + + chatService.sendGroupMessage(roomId, groupMessageRequest, userId); + } } \ No newline at end of file diff --git a/src/main/java/com/goteego/chat/dto/message/DirectMessageRequest.java b/src/main/java/com/goteego/chat/dto/message/DirectMessageRequest.java index 1ff8925..558dd54 100644 --- a/src/main/java/com/goteego/chat/dto/message/DirectMessageRequest.java +++ b/src/main/java/com/goteego/chat/dto/message/DirectMessageRequest.java @@ -1,5 +1,6 @@ package com.goteego.chat.dto.message; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.goteego.chat.domain.enumerate.MessageType; import lombok.AllArgsConstructor; import lombok.Data; @@ -8,6 +9,7 @@ @Data @NoArgsConstructor @AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) public class DirectMessageRequest { private String content; // ํ•„์ˆ˜: ๋ฉ”์‹œ์ง€ ๋‚ด์šฉ private Long recipientId; // ํ•„์ˆ˜: ์ˆ˜์‹ ์ž ์•„์ด๋”” diff --git a/src/main/java/com/goteego/chat/dto/message/GroupMessageRequest.java b/src/main/java/com/goteego/chat/dto/message/GroupMessageRequest.java index a5bb7d8..7bad559 100644 --- a/src/main/java/com/goteego/chat/dto/message/GroupMessageRequest.java +++ b/src/main/java/com/goteego/chat/dto/message/GroupMessageRequest.java @@ -1,5 +1,6 @@ package com.goteego.chat.dto.message; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.goteego.chat.domain.enumerate.MessageType; import lombok.AllArgsConstructor; import lombok.Data; @@ -8,6 +9,7 @@ @Data @AllArgsConstructor @NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) public class GroupMessageRequest { private String content; private MessageType type; diff --git a/src/main/java/com/goteego/chat/service/redis/RedisSubscriber.java b/src/main/java/com/goteego/chat/service/redis/RedisSubscriber.java index f93463b..f05fb01 100644 --- a/src/main/java/com/goteego/chat/service/redis/RedisSubscriber.java +++ b/src/main/java/com/goteego/chat/service/redis/RedisSubscriber.java @@ -6,12 +6,14 @@ import com.goteego.chat.dto.message.NotificationResponse; import com.goteego.chat.dto.message.transfer.DirectMessageTransferDto; import com.goteego.chat.dto.message.transfer.NotificationTransferDto; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.data.redis.listener.ChannelTopic; import org.springframework.messaging.simp.SimpMessagingTemplate; +import org.springframework.messaging.simp.user.SimpUser; import org.springframework.stereotype.Service; +import org.springframework.messaging.simp.user.SimpUserRegistry; + @Service @Slf4j @@ -19,35 +21,29 @@ public class RedisSubscriber { private final ObjectMapper objectMapper; private final SimpMessagingTemplate messagingTemplate; - - // ChannelTopic์„ ์ฃผ์ž…๋ฐ›์•„ ํ† ํ”ฝ ์ด๋ฆ„์„ ๋น„๊ตํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. -// private final ChannelTopic chatTopic; private final ChannelTopic notificationTopic; private final ChannelTopic directChatTopic; private final ChannelTopic groupChatTopic; + private final SimpUserRegistry userRegistry; + + private static final String DIRECT_MESSAGE_PATH = "/queue/messages"; + private static final String GROUP_MESSAGE_PATH = "/sub/chat/room/"; + private static final String NOTIFICATION_PATH = "/queue/notifications"; + public RedisSubscriber(ObjectMapper objectMapper, SimpMessagingTemplate messagingTemplate, -// @Qualifier("chatTopic") ChannelTopic chatTopic, @Qualifier("directChatTopic") ChannelTopic directChatTopic, @Qualifier("groupChatTopic") ChannelTopic groupChatTopic, - @Qualifier("notificationTopic") ChannelTopic notificationTopic) { + @Qualifier("notificationTopic") ChannelTopic notificationTopic, + SimpUserRegistry userRegistry) { this.objectMapper = objectMapper; this.messagingTemplate = messagingTemplate; -// this.chatTopic = chatTopic; this.directChatTopic = directChatTopic; this.groupChatTopic = groupChatTopic; this.notificationTopic = notificationTopic; + this.userRegistry = userRegistry; } - private static final String DIRECT_MESSAGE_PATH = "/queue/messages"; - private static final String GROUP_MESSAGE_PATH = "/sub/chat/room/"; - private static final String NOTIFICATION_PATH = "/queue/notifications"; - - /** - * Redis์—์„œ ๋ฉ”์‹œ์ง€๊ฐ€ ๋ฐœํ–‰(publish)๋˜๋ฉด ๋Œ€๊ธฐํ•˜๊ณ  ์žˆ๋˜ Redis Subscriber๊ฐ€ ํ•ด๋‹น ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ›์•„ ์ฒ˜๋ฆฌ - * @param publishMessage ์ง๋ ฌํ™”๋œ ๋ฉ”์‹œ์ง€ ๊ฐ์ฒด - * @param channel ๋ฉ”์‹œ์ง€๊ฐ€ ๋ฐœํ–‰๋œ ์ฑ„๋„(ํ† ํ”ฝ) ์ด๋ฆ„ - */ public void sendMessage(String publishMessage, String channel) { try { log.info("โœ… Redis์—์„œ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹ , ์ฑ„๋„: {}", channel); @@ -75,6 +71,10 @@ public void sendMessage(String publishMessage, String channel) { } + // 1. SimpMessagingTemplate์€ ์ด ๋ฉ”์‹œ์ง€๋ฅผ ์ž์‹ ์˜ ์„œ๋ฒ„์— ์žˆ๋Š” ๋กœ์ปฌ SimpleBroker์—๊ฒŒ ์ „๋‹ฌ + // 2. ๋กœ์ปฌ SimpleBroker๋Š” ์ž์‹ ์—๊ฒŒ ์—ฐ๊ฒฐ๋œ ๋ชจ๋“  ์›น์†Œ์ผ“ ์„ธ์…˜์„ ๋ชจ๋‘ ์กฐํšŒ + // 3. ์„ธ์…˜๋“ค ์ค‘์—์„œ /sub/chat/room/... ๊ฒฝ๋กœ๋ฅผ ๊ตฌ๋…(subscribe)ํ•˜๊ณ  ์žˆ๋Š” ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ๋งŒ ๋ฉ”์‹œ์ง€๋ฅผ ์ „๋‹ฌ + // 4. ๋งŒ์•ฝ ์ด ์„œ๋ฒ„์— ํ•ด๋‹น ์ฑ„ํŒ…๋ฐฉ์„ ๊ตฌ๋… ์ค‘์ธ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ํ•œ ๋ช…๋„ ์—†๋‹ค๋ฉด, SimpleBroker๋Š” ์•„๋ฌด ์ผ๋„ ํ•˜์ง€ ์•Š๊ณ  ์กฐ์šฉํžˆ ์ž‘์—…์„ ์ข…๋ฃŒ private void handleGroupMessage(GroupMessageResponse message) { messagingTemplate.convertAndSend(GROUP_MESSAGE_PATH + message.getRoomId(), message); log.info("RedisSubscriber - Group message sent to /sub/chat/room/{}", message.getRoomId()); @@ -96,8 +96,41 @@ private void handleNotification(NotificationTransferDto dto) { log.info("RedisSubscriber - Notification sent to user {}", dto.getRecipientId()); } + + // 1. SimpMessagingTemplate์€ ์ž์‹ ์˜ ์„œ๋ฒ„์— ์—ฐ๊ฒฐ๋œ ๋ชจ๋“  ์›น์†Œ์ผ“ ์„ธ์…˜ ์ค‘์—์„œ, StompHandler์—์„œ ์„ค์ •ํ–ˆ๋˜ Principal์˜ ์ด๋ฆ„(name)์ด "userId"์™€ ์ผ์น˜ํ•˜๋Š” ์„ธ์…˜ ์กฐํšŒ + // 2. ์ผ์น˜ํ•˜๋Š” ์„ธ์…˜์„ ์ฐพ์œผ๋ฉด, ํ•ด๋‹น ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ๋งŒ ๋ฉ”์‹œ์ง€๋ฅผ ์ „๋‹ฌ + // 3. ๋งŒ์•ฝ ์ด ์„œ๋ฒ„์— ํ•ด๋‹น "userId"๋ฅผ ๊ฐ€์ง„ ์‚ฌ์šฉ์ž์˜ ์„ธ์…˜์ด ์—†๋‹ค๋ฉด, ์•„๋ฌด์—๊ฒŒ๋„ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ด์ง€ ์•Š๊ณ  ์กฐ์šฉํžˆ ์ž‘์—…์„ ์ข…๋ฃŒ private void sendToUser(Long userId, String destination, Object payload) { - log.info("Attempting to send to user: {}, destination: {}", userId, destination); - messagingTemplate.convertAndSendToUser(String.valueOf(userId), destination, payload); + + + String userIdStr = String.valueOf(userId); + String podName = System.getenv("HOSTNAME"); // ํ˜„์žฌ ์ปจํ…Œ์ด๋„ˆ(Pod)์˜ ์ด๋ฆ„ + + // ======================== [์ง„๋‹จ์šฉ ๋กœ๊ทธ ์‹œ์ž‘] ======================== + // ํ˜„์žฌ ์ด ์„œ๋ฒ„ ์ธ์Šคํ„ด์Šค์˜ SimpUserRegistry์— ๋“ฑ๋ก๋œ ๋ชจ๋“  ์‚ฌ์šฉ์ž ์ด๋ฆ„์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. + java.util.Set connectedUsers = userRegistry.getUsers().stream() + .map(org.springframework.messaging.simp.user.SimpUser::getName) + .collect(java.util.stream.Collectors.toSet()); + + log.warn("##### DIAGNOSTIC [Server: {}] #####", podName); + log.warn(" -> Checking for User: '{}'", userIdStr); + log.warn(" -> Currently registered users on THIS server: {}", connectedUsers); + // ======================== [์ง„๋‹จ์šฉ ๋กœ๊ทธ ๋] ========================== + + // 1. ์ผ๋‹จ ๋ฉ”์‹œ์ง€ ์ „์†ก์„ ์‹œ๋„ + // ใ„ด ํ•ด๋‹น ์‚ฌ์šฉ์ž๊ฐ€ ํ˜„์žฌ ์„œ๋ฒ„์— ์—†์œผ๋ฉด ์ด ์ฝ”๋“œ๋Š” ์•„๋ฌด ์ผ๋„ ์—†์Œ + messagingTemplate.convertAndSendToUser(userIdStr, destination, payload); + + // 2. ์‹ค์ œ ๋ฉ”์‹œ์ง€ ์ „์†ก์ด '์ด ์„œ๋ฒ„์—์„œ' ์ผ์–ด๋‚ฌ๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ๋กœ๊ทธ ์ถœ๋ ฅ + // ใ„ด userRegistry.getUsers()๋Š” ํ˜„์žฌ ์„œ๋ฒ„์— ์—ฐ๊ฒฐ๋œ ๋ชจ๋“  ์‚ฌ์šฉ์ž๋ฅผ ๋ฐ˜ํ™˜ + boolean wasSentFromThisServer = userRegistry.getUsers().stream() + .anyMatch(simpUser -> simpUser.getName().equals(userIdStr)); + + if (wasSentFromThisServer) { + log.info("โœ…โœ…โœ… [Server: {}] Successfully sent message to User '{}' on THIS server.", podName, userId); + } + + } + } \ No newline at end of file diff --git a/src/main/java/com/goteego/global/redis/RedisConfig.java b/src/main/java/com/goteego/global/redis/RedisConfig.java index 0c745b5..5a68609 100644 --- a/src/main/java/com/goteego/global/redis/RedisConfig.java +++ b/src/main/java/com/goteego/global/redis/RedisConfig.java @@ -65,19 +65,17 @@ public RedisTemplate redisTemplate(RedisConnectionFactory connec public RedisMessageListenerContainer redisMessageListener( RedisConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter, - @Qualifier("directChatTopic") ChannelTopic directChatTopic, // directChatTopic ์ฃผ์ž… - @Qualifier("groupChatTopic") ChannelTopic groupChatTopic, // groupChatTopic ์ฃผ์ž… -// @Qualifier("chatTopic") ChannelTopic chatTopic, - @Qualifier("notificationTopic") ChannelTopic notificationTopic - ) { + @Qualifier("directChatTopic") ChannelTopic directChatTopic, + @Qualifier("groupChatTopic") ChannelTopic groupChatTopic, + @Qualifier("notificationTopic") ChannelTopic notificationTopic) { + RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(connectionFactory); - // [chat] ์ฑ„๋„๋กœ๋ถ€ํ„ฐ ๋ฉ”์‹œ์ง€๊ฐ€ ์˜ค๋ฉด listenerAdapter๊ฐ€ ์ฒ˜๋ฆฌํ•˜๋„๋ก ์„ค์ • -// container.addMessageListener(listenerAdapter, chatTopic); + container.addMessageListener(listenerAdapter, directChatTopic); container.addMessageListener(listenerAdapter, groupChatTopic); - // [notification] ์ฑ„๋„๋กœ๋ถ€ํ„ฐ ๋ฉ”์‹œ์ง€๊ฐ€ ์˜ค๋ฉด listenerAdapter๊ฐ€ ์ฒ˜๋ฆฌํ•˜๋„๋ก ์„ค์ • container.addMessageListener(listenerAdapter, notificationTopic); + return container; } @@ -88,21 +86,14 @@ public MessageListenerAdapter listenerAdapter(RedisSubscriber subscriber) { return new MessageListenerAdapter(subscriber, "sendMessage"); } - - // Pub/Sub์—์„œ ์‚ฌ์šฉํ•  ์ฑ„ํŒ… ๊ด€๋ จ ๊ณต์šฉ ์ฑ„๋„ ์ •์˜ -// @Bean -// @Qualifier("chatTopic") -// public ChannelTopic chatTopic() { -// // ์—ฌ๊ธฐ์„œ๋Š” ๋ชจ๋“  ์ฑ„ํŒ… ๋ฉ”์‹œ์ง€๋ฅผ "chat"์ด๋ผ๋Š” ๋‹จ์ผ ํ† ํ”ฝ์œผ๋กœ ์ฒ˜๋ฆฌ -// return new ChannelTopic("chat"); -// } + // 1:1 ์ฑ„ํŒ… ๋ฉ”์‹œ์ง€ ์ „์šฉ ํ† ํ”ฝ @Bean @Qualifier("directChatTopic") public ChannelTopic directChatTopic() { return new ChannelTopic("directChat"); } - // [์ˆ˜์ •] ๊ทธ๋ฃน ์ฑ„ํŒ… ๋ฉ”์‹œ์ง€ ์ „์šฉ ํ† ํ”ฝ + // ๊ทธ๋ฃน ์ฑ„ํŒ… ๋ฉ”์‹œ์ง€ ์ „์šฉ ํ† ํ”ฝ @Bean @Qualifier("groupChatTopic") public ChannelTopic groupChatTopic() { @@ -110,7 +101,7 @@ public ChannelTopic groupChatTopic() { } - // Pub/Sub์—์„œ ์‚ฌ์šฉํ•  ์•Œ๋ฆผ ๊ด€๋ จ ๊ณต์šฉ ์ฑ„๋„ ์ •์˜ + // ์•Œ๋ฆผ ์ „์šฉ ํ† ํ”ฝ @Bean @Qualifier("notificationTopic") public ChannelTopic notificationTopic() { diff --git a/src/main/java/com/goteego/global/web/StompHandler.java b/src/main/java/com/goteego/global/web/StompHandler.java index 3bf4e29..797ab9b 100644 --- a/src/main/java/com/goteego/global/web/StompHandler.java +++ b/src/main/java/com/goteego/global/web/StompHandler.java @@ -22,24 +22,75 @@ import java.util.List; import java.util.Map; import java.util.Optional; - -/** - * StompHandler ํ๋ฆ„ - * ใ„ด JWTFilter์—์„œ WHITE LIST์— /ws๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ JWT ๊ฒ€์ฆ์„ ๊ฑด๋„ˆ๋œ€ - * ใ„ด /ws๋กœ WebSocket ์—ฐ๊ฒฐ์„ ์‹œ๋„ํ•˜๋ฉด HTTP ํ•ธ๋“œ์…ฐ์ดํฌ๊ฐ€ ๋ฐœ์ƒ -> STOMP ํ”„๋กœํ† ์ฝœ ๋‹จ๊ณ„์—์„œ StompHandler๊ฐ€ ๊ฐœ์ž… - * ใ„ด CONNECT ๋ช…๋ น์ด ์˜ค๋ฉด preSend() ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ - * ใ„ด ํ—ค๋”๋‚˜ ์ฟ ํ‚ค์—์„œ JWT ํ† ํฐ์„ ์ถ”์ถœํ•˜์—ฌ ์ง์ ‘ ๊ฒ€์ฆ - * ใ„ด ๊ฒ€์ฆ ์„ฑ๊ณต ์‹œ, accessor.setUser()๋กœ ์ธ์ฆ ์ •๋ณด๋ฅผ ์„ค์ • - * - * ๋ฐ”๋€๊ตฌ์กฐ - * ใ„ด js์—์„œ๋Š” WebSocket ํ•ธ๋“œ์…ฐ์ดํฌ๋ฅผ ์œ„ํ•ด "/api/ws-ticket"์˜ ๊ฒฝ๋กœ๋กœ API ํ˜ธ์ถœ - * ใ„ด ticket(UUID)๋ฅผ STOMP ํ—ค๋”์— Authorization: ticket ๊ตฌ์กฐ๋กœ ์„œ๋ฒ„์— ์ „๋‹ฌ - * ใ„ด StompHandler๋Š” ticket์„ ์ถ”์ถœํ•˜์—ฌ redis์—์„œ userId๋ฅผ ์กฐํšŒ - * ใ„ด userService์˜ getUser(userId)๋ฅผ ํ†ตํ•ด ์‹ค์ œ User ๊ฐ์ฒด ๋ฐ˜ํ™˜ - * ใ„ด UsernamePasswordAuthenticationToken(user) ์ˆ˜ํ–‰ - * ใ„ด accessor์— auth ์ €์žฅ - * ใ„ด ChatController์—์„œ Principal ์‚ฌ์šฉ - */ +// +//@Component +//@RequiredArgsConstructor +//@Slf4j +//public class StompHandler implements ChannelInterceptor { +// +// private final RedisTemplate redisTemplate; +// private final UserService userService; +// +// // ํด๋ผ์ด์–ธํŠธ์—์„œ STOMP ํ—ค๋”์— 'Authorization'์œผ๋กœ ํ‹ฐ์ผ“์„ ๋‹ด์•„ ๋ณด๋‚ด๊ธฐ๋กœ ์•ฝ์† (ํ‹ฐ์ผ“์˜ ๋‚ด์šฉ์€ ๋‹จ์ˆœํžˆ UUID) +// private static final String TICKET_HEADER = "Authorization"; +// +// @Override +// public Message preSend(Message message, MessageChannel channel) { +// StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message); +// +// // STOMP CONNECT ์š”์ฒญ์ผ ๋•Œ๋งŒ ์ธ์ฆ ์ฒ˜๋ฆฌ +// if (StompCommand.CONNECT.equals(accessor.getCommand())) { +// log.info("โœ… STOMP CONNECT ์š”์ฒญ ์ฒ˜๋ฆฌ ์‹œ์ž‘"); +// String podName = System.getenv("HOSTNAME"); // Pod ์ด๋ฆ„ ๊ฐ€์ ธ์˜ค๊ธฐ +// +// // 1. ํ—ค๋”์—์„œ ์ธ์ฆ ํ‹ฐ์ผ“ ์ถ”์ถœ +// String ticket = accessor.getFirstNativeHeader(TICKET_HEADER); +// if (ticket == null || ticket.isBlank()) { +// log.error("โŒ STOMP CONNECT ์—๋Ÿฌ: ์ธ์ฆ ํ‹ฐ์ผ“์ด ํ—ค๋”์— ์—†์Šต๋‹ˆ๋‹ค."); +// throw new AccessDeniedException("์ธ์ฆ ํ‹ฐ์ผ“์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค."); +// } +// +// // 2. Redis์—์„œ ํ‹ฐ์ผ“์œผ๋กœ ์‚ฌ์šฉ์ž ID ์กฐํšŒ +// String redisKey = "ws-ticket:" + ticket; +// String userIdStr = redisTemplate.opsForValue().get(redisKey); +// +// if (userIdStr == null) { +// log.error("โŒ STOMP CONNECT ์—๋Ÿฌ: ์œ ํšจํ•˜์ง€ ์•Š๊ฑฐ๋‚˜ ๋งŒ๋ฃŒ๋œ ํ‹ฐ์ผ“์ž…๋‹ˆ๋‹ค. Ticket: {}", ticket); +// throw new AccessDeniedException("์œ ํšจํ•˜์ง€ ์•Š๊ฑฐ๋‚˜ ๋งŒ๋ฃŒ๋œ ํ‹ฐ์ผ“์ž…๋‹ˆ๋‹ค."); +// } +// +// // 3. ์‚ฌ์šฉ๋œ ํ‹ฐ์ผ“์€ ์ฆ‰์‹œ ์‚ญ์ œ (์ผํšŒ์šฉ์œผ๋กœ ๋งŒ๋“ฆ) +// redisTemplate.delete(redisKey); +// log.info("โœ… ํ‹ฐ์ผ“ ์‚ฌ์šฉ ์™„๋ฃŒ ๋ฐ ์‚ญ์ œ. Ticket: {}", ticket); +// +// // 4. ์‚ฌ์šฉ์ž ์ •๋ณด๋กœ Principal ๊ฐ์ฒด ์ƒ์„ฑ ๋ฐ ์„ธ์…˜์— ๋“ฑ๋ก +// Long userId = Long.parseLong(userIdStr); +// User user = userService.getUserById(userId); +// log.warn("userId = {}", user.getId()); +// log.warn("podName = {}", podName); +// log.warn("user Name = {}", user.getNickname()); +// log.warn("user email = {}", user.getOauthInfo().getOauthEmail()); +// +// +// UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(user, null, Collections.emptyList()); +// accessor.setUser(authentication); +// +// Map sessionAttributes = accessor.getSessionAttributes(); +// if (sessionAttributes != null) { +// sessionAttributes.put("userId", userId); +// } +// +// if (user != null) { +// log.info("โœ… WebSocket ์ธ์ฆ ์„ฑ๊ณต. ์‚ฌ์šฉ์ž ID: {}, ์„ธ์…˜ ์‚ฌ์šฉ์ž: {}", userId, authentication.getName()); +// } +// } +// +// return message; +// } +//} + + +// 21:00 @Component @RequiredArgsConstructor @Slf4j @@ -58,6 +109,7 @@ public Message preSend(Message message, MessageChannel channel) { // STOMP CONNECT ์š”์ฒญ์ผ ๋•Œ๋งŒ ์ธ์ฆ ์ฒ˜๋ฆฌ if (StompCommand.CONNECT.equals(accessor.getCommand())) { log.info("โœ… STOMP CONNECT ์š”์ฒญ ์ฒ˜๋ฆฌ ์‹œ์ž‘"); + String podName = System.getenv("HOSTNAME"); // Pod ์ด๋ฆ„ ๊ฐ€์ ธ์˜ค๊ธฐ // 1. ํ—ค๋”์—์„œ ์ธ์ฆ ํ‹ฐ์ผ“ ์ถ”์ถœ String ticket = accessor.getFirstNativeHeader(TICKET_HEADER); @@ -83,16 +135,28 @@ public Message preSend(Message message, MessageChannel channel) { Long userId = Long.parseLong(userIdStr); User user = userService.getUserById(userId); log.warn("userId = {}", user.getId()); - log.warn("user Name = {}", user.getNickname()); - log.warn("user email = {}", user.getOauthInfo().getOauthEmail()); + log.warn("podName = {}", podName); - UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(user, null, Collections.emptyList()); + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( + userId.toString(), + null, + Collections.emptyList() + ); accessor.setUser(authentication); - if (user != null) { - log.info("โœ… WebSocket ์ธ์ฆ ์„ฑ๊ณต. ์‚ฌ์šฉ์ž ID: {}, ์„ธ์…˜ ์‚ฌ์šฉ์ž: {}", userId, authentication.getName()); + Map sessionAttributes = accessor.getSessionAttributes(); + if (sessionAttributes != null) { + // Spring Security์™€ WebSocket ๋ฉ”์‹œ์ง•์ด ์„ธ์…˜์„ ์‹๋ณ„ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•˜๋Š” ํ‘œ์ค€ ํ‚ค + sessionAttributes.put("user", authentication); + // ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ userId + sessionAttributes.put("userId", userId); + } else { + // ๋กœ๊น… ์ถ”๊ฐ€: ์„ธ์…˜ ์†์„ฑ์ด null์ธ ๋น„์ •์ƒ์ ์ธ ๊ฒฝ์šฐ๋ฅผ ๋Œ€๋น„ + log.error("!!!!!!!!!! StompHeaderAccessor.getSessionAttributes() is NULL !!!!!!!!!!"); } + + log.info("โœ… WebSocket ์ธ์ฆ ์„ฑ๊ณต. Principal Name: {}, Session UserID: {}", authentication.getName(), userId); } return message;