Skip to content

✨ Feat: ad.picke.store 제휴 광고(쿠팡 파트너스·애드픽) 연동 - #443

Open
Roy-wonji wants to merge 5 commits into
fix/#440from
feat/#441
Open

✨ Feat: ad.picke.store 제휴 광고(쿠팡 파트너스·애드픽) 연동#443
Roy-wonji wants to merge 5 commits into
fix/#440from
feat/#441

Conversation

@Roy-wonji

Copy link
Copy Markdown

#️⃣ 연관된 이슈

📝 작업 내용

✨ Feat

내용 파일
소재·클릭 로그·일별 노출 집계 엔티티 AdCreative.java, AdClickLog.java, AdImpressionDaily.java
지면별 광고 조회(가중 로테이션)·노출 집계 AdController.java, AdQueryService.java
클릭 리다이렉트와 매체별 추적 파라미터 병합 AdClickController.java, AdClickService.java, AffiliateLinks.java
매체별 링크 빌더 (쿠팡 subId / 애드픽 pass-through) CoupangLinkBuilder.java, AdpickLinkBuilder.java
관리자 소재 CRUD·노출/클릭/CTR·클릭 내역 AdminAdController.java, AdminAdService.java
ad.picke.store 루트 공개 지면 AdLandingController.java, templates/ad/landing.html
광고 API Swagger 그룹 분리 SwaggerConfig.java

🐛 Fix

내용 파일
정확 일치 화이트리스트 분리로 루트·/error 401 해소 JwtFilter.java, SecurityConfig.java

📌 공유 사항

  1. 이 PR은 🐛 Fix: BattleVoteServiceImplTest 시간대 불일치로 15시(UTC) 이후 CI 실패 #442(fix/#440) 위에 쌓았습니다. 그쪽이 머지되면 base가 dev로 자동 전환됩니다. 먼저 #442를 봐주세요.
  2. 배포 전 별도 작업 없습니다. ddl-auto=update라 테이블은 자동 생성됩니다. docs/db/의 SQL은 수동 관리·사후 검증용입니다.
  3. COUPANG_PARTNERS_ID 환경변수 주입이 필요합니다. 다른 시크릿과 같이 기본값을 두지 않았습니다. 값은 AF6830373입니다.
  4. 지면(AdSlotCode)은 iOS Presentation 모듈 실제 화면 기준으로 5개 잡았습니다. 앱팀 확정 후 실제로 붙이는 지면에만 소재를 등록하면 되고, 소재 없는 지면은 빈 배열이라 앱이 알아서 숨깁니다.
  5. 애드픽은 서브아이디 파라미터 규격 확인 전이라 pass-through입니다. 확인되면 ADPICK_SUB_ID_PARAM 환경변수만 채우면 배포 없이 지면별 성과 분리가 켜집니다.

✅ 체크리스트

  • 컨벤션에 맞는 Type을 선택했나요?
  • Development에 이슈를 연동했나요?
  • Merge 하려는 브랜치가 올바르게 설정되어 있나요?
  • 로컬에서 실행했을 때 에러가 발생하지 않나요?

📸 스크린샷

@Check 제약이 실제 DDL에 나가는지 스키마 생성으로 확인했습니다.

create table ad_creatives (..., 
  constraint ck_ad_creatives_network check (network in ('COUPANG', 'ADPICK')),
  constraint ck_ad_creatives_slot check (slot in ('HOME_FEED', 'BATTLE_RESULT_BOTTOM', 'CHAT_ROOM_INLINE', 'ATTENDANCE_COMPLETE', 'PROFILE_BOTTOM')),
  constraint ck_ad_creatives_status check (status in ('DRAFT', 'ACTIVE', 'PAUSED')));

💬 리뷰 요구사항

  1. CHECK 제약이 enum과 어긋나는 것이 #378의 본질이라 봤습니다. 제약 문자열과 enum 상수를 대조하는 AdCheckConstraintTest를 넣어, 값을 추가하고 제약 갱신을 잊으면 운영이 아니라 CI에서 먼저 깨지게 했습니다. 다만 ddl-auto=update는 기존 제약을 갱신하지 않으므로 실제 ALTER는 여전히 수동입니다. 이 조합이 적절한지 의견 부탁드립니다.
  2. 클릭 로그를 익명(ip_hash + user_agent)으로 뒀습니다. /c/{code}가 외부 브라우저에서 열려 Authorization 헤더가 없고, 공개 URL에 사용자 식별자를 실으면 열거 문제가 생겨서입니다. 지면별 CTR과 정산 대조에는 userId가 필요 없다고 판단했는데, 본인 클릭 어뷰징 탐지가 필요하면 다시 논의하면 좋겠습니다.
  3. ad_click_logs.creative_id, ad_impression_daily.creative_id에 FK를 걸지 않았습니다. 소재를 지워도 집계 이력은 남기려는 의도인데, 팀 컨벤션과 맞는지 봐주세요.

JwtFilter 가 SecurityConfig 보다 먼저 돌면서 토큰이 없으면 무조건 401 을 던진다.
그런데 isWhitelisted 가 startsWith 매칭이라 "/" 를 넣으면 전체 인증이 무력화된다.
정확히 일치할 때만 통과하는 EXACT_WHITELIST 를 분리해 "/" 와 "/error" 를 넣는다.

/error 가 빠져 있던 탓에 존재하지 않는 모든 경로가 404 대신 401 로 나오고 있었다.
ad.picke.store 루트가 인증 오류 JSON 을 반환하던 원인이다.
- ad 도메인 신설: 소재(AdCreative), 클릭 로그, 일별 노출 집계
- 앱 API: GET /api/v1/ads (지면별 가중 로테이션), POST /api/v1/ads/impressions
- 클릭 리다이렉트: GET /c/{code} 에서 매체별 추적 파라미터를 병합해 302, 클릭은 비동기 적재
- 관리자 API: 소재 CRUD, 소재별 노출/클릭/CTR, 클릭 내역 목록
- 쿠팡은 subId 를 병합해 지면별 성과를 분리하고,
  애드픽은 규격 확인 전까지 pass-through 이며 파라미터명을 설정값으로 둔다
- 소재 등록 시 제휴 링크의 lptag 를 우리 파트너스 아이디와 대조해 남의 링크를 막는다
- enum 컬럼에 CHECK 제약을 선언하고, 제약과 enum 이 어긋나면 테스트가 먼저 깨지게 한다
- 노출 집계와 게재 기간 판단을 KST 로 고정
- ad.picke.store 루트 공개 지면 추가 (매체 심사에서 접속 확인 대상)
- Swagger "3. 광고 API" 그룹으로 분리
ERD 는 docs/erd/ad.puml, 참고 스키마는 docs/db 에 둔다.
운영은 ddl-auto=update 라 테이블은 배포 시 자동 생성되며, SQL 은 수동 관리·사후 검증용이다.
@Roy-wonji

Copy link
Copy Markdown
Author

CI 관련 참고입니다.

deploy.ymldev/main 대상 PR에만 걸려 있어서, base가 fix/#440인 지금은 체크가 돌지 않습니다.

#442를 먼저 머지하면 이 PR의 base가 dev로 자동 전환되면서 CI가 돕니다.

지금 base를 dev로 바꾸면 CI는 돌지만, #442에서 고친 BattleVoteServiceImplTest 2건이 15시(UTC) 이후 실행에서 그대로 실패합니다. 그래서 순서를 지키는 쪽으로 뒀습니다.

로컬 검증 결과입니다.

TZ=UTC ./gradlew test
204 tests completed, 2 failed

실패 2건은 ScenarioAudioPipelineServiceTest, ScenarioServiceImplTest로 로컬에 ffmpeg가 없어 나는 것이며 CI에서는 통과합니다.

애드픽은 캠페인 리스트 JSON API 를 인증 없이 공개한다.
쿠팡 오픈API 와 달리 승인 절차가 없어 애드픽 소재는 사람이 등록하지 않고 주기 동기화로 채운다.

- 수집한 캠페인을 같은 ad_creatives 에 source=ADPICK_API 로 저장해
  로테이션·노출 집계·클릭 추적 경로를 그대로 태운다
- 애드픽 가이드가 최대 1분 1회 이하 호출을 요구하고 실제로 연달아 호출하면 403 을 준다.
  요청마다 부르지 않고 스케줄러로만 호출한다
- apRemain 이 0 이면 게재하지 않는다. 잔여가 없으면 클릭해도 전환이 잡히지 않는다
- 피드에서 사라진 캠페인은 지우지 않고 내린다. 쌓인 집계를 계속 읽을 수 있어야 한다
- PAUSED 는 동기화가 되돌리지 않는다. 별도 플래그 없이 어드민의 끄기 스위치로 쓴다
- 동기화 소재는 수정·삭제를 막고 게재 상태만 바꾸게 한다 (PATCH /{id}/status)
- apOS 로 OS 가 갈리므로 target_os 를 두고 GET /api/v1/ads 에 os 파라미터를 받는다
  iOS 사용자에게 Android 캠페인을 보여주면 클릭해도 전환이 일어나지 않는다
- affId 가 비어 있으면 동기화를 건너뛴다. 가입 전에도 나머지 기능은 그대로 돈다
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant