diff --git a/apps/backend/src/services/platforms/affiliate-dash/http-client.ts b/apps/backend/src/services/platforms/affiliate-dash/http-client.ts index 8a5854b1..c54a240a 100644 --- a/apps/backend/src/services/platforms/affiliate-dash/http-client.ts +++ b/apps/backend/src/services/platforms/affiliate-dash/http-client.ts @@ -95,18 +95,20 @@ export async function affiliateDashRequest(input: { logExternalHttpPacket('[affiliate-dash/http]', 'HTTP 响应失败', responsePacket, { level: 'warn', }) - throw createHttpError( - summarizeAffiliateDashMessage(json) || `affiliate-dash 请求失败: HTTP ${response.status}`, - { - statusCode: 502, - errorCode: 'affiliate_dash_http_failed', - context: { - status: response.status, - message: summarizeAffiliateDashMessage(json), - body: text, - }, + const upstreamMessage = + summarizeAffiliateDashMessage(json) || `affiliate-dash 请求失败: HTTP ${response.status}` + // 上游 4xx 透传状态码,便于领取页展示真实原因;5xx 仍归一为 502。 + const statusCode = + response.status >= 400 && response.status < 500 ? response.status : 502 + throw createHttpError(upstreamMessage, { + statusCode, + errorCode: 'affiliate_dash_http_failed', + context: { + status: response.status, + message: summarizeAffiliateDashMessage(json), + body: text, }, - ) + }) } if (!isAffiliateDashSuccessResponse(json)) { diff --git a/apps/backend/src/utils/http.test.ts b/apps/backend/src/utils/http.test.ts index fe0fa410..af0e43ae 100644 --- a/apps/backend/src/utils/http.test.ts +++ b/apps/backend/src/utils/http.test.ts @@ -1,7 +1,7 @@ import assert from 'node:assert/strict' import test from 'node:test' -import { resolveRouteErrorLogLevel } from './http.js' +import { buildErrorPayload, createHttpError, resolveRouteErrorLogLevel } from './http.js' test('expected worker session expiry is debug-level', () => { assert.equal( @@ -14,3 +14,22 @@ test('expected worker session expiry is debug-level', () => { ) assert.equal(resolveRouteErrorLogLevel({ statusCode: 500, errorCode: 'internal_error' }), 'error') }) + +test('affiliate_dash errors surface upstream message even when mapped to 502', () => { + const error = createHttpError('乐玩开放平台 AccessKey 或 SecretKey 未配置', { + statusCode: 502, + errorCode: 'affiliate_dash_http_failed', + }) + const payload = buildErrorPayload(error, '提交 UID 失败') + assert.equal(payload.msg, '乐玩开放平台 AccessKey 或 SecretKey 未配置') + assert.equal(payload.errorCode, 'affiliate_dash_http_failed') +}) + +test('generic 5xx still uses fallback message', () => { + const error = createHttpError('secret internal boom', { + statusCode: 500, + errorCode: 'internal_error', + }) + const payload = buildErrorPayload(error, '服务内部错误') + assert.equal(payload.msg, '服务内部错误') +}) diff --git a/apps/backend/src/utils/http.ts b/apps/backend/src/utils/http.ts index c087a3ef..aed97fdb 100644 --- a/apps/backend/src/utils/http.ts +++ b/apps/backend/src/utils/http.ts @@ -29,12 +29,16 @@ export function buildSuccessPayload(data: unknown, msg: string = 'ok') { export function buildErrorPayload(error: unknown, fallbackMessage: string) { const classification = classifyRouteError(error) - const message = - classification.statusCode >= 500 + const errorMessage = error instanceof Error ? String(error.message || '').trim() : '' + // 依赖方错误(如 affiliate_dash)即使映射为 5xx,也优先透出上游可读原因。 + const preferUpstreamMessage = + errorMessage && + (classification.statusCode < 500 || classification.errorCode.startsWith('affiliate_dash')) + const message = preferUpstreamMessage + ? errorMessage + : classification.statusCode >= 500 ? fallbackMessage || '服务内部错误' - : error instanceof Error - ? error.message - : fallbackMessage + : errorMessage || fallbackMessage return { code: 1,