From 5e460325477b27e369fa252619197c8a4ba84675 Mon Sep 17 00:00:00 2001 From: iRedTea Date: Wed, 31 Aug 2022 10:49:25 +0300 Subject: [PATCH] Try to add online chat --- build.gradle | 2 + src/main/java/site/easystartup/web/chat/a | 0 .../web/chat/config/ChatController.java | 28 ++ .../web/chat/config/WebSocketConfig.java | 22 ++ .../java/site/easystartup/web/chat/domain/a | 0 .../web/chat/domain/model/ChatMessage.java | 15 ++ .../chat/domain/model/ChatNotification.java | 11 + .../web/chat/domain/model/ChatRoom.java | 11 + .../web/chat/domain/model/MessageStatus.java | 5 + .../chat/event/WebSocketEventListener.java | 35 +++ .../request/DiscussionMessageRequest.java | 2 - .../domain/request/DiscussionRequest.java | 6 +- .../web/forum/dto/DiscussionDto.java | 5 - .../web/forum/dto/DiscussionMessageDto.java | 2 - .../easystartup/web/forum/dto/TopicDto.java | 2 - src/main/resources/static/css/main.css | 251 ++++++++++++++++++ .../static/{logo => images}/logo_black.png | Bin src/main/resources/static/index.html | 49 ++++ src/main/resources/static/js/main.js | 90 +++++++ 19 files changed, 520 insertions(+), 16 deletions(-) create mode 100644 src/main/java/site/easystartup/web/chat/a create mode 100644 src/main/java/site/easystartup/web/chat/config/ChatController.java create mode 100644 src/main/java/site/easystartup/web/chat/config/WebSocketConfig.java create mode 100644 src/main/java/site/easystartup/web/chat/domain/a create mode 100644 src/main/java/site/easystartup/web/chat/domain/model/ChatMessage.java create mode 100644 src/main/java/site/easystartup/web/chat/domain/model/ChatNotification.java create mode 100644 src/main/java/site/easystartup/web/chat/domain/model/ChatRoom.java create mode 100644 src/main/java/site/easystartup/web/chat/domain/model/MessageStatus.java create mode 100644 src/main/java/site/easystartup/web/chat/event/WebSocketEventListener.java create mode 100644 src/main/resources/static/css/main.css rename src/main/resources/static/{logo => images}/logo_black.png (100%) create mode 100644 src/main/resources/static/index.html create mode 100644 src/main/resources/static/js/main.js diff --git a/build.gradle b/build.gradle index 7084371..2ab2d57 100644 --- a/build.gradle +++ b/build.gradle @@ -20,6 +20,8 @@ repositories { } dependencies { + implementation 'org.springframework:spring-websocket' + implementation 'org.springframework:spring-messaging:5.3.22' implementation 'org.modelmapper:modelmapper:3.1.0' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'com.google.guava:guava:31.1-jre' diff --git a/src/main/java/site/easystartup/web/chat/a b/src/main/java/site/easystartup/web/chat/a new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/site/easystartup/web/chat/config/ChatController.java b/src/main/java/site/easystartup/web/chat/config/ChatController.java new file mode 100644 index 0000000..79ee23d --- /dev/null +++ b/src/main/java/site/easystartup/web/chat/config/ChatController.java @@ -0,0 +1,28 @@ +package site.easystartup.web.chat.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.messaging.handler.annotation.SendTo; +import org.springframework.messaging.simp.SimpMessageHeaderAccessor; +import org.springframework.messaging.simp.SimpMessagingTemplate; +import org.springframework.stereotype.Controller; +import site.easystartup.web.chat.domain.model.ChatMessage; +import site.easystartup.web.chat.domain.model.ChatNotification; + +@Controller +public class ChatController { + @MessageMapping("/chat.sendMessage") + @SendTo("/topic/public") + public ChatMessage sendMessage(@Payload ChatMessage chatMessage) { + return chatMessage; + } + @MessageMapping("/chat.addUser") + @SendTo("/topic/public") + public ChatMessage addUser(@Payload ChatMessage chatMessage, + SimpMessageHeaderAccessor headerAccessor) { + // Add username in web socket session + headerAccessor.getSessionAttributes().put("username", chatMessage.getSender()); + return chatMessage; + } +} \ No newline at end of file diff --git a/src/main/java/site/easystartup/web/chat/config/WebSocketConfig.java b/src/main/java/site/easystartup/web/chat/config/WebSocketConfig.java new file mode 100644 index 0000000..34af5de --- /dev/null +++ b/src/main/java/site/easystartup/web/chat/config/WebSocketConfig.java @@ -0,0 +1,22 @@ +package site.easystartup.web.chat.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 registerStompEndpoints(StompEndpointRegistry registry) { + registry.addEndpoint("/ws").withSockJS(); + } + @Override + public void configureMessageBroker(MessageBrokerRegistry registry) { + registry.setApplicationDestinationPrefixes("/app"); + registry.enableSimpleBroker("/topic"); + } +} \ No newline at end of file diff --git a/src/main/java/site/easystartup/web/chat/domain/a b/src/main/java/site/easystartup/web/chat/domain/a new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/site/easystartup/web/chat/domain/model/ChatMessage.java b/src/main/java/site/easystartup/web/chat/domain/model/ChatMessage.java new file mode 100644 index 0000000..a307a78 --- /dev/null +++ b/src/main/java/site/easystartup/web/chat/domain/model/ChatMessage.java @@ -0,0 +1,15 @@ +package site.easystartup.web.chat.domain.model; + +import lombok.Data; + +@Data +public class ChatMessage { + private MessageType type; + private String content; + private String sender; + public enum MessageType { + CHAT, + JOIN, + LEAVE + } +} \ No newline at end of file diff --git a/src/main/java/site/easystartup/web/chat/domain/model/ChatNotification.java b/src/main/java/site/easystartup/web/chat/domain/model/ChatNotification.java new file mode 100644 index 0000000..fa60b73 --- /dev/null +++ b/src/main/java/site/easystartup/web/chat/domain/model/ChatNotification.java @@ -0,0 +1,11 @@ +package site.easystartup.web.chat.domain.model; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class ChatNotification { + private String id; + private String senderName; +} \ No newline at end of file diff --git a/src/main/java/site/easystartup/web/chat/domain/model/ChatRoom.java b/src/main/java/site/easystartup/web/chat/domain/model/ChatRoom.java new file mode 100644 index 0000000..70e96b0 --- /dev/null +++ b/src/main/java/site/easystartup/web/chat/domain/model/ChatRoom.java @@ -0,0 +1,11 @@ +package site.easystartup.web.chat.domain.model; + +import lombok.Data; + +@Data +public class ChatRoom { + private String id; + private String chatId; + private String senderId; + private String recipientId; +} \ No newline at end of file diff --git a/src/main/java/site/easystartup/web/chat/domain/model/MessageStatus.java b/src/main/java/site/easystartup/web/chat/domain/model/MessageStatus.java new file mode 100644 index 0000000..5734b9b --- /dev/null +++ b/src/main/java/site/easystartup/web/chat/domain/model/MessageStatus.java @@ -0,0 +1,5 @@ +package site.easystartup.web.chat.domain.model; + +public enum MessageStatus { + RECEIVED, DELIVERED +} \ No newline at end of file diff --git a/src/main/java/site/easystartup/web/chat/event/WebSocketEventListener.java b/src/main/java/site/easystartup/web/chat/event/WebSocketEventListener.java new file mode 100644 index 0000000..4823b97 --- /dev/null +++ b/src/main/java/site/easystartup/web/chat/event/WebSocketEventListener.java @@ -0,0 +1,35 @@ +package site.easystartup.web.chat.event; + +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; +import site.easystartup.web.chat.domain.model.ChatMessage; + +@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); + } + } +} \ No newline at end of file diff --git a/src/main/java/site/easystartup/web/forum/domain/request/DiscussionMessageRequest.java b/src/main/java/site/easystartup/web/forum/domain/request/DiscussionMessageRequest.java index 29e93ed..c287982 100644 --- a/src/main/java/site/easystartup/web/forum/domain/request/DiscussionMessageRequest.java +++ b/src/main/java/site/easystartup/web/forum/domain/request/DiscussionMessageRequest.java @@ -14,9 +14,7 @@ public class DiscussionMessageRequest { @Size(min = 3, max = 1024, message = "Text should be no less 3 and no more 1024 signs") @NotNull private String text; - @NotNull private String sender; - @NotNull private Date date; private boolean edited; private long discussion_id; diff --git a/src/main/java/site/easystartup/web/forum/domain/request/DiscussionRequest.java b/src/main/java/site/easystartup/web/forum/domain/request/DiscussionRequest.java index 7f02202..50244cd 100644 --- a/src/main/java/site/easystartup/web/forum/domain/request/DiscussionRequest.java +++ b/src/main/java/site/easystartup/web/forum/domain/request/DiscussionRequest.java @@ -17,16 +17,12 @@ public class DiscussionRequest { @Size(min = 3, max = 100, message = "Title should be no less 3 and no more 100 signs") @NotNull private String title; - @NotNull private String author; - @NotNull private Date date; - @NotNull private Date last_update; private boolean pinned; - @NotNull private DiscussionStatus status; - private long topic; @NotNull + private long topic; private List messages; } diff --git a/src/main/java/site/easystartup/web/forum/dto/DiscussionDto.java b/src/main/java/site/easystartup/web/forum/dto/DiscussionDto.java index 0a3375d..4e879b1 100644 --- a/src/main/java/site/easystartup/web/forum/dto/DiscussionDto.java +++ b/src/main/java/site/easystartup/web/forum/dto/DiscussionDto.java @@ -14,17 +14,12 @@ public class DiscussionDto { @Size(min = 3, max = 100, message = "Title should be no less 3 and no more 100 signs") @NotNull private String title; - @NotNull private String author; - @NotNull private Date date; - @NotNull private Date last_update; private boolean pinned; - @NotNull private DiscussionStatus status; private long topic; - @NotNull private List messages; } diff --git a/src/main/java/site/easystartup/web/forum/dto/DiscussionMessageDto.java b/src/main/java/site/easystartup/web/forum/dto/DiscussionMessageDto.java index 3c8bb79..ee217ad 100644 --- a/src/main/java/site/easystartup/web/forum/dto/DiscussionMessageDto.java +++ b/src/main/java/site/easystartup/web/forum/dto/DiscussionMessageDto.java @@ -12,9 +12,7 @@ public class DiscussionMessageDto { @Size(min = 3, max = 1024, message = "Text should be no less 3 and no more 1024 signs") @NotNull private String text; - @NotNull private String sender; - @NotNull private Date date; private boolean edited; private long discussion_id; diff --git a/src/main/java/site/easystartup/web/forum/dto/TopicDto.java b/src/main/java/site/easystartup/web/forum/dto/TopicDto.java index 8ed5fba..d98d3a0 100644 --- a/src/main/java/site/easystartup/web/forum/dto/TopicDto.java +++ b/src/main/java/site/easystartup/web/forum/dto/TopicDto.java @@ -12,10 +12,8 @@ public class TopicDto { @Size(min = 3, max = 100, message = "Title should be no less 3 and no more 100 signs") @NotNull private String title; - @NotNull private String author; private int size; private String description; - @NotNull private Date last_update; } diff --git a/src/main/resources/static/css/main.css b/src/main/resources/static/css/main.css new file mode 100644 index 0000000..345f48a --- /dev/null +++ b/src/main/resources/static/css/main.css @@ -0,0 +1,251 @@ +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +html,body { + height: 100%; + overflow: hidden; +} +body { + margin: 0; + padding: 0; + font-weight: 400; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 1rem; + line-height: 1.58; + color: #333; + background-color: #f4f4f4; + height: 100%; +} +body:before { + height: 50%; + width: 100%; + position: absolute; + top: 0; + left: 0; + background: #128ff2; + content: ""; + z-index: 0; +} +.clearfix:after { + display: block; + content: ""; + clear: both; +} +.hidden { + display: none; +} +.form-control { + width: 100%; + min-height: 38px; + font-size: 15px; + border: 1px solid #c8c8c8; +} +.form-group { + margin-bottom: 15px; +} +input { + padding-left: 10px; + outline: none; +} +h1, h2, h3, h4, h5, h6 { + margin-top: 20px; + margin-bottom: 20px; +} +h1 { + font-size: 1.7em; +} +a { + color: #128ff2; +} +button { + box-shadow: none; + border: 1px solid transparent; + font-size: 14px; + outline: none; + line-height: 100%; + white-space: nowrap; + vertical-align: middle; + padding: 0.6rem 1rem; + border-radius: 2px; + transition: all 0.2s ease-in-out; + cursor: pointer; + min-height: 38px; +} +button.default { + background-color: #e8e8e8; + color: #333; + box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.12); +} +button.primary { + background-color: #128ff2; + box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.12); + color: #fff; +} +button.accent { + background-color: #ff4743; + box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.12); + color: #fff; +} +#username-page { + text-align: center; +} +.username-page-container { + background: #fff; + box-shadow: 0 1px 11px rgba(0, 0, 0, 0.27); + border-radius: 2px; + width: 100%; + max-width: 500px; + display: inline-block; + margin-top: 42px; + vertical-align: middle; + position: relative; + padding: 35px 55px 35px; + min-height: 250px; + position: absolute; + top: 50%; + left: 0; + right: 0; + margin: 0 auto; + margin-top: -160px; +} +.username-page-container .username-submit { + margin-top: 10px; +} +#chat-page { + position: relative; + height: 100%; +} +.chat-container { + max-width: 700px; + margin-left: auto; + margin-right: auto; + background-color: #fff; + box-shadow: 0 1px 11px rgba(0, 0, 0, 0.27); + margin-top: 30px; + height: calc(100% - 60px); + max-height: 600px; + position: relative; +} +#chat-page ul { + list-style-type: none; + background-color: #FFF; + margin: 0; + overflow: auto; + overflow-y: scroll; + padding: 0 20px 0px 20px; + height: calc(100% - 150px); +} +#chat-page #messageForm { + padding: 20px; +} +#chat-page ul li { + line-height: 1.5rem; + padding: 10px 20px; + margin: 0; + border-bottom: 1px solid #f4f4f4; +} +#chat-page ul li p { + margin: 0; +} +#chat-page .event-message { + width: 100%; + text-align: center; + clear: both; +} +#chat-page .event-message p { + color: #777; + font-size: 14px; + word-wrap: break-word; +} +#chat-page .chat-message { + padding-left: 68px; + position: relative; +} +#chat-page .chat-message i { + position: absolute; + width: 42px; + height: 42px; + overflow: hidden; + left: 10px; + display: inline-block; + vertical-align: middle; + font-size: 18px; + line-height: 42px; + color: #fff; + text-align: center; + border-radius: 50%; + font-style: normal; + text-transform: uppercase; +} +#chat-page .chat-message span { + color: #333; + font-weight: 600; +} +#chat-page .chat-message p { + color: #43464b; +} +#messageForm .input-group input { + float: left; + width: calc(100% - 85px); +} +#messageForm .input-group button { + float: left; + width: 80px; + height: 38px; + margin-left: 5px; +} +.chat-header { + text-align: center; + padding: 15px; + border-bottom: 1px solid #ececec; +} +.chat-header h2 { + margin: 0; + font-weight: 500; +} +.connecting { + padding-top: 5px; + text-align: center; + color: #777; + position: absolute; + top: 65px; + width: 100%; +} +@media screen and (max-width: 730px) { + .chat-container { + margin-left: 10px; + margin-right: 10px; + margin-top: 10px; + } +} +@media screen and (max-width: 480px) { + .chat-container { + height: calc(100% - 30px); + } + .username-page-container { + width: auto; + margin-left: 15px; + margin-right: 15px; + padding: 25px; + } + #chat-page ul { + height: calc(100% - 120px); + } + #messageForm .input-group button { + width: 65px; + } + #messageForm .input-group input { + width: calc(100% - 70px); + } + .chat-header { + padding: 10px; + } + .connecting { + top: 60px; + } + .chat-header h2 { + font-size: 1.1em; + } +} \ No newline at end of file diff --git a/src/main/resources/static/logo/logo_black.png b/src/main/resources/static/images/logo_black.png similarity index 100% rename from src/main/resources/static/logo/logo_black.png rename to src/main/resources/static/images/logo_black.png diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html new file mode 100644 index 0000000..694e4ea --- /dev/null +++ b/src/main/resources/static/index.html @@ -0,0 +1,49 @@ + + + + + Spring Boot WebSocket Chat Application + + + + +
+
+

Type your username

+
+
+ +
+
+ +
+
+
+
+ + + + + + \ No newline at end of file diff --git a/src/main/resources/static/js/main.js b/src/main/resources/static/js/main.js new file mode 100644 index 0000000..f86bada --- /dev/null +++ b/src/main/resources/static/js/main.js @@ -0,0 +1,90 @@ +'use strict'; +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'); + var socket = new SockJS('/ws'); + 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 + 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); + 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'); + 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) \ No newline at end of file