From 6b265ec42a5c499ef159f5634d0e8ecafd11bf59 Mon Sep 17 00:00:00 2001 From: Curry4182 Date: Wed, 27 Dec 2023 18:20:47 +0900 Subject: [PATCH 1/9] =?UTF-8?q?feat:=20=EC=B1=84=ED=8C=85=EC=9D=84=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20stomp=20=EC=84=A4=EC=A0=95=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/config/StompWebSocketConfig.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 bucketback-api/src/main/java/com/programmers/bucketback/global/config/StompWebSocketConfig.java diff --git a/bucketback-api/src/main/java/com/programmers/bucketback/global/config/StompWebSocketConfig.java b/bucketback-api/src/main/java/com/programmers/bucketback/global/config/StompWebSocketConfig.java new file mode 100644 index 000000000..ee3a5a3df --- /dev/null +++ b/bucketback-api/src/main/java/com/programmers/bucketback/global/config/StompWebSocketConfig.java @@ -0,0 +1,25 @@ +package com.programmers.bucketback.global.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 StompWebSocketConfig implements WebSocketMessageBrokerConfigurer { + + @Override + public void registerStompEndpoints(final StompEndpointRegistry registry) { + registry.addEndpoint("/ws-stomp") + .setAllowedOriginPatterns("*") + .withSockJS(); + } + + @Override + public void configureMessageBroker(final MessageBrokerRegistry registry) { + registry.enableSimpleBroker("/subscribe"); + registry.setApplicationDestinationPrefixes("/app"); + } +} From b8cc6c1d03d09f9f5fd5fef42fd23b7ecc161557 Mon Sep 17 00:00:00 2001 From: Curry4182 Date: Wed, 27 Dec 2023 18:23:13 +0900 Subject: [PATCH 2/9] =?UTF-8?q?build:=20=EC=B1=84=ED=8C=85=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80=EB=A5=BC=20=EC=9C=84=ED=95=B4=20?= =?UTF-8?q?api=20=EB=AA=A8=EB=93=88=EC=97=90=20=EC=9D=98=EC=A1=B4=EC=84=B1?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bucketback-api/build.gradle | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bucketback-api/build.gradle b/bucketback-api/build.gradle index 2471a5670..6eb760cf2 100644 --- a/bucketback-api/build.gradle +++ b/bucketback-api/build.gradle @@ -33,6 +33,11 @@ dependencies { implementation 'io.jsonwebtoken:jjwt-api:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' + + // chat + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + implementation 'org.springframework.boot:spring-boot-starter-websocket' + implementation 'org.webjars:stomp-websocket:2.3.3' } jar { From 7cfae38ff9c0bfb76ae52e40fdf19a9cbcb4429e Mon Sep 17 00:00:00 2001 From: Curry4182 Date: Wed, 27 Dec 2023 18:24:44 +0900 Subject: [PATCH 3/9] =?UTF-8?q?feat:=20=EC=8B=A4=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EC=B1=84=ED=8C=85=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domains/chat/api/ChatController.java | 49 +++++++++++++++++++ .../api/dto/request/ChatCreateRequest.java | 8 +++ .../api/dto/response/ChatGetResponse.java | 10 ++++ 3 files changed, 67 insertions(+) create mode 100644 bucketback-api/src/main/java/com/programmers/bucketback/domains/chat/api/ChatController.java create mode 100644 bucketback-api/src/main/java/com/programmers/bucketback/domains/chat/api/dto/request/ChatCreateRequest.java create mode 100644 bucketback-api/src/main/java/com/programmers/bucketback/domains/chat/api/dto/response/ChatGetResponse.java diff --git a/bucketback-api/src/main/java/com/programmers/bucketback/domains/chat/api/ChatController.java b/bucketback-api/src/main/java/com/programmers/bucketback/domains/chat/api/ChatController.java new file mode 100644 index 000000000..9ba3b2015 --- /dev/null +++ b/bucketback-api/src/main/java/com/programmers/bucketback/domains/chat/api/ChatController.java @@ -0,0 +1,49 @@ +package com.programmers.bucketback.domains.chat.api; + +import java.time.LocalDateTime; + +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.messaging.simp.SimpMessagingTemplate; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; + +import com.programmers.bucketback.domains.chat.api.dto.request.ChatCreateRequest; +import com.programmers.bucketback.domains.chat.api.dto.response.ChatGetResponse; + +import lombok.RequiredArgsConstructor; + +@Controller +@RequiredArgsConstructor +public class ChatController { + + private final SimpMessagingTemplate simpMessagingTemplate; + + @MessageMapping("/publish/messages") + public void sendMessage(final ChatCreateRequest request) { + LocalDateTime createdAt = LocalDateTime.now(); + ChatGetResponse chatGetResponse = new ChatGetResponse( + request.message(), + request.userNickname(), + createdAt + ); + + simpMessagingTemplate.convertAndSend("/subscribe/rooms/" + request.chatRoomId(), chatGetResponse); + } + + @GetMapping("/chat/{roomId}") + public String chatPage( + @PathVariable("roomId") final int roomId, + final Model model + ) { + model.addAttribute("roomId", roomId); + + return "chat"; + } + + @GetMapping("/chat-rooms") + public String chatRoomPage() { + return "chat-rooms"; + } +} diff --git a/bucketback-api/src/main/java/com/programmers/bucketback/domains/chat/api/dto/request/ChatCreateRequest.java b/bucketback-api/src/main/java/com/programmers/bucketback/domains/chat/api/dto/request/ChatCreateRequest.java new file mode 100644 index 000000000..33568799d --- /dev/null +++ b/bucketback-api/src/main/java/com/programmers/bucketback/domains/chat/api/dto/request/ChatCreateRequest.java @@ -0,0 +1,8 @@ +package com.programmers.bucketback.domains.chat.api.dto.request; + +public record ChatCreateRequest( + String userNickname, + Long chatRoomId, + String message +) { +} diff --git a/bucketback-api/src/main/java/com/programmers/bucketback/domains/chat/api/dto/response/ChatGetResponse.java b/bucketback-api/src/main/java/com/programmers/bucketback/domains/chat/api/dto/response/ChatGetResponse.java new file mode 100644 index 000000000..053d8bc40 --- /dev/null +++ b/bucketback-api/src/main/java/com/programmers/bucketback/domains/chat/api/dto/response/ChatGetResponse.java @@ -0,0 +1,10 @@ +package com.programmers.bucketback.domains.chat.api.dto.response; + +import java.time.LocalDateTime; + +public record ChatGetResponse( + String message, + String sendUserName, + LocalDateTime createdAt +) { +} From 8e4c9b760fc18bac1e22f992dba003c7e4d5bb4e Mon Sep 17 00:00:00 2001 From: Curry4182 Date: Wed, 27 Dec 2023 18:26:47 +0900 Subject: [PATCH 4/9] =?UTF-8?q?feat:=20=EC=B1=84=ED=8C=85=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20UI=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/static/css/chat.css | 158 ++++++++++++++++++ .../main/resources/templates/chat-rooms.html | 25 +++ .../src/main/resources/templates/chat.html | 41 +++++ 3 files changed, 224 insertions(+) create mode 100644 bucketback-api/src/main/resources/static/css/chat.css create mode 100644 bucketback-api/src/main/resources/templates/chat-rooms.html create mode 100644 bucketback-api/src/main/resources/templates/chat.html diff --git a/bucketback-api/src/main/resources/static/css/chat.css b/bucketback-api/src/main/resources/static/css/chat.css new file mode 100644 index 000000000..bc1e47467 --- /dev/null +++ b/bucketback-api/src/main/resources/static/css/chat.css @@ -0,0 +1,158 @@ +body, html { + margin: 0; + padding: 0; + font-family: 'Segoe UI', Arial, sans-serif; + background-color: #EDEDED; +} + +.chat-rooms-container, .chat-container { + width: 400px; + margin: 50px auto; + background-color: #fff; + border-radius: 10px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); +} + +.chat-rooms-container h2, .chat-header h3 { + background-color: #0088cc; + color: #fff; + padding: 20px; + margin: 0; + border-top-left-radius: 10px; + border-top-right-radius: 10px; +} + +.rooms-list, .chat-messages { + height: 400px; + overflow-y: auto; + padding: 10px; + border-top: 1px solid #ddd; +} + +.room { + padding: 10px; + border-bottom: 1px solid #ddd; + color: #333; + text-decoration: none; + display: block; +} + +.room:hover { + background-color: #f4f4f4; +} + +.chat-container { + width: 400px; + margin: 50px auto; + background-color: #fff; + border-radius: 10px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); +} + +.chat-header { + background-color: #0088cc; + color: #fff; + padding: 20px; + margin: 0; + border-top-left-radius: 10px; + border-top-right-radius: 10px; + text-align: center; +} + +.chat-messages { + height: 500px; + overflow-y: auto; + padding: 10px; + border-top: 1px solid #ddd; +} + +.chat-message { + display: flex; + margin-bottom: 10px; + align-items: center; +} + +.my-message .message-avatar { + order: 2; + margin-left: 10px; +} + +.other-message .message-avatar { + margin-right: 10px; +} + +.my-message .message-info, +.other-message .message-info { + max-width: 80%; +} + +.message-avatar { + width: 50px; + height: 50px; + border-radius: 50%; + background-position: center center; + background-size: contain; +} + +.message-info { + text-align: left; +} + +.message-author { + font-size: 14px; + color: #333; + font-weight: 600; +} + +.message-time { + font-size: 12px; + color: #999; +} + +.message-text { + background: #DCF8C6; + padding: 10px; + border-radius: 10px; + margin: 2px 0; +} + +.my-message { + flex-direction: row-reverse; +} + +.my-message .message-text { + background: #0088cc; + color: white; + text-align: right; +} + +.other-message .message-text { + background: #f4f4f4; +} + +.chat-input { + display: flex; + padding: 10px; + border-top: 1px solid #ddd; +} + +.chat-input input { + flex: 1; + padding: 10px; + border: 1px solid #ccc; + border-radius: 4px; + margin-right: 10px; +} + +.chat-input button { + padding: 10px 20px; + background-color: #0088cc; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; +} + +.chat-input button:hover { + background-color: #006699; +} diff --git a/bucketback-api/src/main/resources/templates/chat-rooms.html b/bucketback-api/src/main/resources/templates/chat-rooms.html new file mode 100644 index 000000000..7cf7271a0 --- /dev/null +++ b/bucketback-api/src/main/resources/templates/chat-rooms.html @@ -0,0 +1,25 @@ + + + + + + Chat Rooms + + + + + + + \ No newline at end of file diff --git a/bucketback-api/src/main/resources/templates/chat.html b/bucketback-api/src/main/resources/templates/chat.html new file mode 100644 index 000000000..e70d31089 --- /dev/null +++ b/bucketback-api/src/main/resources/templates/chat.html @@ -0,0 +1,41 @@ + + + + + + Group Chat + + + +
+
+

Group Chat Room

+
+
+ +
+
+ + +
+
+ + + + + + + From a60c40cedd8f44eb0579bfbb88f72c8de2f82e1f Mon Sep 17 00:00:00 2001 From: Curry4182 Date: Wed, 27 Dec 2023 18:27:22 +0900 Subject: [PATCH 5/9] =?UTF-8?q?feat:=20=EC=B1=84=ED=8C=85=20UI=EB=A5=BC=20?= =?UTF-8?q?=EC=A1=B0=EC=9E=91=ED=95=98=EA=B8=B0=20=EC=9C=84=ED=95=9C=20js?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/static/js/chat.js | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 bucketback-api/src/main/resources/static/js/chat.js diff --git a/bucketback-api/src/main/resources/static/js/chat.js b/bucketback-api/src/main/resources/static/js/chat.js new file mode 100644 index 000000000..c2ac0a079 --- /dev/null +++ b/bucketback-api/src/main/resources/static/js/chat.js @@ -0,0 +1,148 @@ +let stompClient = null; + +let userNickname = 'user' + generateRandomString(6); + +let myRoomId; + +function generateRandomString(length) { + let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + let result = ''; + for (let i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * characters.length)); + } + return result; +} + +function disconnect() { + if (stompClient != null) { + stompClient.disconnect(); + } + console.log("websocket with stomp for chat is disconnected"); +} + +function getOtherMessageItem(message) { + let messageContainer = document.createElement('div'); + messageContainer.classList.add('chat-message'); + messageContainer.classList.add('other-message'); + + let avatar = document.createElement('div'); + avatar.classList.add('message-avatar'); + avatar.style.backgroundImage = "url('/images/user-avatar.png')"; + + let messageInfo = document.createElement('div'); + messageInfo.classList.add('message-info'); + + let author = document.createElement('div'); + author.classList.add('message-author'); + author.textContent = userNickname; + + let text = document.createElement('div'); + text.classList.add('message-text'); + text.textContent = message; + + let time = document.createElement('div'); + time.classList.add('message-time'); + time.textContent = new Date().toLocaleTimeString([], {hour: '2-digit', minute: '2-digit'}); + + messageInfo.appendChild(author); + messageInfo.appendChild(text); + messageInfo.appendChild(time); + + messageContainer.appendChild(avatar); + messageContainer.appendChild(messageInfo); + + return messageContainer; +} + +function appendMessageOutput(chatGetResponse) { + + if (chatGetResponse.sendUserName === userNickname) { + return; + } + + let messageBox = document.getElementById('msg'); + let messageItems = document.getElementById('chat-messages'); + let message = chatGetResponse.message; + let messageItem = getOtherMessageItem(message); + + messageItems.appendChild(messageItem); + messageItems.scrollTop = messageItems.scrollHeight; + messageBox.value = ''; +} + +function connect(roomId) { + let socket = new SockJS('/ws-stomp'); + stompClient = Stomp.over(socket); + myRoomId = roomId; + stompClient.connect({}, function (frame) { + console.log('websocket with stomp for chat is connected'); + console.log('connected info: ' + frame); + stompClient.subscribe('/subscribe/rooms/' + myRoomId, function (messageOutput) { + appendMessageOutput(JSON.parse(messageOutput.body)); + }); + }); +} + +function getMyMessageItem(message) { + let messageContainer = document.createElement('div'); + messageContainer.classList.add('chat-message'); + messageContainer.classList.add('my-message'); + + let messageInfo = document.createElement('div'); + messageInfo.classList.add('message-info'); + + let text = document.createElement('div'); + text.classList.add('message-text'); + text.textContent = message; + + let time = document.createElement('div'); + time.classList.add('message-time'); + time.textContent = new Date().toLocaleTimeString([], {hour: '2-digit', minute: '2-digit'}); + + messageInfo.appendChild(text); + messageInfo.appendChild(time); + messageContainer.appendChild(messageInfo); + + return messageContainer; +} + +function addMessageItem(message) { + let messageItems = document.getElementById('chat-messages'); + if (message.length > 0) { + let messageItem = getMyMessageItem(message); + + messageItems.appendChild(messageItem); + messageItems.scrollTop = messageItems.scrollHeight; + } +} + +document.addEventListener('DOMContentLoaded', function () { + let messageBox = document.getElementById('msg'); + let sendButton = document.getElementById('sendBtn'); + + function sendMessage() { + let message = messageBox.value.trim(); + if (message) { + stompClient.send('/app/publish/messages', {}, + JSON.stringify({ + 'userNickname': userNickname, + 'chatRoomId': myRoomId, + 'message': message + }) + ); + addMessageItem(message); + messageBox.value = ''; + } + } + + sendButton.addEventListener('click', function () { + sendMessage(); + }); + + messageBox.addEventListener('keypress', function (event) { + if (event.key === 'Enter') { + event.preventDefault(); + sendMessage(); + } + }); +}); From 36135414fdfa85a7ef8c455d823b19053e40d1e2 Mon Sep 17 00:00:00 2001 From: Curry4182 Date: Wed, 27 Dec 2023 18:28:25 +0900 Subject: [PATCH 6/9] =?UTF-8?q?feat:=20=EC=B1=84=ED=8C=85=EC=97=90=20?= =?UTF-8?q?=EC=A0=91=EA=B7=BC=ED=95=98=EA=B8=B0=20=EC=9C=84=ED=95=B4=20sec?= =?UTF-8?q?urity=20=EA=B2=BD=EB=A1=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/config/security/SecurityConfiguration.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/bucketback-api/src/main/java/com/programmers/bucketback/global/config/security/SecurityConfiguration.java b/bucketback-api/src/main/java/com/programmers/bucketback/global/config/security/SecurityConfiguration.java index afc8ec0dc..7d7a42c5a 100644 --- a/bucketback-api/src/main/java/com/programmers/bucketback/global/config/security/SecurityConfiguration.java +++ b/bucketback-api/src/main/java/com/programmers/bucketback/global/config/security/SecurityConfiguration.java @@ -63,7 +63,14 @@ public SecurityFilterChain securityFilterChain(final HttpSecurity http) throws E .requestMatchers("/api/{nickname}/buckets/**").permitAll() .requestMatchers("/api/hobbies").permitAll() + .requestMatchers("/js/**").permitAll() + .requestMatchers("/css/**").permitAll() + .requestMatchers("/images/**").permitAll() + .requestMatchers("/chat/**").permitAll() + .requestMatchers("/chat-rooms").permitAll() + + .requestMatchers("/ws-stomp/**").permitAll() .anyRequest().authenticated() ) .sessionManagement(session -> session From 03b0e61c341891a356286295f0d25c334a2e2e36 Mon Sep 17 00:00:00 2001 From: Curry4182 Date: Wed, 27 Dec 2023 18:30:21 +0900 Subject: [PATCH 7/9] =?UTF-8?q?static=20file:=20=EC=9D=B5=EB=AA=85=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EC=9E=90=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/resources/static/images/user-avatar.png | Bin 0 -> 4407 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 bucketback-api/src/main/resources/static/images/user-avatar.png diff --git a/bucketback-api/src/main/resources/static/images/user-avatar.png b/bucketback-api/src/main/resources/static/images/user-avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..37022c6acef99e9a6a51cfe75efe3476b1b9099d GIT binary patch literal 4407 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 JUJJ2%@gEiU3 Date: Wed, 27 Dec 2023 18:57:37 +0900 Subject: [PATCH 8/9] =?UTF-8?q?docs:=20=EC=B1=84=ED=8C=85=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B4=80=EB=A0=A8=20=EC=A3=BC=EC=84=9D=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domains/chat/api/ChatController.java | 10 ++++++ .../global/config/StompWebSocketConfig.java | 13 ++++++-- .../src/main/resources/static/js/chat.js | 33 ++++++++++++++++++- 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/bucketback-api/src/main/java/com/programmers/bucketback/domains/chat/api/ChatController.java b/bucketback-api/src/main/java/com/programmers/bucketback/domains/chat/api/ChatController.java index 9ba3b2015..0aba03932 100644 --- a/bucketback-api/src/main/java/com/programmers/bucketback/domains/chat/api/ChatController.java +++ b/bucketback-api/src/main/java/com/programmers/bucketback/domains/chat/api/ChatController.java @@ -20,6 +20,11 @@ public class ChatController { private final SimpMessagingTemplate simpMessagingTemplate; + /** + * STOMP 프로토콜을 사용하여 채팅 메시지를 처리하고 구독자들에게 메시지를 전송하는 메소드. + * + * /publish/messages 경로로 메시지를 전송하면 이 메소드가 호출됩니다. + */ @MessageMapping("/publish/messages") public void sendMessage(final ChatCreateRequest request) { LocalDateTime createdAt = LocalDateTime.now(); @@ -29,6 +34,11 @@ public void sendMessage(final ChatCreateRequest request) { createdAt ); + /* + SimpMessagingTemplate를 사용하여 구독자들에게 메시지를 전송합니다. + 메시지는 "/subscribe/rooms/{chatRoomId}" 주소로 전송되며, + 이는 해당 채팅방을 구독하는 클라이언트들에게 전달됩니다. + */ simpMessagingTemplate.convertAndSend("/subscribe/rooms/" + request.chatRoomId(), chatGetResponse); } diff --git a/bucketback-api/src/main/java/com/programmers/bucketback/global/config/StompWebSocketConfig.java b/bucketback-api/src/main/java/com/programmers/bucketback/global/config/StompWebSocketConfig.java index ee3a5a3df..247bb8aba 100644 --- a/bucketback-api/src/main/java/com/programmers/bucketback/global/config/StompWebSocketConfig.java +++ b/bucketback-api/src/main/java/com/programmers/bucketback/global/config/StompWebSocketConfig.java @@ -10,16 +10,25 @@ @EnableWebSocketMessageBroker public class StompWebSocketConfig implements WebSocketMessageBrokerConfigurer { + /** + * STOMP 프로토콜을 사용하여 클라이언트와 서버가 메시지를 주고받을 수 있도록 엔드포인트를 등록합니다. + */ @Override public void registerStompEndpoints(final StompEndpointRegistry registry) { - registry.addEndpoint("/ws-stomp") + registry.addEndpoint("/ws-stomp") // ws-stomp 엔드포인트를 통해 클라이언트가 서버와 연결할 수 있습니다. .setAllowedOriginPatterns("*") - .withSockJS(); + .withSockJS(); // withSockJS() 메소드는 SockJS를 사용할 수 있도록 합니다. } + /** + * 클라이언트가 메시지를 구독할 수 있도록 메시지 브로커를 등록합니다. + */ @Override public void configureMessageBroker(final MessageBrokerRegistry registry) { + // /subscribe로 시작하는 경로를 구독할 수 있도록 등록합니다. registry.enableSimpleBroker("/subscribe"); + + // /app으로 시작하는 경로로 들어오는 메시지를 컨트롤러에서 처리할 수 있도록 등록합니다. registry.setApplicationDestinationPrefixes("/app"); } } diff --git a/bucketback-api/src/main/resources/static/js/chat.js b/bucketback-api/src/main/resources/static/js/chat.js index c2ac0a079..b00e83da7 100644 --- a/bucketback-api/src/main/resources/static/js/chat.js +++ b/bucketback-api/src/main/resources/static/js/chat.js @@ -1,9 +1,15 @@ +// STOMP 클라이언트 변수 초기화 let stompClient = null; +// 사용자 닉네임 생성 (랜덤 문자열) let userNickname = 'user' + generateRandomString(6); +// 사용자의 채팅방 ID +// connet() 함수에서 할당 let myRoomId; +// 랜덤 문자열을 생성하는 함수 +// 사용자 닉네임 생성할 때 사용 function generateRandomString(length) { let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; let result = ''; @@ -13,6 +19,7 @@ function generateRandomString(length) { return result; } +// 웹소켓 연결을 끊는 함수 function disconnect() { if (stompClient != null) { stompClient.disconnect(); @@ -20,26 +27,33 @@ function disconnect() { console.log("websocket with stomp for chat is disconnected"); } +// 다른 사용자의 메시지 항목을 생성하는 함수 function getOtherMessageItem(message) { + // 메시지 컨테이너에 avatar, messageInfo를 추가 하고 반환 한다. let messageContainer = document.createElement('div'); messageContainer.classList.add('chat-message'); messageContainer.classList.add('other-message'); + // 사용자 avatar 설정 let avatar = document.createElement('div'); avatar.classList.add('message-avatar'); avatar.style.backgroundImage = "url('/images/user-avatar.png')"; + // messageInfo 생성 let messageInfo = document.createElement('div'); messageInfo.classList.add('message-info'); + // 메시지 작성자 표시 let author = document.createElement('div'); author.classList.add('message-author'); author.textContent = userNickname; + // 메시지 텍스트 설정 let text = document.createElement('div'); text.classList.add('message-text'); text.textContent = message; + // 메시지 시간 표시 let time = document.createElement('div'); time.classList.add('message-time'); time.textContent = new Date().toLocaleTimeString([], {hour: '2-digit', minute: '2-digit'}); @@ -54,8 +68,11 @@ function getOtherMessageItem(message) { return messageContainer; } +// 수신된 메시지를 화면에 추가하는 함수 +// 서버에서 실시간으로 메시지를 받으면 호출 된다. function appendMessageOutput(chatGetResponse) { + // 자신이 보낸 메시지는 추가하지 않음 if (chatGetResponse.sendUserName === userNickname) { return; } @@ -70,6 +87,7 @@ function appendMessageOutput(chatGetResponse) { messageBox.value = ''; } +// 웹소켓 연결 및 구독 설정 함수 function connect(roomId) { let socket = new SockJS('/ws-stomp'); stompClient = Stomp.over(socket); @@ -83,7 +101,9 @@ function connect(roomId) { }); } +// 자신의 메시지 항목을 생성하는 함수 function getMyMessageItem(message) { + // 메시지 컨테이너에 messageInfo를 추가 하고 반환 한다. let messageContainer = document.createElement('div'); messageContainer.classList.add('chat-message'); messageContainer.classList.add('my-message'); @@ -106,6 +126,7 @@ function getMyMessageItem(message) { return messageContainer; } +// 메시지 아이템을 화면에 추가하는 함수 function addMessageItem(message) { let messageItems = document.getElementById('chat-messages'); if (message.length > 0) { @@ -120,9 +141,13 @@ document.addEventListener('DOMContentLoaded', function () { let messageBox = document.getElementById('msg'); let sendButton = document.getElementById('sendBtn'); + // 메시지 보내기 함수 function sendMessage() { let message = messageBox.value.trim(); - if (message) { + + if (message.length > 0) { + + // 서버로 메시지 전송 stompClient.send('/app/publish/messages', {}, JSON.stringify({ 'userNickname': userNickname, @@ -130,15 +155,21 @@ document.addEventListener('DOMContentLoaded', function () { 'message': message }) ); + + // 자신의 메시지를 화면에 추가 addMessageItem(message); + + // 메시지 입력창 초기화 messageBox.value = ''; } } + // 버튼 클릭 이벤트 리스너 설정 sendButton.addEventListener('click', function () { sendMessage(); }); + // 키보드 이벤트 리스너 설정 messageBox.addEventListener('keypress', function (event) { if (event.key === 'Enter') { event.preventDefault(); From 92a81e1992f073e4d6e21758b6c111846870ece7 Mon Sep 17 00:00:00 2001 From: Curry4182 Date: Wed, 31 Jan 2024 00:01:04 +0900 Subject: [PATCH 9/9] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EC=95=84=EC=9D=B4=EB=94=94=20=EA=B4=80=EB=A6=AC=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domains/chat/api/ChatController.java | 17 +++- .../global/config/chat/ChatPreHandler.java | 79 +++++++++++++++++++ .../{ => chat}/StompWebSocketConfig.java | 17 +++- .../global/error/ChatErrorHandler.java | 43 ++++++++++ .../src/main/resources/static/js/chat.js | 10 ++- .../src/main/resources/templates/chat.html | 2 + 6 files changed, 164 insertions(+), 4 deletions(-) create mode 100644 bucketback-api/src/main/java/com/programmers/bucketback/global/config/chat/ChatPreHandler.java rename bucketback-api/src/main/java/com/programmers/bucketback/global/config/{ => chat}/StompWebSocketConfig.java (75%) create mode 100644 bucketback-api/src/main/java/com/programmers/bucketback/global/error/ChatErrorHandler.java diff --git a/bucketback-api/src/main/java/com/programmers/bucketback/domains/chat/api/ChatController.java b/bucketback-api/src/main/java/com/programmers/bucketback/domains/chat/api/ChatController.java index 0aba03932..03fc5da5e 100644 --- a/bucketback-api/src/main/java/com/programmers/bucketback/domains/chat/api/ChatController.java +++ b/bucketback-api/src/main/java/com/programmers/bucketback/domains/chat/api/ChatController.java @@ -1,8 +1,10 @@ package com.programmers.bucketback.domains.chat.api; +import java.security.Principal; import java.time.LocalDateTime; import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.messaging.handler.annotation.Payload; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; @@ -11,7 +13,10 @@ import com.programmers.bucketback.domains.chat.api.dto.request.ChatCreateRequest; import com.programmers.bucketback.domains.chat.api.dto.response.ChatGetResponse; +import com.programmers.bucketback.error.ErrorCode; +import com.programmers.bucketback.global.config.security.SecurityUtils; +import io.jsonwebtoken.MalformedJwtException; import lombok.RequiredArgsConstructor; @Controller @@ -26,7 +31,13 @@ public class ChatController { * /publish/messages 경로로 메시지를 전송하면 이 메소드가 호출됩니다. */ @MessageMapping("/publish/messages") - public void sendMessage(final ChatCreateRequest request) { + public void sendMessage( + @Payload final ChatCreateRequest request, + final Principal principal + ) { + if (principal == null) + throw new MalformedJwtException(ErrorCode.BAD_SIGNATURE_JWT.getMessage()); + LocalDateTime createdAt = LocalDateTime.now(); ChatGetResponse chatGetResponse = new ChatGetResponse( request.message(), @@ -34,6 +45,7 @@ public void sendMessage(final ChatCreateRequest request) { createdAt ); + /* SimpMessagingTemplate를 사용하여 구독자들에게 메시지를 전송합니다. 메시지는 "/subscribe/rooms/{chatRoomId}" 주소로 전송되며, @@ -47,6 +59,9 @@ public String chatPage( @PathVariable("roomId") final int roomId, final Model model ) { + Long currentMemberId = SecurityUtils.getCurrentMemberId(); + model.addAttribute("currentMemberId", currentMemberId); + model.addAttribute("roomId", roomId); return "chat"; diff --git a/bucketback-api/src/main/java/com/programmers/bucketback/global/config/chat/ChatPreHandler.java b/bucketback-api/src/main/java/com/programmers/bucketback/global/config/chat/ChatPreHandler.java new file mode 100644 index 000000000..96242366c --- /dev/null +++ b/bucketback-api/src/main/java/com/programmers/bucketback/global/config/chat/ChatPreHandler.java @@ -0,0 +1,79 @@ +package com.programmers.bucketback.global.config.chat; + +import org.springframework.messaging.Message; +import org.springframework.messaging.simp.stomp.StompCommand; +import org.springframework.messaging.simp.stomp.StompHeaderAccessor; +import org.springframework.messaging.support.ChannelInterceptor; +import org.springframework.messaging.support.MessageHeaderAccessor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.stereotype.Component; + +import com.programmers.bucketback.error.ErrorCode; +import com.programmers.bucketback.global.config.security.jwt.JwtService; + +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.security.SignatureException; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Component +public class ChatPreHandler implements ChannelInterceptor { + + private static final String JWT_PREFIX = "Bearer "; + private static final String AUTH_HEADER_KEY = "Authorization"; + private final JwtService jwtService; + private final UserDetailsService userDetailsService; + + @Override + public Message preSend( + final Message message, + final org.springframework.messaging.MessageChannel channel + ) { + final String authHeader; + final String jwt; + final String memberId; + StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class); + + if (accessor == null) { + throw new MalformedJwtException(ErrorCode.MEMBER_NOT_LOGIN.getMessage()); + } + + if (accessor.getCommand() == StompCommand.CONNECT || accessor.getCommand() == StompCommand.SEND) { + authHeader = accessor.getFirstNativeHeader(AUTH_HEADER_KEY); + + if (authHeader == null || !authHeader.startsWith(JWT_PREFIX)) { + throw new MalformedJwtException(ErrorCode.MEMBER_NOT_LOGIN.getMessage()); + } + + jwt = authHeader.substring(JWT_PREFIX.length()); + + try { + memberId = jwtService.extractUsername(jwt); + + if (memberId == null) { + throw new MalformedJwtException(ErrorCode.BAD_SIGNATURE_JWT.getMessage()); + } + + final UserDetails userDetails = this.userDetailsService.loadUserByUsername(memberId); + + if (jwtService.isTokenValid(jwt, userDetails)) { + final UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( + userDetails, + null, + userDetails.getAuthorities() + ); + accessor.setUser(authToken); + } + + return message; + + } catch (SignatureException | ExpiredJwtException e) { + throw new MalformedJwtException(ErrorCode.BAD_SIGNATURE_JWT.getMessage()); + } + } + return message; + } +} diff --git a/bucketback-api/src/main/java/com/programmers/bucketback/global/config/StompWebSocketConfig.java b/bucketback-api/src/main/java/com/programmers/bucketback/global/config/chat/StompWebSocketConfig.java similarity index 75% rename from bucketback-api/src/main/java/com/programmers/bucketback/global/config/StompWebSocketConfig.java rename to bucketback-api/src/main/java/com/programmers/bucketback/global/config/chat/StompWebSocketConfig.java index 247bb8aba..a13313758 100644 --- a/bucketback-api/src/main/java/com/programmers/bucketback/global/config/StompWebSocketConfig.java +++ b/bucketback-api/src/main/java/com/programmers/bucketback/global/config/chat/StompWebSocketConfig.java @@ -1,15 +1,23 @@ -package com.programmers.bucketback.global.config; +package com.programmers.bucketback.global.config.chat; import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.simp.config.ChannelRegistration; 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 com.programmers.bucketback.global.error.ChatErrorHandler; + +import lombok.RequiredArgsConstructor; + @Configuration @EnableWebSocketMessageBroker +@RequiredArgsConstructor public class StompWebSocketConfig implements WebSocketMessageBrokerConfigurer { + private final ChatPreHandler chatPreHandler; + /** * STOMP 프로토콜을 사용하여 클라이언트와 서버가 메시지를 주고받을 수 있도록 엔드포인트를 등록합니다. */ @@ -18,6 +26,8 @@ public void registerStompEndpoints(final StompEndpointRegistry registry) { registry.addEndpoint("/ws-stomp") // ws-stomp 엔드포인트를 통해 클라이언트가 서버와 연결할 수 있습니다. .setAllowedOriginPatterns("*") .withSockJS(); // withSockJS() 메소드는 SockJS를 사용할 수 있도록 합니다. + + registry.setErrorHandler(new ChatErrorHandler()); } /** @@ -31,4 +41,9 @@ public void configureMessageBroker(final MessageBrokerRegistry registry) { // /app으로 시작하는 경로로 들어오는 메시지를 컨트롤러에서 처리할 수 있도록 등록합니다. registry.setApplicationDestinationPrefixes("/app"); } + + @Override + public void configureClientInboundChannel(final ChannelRegistration registration) { + registration.interceptors(chatPreHandler); + } } diff --git a/bucketback-api/src/main/java/com/programmers/bucketback/global/error/ChatErrorHandler.java b/bucketback-api/src/main/java/com/programmers/bucketback/global/error/ChatErrorHandler.java new file mode 100644 index 000000000..b7649346d --- /dev/null +++ b/bucketback-api/src/main/java/com/programmers/bucketback/global/error/ChatErrorHandler.java @@ -0,0 +1,43 @@ +package com.programmers.bucketback.global.error; + +import java.nio.charset.StandardCharsets; + +import org.springframework.messaging.Message; +import org.springframework.messaging.simp.stomp.StompCommand; +import org.springframework.messaging.simp.stomp.StompHeaderAccessor; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.messaging.StompSubProtocolErrorHandler; + +import com.programmers.bucketback.error.ErrorCode; + +@Component +public class ChatErrorHandler extends StompSubProtocolErrorHandler { + + private static final String ERROR_CODE_PREFIX = "errorCode: "; + + public ChatErrorHandler() { + super(); + } + + @Override + public Message handleClientMessageProcessingError(final Message clientMessage, final Throwable ex) { + if (ex.getCause().getMessage().equals(ErrorCode.BAD_SIGNATURE_JWT.getMessage())) { + return prepareErrorMessage(ErrorCode.BAD_SIGNATURE_JWT); + } + + return super.handleClientMessageProcessingError(clientMessage, ex); + } + + private Message prepareErrorMessage(final ErrorCode errorCode) { + + String retErrorCodeMessage = ERROR_CODE_PREFIX + errorCode.getCode(); + StompHeaderAccessor accessor = StompHeaderAccessor.create(StompCommand.ERROR); + + return MessageBuilder.createMessage( + retErrorCodeMessage.getBytes(StandardCharsets.UTF_8), + accessor.getMessageHeaders() + ); + } + +} \ No newline at end of file diff --git a/bucketback-api/src/main/resources/static/js/chat.js b/bucketback-api/src/main/resources/static/js/chat.js index b00e83da7..d6b682cb4 100644 --- a/bucketback-api/src/main/resources/static/js/chat.js +++ b/bucketback-api/src/main/resources/static/js/chat.js @@ -8,6 +8,8 @@ let userNickname = 'user' + generateRandomString(6); // connet() 함수에서 할당 let myRoomId; +const token = localStorage.getItem('jwt-token'); + // 랜덤 문자열을 생성하는 함수 // 사용자 닉네임 생성할 때 사용 function generateRandomString(length) { @@ -92,7 +94,9 @@ function connect(roomId) { let socket = new SockJS('/ws-stomp'); stompClient = Stomp.over(socket); myRoomId = roomId; - stompClient.connect({}, function (frame) { + let headers = {Authorization: token}; + + stompClient.connect(headers, function (frame) { console.log('websocket with stomp for chat is connected'); console.log('connected info: ' + frame); stompClient.subscribe('/subscribe/rooms/' + myRoomId, function (messageOutput) { @@ -147,8 +151,10 @@ document.addEventListener('DOMContentLoaded', function () { if (message.length > 0) { + const headers = {Authorization: token}; // 서버로 메시지 전송 - stompClient.send('/app/publish/messages', {}, + stompClient.send('/app/publish/messages', headers, + JSON.stringify({ 'userNickname': userNickname, 'chatRoomId': myRoomId, diff --git a/bucketback-api/src/main/resources/templates/chat.html b/bucketback-api/src/main/resources/templates/chat.html index e70d31089..1ef382cd9 100644 --- a/bucketback-api/src/main/resources/templates/chat.html +++ b/bucketback-api/src/main/resources/templates/chat.html @@ -5,6 +5,7 @@ Group Chat +
@@ -13,6 +14,7 @@

Group Chat Room

+ [[${currentMemberId}]]