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

배경 (Background)

프로젝트 목표

  • 서비스 전반의 구조를 파악하고 그 구조들에서 공통적으로 사용되는 로직을 분리하여 개발 생산성을 향상한다.

문제 정의

  • 우리 서비스(Devths)는 구글 API, FastAPI 등 백엔드 서버 이외의 다양한 외부 서버와 소통한다.
    • 서비스 전반의 구조를 파악하여 개발 과정에서의 혼란을 줄이는 것이 필요하다.
  • 일부 기능들은 하나의 도메인이 아닌 여러 도메인에 공통적으로 사용된다.
    • 해당 기능들을 정리하여 분리를 용이하게 하고 높은 개발 생산성 및 유지보수성을 확보해야 한다.

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

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

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

API 명세(API Specification)

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

Presigned URL 발급

  • AI 챗봇, 게시글, 실시간 채팅 등에서 파일 첨부 시, 백엔드가 파일 바이트를 중계하지 않고 FE가 S3로 직접 업로드할 수 있도록 Presigned URL을 발급한다.
  • 업로드(대용량/병렬 업로드 포함) 시 서버 리소스를 보호하기 위해 “URL 발급”과 “메타데이터 등록”을 분리한다.
  • POST /api/files/presigned

구현 상세

  1. 인증: Authorization 헤더 검증
    • 요청 헤더 Authorization: Bearer {accessToken} 를 Spring Security FilterChain에서 검증

1-1. 인증 실패

  • 토큰 누락/형식 오류/만료/서명 불일치 등으로 인증 실패 시

  • AuthenticationEntryPoint에서 401 Unauthorized 응답을 반환

  • 응답 바디:

    message = "인증 실패: 로그인하지 않은 사용자의 접근입니다.", data = null

  1. 요청 바디 유효성 검증
    • fileName, mimeType 필수 값 검증 (null/blank 체크)
    • mimeType 허용 목록 검증(예: pdf, png, jpeg 등)
    • fileName에 경로 탐색 문자열(../) 등 포함 여부 검사 (S3 Key 안전성)

2-1. 검증 실패

  • 파라미터 형식/값이 유효하지 않으면 400 Bad Request 반환
  • message = "유효하지 않은 S3 Key이거나 요청 파라미터가 잘못되었습니다."
  1. S3 Key 생성 규칙 적용
    • 서버에서 업로드 대상 경로를 결정하고 s3Key를 생성
    • 예: resumes/{yyyy}/{MM}/{uuid}_{fileName}
    • 여기서 fileName은 공백/특수문자 치환, 길이 제한 적용
  2. Presigned URL 생성
    • AWS SDK(S3 Presigner)로 PutObject presigned URL 생성
    • 만료 시간(TTL): 10분
    • Content-Type, Content-Length 제한이 필요하면 조건부 서명 정책(또는 업로드 후 검증)을 고려
  3. 응답 반환
    • 200 OK
    • data.presignedUrl, data.s3Key 반환
    • message = "Presigned URL이 생성되었습니다."

에러/예외 매핑

  • AWS SDK 예외(권한, 버킷 설정, 네트워크) → 500 Internal Server Error
  • 입력값 문제(확장자/mime 불일치 등 정책 위반) → 400 Bad Request
  • 인증 문제 → 401

S3 파일 첨부 완료

  • FE가 Presigned URL로 S3 업로드를 끝낸 뒤, 업로드된 파일의 s3Key와 메타데이터를 DB에 파일 엔티티로 등록
  • refType/refId로 ‘어디에 첨부된 파일인지’를 연결(채팅방/게시글/챗봇 등)
  • POST /api/files

구현 상세

  1. 인증: Authorization 헤더 검증
    • 요청 헤더 Authorization: Bearer {accessToken} 를 Spring Security FilterChain에서 검증

1-1. 인증 실패

  • AuthenticationEntryPoint에서 401 반환
  1. 요청 바디 유효성 검증
    • 필수: originalName, s3Key, mimeType, category, fileSize, refType, sortOrder
    • 선택: refId
    • fileSize 최소/최대 제한
    • category, refType는 enum 매핑 실패 시 invalid
    • sortOrder는 1 이상

2-1. 검증 실패

  • 400 Bad Request 반환
  1. S3 Key 정책 검증
    • s3Key가 서버가 발급하는 prefix 규칙을 만족하는지 검증
      • 예: resumes/, chat/, posts/
  2. 참조 무결성 검증 (refType/refId)
    • refId가 들어오는 경우
      • refType에 맞는 대상 엔티티 존재 여부 확인 (예: chatroomRepository.existsById(refId))
    • 존재하지 않으면 404
  3. DB 저장
    • FileEntity 생성 (File(ownerId, s3Key, originalName, mimeType, category, fileSize, refType, refId, sortOrder))
    • fileRepository.save(fileEntity)
    • 저장 후 생성된 fileId, createdAt 확보
  4. 응답 반환
    • 201 Created
    • data.fileId, data.s3Key, data.createdAt 반환

에러/예외 매핑

  • Validation/enum 매핑 실패 → 400
  • 인증 실패 → 401
  • DB 저장 실패/예상치 못한 런타임 예외 → 500

S3 파일 삭제

  • “첨부 파일”이라는 비즈니스 개념을 파일 엔티티로 관리하되, 삭제는 파일 엔티티 기준으로 수행한다. (엔드포인트 경로는 /files로 통일)
  • DELETE /api/files/{fileId}

구현 상세

  1. 인증: Authorization 헤더 검증
    • 요청 헤더 Authorization: Bearer {accessToken} 를 Spring Security FilterChain에서 검증

1-1. 인증 실패

  • AuthenticationEntryPoint에서 401 반환
  1. PathVariable 검증
    • fileId가 Long 파싱 불가/음수/0 등 → 400 Bad Request
  2. DB 조회
    • fileRepository.findById(fileId) 로 파일 엔티티 조회

3-1. 파일이 존재하지 않음

  • orElseThrow()로 도메인 예외 발생 → 404 Not Found
  • message = "존재하지 않는 파일입니다."
  1. 인가: 소유자 검증
    • file.ownerId == currentUserId 확인

4-1. 인가 실패

  • 다르면 403 Forbidden
  • message = "인가 실패: 본인의 파일에만 접근할 수 있습니다."
  1. S3 삭제
    • s3Client.deleteObject(bucket, file.s3Key) 호출
    • S3 삭제 실패 시 정책
      • 강한 일관성: S3 삭제 실패면 DB 삭제도 롤백
  2. DB 삭제(또는 소프트 삭제)
    • isDeleted=true 갱신
  3. 응답 반환
    • 204 No Content
    • 바디 없음

에러/예외 매핑

  • fileId 파싱/검증 문제 → 400
  • 인증 실패 → 401
  • 소유자 불일치 → 403
  • 파일 없음 → 404
  • S3/DB 처리 중 예외 → 500

비동기 작업 상태 확인

  • 이미지/PDF 분석, 이력서/면접 리포트 생성 등 시간이 오래 걸리는 작업을 비동기로 처리한다.
  • 초기 구현 단순화를 위해 Polling 방식으로 진행 상황을 조회한다.
  • 추후 Webhook/WebSocket 등으로 전환 가능
  • GET /api/tasks/{taskId}

구현 상세

  1. 인증: Authorization 헤더 검증
    • 요청 헤더 Authorization: Bearer {accessToken} 를 Spring Security FilterChain에서 검증

1-1. 인증 실패

  • AuthenticationEntryPoint에서 401 반환
  1. Path Variable 검증
    • taskId Long 파싱/범위 검증 실패 → 400 Bad Request
    • message = "요청 파라미터가 잘못되었습니다."
  2. DB 조회
    • taskRepository.findById(taskId) 로 비동기 작업 엔티티 조회

3-1. Task가 존재하지 않음

  • orElseThrow() → 404 Not Found
  • message = "존재하지 않는 Task입니다."
  1. 인가: 소유자 검증
    • task.ownerId == currentUserId 확인

4-1. 인가 실패

  • 403 Forbidden
  • message = "인가 실패: 본인의 비동기 작업에만 접근할 수 있습니다."

5-1. PROGRESSING

  • 아직 처리 중이면 202 Accepted
  • data.result = null, isNotified=false 등 그대로 반환
  • message = "비동기 작업이 처리 중입니다."

5-2. COMPLETED

  • 200 OK
  • taskType에 따라 data.result 구조가 달라짐
    • RESUME/INTERVIEW → 채팅방 메시지 형태의 REPORT 결과
    • SCHEDULE → 일정 추출 결과(회사명, 데드라인, 위치, 태그 등)
  • message는 taskType별 성공 메시지 사용
    • 예: "이력서 및 포트폴리오 분석에 성공하였습니다."

5-3. FAILED

  • 200 OK + status=FAILED
  • 실패 사유: failReason, failMessage 포함
  • 동시성 고려: 최초 조회 1회만 처리하려면 조건부 업데이트 필요

에러/예외 매핑

  • taskId 파싱 오류 → 400
  • 인증 실패 → 401
  • 소유자 불일치 → 403
  • task 없음 → 404
  • DB/직렬화/기타 예외 → 500