From 3829ada3cf0ab4af53dcea3f5e4782da6391cbf6 Mon Sep 17 00:00:00 2001 From: caadiq Date: Thu, 16 Apr 2026 08:45:50 +0900 Subject: [PATCH] =?UTF-8?q?Nexon=20API=20=EC=A0=90=EA=B2=80=EC=A4=91=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EA=B0=90=EC=A7=80/=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 백엔드: notices/character 라우트에서 OPENAPI00001/00007/00010/00011 응답을 감지해 503 + { maintenance: true, code } 반환 - 프론트 api 클라이언트가 에러에 서버 필드(maintenance 등)를 병합 - 공지 위젯: 점검중이면 "넥슨 Open API 점검중" 안내 + retry 차단 - 캐릭터 검색 오류 메시지로 '점검중' 상황이 "존재하지 않는 캐릭터"와 구분 Co-Authored-By: Claude Opus 4.6 (1M context) --- backend/routes/character.js | 8 ++++++++ backend/routes/notices.js | 7 +++++++ frontend/src/api/client.js | 4 +++- frontend/src/components/NoticeWidget.jsx | 9 ++++++++- 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/backend/routes/character.js b/backend/routes/character.js index e9451f0..7ddef1e 100644 --- a/backend/routes/character.js +++ b/backend/routes/character.js @@ -31,6 +31,10 @@ router.get('/search', async (req, res) => { character_image: basic.character_image, }); } 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) { 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 }); } 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); res.status(500).json({ error: '심볼 조회 실패' }); } diff --git a/backend/routes/notices.js b/backend/routes/notices.js index 6e926fe..6684525 100644 --- a/backend/routes/notices.js +++ b/backend/routes/notices.js @@ -23,6 +23,13 @@ router.get('/', async (req, res) => { }); res.json(data); } 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); res.status(500).json({ error: '공지 조회 실패' }); } diff --git a/frontend/src/api/client.js b/frontend/src/api/client.js index 1e53ee1..0171bac 100644 --- a/frontend/src/api/client.js +++ b/frontend/src/api/client.js @@ -16,7 +16,9 @@ export async function api(url, options = {}) { if (!res.ok) { 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() diff --git a/frontend/src/components/NoticeWidget.jsx b/frontend/src/components/NoticeWidget.jsx index 41df9b5..6484d76 100644 --- a/frontend/src/components/NoticeWidget.jsx +++ b/frontend/src/components/NoticeWidget.jsx @@ -93,11 +93,13 @@ export default function NoticeWidget() { const [expanded, setExpanded] = useState(false) const tab = TABS.find((t) => t.key === activeTab) - const { data, isLoading } = useQuery({ + const { data, isLoading, error } = useQuery({ queryKey: ['notices', activeTab], queryFn: () => api(`/api/notices?type=${activeTab}`), staleTime: 5 * 60 * 1000, + retry: (failureCount, err) => (err?.maintenance ? false : failureCount < 1), }) + const isMaintenance = !!error?.maintenance const list = data?.[tab.dataKey] || [] const allItems = tab.filterOngoing @@ -148,6 +150,11 @@ export default function NoticeWidget() {
))}
+ ) : isMaintenance ? ( +
+
넥슨 Open API 점검중
+
점검이 끝나면 다시 표시됩니다
+
) : initialItems.length === 0 ? (