• Web_ project 진행_8 : Spring Boot + Sring chat, 스프링 채팅 시스템 구현(1)

    2022. 4. 24.

    by. KAEY


    스프링으로 채팅 시스템을 구현하기 위해서, Lombok, MongoDB, Flux 을 사용합니다. 왜 필요할까요?

     

    세 개의 요소들 모두 리엑티브 환경에서 사용하기 유용한 도구들 입니다.

     

     

    Flux는 리액티브 프로그래밍의 방법 중 하나이고, 리액티브 프로그램은 주변의 환경과 끊임없이 상호작용하면서 프로그램이 실질적으로 주도하는 것이 아니라 환경이 변화했을 때 이벤트를 받아 동작하는 과정입니다. lombok 역시 getter, setter 의 과정을 축약시켜주는 등의 도움을 줍니다.

     

     

    즉, 채팅은 실시간으로 소통이 되어야하므로, 리액티브 기반의 프로그래밍이 필요하다 볼 수 있습니다.

    그리고 이를 실시간으로 저장되어야 하는데, 비동기적 통신에 유리한 mongoDB를 채팅의 데이터베이스로 사용합니다. 

     


    들어가기 앞서, 채팅 서버 분리

     왜? 분리해야할까요?

     

     > 서버 과부화 방지

      물론 로컬서버에서의 낮은 이용량을 감당한다면, 충분히 하나의 서버로만 여러 서비스를 구현해놔도 큰 문제는 없을 것입니다. 그러나 사용자의 수가 늘어난다면 이를 분리시켜 놓을 필요가 있습니다. 서비스의 속도 역시 중요한 요소 중 하나이기 때문입니다. 본 프로젝트에서의 채팅은 DB에서 끊임없이 저장하고 불러오는 작용을 하므로 하나의 서버만으로 부담하긴 어렵다고 판단하였습니다.

     

     > 라이브러리 지원

      spring에서 Web socket, Lombok, Flux 등의 라이브러리가 4버전 이상에서 원활한 사용이 가능하기 때문입니다. 본 프로젝트에서 진행한 환경이 이클립스와 spring을 위주로 사용했으므로 너무 다른 환경으로의 변화를 주지 않았습니다.

     

     

     본 프로젝트에서 사용된 서버는 서버 A는 Front 위주의 구현을 담당하는 서버, 서버 B는 실질적인 채팅 데이터 저장 및 실시간, 끊임없는 상호작용을 담당해줍니다. 본 글에서는 서버 A가 메인 서버, 서버 B가 서브 서버 입니다.

     


    서버 A_ Front 위주의 구현, 데이터 수집&정제

     

    Web_ project 진행_7 : JSP을 이용하여 원하는 정보 전달하기 :: MS (tistory.com)

     

    Web_ project 진행_7 : JSP을 이용하여 원하는 정보 전달하기

    *해당 프로젝트 기술은 메아리 프로젝트에서 제가 담당했었던 기능 분야 쪽만 기술 합니다!! 이외엔 팀원들과의 합작입니다. 시나리오 구성  사용자가 커머스 화면으로 진입하여, 채팅 버튼을

    kaey.tistory.com

     

     

     앞서 JSP을 통해서 usernameroomNum 을 전달 해놓았습니다.

    여기서 roomNum은 생성될 때 username1, username2 를 지니게 됩니다. 즉 1:1 채팅에서 상대방과 나의 이름을 담는 객체가 됩니다.

     

    이렇게 초안을 잡고 roomNum의 데이터에 접근할 때 username의 객체 값과 다르다면 해당 roomNum을 확인할 수 없거나, 접촉이 불가능하게 해놓으면 됩니다. 

    카카오톡의 경우로 만든다면 GrouproomNum/OneroomNum 등 으로 만들어서 구분해놓고 구현하면 될 것 같습니다. 본 프로젝트에서는 1:1 만을 지향했으나, 이를 검증하는 과정을 구현하지 않았습니다.

     

     


    구현 1

     채팅 자체 기능은 메인 서버(서버 A)에서 이루어집니다.

     따라서 이 데이터를 보내고 묶는 과정을 JSP에서 이룹니다.

     

      WEB-INF/views/chat/chat.jsp

    	<div id="user_chat_data" class="user_chat_data">
              <div class="profile_name">
                &nbsp;&nbsp;&nbsp;&nbsp; <!-- <img src="./img/profile.png" class="mr-3 rounded-circle"> &nbsp;&nbsp; -->
                <span id="username"></span>
              </div>
             
              <div class="container-fluid chat_section" id="chat-box">
    
              </div>
    
              <div class="type_msg">
                <div class="input_msg_write">
                  <input id="chat-outgoing-msg" type="text" class="write_msg" placeholder="Type a message" />
                  <button id="chat-outgoing-button" class="msg_send_btn" type="button"><i class="fa fa-paper-plane"
                      aria-hidden="true"></i></button>
                </div>
              </div>

     해당 프론트의 초안은 부트스트랩에서 가져왔습니다. 채팅을 할 때 상대방의 아이디를 띄워줄 부분, 나의 프로필 이미지 등을 출력할 수 있도록 구현합니다. 

     

     

    메인 채팅 화면은 다음과 같이 출력되게 됩니다. 그렇다면 채팅의 전송을 눌렀을 때 이루어지는 과정을 처리하는 script 문을 확인 해보겠습니다. 해당 사진에서는 유저 아이디가 저장되어 있지 않네요.

     

     


    메시지 발송자 구분

      document.querySelector("#chat-outgoing-button").addEventListener("click", () => {
         addMessage();
      });
      document.querySelector("#chat-outgoing-msg").addEventListener("keydown", (e) => {
         if (e.keyCode === 13) {
            addMessage();
         }
      });

     발송버튼을 누르거나 엔터키를 누르면(keydown) 메시지가 전송될 수 있도록 설정해놓습니다.

     

     

     

    eventSource.onmessage = (event) => {
         const data = JSON.parse(event.data);     
         if (data.sender === username) { // 로그인한 유저가 보낸 메시지
            // 파란박스(오른쪽)
            initMyMessage(data);
         } else {
            // 회색박스(왼쪽)
            initYourMessage(data);
         }
      }

     DB에서 데이터를 가져와서 data에 저장된 sender 가 username과 동일하다면 내가 보낸 메시지로 판단합니다. 일반적으로 내 메시지가 오른쪽에 상대방의 메시지가 왼쪽이니 이를 구현 해놓습니다.

     

     

     

      function getReceiveMsgBox(data) {
    
         let md = data.createdAt.substring(5, 10)
         let tm = data.createdAt.substring(11, 16)
         convertTime = tm + " | " + md
    
         return `<div class="received_withd_msg">
         <p> ` + data.msg + `</p>
         <span class="time_date">` + convertTime + ` / <b>` + data.sender + `</b> </span>
      </div>`;
      }

     데이터를 읽어오고 채팅 관련한 데이터를 제가 원하는 형태로 정제할 것 입니다.

     보낸 시간을 시간 | 날짜 로 구분합니다. 그후 리턴하는 값에 박스를 만들고, 그 박스 안에 채팅의 내용, 시간, 보낸 사람의 이름등을 저장하여 리턴합니다. 반대로 상대방의 메시지 역시 똑같이 구현합니다.

     

     

    메시지를 admin 계정으로 message test 란 내용을 보냈을 때 정확히 제가 원하는 형태로 출력이 됐습니다.

     

    그렇다면 이 데이터들을 메인서버에서 채팅서버(서버B)로 보낼 수 있도록 하겠습니다.

     

     

     

      async function addMessage() {
         let msgInput = document.querySelector("#chat-outgoing-msg");
    
         let chat = {
            sender: username,
            roomNum: roomNum,
            msg: msgInput.value
         };
    
         fetch("http://localhost:8080/chat", {
            method: "post", //http post 메서드 (새로운 데이터를 write)
            body: JSON.stringify(chat), // JS -> JSON
            headers: {   
               "Content-Type": "application/json; charset=utf-8"
            }
         });
    
         msgInput.value = "";
      };

    async await 을 통해서 비동기식 구현을 해놨습니다. 이를 통해서 채팅 서버는 실시간으로 채팅이 저장될 수 있습니다. (서버를 껏다 키거나 리로드하는 형식의 작업이 불필요함.) 

     

     

     

    채팅 서버에 대한 내용은 다음 링크에서 확인할 수 있습니다.

    Web_ project 진행_9 : Spring Boot + Sring chat, 스프링 채팅 시스템 구현(1) :: MS (tistory.com)

     

    Web_ project 진행_9 : Spring Boot + Sring chat, 스프링 채팅 시스템 구현(1)

    Web_ project 진행_8 : Spring Boot + Sring chat, 스프링 채팅 시스템 구현(1) :: MS (tistory.com) Web_ project 진행_8 : Spring Boot + Sring chat, 스프링 채팅 시스템 구현(1) 스프링으로 채팅 시스템을 구..

    kaey.tistory.com

     

     


    댓글 (비로그인 댓글 허용하지 않습니다.)