diff --git a/apps/admin-web/src/views/PublishManagementView.vue b/apps/admin-web/src/views/PublishManagementView.vue index c923e60..a6d8c9a 100644 --- a/apps/admin-web/src/views/PublishManagementView.vue +++ b/apps/admin-web/src/views/PublishManagementView.vue @@ -24,6 +24,8 @@ import { getPublishPlatformMeta, normalizePublishPlatformId } from '@/lib/publis const PAGE_SIZE = 10 const INLINE_ERROR_HEAD_CHARS = 52 const INLINE_ERROR_TAIL_CHARS = 10 +const DONGCHEDI_PLATFORM_EXCEPTION_MESSAGE = + '懂车帝平台返回异常,请打开懂车帝后台查看并处理平台提示,确认账号状态后再重试。' type PublishTaskStatus = DesktopTaskInfo['status'] @@ -93,7 +95,7 @@ function isPendingStatus(status: PublishTaskStatus): boolean { return status === 'queued' || status === 'in_progress' } -function normalizeTaskErrorMessage(messageText: string | null): string | null { +function normalizeTaskErrorMessage(messageText: string | null, platform?: string | null): string | null { const normalized = extractString(messageText) if (!normalized) { return null @@ -104,6 +106,13 @@ function normalizeTaskErrorMessage(messageText: string | null): string | null { if (normalized.includes('adapter is not implemented for this platform')) { return '当前平台的桌面发布适配器未实现,任务已按失败处理。' } + if ( + platform === 'dongchedi' && + (/\bserver[\s_-]*exception\b/i.test(normalized) || + /\bdongchedi_platform_exception\b/i.test(normalized)) + ) { + return DONGCHEDI_PLATFORM_EXCEPTION_MESSAGE + } return normalized } @@ -417,6 +426,7 @@ const publishTasks = computed(() => extractString(taskError.message) ?? extractString(taskError.detail) ?? extractString(taskError.code), + platformId, ), complianceBlockedRecordId, complianceBlockedAt: task.compliance_blocked_at ?? null, diff --git a/apps/desktop-client/src/main/adapters/dongchedi.ts b/apps/desktop-client/src/main/adapters/dongchedi.ts index 7dce9d2..a86a8ac 100644 --- a/apps/desktop-client/src/main/adapters/dongchedi.ts +++ b/apps/desktop-client/src/main/adapters/dongchedi.ts @@ -2,6 +2,7 @@ import { createHash, createHmac } from 'node:crypto' import type { JsonValue } from '@geo/shared-types' +import { DONGCHEDI_PLATFORM_EXCEPTION_MESSAGE } from '../../shared/publisher-errors' import type { PublishAdapter, PublishAdapterContext } from './base' import { ensureViewLoaded, @@ -127,6 +128,10 @@ function isDongchediChallengeMessage(message: string): boolean { ) } +function isDongchediPlatformExceptionMessage(message: string): boolean { + return /\bserver[\s_-]*exception\b/i.test(message) +} + function redactDiagnosticText(value: string, maxLength = 220): string { return value .replace( @@ -1009,6 +1014,26 @@ function failureResult( } } + if ( + (error instanceof DongchediAdapterError && error.code === 'dongchedi_platform_exception') || + isDongchediPlatformExceptionMessage(message) + ) { + return { + status: 'failed', + summary: DONGCHEDI_PLATFORM_EXCEPTION_MESSAGE, + error: { + code: 'dongchedi_platform_exception', + message: DONGCHEDI_PLATFORM_EXCEPTION_MESSAGE, + detail: + error instanceof DongchediAdapterError && error.detail + ? error.detail + : { + platform_message: message, + }, + }, + } + } + return { status: 'failed', summary: '懂车帝发布失败。', @@ -1080,6 +1105,9 @@ export const dongchediAdapter: PublishAdapter = { taskId: context.taskId, ...detail, }) + if (isDongchediPlatformExceptionMessage(message)) { + throw new DongchediAdapterError('dongchedi_platform_exception', message, detail) + } throw new DongchediAdapterError( 'dongchedi_publish_failed', message || 'dongchedi_draft_failed', @@ -1115,6 +1143,9 @@ export const dongchediAdapter: PublishAdapter = { taskId: context.taskId, ...detail, }) + if (isDongchediPlatformExceptionMessage(message)) { + throw new DongchediAdapterError('dongchedi_platform_exception', message, detail) + } throw new DongchediAdapterError( 'dongchedi_publish_failed', message || 'dongchedi_publish_failed', diff --git a/apps/desktop-client/src/renderer/views/PublishManagementView.vue b/apps/desktop-client/src/renderer/views/PublishManagementView.vue index 7461467..f605510 100644 --- a/apps/desktop-client/src/renderer/views/PublishManagementView.vue +++ b/apps/desktop-client/src/renderer/views/PublishManagementView.vue @@ -196,7 +196,7 @@ function normalizePublishTaskStatus(status: DesktopTaskInfo['status']): DesktopT return status === 'unknown' ? 'failed' : status } -function normalizeTaskErrorMessage(message: string | null): string | null { +function normalizeTaskErrorMessage(message: string | null, platform?: string | null): string | null { const normalized = extractString(message) if (!normalized) { return null @@ -207,7 +207,7 @@ function normalizeTaskErrorMessage(message: string | null): string | null { if (normalized.includes('adapter is not implemented for this platform')) { return '当前平台的桌面发布适配器未实现,任务已按失败处理。' } - return normalizePublisherErrorMessage(normalized) ?? normalized + return normalizePublisherErrorMessage(normalized, platform) ?? normalized } function complianceLevelLabel(level: string | null): string { @@ -607,6 +607,7 @@ const publishTasks = computed(() => extractString(taskError.message) ?? extractString(taskError.detail) ?? extractString(taskError.code), + task.platform, ), complianceBlockedRecordId, complianceBlockedAt: task.compliance_blocked_at ? parseTimestamp(task.compliance_blocked_at) : null, diff --git a/apps/desktop-client/src/shared/publisher-errors.test.ts b/apps/desktop-client/src/shared/publisher-errors.test.ts index a65a658..2be9074 100644 --- a/apps/desktop-client/src/shared/publisher-errors.test.ts +++ b/apps/desktop-client/src/shared/publisher-errors.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest' import { BAIJIAHAO_CHALLENGE_REQUIRED_MESSAGE, + DONGCHEDI_PLATFORM_EXCEPTION_MESSAGE, PUBLISH_ACCOUNT_NOT_LOGGED_IN_MESSAGE, QIEHAO_REAL_NAME_AUTH_MESSAGE, WANGYIHAO_DRAFT_SAVE_PARAMETER_MESSAGE, @@ -10,6 +11,7 @@ import { WEIXIN_GZH_PUBLIC_URL_MISSING_MESSAGE, isQiehaoRealNameAuthError, normalizeBaijiahaoPublishErrorMessage, + normalizeDongchediPublishErrorMessage, normalizePublisherErrorMessage, normalizePublisherLoginErrorMessage, normalizeQiehaoPublishErrorMessage, @@ -99,6 +101,18 @@ describe('publisher error normalization', () => { ) }) + it('normalizes Dongchedi server exception failures to a broad platform prompt', () => { + expect(normalizePublisherErrorMessage('server exception', 'dongchedi')).toBe( + DONGCHEDI_PLATFORM_EXCEPTION_MESSAGE, + ) + expect(normalizePublisherErrorMessage('dongchedi_publish_failed:server exception')).toBe( + DONGCHEDI_PLATFORM_EXCEPTION_MESSAGE, + ) + expect(normalizeDongchediPublishErrorMessage('Error: dongchedi_platform_exception')).toBe( + DONGCHEDI_PLATFORM_EXCEPTION_MESSAGE, + ) + }) + it('leaves unknown publisher errors unchanged', () => { expect(normalizePublisherErrorMessage('qiehao_publish_failed')).toBe('qiehao_publish_failed') }) diff --git a/apps/desktop-client/src/shared/publisher-errors.ts b/apps/desktop-client/src/shared/publisher-errors.ts index 09303ef..367f078 100644 --- a/apps/desktop-client/src/shared/publisher-errors.ts +++ b/apps/desktop-client/src/shared/publisher-errors.ts @@ -12,6 +12,8 @@ export const WEIXIN_GZH_PUBLIC_URL_MISSING_MESSAGE = '微信公众号可能已触发管理员/运营者授权发布,桌面端未能获取到文章外链。请打开公众号后台完成授权发布,并在发表记录中确认文章链接。' export const BAIJIAHAO_CHALLENGE_REQUIRED_MESSAGE = '百家号检测到网络环境异常,需要人机验证。请打开百家号后台手动完成一次发稿或验证后再重试发布。' +export const DONGCHEDI_PLATFORM_EXCEPTION_MESSAGE = + '懂车帝平台返回异常,请打开懂车帝后台查看并处理平台提示,确认账号状态后再重试。' const PUBLISH_PLATFORM_LABELS: Record = { baijiahao: '百家号', @@ -34,6 +36,10 @@ function normalizeErrorText(value: string | null | undefined): string | null { return trimmed ? trimmed : null } +function isDongchediPlatform(platform?: string | null): boolean { + return platform?.trim().toLowerCase() === 'dongchedi' +} + export function isQiehaoRealNameAuthError( message: string | null | undefined, code?: number | string | null, @@ -140,13 +146,39 @@ export function normalizeBaijiahaoPublishErrorMessage( return normalized } -export function normalizePublisherErrorMessage(message: string | null | undefined): string | null { +export function normalizeDongchediPublishErrorMessage( + message: string | null | undefined, + platform?: string | null, +): string | null { + const normalized = normalizeErrorText(message) + if (!normalized) { + return null + } + + const hasDongchediSignal = + isDongchediPlatform(platform) || /\bdongchedi_(?:draft|publish|platform)_/i.test(normalized) + + if ( + hasDongchediSignal && + (/\bserver[\s_-]*exception\b/i.test(normalized) || + /\bdongchedi_platform_exception\b/i.test(normalized)) + ) { + return DONGCHEDI_PLATFORM_EXCEPTION_MESSAGE + } + return normalized +} + +export function normalizePublisherErrorMessage( + message: string | null | undefined, + platform?: string | null, +): string | null { if (message === 'compliance_blocked') { return '内容合规检测已阻断本次发布,请修改文章后重新发布。' } const qiehaoMessage = normalizeQiehaoPublishErrorMessage(message) const wangyihaoMessage = normalizeWangyihaoPublishErrorMessage(qiehaoMessage) const baijiahaoMessage = normalizeBaijiahaoPublishErrorMessage(wangyihaoMessage) - const loginMessage = normalizePublisherLoginErrorMessage(baijiahaoMessage) + const dongchediMessage = normalizeDongchediPublishErrorMessage(baijiahaoMessage, platform) + const loginMessage = normalizePublisherLoginErrorMessage(dongchediMessage) return normalizeWeixinGzhPublishErrorMessage(loginMessage) } diff --git a/server/internal/tenant/app/publish_record_normalize.go b/server/internal/tenant/app/publish_record_normalize.go index c5b54be..2cc7591 100644 --- a/server/internal/tenant/app/publish_record_normalize.go +++ b/server/internal/tenant/app/publish_record_normalize.go @@ -20,18 +20,26 @@ func normalizePublishRecordForResponse(item *PublishRecordResponse) { } func normalizePublishRecordErrorMessage(platform string, message *string) *string { - if strings.TrimSpace(platform) != "baijiahao" || message == nil { + normalizedPlatform := strings.TrimSpace(platform) + if message == nil || (normalizedPlatform != "baijiahao" && normalizedPlatform != "dongchedi") { return message } trimmed := strings.TrimSpace(*message) - if trimmed == "baijiahao_challenge_required" || + if normalizedPlatform == "baijiahao" && (trimmed == "baijiahao_challenge_required" || trimmed == "desktop_account_risk_control" || trimmed == "desktop_account_challenge_required" || - strings.HasPrefix(trimmed, "baijiahao_challenge_required:") { + strings.HasPrefix(trimmed, "baijiahao_challenge_required:")) { normalized := baijiahaoRiskControlPrompt return &normalized } + if normalizedPlatform == "dongchedi" && (trimmed == "dongchedi_platform_exception" || + strings.EqualFold(trimmed, "server exception") || + strings.HasPrefix(trimmed, "dongchedi_publish_failed:server exception") || + strings.HasPrefix(trimmed, "dongchedi_draft_failed:server exception")) { + normalized := dongchediPlatformExceptionPrompt + return &normalized + } return message } diff --git a/server/internal/tenant/app/publish_record_support.go b/server/internal/tenant/app/publish_record_support.go index 62f0bc8..2794d0e 100644 --- a/server/internal/tenant/app/publish_record_support.go +++ b/server/internal/tenant/app/publish_record_support.go @@ -467,6 +467,7 @@ func extractStringPointer(payload map[string]any, keys ...string) *string { } const baijiahaoRiskControlPrompt = "您触发平台风控,请手动发稿一篇并完成验证,方可继续使用" +const dongchediPlatformExceptionPrompt = "懂车帝平台返回异常,请打开懂车帝后台查看并处理平台提示,确认账号状态后再重试。" func desktopTaskErrorMessage(payload map[string]any, platform string) *string { if message := desktopTaskPlatformErrorMessage(payload, platform); message != nil { @@ -485,7 +486,8 @@ func desktopTaskErrorMessage(payload map[string]any, platform string) *string { } func desktopTaskPlatformErrorMessage(payload map[string]any, platform string) *string { - if strings.TrimSpace(platform) != "baijiahao" { + normalizedPlatform := strings.TrimSpace(platform) + if normalizedPlatform != "baijiahao" && normalizedPlatform != "dongchedi" { return nil } @@ -495,13 +497,22 @@ func desktopTaskPlatformErrorMessage(payload map[string]any, platform string) *s continue } trimmed := strings.TrimSpace(*value) - if trimmed == "baijiahao_challenge_required" || - trimmed == "desktop_account_risk_control" || - trimmed == "desktop_account_challenge_required" || - strings.HasPrefix(trimmed, "baijiahao_challenge_required:") { + if normalizedPlatform == "baijiahao" && + (trimmed == "baijiahao_challenge_required" || + trimmed == "desktop_account_risk_control" || + trimmed == "desktop_account_challenge_required" || + strings.HasPrefix(trimmed, "baijiahao_challenge_required:")) { message := baijiahaoRiskControlPrompt return &message } + if normalizedPlatform == "dongchedi" && + (trimmed == "dongchedi_platform_exception" || + strings.EqualFold(trimmed, "server exception") || + strings.HasPrefix(trimmed, "dongchedi_publish_failed:server exception") || + strings.HasPrefix(trimmed, "dongchedi_draft_failed:server exception")) { + message := dongchediPlatformExceptionPrompt + return &message + } } return nil diff --git a/server/internal/tenant/app/publish_record_support_test.go b/server/internal/tenant/app/publish_record_support_test.go index affc17b..6a27105 100644 --- a/server/internal/tenant/app/publish_record_support_test.go +++ b/server/internal/tenant/app/publish_record_support_test.go @@ -43,6 +43,36 @@ func TestDesktopTaskErrorMessageKeepsGenericMessage(t *testing.T) { } } +func TestDesktopTaskErrorMessageMapsDongchediServerException(t *testing.T) { + tests := []struct { + name string + payload map[string]any + }{ + { + name: "raw platform message", + payload: map[string]any{ + "message": "server exception", + }, + }, + { + name: "adapter code", + payload: map[string]any{ + "code": "dongchedi_platform_exception", + "message": "懂车帝平台返回异常,请打开懂车帝后台查看并处理平台提示,确认账号状态后再重试。", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := desktopTaskErrorMessage(tt.payload, "dongchedi") + if got == nil || *got != dongchediPlatformExceptionPrompt { + t.Fatalf("desktopTaskErrorMessage() = %v, want %q", got, dongchediPlatformExceptionPrompt) + } + }) + } +} + func TestNormalizePublishRecordForResponseUsesBaijiahaoPublicURL(t *testing.T) { articleID := "1863525130677225913" editURL := "https://baijiahao.baidu.com/builder/rc/edit?type=news&article_id=1863525130677225913" @@ -64,6 +94,20 @@ func TestNormalizePublishRecordForResponseUsesBaijiahaoPublicURL(t *testing.T) { } } +func TestNormalizePublishRecordForResponseMapsDongchediServerException(t *testing.T) { + rawError := "server exception" + item := &PublishRecordResponse{ + PlatformID: "dongchedi", + ErrorMessage: &rawError, + } + + normalizePublishRecordForResponse(item) + + if item.ErrorMessage == nil || *item.ErrorMessage != dongchediPlatformExceptionPrompt { + t.Fatalf("ErrorMessage = %v, want %q", item.ErrorMessage, dongchediPlatformExceptionPrompt) + } +} + func TestNormalizePublishRecordForResponseUsesWangyihaoPublicURL(t *testing.T) { articleID := "IK5K461I05561G2M" item := &PublishRecordResponse{