[BE] 챗봇 도메인 테크 스펙 - 100-hours-a-week/9-team-Devths-WIKI GitHub Wiki

배경 (Background)

프로젝트 목표

  • 사용자별 AI 채팅방을 생성/조회/삭제하고, 채팅방 내 메시지(사용자 발화/AI 응답/시스템 메시지)를 저장한다.
  • 메시지 저장을 통해 “대화 기록 기반의 재질문/회고/결과 재조회”를 가능하게 한다.
  • 면접 모드의 경우, 면접 세션의 진행 상태(질문 수/진행중/완료)를 별도 엔티티로 관리한다.

설계 및 기술 자료 (Architecture and Technical Documentation)

공통 인증/인가

인증

  • 모든 HTTP API는 Authorization: Bearer {accessToken} 필수
  • Spring Security FilterChain에서 검증
    • JwtAuthenticationFilter#doFilterInternal()
  • 실패 시 AuthenticationEntryPoint에서 401 반환

인가

  • AI 채팅방은 ai_chat_rooms.user_id가 곧 소유자이므로, 모든 roomId 기반 요청은 다음 조건을 만족해야 함:
  • aiChatRoom.userId == currentUserId
  • 불일치 시 403 "인가 실패: 해당 AI 채팅방에 접근 권한이 없습니다."

레이어/클래스 구성

Controller

  • AiChatRoomController
  • AiChatMessageController

Service

  • AiChatRoomService
  • AiChatMessageService
  • AiChatAuthorizationService
  • (면접) AiInterviewService
  • (연동) AiServerClient (FastAPI 호출)

Repository

  • AiChatRoomRepository
  • AiChatMessageRepository
  • AiChatInterviewRepository

데이터베이스 스키마(ERD)

https://www.erdcloud.com/d/NyT4nhhqhXwbhRFQP

주요 테이블

ai_chat_rooms

  • id (PK)
  • user_id (FK) : 방 소유자
  • room_uuid (varchar(36), unique) : AI 서버/벡터DB와 공유할 고유 키
  • title (varchar(100), nullable) : 채팅방 제목(“AI 요약으로 들어가는 채팅방 제목”)
  • created_at, updated_at
  • is_deleted, deleted_at (soft delete)

ai_chat_messages

  • id (PK)
  • room_id (FK)
  • interview_id (FK, nullable) : 면접 세션 연결용
  • role (varchar(20)) : USER / ASSISTANT / SYSTEM ...
  • content (LONGTEXT) : AI 답변 고려하여 LONGTEXT
  • type (varchar(10)) : NORMAL / REPORT / INTERVIEW (라벨)
  • metadata (JSON, nullable) : 응답 메타데이터(점수/요약/강점 등)
  • created_at

ai_chat_interview

  • id (PK)
  • room_id (FK) : 면접이 진행되는 AI 채팅방
  • interview_type (varchar(20)) : 인성/기술 등
  • current_question_count (int, default 0) : 0~5
  • status (varchar(20), default IN_PROGRESS) : 진행중/완료
  • created_at, updated_at

API 명세 (API Specification)

https://docs.google.com/spreadsheets/d/1AsVVNAScY2LcECVPUTbqXE58QLbRbzccSAv-vZw9pOA/edit?gid=273586348#gid=273586348

AI 채팅방 목록 조회

GET /api/ai-chatrooms

목적

  • 로그인 사용자의 AI 채팅방 목록을 조회한다(soft delete 제외).

상세 구현

  • 인증
    • FilterChain에서 JWT 검증
    • 실패 시 401
  • DB 조회
    • aiChatRoomRepository.findAllByUserIdAndIsDeletedFalseOrderByUpdatedAtDesc(userId)
    • 정렬 기준: 최근 채팅한 방 먼저 (updated_at DESC)
  • DTO 매핑
    • roomId, roomUuid, title, createdAt, updatedAt
  • 응답
    • 200 OK
    • "AI 채팅방 목록을 성공적으로 조회하였습니다."

AI 채팅방 생성

POST /api/ai-chatrooms

목적

  • 사용자 소유의 AI 채팅방을 생성한다.
  • title 기본값은 "새 채팅방"

상세 구현

  • 인증 → 실패 시 401
  • 채팅방 엔티티 생성
    • roomUuid = UUID.randomUUID().toString()
    • title = "새 채팅방"
    • createdAt = now, updatedAt = now
    • isDeleted = false
  • DB 저장
    • aiChatRoomRepository.save(room)
  • 응답(201)
    • roomId, roomUuid, title, createdAt 반환

AI 채팅방 텍스트 전송

POST /api/ai-chatrooms/{roomId}/messages

목적

  • 사용자의 메시지를 저장하고, AI 서버(또는 내부 LLM 모듈)에서 응답을 생성하여, USER 메시지 + ASSISTANT 메시지를 함께 저장 후 반환한다.

요청

  • path variable: roomId
  • body: { "content": "메시지 전송" }

상세 구현

  • 인증 → 실패 시 401
  • PathVariable / Body 검증
    • roomId Long 파싱 실패 또는 <=0 → 400
    • content null/blank → 400
  • 채팅방 조회 + soft delete 체크
    • aiChatRoomRepository.findByIdAndIsDeletedFalse(roomId)
    • 없으면 404 "해당 AI 채팅방을 찾을 수 없습니다."
  • 인가(소유자 체크)
    • room.userId == currentUserId 확인
    • 불일치면 403
  • USER 메시지 저장
    • AiChatMessage userMsg = AiChatMessage.ofUser(roomId, interviewId, content)
    • role = USER
    • type = NORMAL (스펙상 기본)
    • metadata = null
    • createdAt = now
    • aiChatMessageRepository.save(userMsg) → messageId 확보
  • AI 서버 호출(응답 생성)
    • AiServerClient.generateReply(roomUuid, content, optionalContext)
      • AI 서버/벡터DB에서 대화 컨텍스트를 room_uuid 기준으로 연결
    • 응답: aiContent, metadata(json)
  • ASSISTANT 메시지 저장
    • AiChatMessage aiMsg = AiChatMessage.ofAssistant(roomId, interviewId, aiContent, metadata)
    • role = ASSISTANT
    • type = NORMAL (or REPORT/INTERVIEW)
    • metadata = json(없으면 null)
    • aiChatMessageRepository.save(aiMsg)
  • 채팅방 updatedAt 갱신
    • room.touchUpdatedAt(now)
    • aiChatRoomRepository.save(room) 또는 dirty-check
  • 응답(201)
    • data.userMessage / data.aiResponse 형태로 반환
    • "AI 챗봇이 성공적으로 응답을 생성했습니다."

예외/에러 매핑

  • 400: roomId/content 검증 실패, 바인딩 오류
  • 401: 인증 실패
  • 403: 소유자 불일치
  • 404: room 없음/삭제됨
  • 500: AI 서버 오류, DB 오류, 예상치 못한 예외

AI 채팅 내역 조회

GET /api/ai-chatrooms/{roomId}/messages?size=n&lastId=k

목적

  • 특정 AI 채팅방의 메시지를 커서 기반으로 조회한다.

파라미터

  • size(default 10)
  • lastId(Long) : 커서

상세 구현

  • 인증 → 실패 401
  • 검증
    • roomId/size/lastId 파싱 및 범위 검증
    • size <= 0 또는 지나치게 큰 값 제한 → 400
  • 채팅방 조회 및 인가
    • aiChatRoomRepository.findByIdAndIsDeletedFalse(roomId) 없으면 404
    • 소유자 불일치면 403
  • 메시지 조회(커서 페이징)
    • 최신부터 과거로: id DESC
    • 첫 페이지: lastId 없으면 최신 size
    • 다음 페이지: id < lastId 조건
  • hasNext/lastId 계산
    • size+1로 조회 후 초과분 있으면 hasNext=true
    • 응답 lastId는 “이번 페이지의 마지막 messageId”로 내려줌
  • DTO 매핑
    • roomId, messageId, interviewId, role, content, type, metadata, createdAt
  • 응답(200)

AI 채팅방 삭제

DELETE /api/ai-chatrooms/{roomId}

목적

  • AI 채팅방을 soft delete 처리한다.

상세 구현

  • 인증 → 401
  • roomId 검증 → 400
  • room 조회(+isDeleted=false) → 없으면 404
  • 소유자 체크 → 불일치 403
  • soft delete
    • room.isDeleted = true
    • room.deletedAt = now
    • aiChatRoomRepository.save(room) (또는 dirty-check)
  • 204 반환

면접 세션 생성 트리거

  • “모의 면접 시작” 버튼 또는 특정 메시지(type=INTERVIEW) 진입 시
  • AiInterviewService#createSession(roomId, interviewType)
    • status=IN_PROGRESS
    • currentQuestionCount=0
    • createdAt=now

질문 수 증가/완료 처리

  • USER 답변이 들어올 때마다 currentQuestionCount++
  • 5회에 도달하면 status=COMPLETED로 변경 + updatedAt 기록

메시지와 인터뷰 연결

  • 인터뷰 진행 중이면, 해당 세션 id를 메시지(interview_id)에 세팅하여 “특정 세션의 문답 묶음”을 조회/리포트 생성에 활용