콘텐츠로 이동

[YETI] PLP 색상 그룹핑 + 컬러칩 + 리스트 장바구니 담기

현재 PLP는 productId(색상) 단위로 카드를 1:1 표시한다. 동일 스타일의 다른 색상이 별도 카드로 분리되어 나열되는 문제가 있다.

이 문서는 아래 세 가지를 정의한다.

  1. 색상 그룹핑: 동일 스타일의 색상 변형을 카드 1장으로 묶어 표시
  2. 컬러칩 인터랙션: 카드 내에서 색상 전환
  3. 리스트 장바구니 담기: 상세 페이지 이동 없이 즉시 담기

1. 프론트 → GET /v2/products/grouped (카테고리/필터/정렬 파라미터 포함)
2. 서버:
a. 카테고리/필터 조건으로 visible 상품 조회
b. style_key로 그룹핑
c. 각 그룹에서 맥락별 규칙으로 대표 색상 선택
d. colorVariants 배열 구성 (visible=true 상품만, 대표 제외, 등록순)
e. 대표 상품 기준으로 정렬
f. 그룹 수 기준 페이지네이션
g. 응답 반환
3. 프론트: 카드 렌더링 — 대표 색상이 초기 선택 상태
4. 사용자: 컬러칩 클릭 → 썸네일/링크 교체 (페이지 이동 없음)
5. 사용자: 장바구니 담기 → 단일 SKU면 즉시, 복수 SKU면 모달

백엔드 개발 시작 전 완료 필요. product 테이블에 컬럼 2개를 추가한다.

컬럼 타입 설명
style_key VARCHAR(100) NULL 그룹핑 키. 아래 6개 조건을 SHA-256 해시한 값. 상품 등록/수정 시 자동 계산
representative BOOLEAN NOT NULL DEFAULT FALSE 카테고리 대표 색상. 어드민에서 수동 지정
ALTER TABLE product
ADD COLUMN style_key VARCHAR(100) NULL AFTER product_code,
ADD COLUMN representative BOOLEAN NOT NULL DEFAULT FALSE AFTER style_key,
ADD INDEX idx_style_key (style_key);

아래 6개 값을 연결한 문자열을 SHA-256 해시한다. 기존 otherColor API의 그룹핑 조건과 동일하게 유지한다.

SHA-256( brandId | seasonCode | category2Code | productName | genderCode | initialPrice )

productName에 사이즈가 포함된 상품(예: TUNDRA 35)은 이 계산으로 사이즈별 그룹이 자연스럽게 분리된다. 별도 처리 불필요.

배포 전 전체 상품에 대해 style_key를 일괄 계산 후 UPDATE.

-- 예시 (실제 구현은 배치 또는 마이그레이션 스크립트로 처리)
UPDATE product
SET style_key = SHA2(
CONCAT(brand_id, '|', season_code, '|', category2_code, '|',
product_name, '|', gender_code, '|', initial_price), 256
)
WHERE style_key IS NULL;

배포 순서 미결: 마이그레이션 완료 후 배포 vs 코드에서 style_key IS NULL 방어 처리 후 배포 — 개발팀 결정 필요.


서버가 그룹핑 시 맥락(호출 출처)에 따라 아래 규칙으로 대표 색상을 결정한다. 프론트는 별도 계산 없이 응답의 최상위 productId를 그대로 대표로 사용한다.

맥락 대표 결정 기준
카테고리 기본 1순위: representative=true AND visible=true → 2순위: visible=true 중 등록순 첫 번째 → 3순위: 전체 품절 시 카드 미노출
카테고리 + Color 필터 필터 colorCode에 해당하는 visible=true 상품 → 대표. 없으면 그룹 전체 미포함. (representative 무시)
컬렉션 운영자가 수동 추가한 productId의 색상 → 대표. (representative 무시)
컬렉션 + Color 필터 필터 색상에 해당하는 visible=true 상품 → 대표. 없으면 카드 미포함
검색 (색상 키워드 있음) 검색어에서 추출된 색상과 매칭되는 상품 → 대표
검색 (색상 키워드 없음) 카테고리 기본과 동일: representative=true → fallback(등록순)
검색 + Color 필터 필터 색상 우선. 없으면 그룹 미포함

기존 /v2/products는 변경 없이 그대로 유지한다. 신규 엔드포인트로 분리.

GET /v2/products/grouped

카테고리

파라미터 타입 설명
mainCategoryId number 1차 카테고리 ID
subcategoryIds number[] 서브카테고리 ID
categoryCodes string[] Product Type(2nd) 카테고리 코드

필터

파라미터 타입 설명
colorCodes string[] 색상 필터 (BLA, BLU, RED …)
sizes string[] 사이즈 필터
minPrice number 가격 범위 최솟값
maxPrice number 가격 범위 최댓값
brandCodes string[] 브랜드 필터
attributes string[] 속성 필터 ("key:value" 형식)

정렬 / 페이지네이션

파라미터 타입 설명
sort string POPULAR / PRICE_ASC / PRICE_DESC / NEWEST. 대표 상품 기준 적용
page number 페이지 번호 (0-based)
size number 페이지당 그룹 수

그룹 단위로 반환. 배열 항목 하나 = 카드 1장.

{
"content": [
{
"productId": 123,
"productCode": "YT26FATUNDRA35001",
"brandName": "YETI",
"productName": "TUNDRA 35",
"currentPrice": 380000,
"initialPrice": 380000,
"discountedRate": 0,
"color": "WHITE",
"thumbnailUrl": "https://cdn.example.com/products/123/thumb.jpg",
"hoverUrl": "https://cdn.example.com/products/123/hover.jpg",
"colorVariants": [
{
"productId": 124,
"color": "BLACK",
"thumbnailUrl": "https://cdn.example.com/products/124/thumb.jpg"
},
{
"productId": 125,
"color": "CAMP GREEN",
"thumbnailUrl": "https://cdn.example.com/products/125/thumb.jpg"
}
]
}
],
"totalElements": 42
}

응답 필드 설명

필드 설명
productId ~ hoverUrl 대표 색상 상품 정보
currentPrice / initialPrice / discountedRate 대표 상품 기준. 같은 style_key 내 상품은 initialPrice가 동일하므로 가격 정렬 시 충돌 없음
colorVariants 대표 제외 나머지 색상 목록. visible=true 상품만 포함. 등록순 ASC
colorVariants: [] 단독 색상 상품 (그룹 내 visible 상품이 1개)
totalElements 전체 그룹 수 (≠ 전체 상품 수). 프론트 페이지네이션 기준값

Redis + Spring Cache 활용.

항목 내용
캐시 대상 전체 그룹 리스트
페이지네이션 page/size는 캐시 키 제외. 캐시된 리스트에서 in-memory slice
캐시 키 예 plp:category:{mainCategoryId}:{subcategoryIds}:{colorCodes}:{sizes}:{sort}
무효화 트리거 상품 visible/representative 변경, CategorySort 변경, 컬렉션 상품 추가/삭제

┌─────────────────────────┐
│ │
│ 메인 썸네일 │ ← 선택된 색상의 thumbnailUrl
│ (hover → hoverUrl) │ ← hover 시 hoverUrl로 전환 (기존 동작)
│ │
├─────────────────────────┤
│ [칩1] [칩2] [칩3] ... │ ← 컬러칩 (대표 먼저, 이후 colorVariants 순서)
├─────────────────────────┤
│ Brand Name │
│ Product Name │
│ ₩380,000 │
│ [장바구니 담기] │
└─────────────────────────┘
항목 정책
칩 목록 순서 대표 색상 칩 먼저, 이후 colorVariants 순서 그대로
초기 선택 상태 API 응답의 최상위 productId 기준 (서버가 맥락별 대표 결정 — 프론트 별도 계산 없음)
칩 이미지 해당 색상의 thumbnailUrl
색상 1개인 경우 칩 1개 표시, 항상 선택 상태
품절 상품 칩 미노출 (colorVariantsvisible=true만 포함)
사용자 액션 동작
컬러칩 클릭 메인 썸네일을 해당 색상 thumbnailUrl로 교체 + 선택 칩 강조 표시 + 썸네일 클릭 링크를 해당 productId 기준으로 업데이트. 페이지 이동 없음
메인 썸네일 클릭 현재 선택된 색상의 상품 상세 페이지로 이동 (/products/{selectedProductId})
메인 썸네일 hover hoverUrl로 이미지 전환 (기존 동작 유지)
Color 필터 선택 colorCodes 파라미터 추가 후 API 재요청. 서버가 이미 필터 색상을 대표로 설정하여 응답 — 프론트 추가 처리 불필요

색상 전환 후 hoverUrl 처리 미결: colorVariantshoverUrl 필드 추가 (백엔드 협의) vs 색상 전환 후 hover 비활성화 — 방향 결정 필요.

경우 처리
sizes.length === 1 (YETI 등 단일 SKU) 선택된 색상의 productSizeId로 즉시 장바구니 추가. 옵션 선택 UI 없음
sizes.length > 1 (복수 사이즈 상품) 기존 사이즈 선택 모달 그대로 사용

sizes.length 판단은 프론트에서 분기 처리.


항목 내용
진입 위치 상품 관리 또는 카테고리 진열설정 화면 (정확한 위치는 개발/디자인팀 협의)
기능 그룹 내 상품 목록 조회 → 특정 색상에 representative=true 지정/해제
단일 보장 그룹 내 representative=true는 1개만 허용. 새로 지정 시 기존 representative 자동 해제
캐시 representative 변경 시 해당 카테고리 캐시 즉시 무효화
주의 auto-visible 배치 로직은 representative 컬럼을 절대 건드리지 않음

항목 내용
otherColor API 변경 없음. PDP 전용으로 그대로 유지
GET /v2/products 변경 없음. 기존 화면에서 그대로 사용
auto-visible 배치 변경 없음
컬러코드 체계 기존 colorCode 목록 (BLA, BLU, RED 등) 그대로 활용

항목 담당 내용
style_key NULL 방어 배포 순서 개발팀 마이그레이션 완료 후 배포 vs 코드에서 NULL 방어 처리 후 먼저 배포
이미지 없는 representative 처리 개발팀 representative=true 상품에 썸네일이 없을 경우: fallback 이미지 사용 vs 다음 visible=true 색상 상품으로 대표 fallback
색상 전환 후 hoverUrl 개발/백엔드 colorVariantshoverUrl 추가 vs 색상 전환 후 hover 비활성화
어드민 대표 색상 지정 UI 위치 개발/디자인 상품 관리 vs 카테고리 진열설정 화면 중 위치 확정 필요

  • product 테이블에 style_key, representative 컬럼이 추가된다
  • 기존 상품 전체에 style_key 값이 마이그레이션된다
  • 상품 등록/수정 시 style_key가 자동 계산된다
  • GET /v2/products/grouped 호출 시 style_key 기준으로 그룹핑된 응답이 반환된다
  • totalElements가 상품 수가 아닌 그룹 수를 반환한다
  • Color 필터 선택 시 해당 색상 상품이 대표로 설정되어 응답된다
  • 전체 품절 그룹은 응답에 포함되지 않는다
  • 기존 GET /v2/products는 영향 없다
  • 카드에 컬러칩이 표시된다 (대표 칩 먼저, colorVariants 순서대로)
  • 초기 렌더링 시 API 응답의 대표 색상이 선택 상태로 표시된다
  • 컬러칩 클릭 시 썸네일과 상품 링크가 해당 색상 기준으로 전환된다
  • 썸네일 클릭 시 선택된 색상 상품 상세 페이지로 이동된다
  • sizes.length === 1인 경우 옵션 선택 없이 즉시 장바구니에 담긴다
  • sizes.length > 1인 경우 기존 사이즈 선택 모달이 동작한다
  • 그룹 내 상품 목록에서 특정 색상에 representative=true를 지정할 수 있다
  • representative 변경 시 그룹 내 기존 대표가 자동 해제된다
  • representative 변경 시 캐시가 무효화된다

버전 날짜 변경 내용
v1.0 2026-07-09 최초 작성 — DB 마이그레이션, 그룹핑 API, 컬러칩 카드 컴포넌트, 장바구니 담기, 어드민 대표 색상 지정