Skip to content

Refactor: 문서 썸네일 구현 #195

Merged
lunarbae628 merged 25 commits into
devfrom
refacto/190-doc-preview
Apr 29, 2026
Merged

Refactor: 문서 썸네일 구현 #195
lunarbae628 merged 25 commits into
devfrom
refacto/190-doc-preview

Conversation

@lunarbae628
Copy link
Copy Markdown
Collaborator

@lunarbae628 lunarbae628 commented Apr 26, 2026

🛰️ Issue Number

🪐 작업 내용

image

1. 문서 썸네일 도메인 추가

  • 문서별 대표 썸네일을 관리하기 위한 Thumbnail 엔티티를 추가했습니다.
  • 문서 생성 시 기본 Thumbnail row를 함께 생성하도록 변경했습니다.
  • 썸네일 상태를 EMPTY, PENDING, READY, FAILED로 관리합니다.
  • 썸네일 갱신 요청마다 requestToken을 증가시켜 오래된 썸네일 생성 결과가 최신 결과를 덮어쓰지 않도록 했습니다.

주요 흐름:

  • 저장 API 호출 시 썸네일 갱신 요청 토큰 발급
  • 프론트에서 문서 내용을 기반으로 썸네일 이미지 생성/업로드
  • 썸네일에 노출되는 수정사항을 프론트단에서 signature계산하여 썸네일 생성 최적화
  • PUT /api/document/{docId}/thumbnail로 썸네일 확정
  • 확정 시 requestToken, 문서 소유자, 이미지 상태, 이미지 목적을 검증
sequenceDiagram
    participant FE as Frontend
    participant SAVE as Save API
    participant IMG as Image API
    participant S3 as S3
    participant TH as Thumbnail API

    FE->>SAVE: PUT /api/document/{docId}/save/{saveId}
    SAVE->>SAVE: 문서 저장 + Thumbnail.requestToken++
    SAVE-->>FE: updatedAt + requestToken + signature + status

    FE->>FE: 현재 상단 signature 계산

    alt signature 동일
        FE->>FE: 썸네일 업로드 생략
    else signature 다름
        FE->>IMG: POST /api/images/upload-url purpose=DOC_THUMBNAIL
        IMG-->>FE: imageId + uploadUrl
        FE->>S3: PUT presigned URL
        FE->>IMG: POST /api/images/{imageId}/complete
        FE->>TH: PUT /api/document/{docId}/thumbnail
        TH->>TH: requestToken 최신 여부 검증
        TH-->>FE: 대표 썸네일 READY
    end
Loading

2. 썸네일 확정 API 추가

  • ThumbnailController를 추가해 문서 대표 썸네일 확정 API를 제공하도록 했습니다.
  • 확정 가능한 이미지는 다음 조건을 모두 만족해야 합니다.
    • 같은 문서의 이미지
    • 현재 로그인 사용자의 이미지
    • 업로드 완료 상태(ACTIVE)
    • 썸네일 목적(DOC_THUMBNAIL)

3. 문서 목록 응답을 썸네일 기반으로 변경

  • 기존 문서 목록의 텍스트 preview 추출 흐름을 제거하고, 썸네일 URL과 썸네일 상태를 반환하도록 변경했습니다.
  • DocPageResponsethumbnailUrl, thumbnailStatus를 추가했습니다.
  • DocListAssembler에서 문서 목록 조회 시 썸네일과 현재 이미지를 fetch join으로 조회해 목록 응답을 구성하도록 변경했습니다.

4. 이미지 업로드 목적 분리

  • ImagePurpose를 추가했습니다.
    • DOC_CONTENT
    • DOC_THUMBNAIL
  • 이미지 업로드 URL 요청 DTO에 purpose를 추가했습니다.
  • purpose에 따라 S3 object key prefix를 분리했습니다.
    • 본문 이미지: users/{userId}/docs/{docId}/images/{uuid}.{ext}
    • 썸네일 이미지: users/{userId}/docs/{docId}/thumbnails/{uuid}.{ext}

5. 썸네일 교체 시 이전 S3 객체 삭제 Outbox 추가

  • 썸네일이 새 이미지로 교체될 때 이전 썸네일 이미지를 바로 삭제하지 않고 S3 삭제 Outbox에 적재하도록 구현했습니다.
  • DB 트랜잭션과 S3 삭제 작업을 직접 묶지 않고, Outbox worker가 비동기로 삭제하도록 분리했습니다.
  • S3 삭제 성공 시 이미지 상태를 DELETED로 변경합니다.
  • S3 객체가 이미 없는 경우(404)는 최종 목표가 달성된 것으로 보고 성공 처리합니다.

6. Outbox 공통화 및 Mongo Outbox 패키지 정리

  • 기존 Mongo 삭제 Outbox와 신규 S3 삭제 Outbox에서 공통으로 사용하는 필드를 BaseOutboxEntity로 분리했습니다.
    • status
    • retryCount
    • maxRetry
    • doneAt
    • lastError
    • version
  • OutboxStatus를 공통 enum으로 분리했습니다.
  • Mongo 삭제 Outbox 패키지를 global.mongo.outbox에서 global.outbox.mongo로 이동했습니다.
  • 신규 S3 삭제 Outbox는 global.outbox.s3 하위에 구성했습니다.

7. Outbox worker 안정성 개선

  • Mongo/S3 Outbox worker의 claim 처리를 OPEN -> PROCESSING 조건부 update 쿼리로 변경했습니다.
  • 여러 worker가 같은 OPEN row를 동시에 처리하지 않도록 DB update의 원자성을 활용했습니다.
  • PROCESSING 상태에서 일정 시간 이상 멈춘 row는 worker 시작 시 다시 OPEN으로 복구하도록 했습니다.
  • timeout 복구는 실제 외부 삭제 실패가 아니므로 retryCount를 증가시키지 않도록 분리했습니다.

8. 문서 및 테스트 정리

  • 썸네일 확정 API Swagger 문서를 추가했습니다.
  • 이미지 업로드 Swagger에 purpose 필드를 반영했습니다.
  • 썸네일 서비스, 이미지 업로드 목적별 object key, S3 삭제 서비스, Mongo Outbox 관련 테스트를 추가/수정했습니다.
  • 분기/버전/브랜치의 용어 혼용을 브랜치로 통일했습니다.

📚 Reference

  • Outbox Pattern
  • AWS S3 Presigned PUT URL
  • Spring Data JPA @Modifying update query
  • JPA optimistic locking / version field
  • 문서 썸네일 동기화 flow

✅ Check List

  • 코드가 정상적으로 컴파일되나요?
  • 테스트 코드를 통과했나요?
  • merge할 브랜치의 위치를 확인했나요?
  • Label을 지정했나요?

- 문서 생성 시 썸네일 메타데이터 생성
- 저장 응답에 썸네일 동기화 정보 포함
- requestToken 검증 기반 썸네일 확정 API 추가
- 문서 목록 preview를 thumbnailUrl/status로 대체
- 썸네일 교체 시 이전 썸네일 이미지를 S3 삭제 아웃박스에 적재
- S3 삭제 워커와 재시도/완료 상태 처리 추가
- OutboxStatus와 BaseOutboxEntity를 공통 패키지로 분리
- Mongo 삭제 아웃박스를 global.outbox.mongo 패키지로 이동
- 관련 단위 테스트 추가 및 패키지 변경에 따른 테스트 수정
- Mongo/S3 oubox claim을 OPEN 조건부 update 쿼리로 변경
- 여러 worker가 같은 OPEN row를 중복 처리하지 않도록 방지
- PROCESSING timeout 복구 시 retryCount를 증가시키지 않도록 분리
- timeout 복구 정책 변경에 맞춰 통합 테스트 기대값 수정
@lunarbae628 lunarbae628 self-assigned this Apr 26, 2026
@lunarbae628 lunarbae628 added the ✨Feature New feature or request label Apr 26, 2026
@lunarbae628 lunarbae628 linked an issue Apr 26, 2026 that may be closed by this pull request
@lunarbae628 lunarbae628 merged commit 7125a0c into dev Apr 29, 2026
2 checks passed
@lunarbae628 lunarbae628 deleted the refacto/190-doc-preview branch April 29, 2026 20:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨Feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor: 문서 미리보기 썸네일로 변환 및 성능 개선

1 participant