From cf78f934212b7f646d79440cb29811620e8783fc Mon Sep 17 00:00:00 2001 From: seaHi Date: Sun, 27 Sep 2026 13:25:17 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=94=B9=E8=BF=9B=20Cloudflare=20?= =?UTF-8?q?=E7=AB=99=E7=82=B9=E8=A7=A3=E6=9E=90=E9=94=99=E8=AF=AF=E5=A4=84?= =?UTF-8?q?=E7=90=86=E5=B9=B6=E4=BF=9D=E7=95=99=E8=AF=A6=E7=BB=86=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/server/cloudflare-audience.test.ts | 27 +++++++++++++++++++++- src/lib/server/cloudflare.ts | 23 ++++++++++-------- 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/lib/server/cloudflare-audience.test.ts b/src/lib/server/cloudflare-audience.test.ts index 1cc4997..4166f14 100644 --- a/src/lib/server/cloudflare-audience.test.ts +++ b/src/lib/server/cloudflare-audience.test.ts @@ -99,7 +99,32 @@ describe('Web Analytics 站点解析与统计', () => { it('站点列表返回失败状态时明确报错', async () => { mockAnalytics(() => Response.json({ success: false, result: [] })); const { audience } = await fetchAnalytics(platform, undefined, siteToken); - expect(audience?.error).toContain('站点列表不可用'); + expect(audience?.error).toContain('站点解析失败'); + expect(audience?.error).toContain('未返回具体错误说明'); + }); + + it('保留 Cloudflare 返回的错误码和说明,避免将所有 403 都归因为缺少权限', async () => { + mockAnalytics(() => + Response.json( + { + success: false, + errors: [{ code: 10000, message: 'Authentication error' }] + }, + { status: 403 } + ) + ); + const { audience } = await fetchAnalytics(platform, undefined, siteToken); + expect(audience?.error).toContain('HTTP 403'); + expect(audience?.error).toContain('10000: Authentication error'); + expect(audience?.pageViews).toBeNull(); + }); + + it('非 JSON 错误响应仍保留 HTTP 状态', async () => { + mockAnalytics(() => new Response('Forbidden', { status: 403 })); + const { audience } = await fetchAnalytics(platform, undefined, siteToken); + expect(audience?.error).toContain('HTTP 403'); + expect(audience?.error).toContain('未返回具体错误说明'); + expect(audience?.error).not.toContain(''); }); it('网络或超时异常只影响访问统计,不导致整个统计 Promise 失败', async () => { diff --git a/src/lib/server/cloudflare.ts b/src/lib/server/cloudflare.ts index 6ecf611..71a6c34 100644 --- a/src/lib/server/cloudflare.ts +++ b/src/lib/server/cloudflare.ts @@ -51,18 +51,21 @@ async function getSiteTagByToken( signal } ); - if (!response.ok) { - throw new Error( - `Web Analytics 站点解析失败(HTTP ${response.status}),请检查 CF_ACCOUNT_ID 和 CF_API_TOKEN 的 Account Settings Read 权限` - ); - } - const data = (await response.json()) as { - success: boolean; + // 错误响应也包含诊断信息;代理返回非 JSON 时仍保留 HTTP 状态。 + const data = (await response.json().catch(() => null)) as { + success?: boolean; result?: { site_tag: string; site_token: string }[]; result_info?: { total_pages?: number }; - }; - if (!data.success || !Array.isArray(data.result)) { - throw new Error('Web Analytics 站点列表不可用,请检查账户和 API Token 权限'); + errors?: { code?: number; message?: string }[]; + } | null; + if (!response.ok || !data?.success || !Array.isArray(data.result)) { + const details = data?.errors + ?.map((error) => [error.code, error.message].filter((value) => value != null).join(': ')) + .filter(Boolean) + .join('; '); + throw new Error( + `Web Analytics 站点解析失败(HTTP ${response.status})${details ? `:${details}` : ':Cloudflare 未返回具体错误说明'}。请核对当前部署的 CF_API_TOKEN、CF_ACCOUNT_ID、账户读取权限及 Token 的 IP/有效期限制。` + ); } const matched = data.result.find( (site) => site.site_token === siteToken || site.site_tag === siteToken