Nexon API 점검중 상태 감지/표시
- 백엔드: notices/character 라우트에서 OPENAPI00001/00007/00010/00011
응답을 감지해 503 + { maintenance: true, code } 반환
- 프론트 api 클라이언트가 에러에 서버 필드(maintenance 등)를 병합
- 공지 위젯: 점검중이면 "넥슨 Open API 점검중" 안내 + retry 차단
- 캐릭터 검색 오류 메시지로 '점검중' 상황이 "존재하지 않는 캐릭터"와 구분
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7151315371
commit
3829ada3cf
4 changed files with 26 additions and 2 deletions
|
|
@ -31,6 +31,10 @@ router.get('/search', async (req, res) => {
|
||||||
character_image: basic.character_image,
|
character_image: basic.character_image,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
const code = err.response?.data?.error?.name;
|
||||||
|
if (['OPENAPI00001', 'OPENAPI00007', 'OPENAPI00010', 'OPENAPI00011'].includes(code)) {
|
||||||
|
return res.status(503).json({ error: 'API 점검중입니다', code, maintenance: true });
|
||||||
|
}
|
||||||
if (err.response?.status === 400) {
|
if (err.response?.status === 400) {
|
||||||
return res.status(404).json({ error: '존재하지 않는 캐릭터입니다' });
|
return res.status(404).json({ error: '존재하지 않는 캐릭터입니다' });
|
||||||
}
|
}
|
||||||
|
|
@ -65,6 +69,10 @@ router.get('/symbols', async (req, res) => {
|
||||||
|
|
||||||
res.json({ ocid, character_class: data.character_class, symbols: parsed });
|
res.json({ ocid, character_class: data.character_class, symbols: parsed });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
const code = err.response?.data?.error?.name;
|
||||||
|
if (['OPENAPI00001', 'OPENAPI00007', 'OPENAPI00010', 'OPENAPI00011'].includes(code)) {
|
||||||
|
return res.status(503).json({ error: 'API 점검중입니다', code, maintenance: true });
|
||||||
|
}
|
||||||
console.error('심볼 조회 오류:', err.response?.data || err.message);
|
console.error('심볼 조회 오류:', err.response?.data || err.message);
|
||||||
res.status(500).json({ error: '심볼 조회 실패' });
|
res.status(500).json({ error: '심볼 조회 실패' });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,13 @@ router.get('/', async (req, res) => {
|
||||||
});
|
});
|
||||||
res.json(data);
|
res.json(data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
const errData = err.response?.data?.error;
|
||||||
|
const code = errData?.name;
|
||||||
|
// Nexon 점검 코드: OPENAPI00001(게임 점검), OPENAPI00007(api 점검), OPENAPI00011(오픈 API 점검)
|
||||||
|
const underMaintenance = ['OPENAPI00001', 'OPENAPI00007', 'OPENAPI00010', 'OPENAPI00011'].includes(code);
|
||||||
|
if (underMaintenance) {
|
||||||
|
return res.status(503).json({ error: 'API 점검중입니다', code, maintenance: true });
|
||||||
|
}
|
||||||
console.error(`공지 조회 오류 (${type}):`, err.response?.data || err.message);
|
console.error(`공지 조회 오류 (${type}):`, err.response?.data || err.message);
|
||||||
res.status(500).json({ error: '공지 조회 실패' });
|
res.status(500).json({ error: '공지 조회 실패' });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,9 @@ export async function api(url, options = {}) {
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const error = await res.json().catch(() => ({}))
|
const error = await res.json().catch(() => ({}))
|
||||||
throw new Error(error.error || `HTTP ${res.status}`)
|
const e = new Error(error.error || `HTTP ${res.status}`)
|
||||||
|
Object.assign(e, error, { status: res.status })
|
||||||
|
throw e
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.json()
|
return res.json()
|
||||||
|
|
|
||||||
|
|
@ -93,11 +93,13 @@ export default function NoticeWidget() {
|
||||||
const [expanded, setExpanded] = useState(false)
|
const [expanded, setExpanded] = useState(false)
|
||||||
const tab = TABS.find((t) => t.key === activeTab)
|
const tab = TABS.find((t) => t.key === activeTab)
|
||||||
|
|
||||||
const { data, isLoading } = useQuery({
|
const { data, isLoading, error } = useQuery({
|
||||||
queryKey: ['notices', activeTab],
|
queryKey: ['notices', activeTab],
|
||||||
queryFn: () => api(`/api/notices?type=${activeTab}`),
|
queryFn: () => api(`/api/notices?type=${activeTab}`),
|
||||||
staleTime: 5 * 60 * 1000,
|
staleTime: 5 * 60 * 1000,
|
||||||
|
retry: (failureCount, err) => (err?.maintenance ? false : failureCount < 1),
|
||||||
})
|
})
|
||||||
|
const isMaintenance = !!error?.maintenance
|
||||||
|
|
||||||
const list = data?.[tab.dataKey] || []
|
const list = data?.[tab.dataKey] || []
|
||||||
const allItems = tab.filterOngoing
|
const allItems = tab.filterOngoing
|
||||||
|
|
@ -148,6 +150,11 @@ export default function NoticeWidget() {
|
||||||
<div key={i} className="h-24 rounded-lg bg-white/[0.02] animate-pulse" />
|
<div key={i} className="h-24 rounded-lg bg-white/[0.02] animate-pulse" />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
) : isMaintenance ? (
|
||||||
|
<div className="py-12 text-center">
|
||||||
|
<div className="text-sm text-amber-300 font-medium">넥슨 Open API 점검중</div>
|
||||||
|
<div className="text-xs text-gray-500 mt-1">점검이 끝나면 다시 표시됩니다</div>
|
||||||
|
</div>
|
||||||
) : initialItems.length === 0 ? (
|
) : initialItems.length === 0 ? (
|
||||||
<AnimatePresence mode="wait">
|
<AnimatePresence mode="wait">
|
||||||
<motion.div
|
<motion.div
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue