Files

16 KiB

이벤트 접속 국가별 언어 필터 PRD

문서 정보

항목 내용
문서 상태 구현 기준 확정
작성일 2026-08-19
최종 수정일 2026-08-20
대상 제품 관리자 이벤트 배너, 앱 이벤트 목록·팝업
작성자·결정권자 사용자
관련 API Contract 별도 문서 없음. 이 문서 8절을 기준으로 사용
관련 구현 계획 docs/20260819_이벤트_접속국가별_언어필터/plan-task.md
관련 기존 문서 docs/20260819_추천탭_배너_접속국가별_언어필터/prd.md

1. Overview

관리자가 이벤트를 등록할 때 기존 콘텐츠 배너처럼 언어를 지정한다. 관리자 목록은 언어로 필터링하지 않고 각 이벤트의 언어를 표시한다. 앱의 GET /event, GET /event/popup은 기존 접속 국가 판정 결과가 JP이면 일본어, 그 외에는 한국어 이벤트만 반환한다.

2. Problem Statement

  • Event에는 언어 필드가 없어 관리자가 한국어·일본어 이벤트를 구분해 등록할 수 없다.
  • 앱 이벤트 목록과 팝업 조회는 접속 국가를 고려하지 않아 여러 언어의 이벤트가 섞일 수 있다.
  • 팝업을 조회한 뒤 메모리에서 언어를 걸러내면 다른 언어의 최신 팝업이 fetchFirst()를 차지해 필요한 팝업을 놓칠 수 있다.

문제를 해결했다는 판단은 관리자가 허용된 언어로 이벤트를 등록·식별할 수 있고, 두 앱 API가 기존 조회 조건을 유지하면서 DB 조회 단계에서 판정 언어만 반환하는 것으로 한다.

3. Goals

  • 관리자는 이벤트 등록 시 KO, EN, JA 중 하나를 필수로 지정한다.
  • 관리자 이벤트 목록은 기존 활성·종료 시각 조건 안에서 모든 언어를 반환하고 lang을 포함한다.
  • 기존 MemberContentPreferenceService.resolveCountryCode(member)로 강제 KR/JP 회원 매핑, 요청 국가 정규화, 누락 시 KR 기본값을 그대로 재사용한다.
  • 판정 국가가 JP이면 Lang.JA, 그 외에는 Lang.KO를 선택한다.
  • 언어 조건을 QueryDSL where에 적용해 정렬과 fetchFirst()보다 먼저 필터링한다.
  • 기존 이벤트 데이터와 레거시 등록 경로의 기본 언어는 KO로 유지한다.

4. Non-Goals

  • 이벤트 수정 API에서 lang을 변경하지 않는다.
  • EventController.createEventPOST /event 요청 계약을 변경하지 않는다.
  • 콘텐츠 메인 탭 6곳이 EventService.getEventList(isAdult)로 조립하는 eventBannerList에 언어 필터를 적용하지 않는다.
  • Lang에 새 enum을 추가하거나 EN을 노출할 접속 국가 정책을 추가하지 않는다.
  • Accept-Language를 이벤트 언어 판정에 사용하지 않는다.
  • 요청 언어의 이벤트가 없을 때 다른 언어로 fallback하지 않는다.
  • 기존 이벤트의 성인·활성·시작·종료·정렬 정책과 응답 이미지 URL 처리를 변경하지 않는다.

5. 대상 사용자와 권한

사용자 주요 동작 국가·언어 정책
관리자 이벤트 등록, 언어 전체 목록 조회 등록 시 KO/EN/JA 중 하나를 지정, 목록에서 lang 확인
강제 JP 매핑 회원 앱 목록·팝업 조회 요청 헤더보다 우선하는 JP 판정, JA 조회
강제 KR 매핑 회원 앱 목록·팝업 조회 요청 헤더보다 우선하는 KR 판정, KO 조회
일반 로그인 회원 앱 목록·팝업 조회 정규화한 CloudFront-Viewer-CountryJP이면 JA, 그 외 KO
비로그인 사용자 앱 목록 조회 정규화한 요청 국가가 JP이면 JA, 누락·그 외 KO
  • 관리자 이벤트 API의 기존 ROLE_ADMIN 인가를 유지한다.
  • 앱 이벤트 목록은 기존 비로그인 접근을 유지하고, 팝업은 기존처럼 인증 사용자만 접근한다.

6. 핵심 흐름

6.1 관리자 등록·조회

  1. 관리자가 POST /admin/event/banner multipart 요청에 lang을 필수로 보낸다.
  2. 서버는 기존 Lang.fromCode 규칙으로 ko/KO, en/EN, ja/JA를 처리한다.
  3. 허용되지 않는 값은 common.error.invalid_request로 거부하고 이벤트를 저장하지 않는다.
  4. GET /admin/event/banner는 기존처럼 활성 상태이고 종료 시각이 지나지 않은 이벤트를 언어 조건 없이 반환한다.
  5. 각 관리자 응답 항목은 lang을 포함한다.

6.2 앱 목록·팝업 조회

  1. CountryInterceptorCloudFront-Viewer-CountryCountryContext에 저장한다.
  2. EventController는 인증 사용자 Member?EventService에 전달한다.
  3. EventServiceMemberContentPreferenceService.resolveCountryCode(member)를 호출한다.
  4. 국가 결과가 JP이면 Lang.JA, 그 외에는 Lang.KO를 선택한다.
  5. EventRepository는 기존 성인·활성·게시 기간 조건과 언어 조건을 DB에서 함께 적용한다.
  6. 해당 언어의 이벤트가 없으면 목록은 빈 eventList, 팝업은 null을 반환한다.

6.3 기존 내부 조회

  1. 콘텐츠 메인 탭 6곳은 기존 EventService.getEventList(isAdult)를 유지한다.
  2. 이 경로는 Repository에 lang = null을 전달해 언어 조건을 추가하지 않는다.
  3. 기존 응답 조립 범위와 순서를 변경하지 않는다.

7. 기능 요구사항

ID 상태 요구사항 수용 기준 Goal 연결
EVENT-LANG-001 확정 관리자 이벤트 등록은 lang을 필수로 받는다. KO, EN, JA가 저장되고 누락·잘못된 값은 저장 전 거부된다. P1-T2
EVENT-LANG-002 확정 이벤트 언어는 등록 후 수정하지 않는다. 두 수정 API의 요청·서비스 시그니처에 lang이 추가되지 않는다. P1-GATE
EVENT-LANG-003 확정 관리자 목록은 모든 언어를 조회하고 lang을 반환한다. 기존 활성·종료 시각 조건을 만족하는 KO/EN/JA 항목이 언어 필터 없이 반환되고 각 항목에 lang이 있다. P1-T2
EVENT-LANG-004 확정 GET /event는 판정 국가가 JP이면 JA, 그 외에는 KO 이벤트만 반환한다. 서로 다른 언어의 활성 이벤트가 함께 있어도 판정 언어만 eventList에 있다. P2-T1
EVENT-LANG-005 확정 GET /event/popup은 판정 국가가 JP이면 JA, 그 외에는 KO 팝업만 반환한다. 다른 언어 팝업의 ID가 더 최신이어도 언어 필터 후 선택된 팝업을 반환한다. P2-T1
EVENT-LANG-006 확정 로그인 회원은 기존 강제 KR/JP 매핑을 포함한 국가 판정을 사용한다. 강제 JP 회원은 비JP 헤더에서도 JA, 강제 KR 회원은 JP 헤더에서도 KO를 선택한다. P2-T1
EVENT-LANG-007 확정 콘텐츠 메인 탭의 기존 eventBannerList는 언어 필터 없이 조회한다. getEventList(isAdult) 경로가 lang = null을 유지하고 KO/JA/EN 모두를 조회할 수 있다. P2-T2
EVENT-LANG-008 확정 기존 이벤트와 레거시 등록 경로의 언어는 KO다. 기존 row를 KO로 backfill하고 NOT NULL DEFAULT 'KO'를 적용하며 POST /eventlang을 추가하지 않는다. P1-T1
EVENT-LANG-009 확정 앱 언어 필터는 DB 조회 조건으로 적용한다. QueryDSL whereevent.lang.eq(lang)이 정렬·fetchFirst() 전에 적용된다. P2-T1

8. API 계약

8.1 관리자 등록

항목 내용
Method / Path POST /admin/event/banner
인증 ROLE_ADMIN
Content-Type multipart/form-data
신규 필수 파라미터 lang: ko, en, ja 또는 대소문자를 달리한 동일 enum 코드
잘못된 값 common.error.invalid_request, 저장·업로드 없음
기존 파라미터 스키마와 필수·선택 정책 유지

8.2 관리자 목록

항목 내용
Method / Path GET /admin/event/banner
조회 조건 기존 is_active = true, end_date >= now; 언어 조건 없음
응답 변경 GetAdminEventResponse.lang: Lang 추가
기존 응답 필드 이름·타입·URL 처리 유지

8.3 앱 조회

Method / Path 변경 내용 빈 결과
GET /event 기존 응답 스키마를 유지하고 판정 언어 조건만 추가 eventList = []
GET /event/popup 기존 응답 스키마를 유지하고 판정 언어 조건만 추가 data = null
  • 신규 request header를 추가하지 않고 기존 CloudFront-Viewer-Country를 사용한다.
  • GET /event는 기존처럼 비로그인 접근을 허용하고, GET /event/popup은 기존 인증 필수 정책을 유지한다.
  • 앱 응답에 lang을 추가하지 않는다.
  • EN 이벤트는 관리자가 등록·조회할 수 있지만 현재 두 앱 API의 국가 정책으로는 노출되지 않는다.

9. 데이터 정책

  • Event.lang은 기존 kr.co.vividnext.sodalive.i18n.LangEnumType.STRING으로 저장한다.
  • 엔티티 기본값은 Lang.KO로 둔다.
  • 운영 DB DDL은 event.lang VARCHAR(10)을 nullable로 추가한 뒤 기존 NULLKO로 backfill하고 NOT NULL DEFAULT 'KO'로 변경한다.
  • DDL은 두 번 실행해도 이미 적용된 단계를 건너뛸는 기존 information_schema + PREPARE 패턴을 따른다.
  • 기존 row의 언어를 별도로 추론하거나 이미지·제목을 분석해 자동 분류하지 않는다.

10. 성능·품질·보안 요구사항

  • 언어 조건은 Repository QueryDSL where에서 적용하고 메모리 후처리를 추가하지 않는다.
  • 기존 Lang, MemberContentPreferenceService, CountryContext를 재사용하고 신규 dependency·resolver abstraction을 추가하지 않는다.
  • 언어 파라미터는 Lang.fromCode로 정규화·검증하고 잘못된 값을 엔티티 생성과 S3 업로드 전에 거부한다.
  • 민감정보·헤더·파일 본문을 신규 로그에 기록하지 않는다.
  • TDD는 관리자 등록·목록, 앱 국가별 목록·팝업, 기존 내부 전체 언어 조회 비회귀를 포함한다.
  • focused test 후 직접 영향 회귀와 ktlintCheck를 실행한다. 전체 회귀는 targeted test로 영향 범위를 판단할 수 없거나 공통 경계 회귀가 발생할 때만 확장한다.

11. 성공 기준

  • 관리자가 KO, EN, JA 이벤트를 등록하고 목록에서 각 언어를 확인한다. (EVENT-LANG-001, EVENT-LANG-003)
  • 수정 API와 레거시 POST /event의 요청 계약이 변경되지 않는다. (EVENT-LANG-002, EVENT-LANG-008)
  • 기존 이벤트 모두가 KO로 이관되고 신규 언어 누락 row가 생성되지 않는다. (EVENT-LANG-008)
  • 판정 국가 JP에서 GET /event, GET /event/popup이 JA만 반환한다. (EVENT-LANG-004~006)
  • JP 이외와 국가 누락에서 두 API가 KO만 반환한다. (EVENT-LANG-004, EVENT-LANG-005)
  • 다른 언어의 최신 팝업이 있어도 언어 필터 후 선택된 팝업이 반환된다. (EVENT-LANG-005, EVENT-LANG-009)
  • 콘텐츠 메인 탭 6곳의 기존 eventBannerList는 언어 필터 없이 조회된다. (EVENT-LANG-007)
  • 기존 성인·활성·게시 기간·정렬·URL·응답 스키마 정책이 유지된다.

11.1 구현 완료 검증 — 2026-08-20

  • 관리자 등록·목록: AdminEventBannerControllerIntegrationTest 4개와 EventRepositoryTest의 관리자 전체 언어 조회가 통과했다. Lang.fromCode는 기존 KO, EN, JA enum의 code/name을 대소문자 무시로 처리하며, 누락·잘못된 값은 저장·업로드 없이 거부된다.
  • 레거시 계약: controller·service diff에서 관리자 수정·삭제와 POST /event, PUT /event, DELETE /event/{id}lang이 추가되지 않았다.
  • DB 이관: 20260819_event_lang_ddl.sql이 nullable 컬럼 추가 → NULL의 KO backfill → NOT NULL DEFAULT 'KO' 순서를 갖고, 엔티티 기본값과 JA/KO 영속성 테스트도 통과했다. 다만 운영 DDL은 실행하지 않았으므로 실제 기존 row 전체 이관 완료는 증명하지 않고 체크하지 않았다.
  • JP·강제 매핑: security-on 익명 JP 목록과 인증 JP 팝업 통합 테스트, EventServiceTest의 JP→JA 전달, MemberContentPreferenceIntegrationTest의 강제 JP/KR 매핑이 통과했다.
  • 비JP·누락: 국가 누락의 익명 KO 목록과 인증 KO 팝업 통합 테스트, 일반 회원의 US·누락 판정과 비로그인 JP 정규화·누락 KR 테스트가 통과했다. service의 JP 이외→KO 분기도 diff로 확인했다.
  • 팝업 선필터: 더 최신 KO와 성인 JA 팝업이 있어도 비성인 JA 조회가 대상 JA를 반환하는 Repository 테스트가 통과했고, 언어 predicate가 where에서 정렬·fetchFirst() 전에 적용됨을 확인했다.
  • 콘텐츠 메인: 저수준 service의 lang = null 전달과 Repository의 KO/EN/JA 3건 전체 조회 테스트가 통과했다. 6개 production 호출자와 해당 응답 DTO 디렉터리 diff는 없었다.
  • 기존 정책: Repository 테스트가 활성·시작·종료·성인·ID 내림차순 조건을 함께 검증했고, EventService URL 변환 및 GetEventResponse·EventItem diff가 없음을 확인했다. 구현 리뷰 후 익명 팝업 401과 인증 팝업의 국가별 언어를 함께 검증했고 기준 HEAD 대비 SecurityConfig diff가 없음을 확인했다.

12. Open Questions

  • 없음.

13. 요구사항 추적표

요구사항 범위 계획 Phase Goal 자동 검증
EVENT-LANG-001~003, EVENT-LANG-008 1 P1-T1, P1-T2, P1-GATE 엔티티·관리자 controller/service/repository 통합 테스트
EVENT-LANG-004~006, EVENT-LANG-009 2 P2-T1, P2-GATE EventServiceTest, EventRepositoryTest, EventControllerIntegrationTest
EVENT-LANG-007 2 P2-T2, P2-GATE 언어 없는 기존 서비스 호출과 Repository null 조건 비회귀 테스트

14. Decision Log

날짜 ID 상태 결정 근거 영향 요구사항·Goal
2026-08-19 DEC-001 확정 앱 국가 판정은 기존 MemberContentPreferenceService.resolveCountryCode(member)를 사용한다. 사용자 인터뷰 A EVENT-LANG-004~006, P2-T1
2026-08-19 DEC-002 확정 등록 언어는 기존 LangKO, EN, JA 모두를 허용한다. 사용자가 A를 철회하고 B로 확정, 신규 언어 타입·검증 중복 방지 EVENT-LANG-001, P1-T2
2026-08-19 DEC-003 확정 언어는 등록할 때만 지정하고 수정하지 않는다. 사용자 인터뷰 A, 콘텐츠 배너의 현재 패턴 EVENT-LANG-002, P1-GATE
2026-08-19 DEC-004 확정 관리자 목록은 언어 전체를 조회하고 lang을 응답한다. 사용자 인터뷰 A EVENT-LANG-003, P1-T2
2026-08-19 DEC-005 확정 국가별 필터는 EventController.getEventList, getEventPopup에만 적용한다. 사용자 인터뷰 A EVENT-LANG-004, EVENT-LANG-005, EVENT-LANG-007, P2-T1~T2
2026-08-19 DEC-006 확정 기존 이벤트는 KO로 backfill하고 NOT NULL DEFAULT 'KO'를 적용한다. 사용자 인터뷰 A, 기존 배너 DDL 패턴 EVENT-LANG-008, P1-T1
2026-08-19 DEC-007 확정 언어 입력은 AdminEventBannerController에만 추가하고 레거시 POST /event는 KO 기본값을 사용한다. 사용자 인터뷰 A EVENT-LANG-002, EVENT-LANG-008, P1-T1~T2
2026-08-20 DEC-008 확정 GET /event의 익명 접근은 유지하고 GET /event/popup은 기준 HEAD의 인증 필수 정책을 유지한다. 구현 리뷰에서 언어 필터와 무관한 익명 공개 확장을 확인 EVENT-LANG-005, P2-R1

15. 변경 관리

요구사항이 변경되면 다음 순서로 갱신한다.

  1. 이 문서의 Decision Log에 변경 이유와 날짜를 추가한다.
  2. 관련 요구사항·수용 기준·API 계약을 갱신한다.
  3. plan-task.md의 범위·Files·Interfaces·체크박스를 코드 변경 전에 먼저 갱신한다.
  4. 기존 Progress·검증 기록을 삭제하거나 덮어쓰지 않는다.