Day96

강태훈·2026년 5월 19일

nbcamp TIL

목록 보기
96/97

공유 관련 정책 정리


1. 공유 기능 전체 개요

사용자가 캐릭터 카드를 공유할 때 두 가지 방식을 제공한다.

방식설명
링크 공유캐릭터 카드 URL을 공유. SNS/메신저에서 OG 태그 기반 미리보기 카드 표시
이미지 다운로드캐릭터 카드를 이미지 파일로 변환하여 로컬 저장

2. 링크 공유 - Open Graph 미리보기

링크 공유 시 카카오톡, 디스코드 등 메신저에서 미리보기 카드가 표시되도록 OG 태그를 설정한다. SNS/메신저가 URL을 감지하면 해당 서버가 직접 URL에 접근하여 HTML <head>의 OG 태그를 읽어 카드를 생성한다.

필수 OG 태그

태그역할
og:title카드에 표시되는 제목
og:description카드에 표시되는 설명
og:image썸네일 이미지 (R2에 저장된 캐릭터 이미지 URL)
og:url실제 페이지 URL

주의사항

  • og:image는 반드시 절대 경로(https://...)로 지정해야 한다. 상대 경로로 지정 시 외부 서버에서 이미지를 찾지 못한다.
  • 권장 이미지 크기는 1200 x 630px이다.
  • OG 태그는 서버(Thymeleaf)에서 동적으로 생성한다. 프론트엔드 JS로는 처리할 수 없다. (메신저 서버는 JS를 실행하지 않고 HTML만 읽기 때문)

3. 이미지 다운로드 - html2canvas

방식 선택 근거

이미지 생성 방식은 서버 생성프론트엔드 생성(html2canvas) 두 가지가 있다.

항목서버 생성html2canvas (채택)
구현 위치Java (서버)브라우저 (JS)
디자인 반영코드로 직접 조립화면에 보이는 그대로 캡처
서버 부하있음없음
캐릭터+아이템 합성별도 구현 필요CSS 레이아웃 그대로 캡처

캐릭터 위에 아이템을 겹쳐 합성하는 구조이므로, 화면에 렌더링된 결과를 그대로 이미지로 변환하는 html2canvas 방식이 적합하다. 서버 부하도 없고 디자인 변경 시 별도 수정이 필요 없다.

사용 라이브러리

html2canvas 또는 dom-to-image (프론트엔드 JS 라이브러리)


4. 하루 1회 공유 보상 정책

공유 보상(별조각)은 하루 최대 1회로 제한하며, 중복 지급을 막기 위해 비즈니스 로직DB 제약조건 두 단계로 검증한다.

4-1. 비즈니스 로직 (날짜 기준 조회)

사용자가 공유 이벤트를 발생시키면 ShareService에서 오늘 날짜(LocalDate today)를 기준으로 DB를 먼저 조회한다.

// 오늘 이미 보상을 받은 기록이 있는지 확인
boolean alreadyRewarded = shareLogRepository
    .existsByUserIdAndShareDateAndRewardPaidTrue(userId, today);

// 이미 받았으면 0, 처음이면 50 지급
boolean rewardPaid = !alreadyRewarded;
int rewardAmount = rewardPaid ? 50 : 0;

보상 없이 기록만 남기는 경우 rewardPaid = false로 저장한다.

4-2. DB 유니크 제약조건 (멱등성 키 방어)

비즈니스 로직만으로는 공유 버튼을 짧은 순간에 두 번 클릭하는 경우(Race Condition) 두 요청이 동시에 통과할 수 있다. 이를 방지하기 위해 프론트엔드가 요청 시 고유한 멱등성 키를 생성하여 함께 전송하도록 설계한다.

  • 키 형식: SHARE_REWARD:{userId}:{yyyy-MM-dd}

share_logs 테이블에 해당 키에 UNIQUE 제약조건을 걸어두어, 동시에 두 요청이 로직을 통과하더라도 DB 저장 시점에 하나는 중복 키 에러로 차단된다.

CONSTRAINT uq_share_logs_idempotency_key UNIQUE (idempotency_key)

5. 이미지 스토리지 선택: Cloudflare R2

캐릭터 카드 공유에 사용할 이미지 스토리지로 Cloudflare R2를 채택한다.

결정 배경

운영자가 캐릭터 이미지를 미리 업로드해두고, 사용자가 카드를 공유할 때 해당 이미지 URL을 내려주는 구조다. 이 구조에서 핵심 비용은 이미지를 사용자에게 전달할 때 발생하는 egress(데이터 전송) 비용이다.

AWS S3 대비 R2를 선택한 이유

항목Cloudflare R2AWS S3
Egress 비용무료GB당 $0.09
스토리지 비용GB당 $0.015GB당 $0.023
무료 티어스토리지 10GB, 읽기 1000만 건/월12개월 한정
S3 호환 API지원-
이미지 변환미지원미지원
  • 이미지 변환 기능이 필요 없는 구조이므로 Cloudinary의 핵심 장점을 활용할 수 없어 제외했다.
  • R2는 egress가 무료이므로 트래픽이 늘어도 이미지 전송 비용이 발생하지 않는다.
  • S3 호환 API를 지원하므로 추후 S3로 마이그레이션이 필요할 경우 코드 변경이 거의 없다.
  • 현재 규모에서는 무료 티어 안에서 운영 가능하다.

향후 전환 기준

트래픽이 크게 증가하거나 AWS 생태계와의 깊은 통합이 필요해지는 시점에 S3로 전환을 검토한다.

0개의 댓글