Skip to content

[feat] 롱 폴링 기반 상담 채팅 시스템 구현 #31

@clainyun

Description

@clainyun

[feat] 롱 폴링 기반 상담 채팅 시스템 구현

📌 이슈 유형

  • ✨ 새 기능 추가 (feat)
  • 🐛 버그 수정 (fix)
  • 🔧 기능 개선 (refactor)
  • 📚 문서 작업 (docs)
  • 🧪 테스트 (test)
  • 🏗️ 빌드/배포 (ci/build)
  • 🔥 긴급 수정 (hotfix)
  • 🧹 기타 작업 (chore)

🎯 배경 / 목적

회원과 기관 간 실시간 상담을 위한 채팅 기능이 필요합니다. WebSocket 대신 구현이 간단한 롱 폴링 방식을 채택하여 기관 소속 여러 관리자(기관장, 직원)가 동시에 참여할 수 있는 다대일 채팅을 구축합니다.

상담 요청 생성 이후, 회원이 상담 요청 상세 화면에서 ‘상담 시작’ 버튼을 누르는 순간,
ConsultRequest + ChatRoom 이 동시에 생성되며 상담이 활성화됩니다.

App/Web 공통으로 사용할 수 있는 RESTful API를 제공하며, 기관 측 메시지는 화면에 기관명으로만 표기되도록 합니다.


✅ 완료 조건 (AC: Acceptance Criteria)

  • ConsultRequest 상태를 ACTIVE / CLOSED로 간소화

    • ACTIVE: 상담 진행 가능
    • CLOSED: 상담 완전 종료
  • 같은 회원 + 같은 상담 서비스에 대해 ACTIVE 상태의 ConsultRequest는 하나만 존재

  • 회원이 ‘상담 시작’ 클릭 시
    ConsultRequest + ChatRoom 동시 생성

  • CLOSED 상태에서 재시작 시
    새로운 ConsultRequest + 새로운 ChatRoom 생성

  • ConsultRequest 1건은 최대 1개의 ChatRoom을 가진다 (1:1)

  • 회원 ↔ 기관 관리자 다대일 채팅 지원

  • 롱 폴링 방식으로 신규 메시지 실시간 수신 (30초 타임아웃)

  • 기관 관리자 메시지는 DTO에서 기관명만 노출

  • 메시지 전송 / 조회 / 삭제 API 구현

  • Pagination 및 Soft Delete 지원

  • Swagger 문서화

  • 권한 검증 (회원: 자신의 상담만, 관리자: 소속 기관 상담만 처리 가능)

  • 읽음 처리 기능(isRead, readAt)은 후속 작업(Phase 2)에서 구현


🛠️ 해결 방안 / 구현 상세

1. 엔티티 설계


ConsultRequest (상담 요청 — 기존 수정)

- status: ACTIVE / CLOSED
- ACTIVE: 상담 가능 상태
- CLOSED: 상담 종료
- 중복 방지: 같은 회원 + 같은 상담 서비스는 ACTIVE 1개만 허용
- CLOSED재시작  새로운 ConsultRequest 생성

ChatRoom (채팅방 — 신규)

- id
- consultRequest (ConsultRequest 1:1, unique)
- lastMessageContent
- lastMessageAt
- isActive (ACTIVE/CLOSED와 연동)
- createdAt (BaseEntity)

ChatMessage (메시지 — 신규)

- id
- chatRoom
- senderType (MEMBER, INSTITUTION_ADMIN)
- senderId (회원 ID 또는 관리자 ID)
- content
- deleted (Soft Delete)
- createdAt (BaseEntity)

ChatParticipant / 읽음 처리 필드는 Phase 2에서 추가 예정


2. API 설계 (ConsultRequest Id 기반 유지)

▶ 상담 시작 (ConsultRequest + ChatRoom 동시 생성)

POST /api/v1/consult-requests

Body 예:

{
  "institutionId": 1,
  "counselId": 3
}

Response:

{
  "consultRequestId": 12,
  "chatRoomId": 8
}

▶ 메시지 API

POST   /api/v1/consult-requests/{requestId}/chat/messages
GET    /api/v1/consult-requests/{requestId}/chat/messages
GET    /api/v1/consult-requests/{requestId}/chat/messages/poll
DELETE /api/v1/consult-requests/{requestId}/chat/messages/{messageId}
GET    /api/v1/consult-requests/{requestId}/chat

※ 채팅방 목록 조회는 Phase 2 (현재 스코프에서 제외)


3. 롱 폴링 구현 전략

  • 타임아웃: 30초
  • 폴링 간격: 0.5초
  • 종료 조건: 신규 메시지 발생 즉시 반환
  • 요청 파라미터: lastMessageId
  • 응답: 신규 메시지 리스트 (없으면 빈 배열)

4. 권한 검증

회원

consultRequest.member.id == currentUser.id

기관 관리자

consultRequest.institution.id == admin.institution.id

🧩 작업 범위

✔ 기존 수정

  • ConsultRequestStatus.java — ACTIVE/CLOSED로 축소
  • ConsultRequest.java — 상태 전이 메서드 정리
  • ConsultRequestRepository.java — ACTIVE 중복 방지 쿼리 추가
  • SecurityConfig.java — 채팅 API 허용 추가

✔ 신규 생성

  • ChatRoom, ChatMessage 엔티티
  • Repository (ChatRoomRepository, ChatMessageRepository)
  • ChatService (startChat, sendMessage, getMessages, pollMessages, deleteMessage, getChatRoomInfo)
  • ChatController
  • DTO 생성

✔ DB 변경

  • consult_request 테이블 상태값 변경
  • chat_room, chat_message 테이블 추가

🧪 테스트 계획

단위 테스트

  • ChatRoom 생성 (상담 시작 시)
  • ACTIVE 중복 방지 로직
  • Soft Delete
  • 권한 검증
  • Pagination

통합 테스트

  • 상담 요청 생성 → 상담 시작 → ChatRoom 자동 생성
  • 메시지 전송 → 롱 폴링 수신
  • 기관 관리자 다대일 채팅

수동 테스트

  1. 회원이 ‘상담 시작’ 버튼 클릭 → ChatRoom 생성
  2. 기관명 표시 정상 동작
  3. 롱 폴링 정상 동작
  4. Soft Delete 후 재조회 제외
  5. 권한 체크 정상 동작

🔐 보안/성능 고려사항

  • ConsultRequest 기반 권한 검증 철저
  • 인덱스: (chat_room_id, id), (chat_room_id, created_at)
  • Soft Delete 적용
  • 기관 관리자 실명 노출 금지 → 기관명 표시

🚀 배포 체크리스트

  • 환경변수 변경 없음
  • JPA 신규 테이블/컬럼 정상 생성
  • 기존 API 영향 없음
  • Swagger 동기화
  • 롱 폴링 타임아웃 및 응답 지연 여부 확인

🙋‍♂️ 담당자


📎 참고

  • 11/23 회의록
  • 엔티티: ConsultRequest, InstitutionAdmin, Member, Institution
  • MDN Long Polling Docs
  • 원칙: 상담요청 1건 = ChatRoom 1개

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions