BLC 프로젝트 웹 소캣 흐름

한강섭·2025년 7월 5일

BLC 프로젝트

목록 보기
8/9
post-thumbnail

주제

현재 우리 프로젝트가 어떻게 웹소캣을 사용하고 있고 핵심 로직이 어떻게 구현되어 있는지 파트별로 분석해보겠습니다!

사용자가 입력을 하고 어떻게 실시간 소통이 되는 지 흐름대로 가보겠습니다.


흐름

클라이언트 : 구독 후 메시지 입력

Vue Store : sendMessage() 호출

STOMP : /app/chat.sendMessage/{gameId}로 전송

백엔드 : ChatWebSocketController.sendMessage() 처리

서비스 : ChatMessageService.createMessage() → DB 저장

응답 : ChatMessageResponseDto 반환

STOMP : @SendTo("/topic/game/{gameId}")로 브로드캐스트 (

모든 구독자 : 메시지 수신 및 화면 업데이트


핵심 코드

사용자 채팅방 입장

// 사용자가 게임 상세 페이지로 이동
router.push(`/games/123`)  // 123번 게임 채팅방 입장

컴포넌트 마운트 → 연결 시작

// GameDetail.vue에서
onMounted(async () => {
  // 1. 게임 정보 조회
  await gameStore.fetchGameDetail(gameId.value)
  
  // 2. 채팅 연결 시작! ← 여기서 구독 과정 시작
  await chatStore.connectToGame(gameId.value, game.value)
})

채팅 연결 과정 (구독 포함)

// chatStore.connectToGame()에서
async connectToGame(gameId, gameData) {
  this.currentGameId = gameId
  this.currentGame = gameData
  
  try {
    // A) 기존 채팅 기록 먼저 로드
    await this.loadChatHistory(this.currentRoomId)
    
    // B) 웹소켓 연결 + 구독! ← 핵심!
    await this.connectStomp()
    
  } catch (error) {
    console.error('❌ 게임 연결 실패:', error)
  }
}

웹소켓 연결 + 구독 과정

// chatStore.connectStomp()에서
async connectStomp() {
  // 1) SockJS 웹소켓 연결
  const socket = new SockJS('http://localhost:8080/chat-socket')
  this.stompClient = new Client({ webSocketFactory: () => socket })
  
  // 2) 연결 성공하면 구독 시작!
  this.stompClient.onConnect = (frame) => {
    console.log('✅ STOMP 연결 성공!')
    this.connected = true
    
    // 🔔 구독 신청! ← 이게 핵심!
    this.stompClient.subscribe(`/topic/game/${this.currentGameId}`, (message) => {
      console.log('📨 새 메시지 수신:', message.body)
      const newMessage = JSON.parse(message.body)
      this.addMessage(newMessage) // 받은 메시지를 화면에 추가
    })
    
    console.log(`📡 구독 완료: /topic/game/${this.currentGameId}`)
  }
  
  // 3) 연결 시작
  this.stompClient.activate()
}

웹소켓에서는 구독이라는 용어가 있다
/topic/game/123 123번 게임 채팅을 듣겠다고 신청 (구독)
누군가 123번 게임에 채팅하면 -> 구독한 모든 사람에게 전달

기존 채팅 기록 로드

// 웹소켓 구독과 별개로 HTTP API로 기존 메시지 가져오기
async loadChatHistory(roomId) {
  const response = await http.get(`/api/chats/rooms/${roomId}`)
  const messages = response.data || []
  
  // 기존 메시지들을 화면에 표시
  messages.forEach(apiMessage => {
    const message = this.formatMessage(apiMessage)
    if (message.team === 'home') {
      this.homeMessages.push(message)
    } else {
      this.awayMessages.push(message)
    }
  })
}

이제 메시지 송수신 준비 완료!

// 사용자가 메시지 입력하고 전송 버튼 클릭
async sendMessage(content, team) {
  // 구독이 완료된 상태에서만 전송 가능
  if (!this.stompClient || !this.connected) {
    console.error('아직 연결되지 않았습니다!')
    return
  }

  // 메시지 전송
  this.stompClient.publish({
    destination: `/app/chat.sendMessage/${this.currentGameId}`,
    body: JSON.stringify({
      teamId: team === 'home' ? 1 : 2,
      content: content.trim(),
      type: 'TEXT'
    })
  })
}

백엔드에서 메시지 처리 후 브로드캐스트

@MessageMapping("/chat.sendMessage/{gameId}")
@SendTo("/topic/game/{gameId}")  // ← 구독자들에게 전송!
public ChatMessageResponseDto sendMessage(
    @DestinationVariable Long gameId,
    @Payload ChatMessageRequestDto messageRequest
) {
    // DB에 저장
    Long roomId = chatRoomService.getRoomIdByGameId(gameId);
    ChatMessageResponseDto response = chatMessageService.createMessage(roomId, messageRequest);
    
    // 이 리턴값이 /topic/game/{gameId}를 구독한 모든 클라이언트에게 전달됨!
    return response;
}

모든 구독자가 메시지 수신

// 위에서 구독한 콜백 함수가 실행됨
this.stompClient.subscribe(`/topic/game/${this.currentGameId}`, (message) => {
  console.log('📨 새 메시지 수신:', message.body)
  const newMessage = JSON.parse(message.body)
  this.addMessage(newMessage) // 화면에 새 메시지 추가!
})
profile
기록하고 공유하는 개발자

0개의 댓글