<aside> 📌

목차

게시물 · 미디어 🖼️

개요

게시물 · 미디어 시스템은 밴드가 생성한 콘텐추의 라이프사이클과 파일 저장 인프라의 기반을 관리합니다. 밴드는 텍스트, 이미지, 동영상으로 업데이트를 공유할 수 있으며, 팬은 좋아요와 스레드형 댓글로 상호작용합니다.

Post Type 미디어 제약 용도
TEXT 0 URLs 순수 텍스트 업데이트입니다.
PHOTO 1개 이상의 URL 이미지 갤러리입니다.
VIDEO 1개 URL 선택적 썸네일을 가진 단일 동영상 파일입니다.

MediaService는 Presigned URL을 통한 S3 업로드를 관리하며 POST, POST_THUMBNAIL, USER_PROFILE, BAND_PROFILE, SESSION_PORTFOLIO 카테고리를 지원합니다.

Post 서비스 — CRUD · 댓글 · 좋아요

Post 엔티티 및 데이터 구조

Entity 설명 관계
Post 핵심 메타데이터(제목, 설명, 타입, 썸네일)입니다. aggregate root
PostMedia sortOrder를 가진 이미지 또는 동영상 URL입니다. One-to-Many (Post)
PostTag 게시물당 최대 8개의 분류 문자열입니다. One-to-Many (Post)
PostLike 특정 게시물에 대한 사용자 좋아요를 추적합니다. One-to-Many (Post)
PostComment Fan과 Band 정체성을 모두 지원하는 사용자 댓글입니다. One-to-Many (Post)

Post 생성 흐름

  1. Validation: 사용자가 대상 밴드의 멤버십을 보유하고 있는지 검증합니다.
  2. Media/Tag Constraints: 태그 제한(최대 8개)과 PostType(TEXT, PHOTO, VIDEO)에 따른 미디어 요구사항이 강제됩니다.
  3. Persistence: Post aggregate와 자식 엔티티들이 데이터베이스에 저장됩니다.
  4. Async Tasks: VIDEO 게시물에 썸네일이 없는 경우 PostVideoThumbnailRequestedEvent가 발행되고, 검색 색인을 위해 PostChangedEvent가 발행됩니다.
  5. Notification: 트랜잭션 커밋 이후 PostPushMessage 인스턴스가 밴드 팔로워들에게 발송됩니다.

PostRepository는 게시물 ID를 커서로 사용하는 커서 기반 페이지네이션을 구현하며, id < :cursor 로직을 이용한 인덱스 range seek 쿼리를 수행하여 대규모 피드를 효율적으로 처리합니다.

댓글 시스템 및 Identity Resolution

PostCommentListResponse는 댓글을 두 그룹으로 분리합니다. myComments는 현재 사용자 본인의 댓글로 상단에 표시되며, items는 차단된 사용자를 제외하고 필터링된 다른 사용자들의 댓글입니다.

도메인 간 어댑터 및 포트

Port Adapter 목적
FollowPort FollowAdapter (Band 도메인) 푸시 알림 배포 대상 팔로워 ID를 조회합니다.
UserPort UserAdapter Fan 프로필을 조회하고 댓글 노출 필터링을 위한 사용자 차단 목록을 가져옵니다.
BandPort BandAdapter 밴드 멤버십 상태를 검증하고 밴드 프로필 식별자를 조회합니다.
NotifyPort NotificationAdapter 사용자에게 FCM 및 인앱 알림을 발송합니다.
PostPort SearchAdapter Elasticsearch 문서 색인에 필요한 게시물 엔티티 데이터를 제공합니다.

게시물이 생성되면 notifyFollowersAfterCommit이 호출되며, TransactionSynchronizationManager를 사용하여 데이터베이스 트랜잭션이 성공적으로 완료된 경우에만 알림이 발송되도록 보장합니다. 모든 CRUD 연산은 PostChangedEvent를 트리거하며, SearchIndexEventListener가 이를 구독하여 SearchAdapter를 호출해 Elasticsearch 색인을 갱신합니다.

미디어 업로드 — S3 · Presigned URL

BScene 플랫폼은 서버 처리 부하와 네트워크 지연을 줄이기 위해 클라이언트 측 업로드 전략을 구현합니다. MediaService는 시간 제한이 있는 암호학적으로 안전한 S3 Presigned URL을 생성하는 중앙 오케스트레이터 역할을 합니다.

미디어 카테고리 및 키 생성

Category 용도 S3 Prefix
POST 밴드 게시물에 첨부되는 일반 이미지/동영상입니다. post/
POST_THUMBNAIL 자동 생성된 동영상 프레임입니다. post_thumbnail/
STREAM_THUMBNAIL 라이브 스트림 미리보기 이미지입니다. stream_thumbnail/
BAND_PROFILE 밴드 로고 및 커버 사진입니다. band_profile/
PERFORMANCE_POSTER 공연 및 이벤트 포스터입니다. performance_poster/
USER_PROFILE 기본 사용자 계정 아바타입니다. user_profile/
SESSION_PORTFOLIO 세션 지원용 PDF 또는 미디어입니다. session_portfolio/

클라이언트가 URL을 요청하면 시스템은 파일명 충돌을 방지하기 위해 UUID로 고유 식별자를 생성하며, Presigned URL은 보안 제약을 강제하기 위해 5분의 짧은 만료 시간을 사용합니다.

비동기 동영상 썸네일 추출

sequenceDiagram
    participant Post as "PostService"
    participant Event as "PostVideoThumbnailRequestedEvent"
    participant Thumb as "PostThumbnailService"
    participant S3
    participant FFmpeg as "ffmpeg (ProcessBuilder)"
    Post->>Event: "커밋 후 이벤트 발행"
    Event->>Thumb: "@Async(postThumbnailExecutor) handle"
    Thumb->>S3: "원본 동영상 다운로드"
    Thumb->>FFmpeg: "-frames:v 1 로 첫 프레임 추출"
    Thumb->>S3: "POST_THUMBNAIL 카테고리로 .jpg 업로드"
    Thumb->>Post: "applyGeneratedThumbnail로 URL 갱신"

시스템은 presignedUrl(임시 업로드 권한)과 fileUrl(영구 공개 위치)를 구분합니다. PostCreateRequest는 mediaUrls 목록과 선택적 thumbnailUrl을 받으며, PostSummaryResponse는 thumbnailUrl이 없는 경우 첫 번째 미디어 항목을 기본값으로 사용하여 목록 화면에서 시각 요소가 항상 표시되도록 보장합니다.