From 34503990f2741033e15e6edb3879b3f79238c8dc Mon Sep 17 00:00:00 2001 From: kdletters <61648117+kdletters@users.noreply.github.com> Date: Tue, 15 Sep 2026 23:29:22 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20AGC=20=E7=BB=B4=E6=8A=A4?= =?UTF-8?q?=E6=9C=9F=E7=99=BB=E5=BD=95=E9=94=99=E8=AF=AF=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 按 HTTP 状态展示认证服务不可用、超时和维护提示 增加 503 非 JSON 维护页认证界面回归测试 同步 AGC 启动认证错误提示技术方案 --- .../src/services/clientAuth.ts | 28 ++++++++++++++--- .../tests/appSurface/auth.suite.ts | 31 +++++++++++++++++++ ...案】AI游戏创作智能体App实施计划-2026-06-24.md | 1 + 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/apps/ai-game-creator-shell/src/services/clientAuth.ts b/apps/ai-game-creator-shell/src/services/clientAuth.ts index 54c4c27e8..c0b5e0750 100644 --- a/apps/ai-game-creator-shell/src/services/clientAuth.ts +++ b/apps/ai-game-creator-shell/src/services/clientAuth.ts @@ -72,6 +72,24 @@ export function getClientAuthRefreshOperation(apiBaseUrl: string) { const CLIENT_AUTH_NETWORK_ERROR_MESSAGE = '无法连接登录服务,请确认配套后端或 API 代理已启动后重试'; +function getClientAuthHttpErrorMessage(status: number, fallback: string) { + switch (status) { + case 408: + case 504: + return '登录服务响应超时,请检查服务器地址和网络后重试'; + case 429: + return '登录请求过于频繁,请稍后重试'; + case 500: + return '登录服务内部错误(HTTP 500),请稍后重试'; + case 502: + return '登录服务暂不可用:上游服务请求失败,请稍后重试'; + case 503: + return '登录服务暂不可用(HTTP 503),服务器可能正在维护,请稍后重试'; + default: + return fallback; + } +} + function getClientAuthNetworkErrorMessage(error: unknown) { const detail = error instanceof Error ? error.message.trim() : String(error).trim(); @@ -121,24 +139,26 @@ export function getClientAuthErrorMessage(error: unknown, fallback: string) { } async function readAuthErrorMessage(response: Response, fallback: string) { + const httpFallback = getClientAuthHttpErrorMessage(response.status, fallback); const text = await readClientHttpResponseText(response, { url: 'auth error response', }); if (!text.trim()) { - return fallback; + return httpFallback; } let parsed: unknown; try { parsed = JSON.parse(text) as unknown; } catch { - return fallback; + return httpFallback; } try { unwrapApiResponse(parsed); } catch (error) { - return error instanceof Error ? error.message : fallback; + const message = error instanceof Error ? error.message.trim() : ''; + return message && message !== '请求失败' ? message : httpFallback; } - return fallback; + return httpFallback; } async function requestAuthJson( diff --git a/apps/ai-game-creator-shell/tests/appSurface/auth.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/auth.suite.ts index ffeb58e41..bfb771cb0 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/auth.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/auth.suite.ts @@ -1272,6 +1272,37 @@ export function registerAuthTests() { ).toHaveLength(1); }); + it('shows the HTTP maintenance error when startup auth receives a 503', async () => { + window.localStorage.setItem( + 'genarrative.auth.access-token.v1', + 'existing-token', + ); + vi.spyOn(globalThis, 'fetch').mockImplementation( + async (input: RequestInfo | URL) => { + if (String(input) === '/api/auth/me') { + return new Response( + '503 Service Unavailable', + { status: 503, headers: { 'Content-Type': 'text/html' } }, + ); + } + throw new Error(`unexpected fetch ${String(input)}`); + }, + ); + + render( + React.createElement(AuthenticatedClient, null, () => + React.createElement('main', { 'aria-label': '已登录' }), + ), + ); + + expect(await screen.findByRole('main', { name: '登录' })).not.toBeNull(); + expect( + screen.getByText( + '登录服务暂不可用(HTTP 503),服务器可能正在维护,请稍后重试', + ), + ).not.toBeNull(); + }); + it('still calls logout when token refresh fails during logout retry', async () => { window.localStorage.setItem( 'genarrative.auth.access-token.v1', diff --git a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md index a232c7612..8d8af7f25 100644 --- a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md +++ b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md @@ -63,6 +63,7 @@ npm 游戏的可预览产物固定为对应 package 目录下的 `dist/index.htm - AGC 客户端启动恢复按“读取本地凭据 → 刷新会话(无 token 或失效时)→ 读取当前用户 → Tauri 本地运行时会话安装”阶段执行。界面必须展示当前阶段和已等待时间;不能以无期限的单一 loading 文案隐藏网络或 Runner 故障。 - 客户端 HTTP 传输默认使用 15 秒超时并通过独立 `AbortController` 终止请求;调用方可为确需长耗时的请求显式传入 `timeoutMs: null`。调用方主动取消仍保留原始 `AbortError`,超时使用稳定的 `ClientHttpTimeoutError`,由认证层转换为可操作的中文提示。 - 会话恢复或本地 Runner 连接超时后必须进入登录页并提供“重试登录状态检查”。重试递增恢复代次并以运行标识忽略旧恢复任务的迟到 UI 写回;不得清除仍可用于后续重试的 access token,也不得重复并发刷新同一服务器的 refresh 请求。 +- AGC 壳启动认证遇到空响应、非 JSON 维护页或 5xx 时,必须按 HTTP 状态生成可操作的中文提示(503 明确标记服务暂不可用/可能维护),不能退化为 `读取当前用户失败`;后端返回的结构化错误 message 仍优先展示。 - Tauri Runner 的启动与 IPC 超时继续以 `runner/protocol.rs` 的 30 秒启动、10 秒读写为权威;启动等待循环会把 endpoint 探测预算裁剪到剩余启动期限,避免单次 ping 把 30 秒门禁延长。考虑复用旧 endpoint 前可能先消耗一次 IPC 等待,前端 UI 兜底取 45 秒,不改变 Runner 协议、启动策略或认证接口。 ## 2026-08-26 运行中自主扩图提案