feat: 매치 맵 표기 및 상세 백필 파이프라인 구축

- 매치 목록 응답에 상세 캐시(sa_match_details) 맵 이름 조인
- 온디맨드 백필: 검색 시 맵 미보유 최신 매치 비동기 적재 (개발 4 / 운영 20건)
- 야간 크론 백필: 미보유 전량 적재 (개발은 유저당 20건 캡으로 일일 쿼터 보호)
- 실패 마킹: 3회 초과 실패 매치는 백필 제외, 성공 시 기록 자동 정리
- useApi 인터셉터: 서버 제공 에러 메시지 우선 노출

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-12 10:11:30 +09:00
parent b8d636ba9d
commit c4a93db84b
7 changed files with 208 additions and 6 deletions
+16 -3
View File
@@ -55,15 +55,22 @@ api.interceptors.response.use(
// 성공 시 data 만 언래핑하여 반환
return payload.data as never
}
// HTTP 200 이지만 비즈니스 로직상 실패인 경우
// HTTP 200 이지만 비즈니스 로직상 실패인 경우 — 서버 메시지 우선, 없으면 코드 사전 매핑
const errCode = payload.error?.code || 'BIZ_001'
showErrorUI(ErrorCodeLexicon[errCode] ?? ErrorCodeLexicon.SYS_001 ?? '일시적인 시스템 오류가 발생했습니다.')
showErrorUI(
payload.error?.message
|| ErrorCodeLexicon[errCode]
|| ErrorCodeLexicon.SYS_001
|| '일시적인 시스템 오류가 발생했습니다.',
)
return Promise.reject(payload.error)
}
return payload as never
},
(error) => {
let errCode = 'SYS_001'
// 백엔드 Exception Filter 가 내려준 사용자용 메시지 (점검 안내 등) — 있으면 우선 노출
let serverMessage: string | undefined
if (error?.response) {
const status: number = error.response.status
@@ -71,13 +78,19 @@ api.interceptors.response.use(
if (payload?.error?.code) {
errCode = payload.error.code
serverMessage = payload.error.message
} else if (status === 401) errCode = 'AUTH_001'
else if (status === 403) errCode = 'AUTH_003'
else if (status === 400 || status === 422) errCode = 'VAL_001'
else if (status === 404) errCode = 'RES_001'
}
showErrorUI(ErrorCodeLexicon[errCode] ?? ErrorCodeLexicon.SYS_001 ?? '일시적인 시스템 오류가 발생했습니다.')
showErrorUI(
serverMessage
|| ErrorCodeLexicon[errCode]
|| ErrorCodeLexicon.SYS_001
|| '일시적인 시스템 오류가 발생했습니다.',
)
return Promise.reject(error)
},
)
+2
View File
@@ -10,6 +10,8 @@ export interface SaMatchItem {
kill: number
death: number
assist: number
// 매치 맵 — 상세 캐시 보유 시에만 값 존재, 미보유면 빈 문자열
matchMap: string
}
// 매치 상세 참가자
+1 -1
View File
@@ -123,7 +123,7 @@ function adaptMatches(items: SaMatchItem[]): MatchRecord[] {
matchId: m.matchId,
mode: TYPE_LABEL[m.matchType] ?? m.matchType,
win: m.matchResult === '1',
map: '',
map: m.matchMap ?? '',
k: m.kill,
d: m.death,
a: m.assist,