https://byeolhabae.tistory.com/26
[Spring] 응애의 1:1 채팅 구현기(1) - 메시지 브로커를 선택하다
2025 산학프로젝트에서 사설 수리업체와 수리를 원하는 유저간 1:1 채팅을 구현할 일이 생겼다요구사항은 다음과 같다.수리가 필요한 유저와 수리센터간 일대일 챗팅에서 실시간으로 메시지 확
byeolhabae.tistory.com
이전 포스팅에서 왜 Rabbit MQ를 사용하는지 간단하게 작성했다. 이번 학기 소프트웨어 공학을 들어서 그런가 전에는 구글링을 통해 많은 사람들이 사용하는 기술을 따라 사용했는데 요즘은 요구사항을 작성하고 적합한 서비스에 대해 고민 해보는 시간이 길어졌다
먼저 간략한 서비스 소개를 하자면 사설 수리업체와 수리가 필요한 유저를 매칭해주는 플랫폼이다. 다양한 기능이 있지만 내가 맡은 부분은 유저가 수리 요청 게시글을 작성하면 수리업체가 지원하고 유저와의 챗팅을 통해 수리 예약을 잡는 것이다. 게시글의 상태가 바뀜에 따라 자동으로 상태 변경 메시지가 수신되어야한다
알림과 챗팅 관련 기능 요구사항은 다음과 같다
| 수리가 필요한 유저와 수리센터간 일대일 챗팅에서 실시간으로 메시지 확인하는 것이 가능하도록 해야한다. 또한 기존 메시지 데이터가 읽음/안읽음 처리가 되어야하며 상대방이 보는 채팅방 또한 안읽음 읽음 으로 변경되어야 한다. + 앱 접속 기간 동안 수신되는 메시지가 있으면 알림(빨간점)을 표시해준다 |


view를 기반으로 다음과 같이 match_room, match_chat 테이블을 설계해보았다
이때 가장 고민했던 부분은 match_room 테이블에 유저 ID를 2개 넣는 방식으로 갈지 아니면 유저 ID 1개와 shop ID를 넣는 방식으로 구성할지에 대한 선택이었다.
일반적으로 채팅방은 참여자 2명이라는 구조를 가지므로 처음에는 user_id1, user_id2와 같은 필드를 넣는 구조도 고려했다. 하지만 이 설계는 역할 구분이 불명확하다는 단점이 있었고 점주일 경우에는 챗팅 목록에 shop 이름과 이미지가 떠야했기에 user와 shop으로 저장하기로 결정하였다. 반면 챗팅에서는 메시지를 누가 보냈고 누가 받아야하는지 명시적으로 적어야하고 이를 기반으로 개별 사용자 큐에 전송해야하기에 sender_id와 receiver_id를 구분하였다
또한 자동 메시지와 사용자 메시지가 있기에 type으로 구분하도록 하였다
다음으로 웹소켓과 레빗엠큐 셋팅 전 실시간 보장 범위에 대해서 고민했다. 처음에는 챗팅 목록 갱신, 챗팅 메시지, 알림 모두 실시간으로 제공하려 했으나 이렇게 하면 웹소켓을 3개나 연결해야했기에 성능저하, 리소스 낭비와 같은 문제들이 있을 것이라 생각했다. 챗팅 메시지와 알림을 실시간으로 제공하고 알림 뱃지를 클릭하거나 새로고침을 할때 챗팅 목록 api를 한번 더 호출하여 그때 갱신 되도록 하여 사용자 입장에서는 실시간성이 필요한 기능(메시지, 알림)은 그대로 유지하면서 채팅 목록의 변동은 알림을 통해 간접적으로 유도되므로 UX에 큰 영향을 주지 않도록 했다.
이제 이 포스팅의 주제인 'WebSocket 과 RabbitMQ를 활용하여 채팅, 알림 구현'의 전체 흐름을 살펴보자면
클라이언트가 전송 api를 호출하면 비동기로 chat.user.{receiverId} 큐로 들어간다. 이때 메시지가 큐에 도달하면 websocket을 사용하여 실시간으로 메시지 전송 및 알림을 푸시한다.

WebSocket 연결 시 토큰 검증을 위한 JwtHandshakeInterceptor
일반적인 HTTP API는 @AuthenticationPrincipal 등을 이용해 사용자 인증을 처리하지만 WebSocket 연결은 초기 Handshake 단계에서만 HTTP처럼 동작하며 이후엔 지속 연결이다. 따라서 WebSocket에서도 인증 정보를 확보하려면 Handshake 시점에 사용자 검증을 직접 처리해야 해야한다. 클라이언트가 /ws?token=xxxxx 형식으로 연결을 시도하면 JwtHandshakeInterceptor는 토큰 파라미터를 추출하여 JWT 유효성 검사를 하고 토큰에서 사용자 ID를 추출하고 웹소켓 세션에 저장해두었다가 이후 메시지를 보낼때 cnvertAndSendToUser()를 통해 해당 유저 전용 채널로 메시지를 전달할 수 있다
'BackEnd' 카테고리의 다른 글
| [Spring] 응애의 1:1 채팅 구현기(1) - 메시지 브로커를 선택하다 (1) | 2025.05.24 |
|---|---|
| [Spring] 도메인과 서비스 리팩토링 (1) | 2025.05.22 |