//////
Search
💬

실시간 채팅 구현

유형
웹소켓
공부
공부
Date
2023/03/22
링크
비고
프로젝트 구현이 아닌 웹소켓 실시간 채팅 공부 내용입니다.

웹소켓 공식문서

WebSocketConfig

채팅 접속 url 을 제외하고는 강사님의 설정과 동일하다.
공식문서 26.4.2 Enable STOMP over WebSocket
package multi.second.project.infra.config; import org.springframework.context.annotation.Configuration; 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; @Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/topic"); config.setApplicationDestinationPrefixes("/app"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws/chat").withSockJS(); } }
Java
복사

SecurityConfig

채팅 또한 회원가입을 한 사람에게만 적용이 되도록 아래와 같이 수정해주었습니다.

ChatMessage

DTO 와 같은 역할을 한다.
package multi.second.project.domain.chat; public class ChatMessage { private MessageType type; private String content; private String sender; public enum MessageType { CHAT, JOIN, LEAVE } public MessageType getType() { return type; } public void setType(MessageType type) { this.type = type; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public String getSender() { return sender; } public void setSender(String sender) { this.sender = sender; } }
Java
복사

WebSocketEventListener

채팅방에서 발행하는 상황에 대한 내용을 담고 있다.
특정 상황이 발생하면 정보가 콘솔에 찍히도록 logger.info 를 이용함
package multi.second.project.domain.chat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.event.EventListener; import org.springframework.messaging.simp.SimpMessageSendingOperations; import org.springframework.messaging.simp.stomp.StompHeaderAccessor; import org.springframework.stereotype.Component; import org.springframework.web.socket.messaging.SessionConnectedEvent; import org.springframework.web.socket.messaging.SessionDisconnectEvent; @Component public class WebSocketEventListener { private static final Logger logger = LoggerFactory.getLogger(WebSocketEventListener.class); @Autowired private SimpMessageSendingOperations messagingTemplate; @EventListener public void handleWebSocketConnectListener(SessionConnectedEvent event) { logger.info("Received a new web socket connection"); } @EventListener public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) { StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage()); String username = (String) headerAccessor.getSessionAttributes().get("username"); if(username != null) { logger.info("User Disconnected : " + username); ChatMessage chatMessage = new ChatMessage(); chatMessage.setType(ChatMessage.MessageType.LEAVE); chatMessage.setSender(username); messagingTemplate.convertAndSend("/topic/public", chatMessage); } } }
Java
복사

html

자바스크립과 CSS 를 한 파일에 같이 넣어서 html 부분만 가져왔다.
그전에 다루거나 배웠던 요소들과 크게 다르지 않아 설명은 넘깁니다.
<body> <noscript> <h2>Sorry! Your browser doesn't support Javascript</h2> </noscript> <div id="username-page"> <div class="username-page-container"> <h1 class="title">Type your username</h1> <form id="usernameForm" name="usernameForm"> <div class="form-group"> <input type="text" id="name" placeholder="Username" autocomplete="off" class="form-control" /> </div> <div class="form-group"> <button type="submit" class="accent username-submit">Start Chatting</button> </div> </form> </div> </div> <div id="chat-page" class="hidden"> <div class="chat-container"> <div class="chat-header"> <h2>Spring WebSocket Chat Demo</h2> </div> <div class="connecting"> Connecting... </div> <ul id="messageArea"> </ul> <form id="messageForm" name="messageForm" nameForm="messageForm"> <div class="form-group"> <div class="input-group clearfix"> <input type="text" id="message" placeholder="Type a message..." autocomplete="off" class="form-control"/> <button type="submit" class="primary">Send</button> </div> </div> </form> </div> </div> </body>
HTML
복사

JavaScript

두 라이브러리는 추가해주어야 합니다.
SocketJs 라이브러리
Stomp 라이브러리
이해를 돕기 위한 공식문서 내용
공식문서 26.4.3 Flow of Messages
나머지는 아래 주석을 참고해주세용
<script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.1.4/sockjs.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script> <script> 'use strict'; // 문서의 id 를 찾아옴 var usernamePage = document.querySelector('#username-page'); var chatPage = document.querySelector('#chat-page'); var usernameForm = document.querySelector('#usernameForm'); var messageForm = document.querySelector('#messageForm'); var messageInput = document.querySelector('#message'); var messageArea = document.querySelector('#messageArea'); var connectingElement = document.querySelector('.connecting'); var stompClient = null; var username = null; var colors = [ '#2196F3', '#32c787', '#00BCD4', '#ff5652', '#ffc107', '#ff85af', '#FF9800', '#39bbb0' ]; function connect(event) { // username = document.querySelector('#name').value.trim(); if(username) { usernamePage.classList.add('hidden'); chatPage.classList.remove('hidden'); // 채팅방 생성 (localhost:8080/chat 에 들어가면 채팅방이 실행된다.) var socket = new SockJS('/ws/chat'); stompClient = Stomp.over(socket); stompClient.connect({}, onConnected, onError); } event.preventDefault(); } function onConnected() { // Subscribe to the Public Topic stompClient.subscribe('/topic/public', onMessageReceived); // Tell your username to the server // 채팅방 입장시 적은 username 을 서버에 알려준다. stompClient.send("/app/chat.addUser", {}, JSON.stringify({sender: username, type: 'JOIN'}) ) connectingElement.classList.add('hidden'); } // 채팅방 서버에 접속할 수 없을 때 나타나는 에러문구 function onError(error) { connectingElement.textContent = 'Could not connect to WebSocket server. Please refresh this page to try again!'; connectingElement.style.color = 'red'; } // 메세지 보내는 함수 function sendMessage(event) { var messageContent = messageInput.value.trim(); if(messageContent && stompClient) { var chatMessage = { sender: username, content: messageInput.value, type: 'CHAT' }; stompClient.send("/app/chat.sendMessage", {}, JSON.stringify(chatMessage)); messageInput.value = ''; } event.preventDefault(); } // 메세지 받는 함수 function onMessageReceived(payload) { var message = JSON.parse(payload.body); // ul 밑 li 로 주고받은 메세지들이 추가 // li, i, p 등에 대한 내용은 위를 참조 var messageElement = document.createElement('li'); if(message.type === 'JOIN') { // 채팅방에 들어왔을 때 나타나는 문구 messageElement.classList.add('event-message'); message.content = message.sender + ' joined!'; } else if (message.type === 'LEAVE') { // 채팅방을 떠났을 때 나타나는 문구 messageElement.classList.add('event-message'); message.content = message.sender + ' left!'; } else { // 채팅방을 들어오거나 나가지 않았을때 외의 상황은 대화상황 messageElement.classList.add('chat-message'); var avatarElement = document.createElement('i'); // 채팅방 프로필 안에 들어갈 글자 (username 의 첫 글자) var avatarText = document.createTextNode(message.sender[0]); avatarElement.appendChild(avatarText); avatarElement.style['background-color'] = getAvatarColor(message.sender); messageElement.appendChild(avatarElement); var usernameElement = document.createElement('span'); var usernameText = document.createTextNode(message.sender); usernameElement.appendChild(usernameText); messageElement.appendChild(usernameElement); } // 주고받은 메세지 목록 var textElement = document.createElement('p'); var messageText = document.createTextNode(message.content); textElement.appendChild(messageText); messageElement.appendChild(textElement); messageArea.appendChild(messageElement); messageArea.scrollTop = messageArea.scrollHeight; } // 프로필 색상 변경 function getAvatarColor(messageSender) { var hash = 0; for (var i = 0; i < messageSender.length; i++) { hash = 31 * hash + messageSender.charCodeAt(i); } var index = Math.abs(hash % colors.length); return colors[index]; } usernameForm.addEventListener('submit', connect, true) messageForm.addEventListener('submit', sendMessage, true) </script>
JavaScript
복사

해결해야 하는 부분 / 하고 싶은 부분

config 와 컨트롤러에 적힌 url 을 이용하여 실시간 채팅이 실행된다.
현재 진행되고 있는 프로젝트에 이를 적용할 경우, 화면 상단의 메세지 모양의 버튼을 클릭했을 때 실행이 되어야 한다.
url 은 diagram 을 보았을 때, 대략 planner/detail/{tp_idx} 정도가 될 것 같다.
플래너 관련 diagram
하지만 이렇게 해두면 이 플래너를 공개설정으로 해두었을 경우, 구경하러 온 모든 사람들이 실시간 채팅에 참여할 수 있게 된다. 때문에 참여 멤버에 관한 정보를 받아와서 참여 멤버가 아닌 경우
버튼을 클릭할 수 없게 처리
버튼의 이미지도 변경(회색톤으로)
처리를 해야 할 것 같다. (이를 어떻게 구현 해야 할지 고민해야겠다,,)
현재 입장할 때 닉네임을 적고 들어가야 하는데, 이와 같이 적을 필요없이 채팅방에 입장할 때 userId 를 받아오게 하고 싶었으나, 해결방법을 찾지 못했다.
어디서부터 시작해야 할지 몰라 일단 두고, 시간날 때 틈틈히 알아보려 함.
사실 이 기능을 굳이 수정 해야 할 필요가 있나? 싶은 생각도 든다..