From 9d0e109d76a937c7eee99fb502f242401c70fcca Mon Sep 17 00:00:00 2001 From: yhpark95 <98851575+yhpark95@users.noreply.github.com> Date: Thu, 21 Sep 2023 12:13:32 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20WebSocket,=20STOMP,=20Redis=20pub/sub?= =?UTF-8?q?=EC=9D=84=20=ED=99=9C=EC=9A=A9=ED=95=9C=20=EC=B1=84=ED=8C=85=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84(=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=EC=A0=9C=EC=99=B8)=20#81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chat/controller/ChatController.java | 6 +++ .../kr/codesquad/chat/dto/ChatMapper.java | 4 ++ .../codesquad/chat/service/ChatService.java | 37 +++++++------------ .../chat/service/RedisSubscriber.java | 36 ++++++++++++++++++ .../kr/codesquad/core/config/RedisConfig.java | 30 ++++++++++++++- .../core/config/WebSocketConfig.java | 24 +++++++----- 6 files changed, 102 insertions(+), 35 deletions(-) create mode 100644 be/src/main/java/kr/codesquad/chat/service/RedisSubscriber.java diff --git a/be/src/main/java/kr/codesquad/chat/controller/ChatController.java b/be/src/main/java/kr/codesquad/chat/controller/ChatController.java index 5e138ab0f..87df78b39 100644 --- a/be/src/main/java/kr/codesquad/chat/controller/ChatController.java +++ b/be/src/main/java/kr/codesquad/chat/controller/ChatController.java @@ -4,11 +4,13 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import kr.codesquad.chat.dto.request.ChatRoomCreateRequest; +import kr.codesquad.chat.dto.request.SendMessageRequest; import kr.codesquad.chat.dto.response.ChatRoomCreateResponse; import kr.codesquad.chat.service.ChatService; import kr.codesquad.util.Constants; @@ -29,4 +31,8 @@ public ResponseEntity createChatRoom( .body(chatService.createRoom(chatRoomCreateRequest, loginId)); } + @MessageMapping("/comm/message") + public void message(SendMessageRequest message) { + chatService.sendMessage(message); + } } diff --git a/be/src/main/java/kr/codesquad/chat/dto/ChatMapper.java b/be/src/main/java/kr/codesquad/chat/dto/ChatMapper.java index ff7316a27..3b6dd2c11 100644 --- a/be/src/main/java/kr/codesquad/chat/dto/ChatMapper.java +++ b/be/src/main/java/kr/codesquad/chat/dto/ChatMapper.java @@ -5,7 +5,9 @@ import org.mapstruct.factory.Mappers; import kr.codesquad.chat.dto.request.ChatRoomCreateRequest; +import kr.codesquad.chat.dto.request.SendMessageRequest; import kr.codesquad.chat.dto.response.ChatRoomCreateResponse; +import kr.codesquad.chat.entity.ChatMessage; import kr.codesquad.chat.entity.ChatRoom; @Mapper @@ -17,4 +19,6 @@ public interface ChatMapper { @Mapping(target = "chatRoomId", source = "id") ChatRoomCreateResponse toChatRoomCreateResponse(ChatRoom chatRoom); + + ChatMessage toChatMessage(SendMessageRequest sendMessageRequest); } diff --git a/be/src/main/java/kr/codesquad/chat/service/ChatService.java b/be/src/main/java/kr/codesquad/chat/service/ChatService.java index c233d7e1b..e6b1ac867 100644 --- a/be/src/main/java/kr/codesquad/chat/service/ChatService.java +++ b/be/src/main/java/kr/codesquad/chat/service/ChatService.java @@ -1,14 +1,9 @@ package kr.codesquad.chat.service; -import java.io.IOException; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.listener.ChannelTopic; import org.springframework.stereotype.Service; -import org.springframework.web.socket.TextMessage; -import org.springframework.web.socket.WebSocketSession; +import org.springframework.transaction.annotation.Transactional; import com.fasterxml.jackson.databind.ObjectMapper; @@ -16,6 +11,7 @@ import kr.codesquad.chat.dto.request.ChatRoomCreateRequest; import kr.codesquad.chat.dto.request.SendMessageRequest; import kr.codesquad.chat.dto.response.ChatRoomCreateResponse; +import kr.codesquad.chat.entity.ChatMessage; import kr.codesquad.chat.repository.ChatMessageRepository; import kr.codesquad.chat.repository.ChatRoomRepository; import kr.codesquad.user.entity.User; @@ -32,7 +28,8 @@ public class ChatService { private final ChatMessageRepository chatMessageRepository; private final UserRepository userRepository; private final ObjectMapper objectMapper; - private final ConcurrentMap> sessionMap = new ConcurrentHashMap<>(); + private final ChannelTopic channelTopic; + private final RedisTemplate redisTemplate; public ChatRoomCreateResponse createRoom(ChatRoomCreateRequest chatRoomCreateRequest, String loginId) { User user = userRepository.findByLoginId(loginId); @@ -40,22 +37,14 @@ public ChatRoomCreateResponse createRoom(ChatRoomCreateRequest chatRoomCreateReq chatRoomRepository.save(ChatMapper.INSTANCE.toChatRoom(chatRoomCreateRequest, user.getId()))); } - public void sendMessage(Long chatRoomId, WebSocketSession session, SendMessageRequest message) { - if (!sessionMap.containsKey(chatRoomId)) { - sessionMap.put(chatRoomId, new HashSet<>()); - } - Set sessions = sessionMap.get(chatRoomId); - sessions.add(session); - sessionMap.put(chatRoomId, sessions); - sessions.parallelStream().forEach(s -> sendMessage(s, message)); - } + @Transactional + public void sendMessage(SendMessageRequest chatMessageRequest) { + + //채팅 생성 및 저장 + ChatMessage chatMessage = chatMessageRepository.save(ChatMapper.INSTANCE.toChatMessage(chatMessageRequest)); + String topic = channelTopic.getTopic(); - private void sendMessage(WebSocketSession session, T message) { - try { - session.sendMessage(new TextMessage(objectMapper.writeValueAsString(message))); - } catch (IOException e) { - log.error(e.getMessage(), e); - } + redisTemplate.convertAndSend(topic, chatMessageRequest); } } diff --git a/be/src/main/java/kr/codesquad/chat/service/RedisSubscriber.java b/be/src/main/java/kr/codesquad/chat/service/RedisSubscriber.java new file mode 100644 index 000000000..c631e1158 --- /dev/null +++ b/be/src/main/java/kr/codesquad/chat/service/RedisSubscriber.java @@ -0,0 +1,36 @@ +package kr.codesquad.chat.service; + +import org.springframework.data.redis.connection.Message; +import org.springframework.data.redis.connection.MessageListener; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.messaging.simp.SimpMessageSendingOperations; +import org.springframework.stereotype.Service; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import kr.codesquad.chat.dto.request.SendMessageRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RequiredArgsConstructor +@Service +public class RedisSubscriber implements MessageListener { + private final ObjectMapper objectMapper; + private final RedisTemplate redisTemplate; + private final SimpMessageSendingOperations messagingTemplate; + + @Override + public void onMessage(Message message, byte[] pattern) { + try { + String publishMessage = (String)redisTemplate.getStringSerializer().deserialize(message.getBody()); + + SendMessageRequest roomMessage = objectMapper.readValue(publishMessage, SendMessageRequest.class); + + messagingTemplate.convertAndSend("/sub/chat/room/" + roomMessage.getChatRoomId(), roomMessage.getContent()); + + } catch (Exception e) { + throw new RuntimeException("뭐"); + } + } +} diff --git a/be/src/main/java/kr/codesquad/core/config/RedisConfig.java b/be/src/main/java/kr/codesquad/core/config/RedisConfig.java index 7d4751b7e..a053b164a 100644 --- a/be/src/main/java/kr/codesquad/core/config/RedisConfig.java +++ b/be/src/main/java/kr/codesquad/core/config/RedisConfig.java @@ -5,9 +5,14 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.listener.ChannelTopic; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; +import org.springframework.data.redis.listener.adapter.MessageListenerAdapter; import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; +import kr.codesquad.chat.service.RedisSubscriber; import kr.codesquad.redis.RedisProperties; import lombok.RequiredArgsConstructor; @@ -28,8 +33,29 @@ public RedisTemplate redisTemplate() { RedisTemplate redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory()); redisTemplate.setKeySerializer(new StringRedisSerializer()); - redisTemplate.setValueSerializer(new StringRedisSerializer()); - + redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(String.class)); return redisTemplate; } + + @Bean + public RedisMessageListenerContainer redisMessageListenerContainer( + RedisConnectionFactory connectionFactory, + MessageListenerAdapter listenerAdapter, + ChannelTopic channelTopic + ) { + RedisMessageListenerContainer container = new RedisMessageListenerContainer(); + container.setConnectionFactory(connectionFactory); + container.addMessageListener(listenerAdapter, channelTopic); + return container; + } + + @Bean + public MessageListenerAdapter listenerAdapter(RedisSubscriber subscriber) { + return new MessageListenerAdapter(subscriber, "onMessage"); + } + + @Bean + public ChannelTopic channelTopic() { // (4) + return new ChannelTopic("chatRoom"); + } } diff --git a/be/src/main/java/kr/codesquad/core/config/WebSocketConfig.java b/be/src/main/java/kr/codesquad/core/config/WebSocketConfig.java index 584469a25..45f616223 100644 --- a/be/src/main/java/kr/codesquad/core/config/WebSocketConfig.java +++ b/be/src/main/java/kr/codesquad/core/config/WebSocketConfig.java @@ -1,23 +1,29 @@ package kr.codesquad.core.config; import org.springframework.context.annotation.Configuration; -import org.springframework.web.socket.config.annotation.EnableWebSocket; -import org.springframework.web.socket.config.annotation.WebSocketConfigurer; -import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; +import org.springframework.messaging.simp.config.MessageBrokerRegistry; +import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; +import org.springframework.web.socket.config.annotation.StompEndpointRegistry; +import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; -import kr.codesquad.core.websocket.WebSocketHandler; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @Slf4j @Configuration @RequiredArgsConstructor -@EnableWebSocket -public class WebSocketConfig implements WebSocketConfigurer { - private final WebSocketHandler webSocketHandler; +@EnableWebSocketMessageBroker +public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override - public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { - registry.addHandler(webSocketHandler, "/ws/chat").setAllowedOrigins("*"); + public void registerStompEndpoints(StompEndpointRegistry registry) { + registry.addEndpoint("/ws/chat") + .setAllowedOriginPatterns("*"); + } + + @Override + public void configureMessageBroker(MessageBrokerRegistry registry) { + registry.enableSimpleBroker("/sub"); + registry.setApplicationDestinationPrefixes("/pub"); } }