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>
반응형