<aside> 📌

목차

채팅 시스템 💬

개요

BScene 플랫폼의 채팅 시스템은 두 가지 서로 다른 메시징 방식을 지원합니다. 하나는 영속적인 1:1 Direct Message(DM)이고, 다른 하나는 휘발성 Live Stream Chat입니다. Persistent Chat은 Session Recruitment와 통합되어 밴드와 지원자가 모집 컨텍스트 안에서 커뮤니케이션할 수 있도록 하며, Live Chat은 활성 오디오 스트림의 라이프사이클과 연동됩니다.

컴포넌트 기능
ChatWebSocketHandler dm.send, dm.read, ping 프레임을 처리합니다.
ChatWebSocketSessionRegistry ConcurrentHashMap을 통해 사용자별 활성 WebSocket 세션을 관리합니다.
LiveChatWebSocketHandler 휘발성이며 처리량이 많은 스트림 메시지를 처리합니다.
ChatWebSocketTicketService WebSocket 핸드쉐이크를 위한 인증 티켓을 발급합니다.

시스템은 STOMP 대신 표준 WebSocket과 커스텀 JSON 프레임 구조를 사용하며, ChatWebSocketSessionRegistry는 ConcurrentWebSocketSessionDecorator를 사용하여 버퍼 제한이 적용된 스레드 안전한 전달을 보장합니다.

채팅방 · 메시지 영속화

ChatRoom 엔티티

Field Type 설명
sender User 채팅을 시작한 사용자입니다.
recipient User 채팅 시작의 대상이 되는 사용자입니다.
contextType ChatContextType RECRUITMENT 또는 SESSION_SEARCH 중 하나입니다.
sessionRecruitment SessionRecruitment 연결된 모집 게시글입니다(nullable).
sessionApplication SessionApplication 연결된 지원 프로필입니다(nullable).
senderLeftAt LocalDateTime sender가 채팅방을 삭제한 시각입니다.
recipientLeftAt LocalDateTime recipient가 채팅방을 삭제한 시각입니다.

ChatMessage는 채팅방 내 개별 메시지를 저장하며, 메시지는 2,000자로 제한되고 readAt 필드가 수신자가 메시지를 열람한 시각을 추적합니다.

서비스 레이어 구현

sequenceDiagram
    participant Client
    participant Msg as "ChatMessageService"
    participant Room as "ChatRoomService"
    participant DB as "Database"
    Client->>Msg: "send(chatRoom, content, sender)"
    Msg->>Msg: "메시지 길이 검증 (2000자)"
    Msg->>Msg: "sender 전송 권한 검증"
    Msg->>DB: "ChatMessage 영속화"
    Msg->>Room: "rejoinIfLeft(recipient)"
    Room->>DB: "recipientLeftAt 초기화"
    Msg-->>Client: "ChatMessage 응답"

채팅 컨텍스트

API 응답 및 차단 연동

WebSocket 핸들러 — 상시 채팅 · 라이브 스트림 채팅

WebSocket 인프라 및 티켓 인증

서버는 ChatWebSocketConfig에서 설정되는 두 개의 주요 WebSocket 엔드포인트를 노출합니다. 로그로 남을 수 있는 URL 파라미터에 JWT를 노출하지 않기 위해 단기 수명의 Ticket 시스템을 사용합니다.

sequenceDiagram
    participant Client
    participant REST as "ChatWebSocketTicketController"
    participant Ticket as "ChatWebSocketTicketService"
    participant Redis
    participant WS as "ChatWebSocketHandshakeInterceptor"
    Client->>REST: "티켓 발급 요청"
    REST->>Ticket: "32바이트 secure random 생성"
    Ticket->>Redis: "ticket -> userId (TTL 30초)"
    REST-->>Client: "ticket 반환"
    Client->>WS: "WebSocket 핸드쉐이크 (?ticket=...)"
    WS->>Redis: "티켓 검증 및 소모"
    WS-->>Client: "연결 수락"

Persistent DM Chat

ChatWebSocketHandler는 사용자 간 DM을 위한 장수명 연결을 관리하며, 여러 기기·세션에 걸쳐 현재 온라인 상태인 사용자를 추적하기 위해 ChatWebSocketSessionRegistry에 의존합니다.

Live Stream Chat

Live chat은 특정 liveId에 스코프된 room 기반 구조이며, 모더레이션과 사용자의 현재 모드에 기반한 프로필 해석 기능을 갖추고 있습니다.

세션 관리 및 정리

두 레지스트리(ChatWebSocketSessionRegistry, LiveChatWebSocketSessionRegistry) 모두 self-healing 로직을 구현하여, 메시지 전달이 실패하거나 브로드캐스트 중 세션이 닫힌 것으로 확인되면 해당 세션을 내부 맵에서 자동으로 제거합니다. 라이브 스트림이 종료되면 LiveChatRoomCloser가 해당 room의 모든 활성 세션을 순회하며 최종 종료 메시지를 전송하고 CloseStatus.NORMAL로 연결을 종료합니다.

구현 비교

항목 Persistent Chat (DM) Live Stream Chat
Endpoint /ws/chat /ws/lives/{liveId}/chat
Subprotocol dm.v1 live-chat.v1
Registry Key userId liveId
Filtering 없음(1:1) 차단된 사용자 및 신고자
Persistence Database(JPA) In-memory Broadcast Only
Max Length 서비스 정의값(2,000자) 500자