Project/AutoSchedule

8일차 - WebSocket 환경 세팅

sowon02 2025. 11. 10. 16:37

오늘은 프로젝트에 실시간 협업 기능을 추가하기 위해 WebSocket과 STOMP를 구현했다.
이 프로젝트에서는 여러 사용자가 동시에 작업 및 일정을 관리하기 때문에, 서버 - 클라이언트 간 양방향 통신이 필수적이다.

 

이번에 WebSocket을 도입한 이유는 다음과 같다. 

 

  • 실시간 일정 편집: 팀원이 캘린더에서 회의, 개인일정, 작업을 추가하거나 이동, 삭제할 때
    다른 사람의 화면에서도 즉시 반영되어야 한다.
  • 자동 스케줄 결과 배포: AI가 최적화한 스케줄을 계산 완료 즉시,
    모든 클라이언트에 결과와 세부 일정을 push해야 한다.
  • 충돌 경고 / 진행률 표시: 스케줄 최적화 중 충돌 감지, 진행률, 추천 변경안 같은 중간 상태를
    실시간으로 띄우려면 WebSocket 스트리밍이 가장 효율적이다.
  • 협업 알림: 특정 작업 담당자 변경, 회의 확정, 연기 등의 알림을
    팀원에게 즉시 전달하기 위해 STOMP 브로커가 필요하다.
  • 상세 뷰 동기화: 여러 사용자가 동일한 Task나 일정 상세 화면을 보고 있을 때,
    한 사람이 메모나 상태를 수정하면 다른 사람의 화면도 즉시 갱신되어야 한다.

이러한 이유로 오늘은 WebSocket/STOMP 기반의 실시간 이벤트 시스템을 구축하였다.


 

 

오늘 진행한 작업

 

  • WebSocket 엔드포인트 및 STOMP 브로커 설정
  • 도메인 이벤트를 통합 관리하는 CollaborationEventPublisher 구현
  • 작업 / 일정 / 팀 / 스케줄 최적화 서비스에 실시간 이벤트 연동
  • 프론트엔드에서 STOMP 클라이언트 연결 및 구독 로직 추가

 


1. 서버 WebSocket/STOMP 설정

먼저 서버 쪽에 WebSocket 엔드포인트(/ws)를 열고, STOMP 브로커를 설정했다.
브로커는 /topic/** 경로를 구독용으로, /app/** 경로를 송신용으로 구분하였다.

 

또한 SecurityConfig에서 /ws/** 경로를 인증 예외 처리하여,
WebSocket 핸드셰이크 과정에서 401 에러가 발생하지 않도록 했다.

// SecurityConfig.java
@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .csrf().disable()
        .authorizeRequests()
        .antMatchers("/ws/**", "/swagger-ui/**", "/v3/api-docs/**").permitAll()
        .anyRequest().authenticated();
}

2. CollaborationEventPublisher 구현

오늘 작업의 핵심은 도메인 이벤트를 한 곳에서 관리하는 허브를 만드는 것이었다.
CollaborationEventPublisher는 다음과 같은 역할을 수행한다.

  • 작업(Task): /topic/tasks/{teamId}
  • 일정(CalendarEvent): /topic/calendar/{teamId}
  • 스케줄 진행률(ScheduleProgress): /topic/schedules/{teamId}
  • 충돌 알림(ConflictAlert): /topic/conflicts/{teamId}
  • 협업 알림(Notification): /topic/notifications/team/{teamId}, /topic/notifications/user/{userId}
  • 상세 뷰 동기화: /topic/detail/{entityType}/{entityId}

이제 서비스 단에서는 단순히 eventPublisher.publishTaskEvent(message) 같은 한 줄 호출로 실시간 전파가 가능하다.


3. 서비스별 이벤트 연동

각 서비스에서 데이터 변경 직후, STOMP 메시지를 발행하도록 수정했다.

 

  • TaskService
    • 작업 생성/수정/삭제 후 실시간 브로드캐스트
    • 담당자 변경 시 개인 알림 발송
    • 상세 뷰(/topic/detail/task/{id}) 갱신
  • CalendarEventService
    • 일정 저장/수정 시 충돌 감지
    • ConflictAlertMessage를 /topic/conflicts/{teamId} 로 발행
    • 일정 변경 알림 + 상세 뷰 갱신
  • TeamService
    • 팀 생성/수정/삭제 시 팀 알림 브로드캐스트
    • 초대 확정 시 팀/사용자 채널 모두 전송
  • ScheduleOptimizationService
    • 최적화 진행률을 /topic/schedules/{teamId} 로 스트리밍
    • 완료/실패 시 팀 알림 자동 발송

4. WebSocketMessageController 추가

테스트 및 스터빙용 컨트롤러도 추가했다.
/app/** 경로로 들어온 STOMP 메시지를 동일한 /topic/** 경로로 그대로 릴레이해,
프론트에서 실시간 협업 흐름을 직접 검증할 수 있다.


5. 프론트엔드 STOMP 연동

프론트엔드에서는 공용 STOMP 클라이언트 헬퍼 (src/lib/ws.ts)를 새로 정리했다.

 

  • SockJS 기반 연결 + 5초 자동 재연결
  • JWT 토큰을 Authorization 헤더로 전송
  • 개발 모드에서 STOMP 로그 출력
export function createStompClient(config?: Partial<ClientConfig>) {
  const token = localStorage.getItem('accessToken');
  const client = new Client({
    webSocketFactory: () => new SockJS(DEFAULT_WS_URL),
    reconnectDelay: 5000,
    connectHeaders: token ? { Authorization: `Bearer ${token}` } : undefined,
    ...config
  });
  return client;
}

 

 

 

 

Tasks, Calendar, TeamLayout 화면에서 새로운 토픽을 구독하도록 수정했다.

  • /topic/tasks/{teamId} → 작업 생성/수정/삭제 실시간 반영
  • /topic/schedules/{teamId} → 최적화 진행률 업데이트
  • /topic/notifications/** → 팀/사용자 알림 토스트 표시
  • /topic/conflicts/{teamId} → 충돌 시 경고 토스트 표시

6. 인증 및 타입 안정화

로그인 시 사용자 ID를 공용 스토어(store/auth.ts)에 저장하고, Axios 요청 헤더에 Authorization을 자동으로 붙이도록 변경했다.
또한 @types/sockjs-client를 devDependencies에 추가해 타입 오류 없이 빌드를 완료했다.


7. 결론

오늘은 WebSocket과 STOMP를 활용해 실시간 협업 인프라를 구축했다.
CollaborationEventPublisher를 중심으로 각 서비스의 이벤트를 하나로 묶어,
작업·일정·팀 알림 등이 서버 변경 직후 바로 브라우저에 반영되도록 만들었다.

 

원래는 오늘 보안 파트(인증·인가·토큰 재발급) 까지 세 단계로 마무리할 계획이었지만,
소켓 구조가 생각보다 복잡하고 브로커 경로 테스트에 시간이 많이 걸려서
보안 파트는 다음 단계로 미루게 되었다..