[BE-61] 지원 서류 업로드·조회 API (멀티파트) (FR-81~84)
작업 내용 (설계 의도)
근거 TDD: 20260808-지원관리-확장-tdd.md — “API 계약 2단계 지원 서류”
변경 사항
POST /api/documents(multipart/form-data),GET /api/documents/series,GET /api/documents/series/{seriesId},GET /api/documents/versions/{versionId}/content를 신설합니다.MultipartFile타입은 presentation에만 존재합니다 — 컨트롤러가 바이트 배열·파일명·크기를 꺼내 Command로 변환하고, application·domain에는MultipartFile이 노출되지 않습니다.- 다운로드는 스트리밍 대신 바이트 반환입니다 — 최대 20MB이고 사용자가 1명이라 메모리 부담이 없습니다.
Content-Disposition: attachment; filename*=UTF-8''{인코딩된 원본 파일명}으로 한글 파일명을 보존합니다. seriesId가 있으면 기존 계열에 새 버전을, 없으면seriesTitle로 신규 계열을 만듭니다. 둘 다 없으면 400.- 피처 플래그
document.upload가 OFF면 409FEATURE_DISABLED입니다. isCurrentProfileSource는 recommendation 컨텍스트 조회가 필요하므로 application 레이어가 조합합니다. 2단계 배포 순서상 BE-62 이전에는 항상false입니다.
파일 소유: DocumentApiController.kt(신규)만 만듭니다.
의존
- BE-57 (문서 도메인·파일 게이트웨이)
다이어그램
처리 흐름
sequenceDiagram participant FE as web(SPA) participant C as DocumentApiController participant U as UploadDocumentUseCase participant D as DocumentDomainService participant G as DocumentFileGateway FE->>C: POST /api/documents (multipart) C->>C: MultipartFile → ByteArray + 메타 (Command 변환) C->>U: execute(command) U->>D: upload(input) D->>G: store(relativePath, bytes) D-->>U: ApplicationDocumentVersion U-->>C: DocumentVersionResponse C-->>FE: 201 FE->>C: GET /versions/{id}/content C->>U: execute(versionId) U->>D: readContentOf(versionId) D->>G: read(relativePath) C-->>FE: 200 octet-stream + Content-Disposition
클래스 의존
flowchart LR subgraph Presentation["presentation/document"] Api[DocumentApiController] Req[DocumentUploadRequest] end subgraph Application["application/document"] Upload[UploadDocumentUseCase] ListS[ListDocumentSeriesUseCase] GetS[GetDocumentSeriesUseCase] Download[DownloadDocumentUseCase] end subgraph Domain["domain/document"] DS[DocumentDomainService] Flag[FeatureFlagGateway] end Api --> Req Api --> Upload Api --> ListS Api --> GetS Api --> Download Upload --> DS Upload --> Flag Download --> DS
테스트 케이스
- PDF를 업로드하면 201과 버전 정보가 반환되고 실제 파일이 저장 루트에 생성된다
seriesId없이seriesTitle만 주면 신규 계열이 생성된다seriesId를 주면 기존 계열에 다음 버전이 추가된다seriesId·seriesTitle둘 다 없으면 400이다- 존재하지 않는
seriesId면 404DOCUMENT_SERIES_NOT_FOUND다 - 21MB 파일 업로드는 400
DOCUMENT_SIZE_EXCEEDED이고 파일이 생성되지 않는다 exe확장자는 400DOCUMENT_EXTENSION_NOT_ALLOWED다- 문서 제목에
../를 넣어도 저장 루트를 벗어나지 않는다 - 계열 목록을
documentType필터로 조회할 수 있다 - 계열 상세 조회 시 버전이 번호 오름차순으로 반환된다
- 다운로드 시 한글 원본 파일명이
Content-Disposition에 보존된다 - 파일이 물리적으로 사라진 버전을 다운로드하면 404다
- 피처 플래그 OFF면 업로드가 409
FEATURE_DISABLED다 - 계열이 0건이면 빈 배열을 반환한다 (0건 경계)