Java/SpringBoot

[SpringBoot] 멀티 채팅방 (2)

simonpark817 2024. 6. 19. 12:26

[Ajax, Stomp 기반 멀티 채팅방 구현]

  • 저번 '멀티 채팅방 (1)' 포스팅에 이어 ChatRoom과 ChatMessage 관련 MVC를 모든 구현하였다면 이제 템플릿을 통해 화면을 구현하고 실제로 JavaScript에 Ajax를 적용하여 실시간으로 채팅이 되는 것을 확인해봅시다.
  • 전체 코드와 패키지 구조는 깃허브 리포지터리 주소를 통해 참고 부탁드립니다.
  • 리포지터리 주소 : https://github.com/psm817/chat_review
 

GitHub - psm817/chat_review

Contribute to psm817/chat_review development by creating an account on GitHub.

github.com

 

[list.html 생성]

  • 먼저 전체 채팅방 리스트를 보여줄 list.html을 생성하여 아래와 같이 코드를 작성한다.
  • li태그에 th:each를 적용하여 반복문을 통해 존재하는 모든 ChatRoom을 불러오고 각 채팅방의 id와 이름을 화면에 보이도록 한다.
<h1>채팅방 목록</h1>

<div>
    <a href="make">채팅방 생성</a>
</div>

<ul>
    <li th:each="chatRoom : ${chatRooms}">
        <a th:href="@{|/chat/room/${chatRoom.id}|}">
            <span th:text="${chatRoom.id}"></span>
            <span th:text="${chatRoom.name}"></span>
        </a>
    </li>
</ul>

 

[make.html 생성]

  • 이번에는 채팅방을 생성하는 make.html을 생성하여 아래와 같이 코드를 작성한다.
  • form 태그의 method="POST"를 통해 ChatRoomController에서 구현한 PostMapping 쪽으로 작성된 채팅방 이름 데이터가 전달되고 채팅방을 생성한다.
<h1>채팅방 생성</h1>

<div>
    <a href="list">채팅방 목록</a>
</div>

<form method="POST">
    <div>
        <input type="text" name="name" required placeholder="이름">
    </div>
    <div>
        <input type="submit" value="채팅방 생성">
    </div>
</form>

 

[room.html 생성]

  • 마지막으로 각 채팅방에 들어와 채팅 메시지를 실제로 주고받는 room.html을 생성하여 아래와 같이 코드를 작성한다.
  • form 태그에 작성된 onsubmit="submitWriteForm(this)"는 유효성 체크를 JavaScript로 하기 위해서 작성해둔 것으로 실제로 html 아래에 <script> 태그 안에 function submitWriteForm()를 통해 채팅 메시지를 작성할 때 작성자 또는 내용을 작성하지 않으면 alert 알림이 가도록 구현하였다. 이 때 각 변수를 구분해주기 위해서 input 태그 안에 있는 name은 반드시 작성해줘야 한다.
  • '더 불러오기'  버튼은 컨트롤러, 서비스, 리포지터리에서 이미 구현된 findByChatRoomIdAndIdAfter()의 기능으로 이미 작성된 채팅 메시지 외에 추가된 메시지가 있다면 loadMoreChatMessages() 라는 스크립트 함수를 호출하여 채팅 메시지를 불러오는 기능이다.
  • script 안에 코드에서 Ajax를 구현한 코드는 fetch라고 작성된 부분이라고 생각하면 된다.
  • 실제로 브라우저에서 새로고침을 하지 않아도 작성한 채팅 메시지가 바로 보이는 이유는 script 안에 '더 불러오기' 버튼 역할을 하는 loadMoreChatMessages() 함수에서 setTimeout을 5초로 주었기 때문에 실시간처럼 메시지를 불러오는 것이다.
  • 이렇게 Ajax를 사용하게 되면 구현은 가능하지만 채팅 메시지를 보내지 않을 때는 불필요한 데이터 전달때문에 서버가 부하되는 치명적인 단점이 있다.
<h1 th:text="|${roomId}번 채팅방|"></h1>

<div>
    <a href="make">채팅방 생성</a>
    <a href="list">채팅방 목록</a>
</div>

<a onclick="return false;" href="https://www.naver.com">네이버로 이동</a>

<form onsubmit="submitWriteForm(this); return false;" method="POST">
    <input type="text" name="writerName" placeholder="작성자 명">
    <input type="text" name="content" placeholder="내용">
    <input type="submit" value="작성">
</form>

<div>
    <div>
        <button onclick="loadMoreChatMessages();" type="button">더 불러오기</button>
    </div>
    <ul class="chat__messages">
        <li th:each="chatMessage : ${room.chatMessages}">
            <th:block th:text="${chatMessage.writerName}"></th:block>
            :
            (<th:block th:text="${chatMessage.id}"></th:block>)
            <th:block th:text="${chatMessage.content}"></th:block>
        </li>
    </ul>
</div>

<script th:inline="javascript">
    const roomId = /*[[${roomId}]]*/ 0;
    let lastChatMessageId = /*[[${room.chatMessages.size > 0 ? room.chatMessages.first.id : 0}]]*/ 0;
</script>

<script>
    const chatMessagesEl = document.querySelector(".chat__messages");

    function submitWriteForm(form) {
        form.writerName.value = form.writerName.value.trim();

        if ( form.writerName.value.length == 0 ) {
            alert("작성자 명을 입력해주세요.");
            form.writerName.focus();
            return;
        }

        form.content.value = form.content.value.trim();

        if ( form.content.value.length == 0 ) {
            alert("내용을 입력해주세요.");
            form.content.focus();
            return;
        }

        const action = `/chat/room/${roomId}/write`;

        fetch(
            action,
            {
                method: 'POST',
                headers:  {
                    'accept' : 'application/json',
                    'Content-Type' : 'application/json',
                },
                body: JSON.stringify({
                    writerName: form.writerName.value,
                    content: form.content.value,
                }),
            }
        ).catch(error => alert(error));

        form.content.value = '';
        form.content.focus();
    }

    function loadMoreChatMessages() {
        const action = `/chat/room/${roomId}/messagesAfter/${lastChatMessageId}`;

        fetch(
            action,
            {
                method: 'GET',
                headers:  {
                    'accept' : 'application/json',
                }
            }
        )
        .then(response => response.json())
        .then(json => drawMoreChatMessages(json.data.messages))
        .then(() => setTimeout(loadMoreChatMessages, 500))
        .catch(error => alert(error));
    }

    function drawMoreChatMessages(messages) {
        messages.forEach((message) => {
            drawMoreChatMessage(message);
        });
    }

    function drawMoreChatMessage(message) {
        lastChatMessageId = message.id;
        chatMessagesEl
            .insertAdjacentHTML(
                "afterBegin",
                `<li>${message.writerName} : (${message.id}) ${message.content}</li>`
            );
    }

    loadMoreChatMessages();
</script>

 

 

Ajax 방식을 이용한 멀티 채팅방 구현

 

 

반응형