feat(dongchedi): translate platform "server exception" into actionable copy

Map dongchedi adapter failures (raw "server exception" or
dongchedi_platform_exception) to a Chinese prompt steering the user to
the dongchedi backend, so renderer/admin views and desktop-task error
payloads no longer surface the opaque platform message.
This commit is contained in:
2026-05-11 12:26:38 +08:00
parent f34c6f2ceb
commit 6fa9a6c052
8 changed files with 164 additions and 13 deletions
@@ -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<PublishTaskItem[]>(() =>
extractString(taskError.message) ??
extractString(taskError.detail) ??
extractString(taskError.code),
platformId,
),
complianceBlockedRecordId,
complianceBlockedAt: task.compliance_blocked_at ?? null,
@@ -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',
@@ -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<PublishTaskItem[]>(() =>
extractString(taskError.message) ??
extractString(taskError.detail) ??
extractString(taskError.code),
task.platform,
),
complianceBlockedRecordId,
complianceBlockedAt: task.compliance_blocked_at ? parseTimestamp(task.compliance_blocked_at) : null,
@@ -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')
})
@@ -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<string, string> = {
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)
}
@@ -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
}
@@ -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
@@ -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{