웹소켓 공식문서
WebSocketConfig
•
채팅 접속 url 을 제외하고는 강사님의 설정과 동일하다.
•
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 라이브러리
•
•
나머지는 아래 주석을 참고해주세용
<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 를 받아오게 하고 싶었으나, 해결방법을 찾지 못했다.
◦
어디서부터 시작해야 할지 몰라 일단 두고, 시간날 때 틈틈히 알아보려 함.
◦
사실 이 기능을 굳이 수정 해야 할 필요가 있나? 싶은 생각도 든다..