feat(doubao): implement challenge handling and monitoring improvements
Desktop Client Build / Resolve Build Metadata (push) Successful in 26s
Frontend CI / Frontend (push) Successful in 3m12s
Backend CI / Backend (push) Failing after 10m8s
Desktop Client Build / Build Desktop Client (push) Successful in 23m53s
Desktop Client Build / Publish Client Artifacts to NAS (push) Successful in 26s
Desktop Client Build / Resolve Build Metadata (push) Successful in 26s
Frontend CI / Frontend (push) Successful in 3m12s
Backend CI / Backend (push) Failing after 10m8s
Desktop Client Build / Build Desktop Client (push) Successful in 23m53s
Desktop Client Build / Publish Client Artifacts to NAS (push) Successful in 26s
- Added classification for `doubao_challenge_required` as a challenge in platform-auth-adapters. - Enhanced account action summaries to include specific messages for Doubao human verification. - Introduced monitoring functions to filter out blocked platforms based on account health. - Updated task leasing to support platform-specific filtering for monitor tasks. - Refined issue aggregation in HomeView to consolidate Doubao challenge failures into a single actionable item. - Improved backend logic to handle platform IDs in task leasing requests and responses. - Added tests for new functionality, ensuring proper handling of Doubao challenges and task leasing logic.
This commit is contained in:
@@ -12,6 +12,7 @@ import { useDesktopRuntime } from '../composables/useDesktopRuntime'
|
||||
import { formatRelativeTime } from '../lib/formatters'
|
||||
import { translateDesktopPlatform } from '../lib/media-catalog'
|
||||
import { healthTone } from '../lib/runtime-ui'
|
||||
import type { RuntimeTask } from '../types'
|
||||
|
||||
function translatePlatform(platform: string) {
|
||||
return translateDesktopPlatform(platform)
|
||||
@@ -55,6 +56,40 @@ type RuntimeIssueItem = {
|
||||
at: number
|
||||
}
|
||||
|
||||
const DOUBAO_CHALLENGE_DETAIL =
|
||||
'豆包账号触发人机验证,请在桌面客户端打开豆包完成验证码后再继续采集。'
|
||||
const DOUBAO_CHALLENGE_PATTERN =
|
||||
/doubao_challenge_required|challenge_required|captcha_gate|risk_control|人机验证|人工验证|验证码|风控|安全验证/i
|
||||
|
||||
function isDoubaoPlatform(platform: string) {
|
||||
return platform.toLowerCase() === 'doubao'
|
||||
}
|
||||
|
||||
function doubaoTaskChallengeKey(task: RuntimeTask) {
|
||||
return `${task.platform.toLowerCase()}::${task.accountId || task.accountName || 'unknown'}`
|
||||
}
|
||||
|
||||
function doubaoAccountChallengeKey(account: {
|
||||
platform: string
|
||||
id: string
|
||||
displayName: string
|
||||
platformUid: string
|
||||
}) {
|
||||
return `${account.platform.toLowerCase()}::${account.id || account.displayName || account.platformUid || 'unknown'}`
|
||||
}
|
||||
|
||||
function isDoubaoChallengeTask(task: RuntimeTask) {
|
||||
if (!isDoubaoPlatform(task.platform)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return DOUBAO_CHALLENGE_PATTERN.test(`${task.summary || ''} ${task.title || ''}`)
|
||||
}
|
||||
|
||||
function isDoubaoChallengeAccount(account: { platform: string; authState: string }) {
|
||||
return isDoubaoPlatform(account.platform) && account.authState === 'challenge_required'
|
||||
}
|
||||
|
||||
function translateEventTitle(title: string) {
|
||||
const map: Record<string, string> = {
|
||||
'Accounts Synced': '本地账号同步',
|
||||
@@ -115,37 +150,110 @@ const blockedAccounts = computed(() =>
|
||||
const blockedTasks = computed(() =>
|
||||
tasks.value.filter((item) => ['failed', 'unknown'].includes(item.status)),
|
||||
)
|
||||
const issueBreakdown = computed(() => ({
|
||||
tasks: blockedTasks.value.length,
|
||||
accounts: blockedAccounts.value.length,
|
||||
total: blockedTasks.value.length + blockedAccounts.value.length,
|
||||
}))
|
||||
const issueItems = computed<RuntimeIssueItem[]>(() => {
|
||||
const taskIssues = blockedTasks.value.map((task) => ({
|
||||
id: `task-${task.id}`,
|
||||
kind: 'task' as const,
|
||||
title: task.title || task.jobId,
|
||||
meta: `${task.accountName} · ${translatePlatform(task.platform)}`,
|
||||
detail: task.summary,
|
||||
badgeLabel: translateTaskStatus(task.status),
|
||||
badgeTone: 'danger' as const,
|
||||
at: task.updatedAt,
|
||||
}))
|
||||
const accountIssues = blockedAccounts.value.map((account) => ({
|
||||
id: `account-${account.id}`,
|
||||
kind: 'account' as const,
|
||||
title: account.displayName,
|
||||
meta: `${translatePlatform(account.platform)} · ${formatUid(account.platformUid)}`,
|
||||
detail:
|
||||
account.authReason === 'risk_control'
|
||||
? '平台触发风控,需要人工验证后恢复任务执行。'
|
||||
: '授权状态不可用,需要重新登录或确认账号状态。',
|
||||
badgeLabel: blockedAccountLabel(account),
|
||||
badgeTone: healthTone(account.health) === 'warn' ? ('warn' as const) : ('danger' as const),
|
||||
at: account.lastVerifiedAt ?? account.lastSyncAt,
|
||||
}))
|
||||
const doubaoChallengeTaskGroups = new Map<string, RuntimeTask[]>()
|
||||
|
||||
return [...taskIssues, ...accountIssues].sort((left, right) => right.at - left.at)
|
||||
for (const task of blockedTasks.value) {
|
||||
if (!isDoubaoChallengeTask(task)) {
|
||||
continue
|
||||
}
|
||||
|
||||
const key = doubaoTaskChallengeKey(task)
|
||||
const group = doubaoChallengeTaskGroups.get(key)
|
||||
if (group) {
|
||||
group.push(task)
|
||||
} else {
|
||||
doubaoChallengeTaskGroups.set(key, [task])
|
||||
}
|
||||
}
|
||||
|
||||
const doubaoChallengeAccountKeys = new Set(
|
||||
blockedAccounts.value
|
||||
.filter(isDoubaoChallengeAccount)
|
||||
.map((account) => doubaoAccountChallengeKey(account)),
|
||||
)
|
||||
const doubaoChallengeKeys = new Set([
|
||||
...doubaoChallengeTaskGroups.keys(),
|
||||
...doubaoChallengeAccountKeys,
|
||||
])
|
||||
|
||||
const taskIssues = blockedTasks.value
|
||||
.filter(
|
||||
(task) =>
|
||||
!isDoubaoPlatform(task.platform) || !doubaoChallengeKeys.has(doubaoTaskChallengeKey(task)),
|
||||
)
|
||||
.map((task) => ({
|
||||
id: `task-${task.id}`,
|
||||
kind: 'task' as const,
|
||||
title: task.title || task.jobId,
|
||||
meta: `${task.accountName} · ${translatePlatform(task.platform)}`,
|
||||
detail: task.summary,
|
||||
badgeLabel: translateTaskStatus(task.status),
|
||||
badgeTone: 'danger' as const,
|
||||
at: task.updatedAt,
|
||||
}))
|
||||
|
||||
const doubaoChallengeTaskIssues = Array.from(doubaoChallengeTaskGroups.entries())
|
||||
.filter(([key]) => !doubaoChallengeAccountKeys.has(key))
|
||||
.map(([key, challengeTasks]) => {
|
||||
const mergedTasks = blockedTasks.value.filter(
|
||||
(task) => isDoubaoPlatform(task.platform) && doubaoTaskChallengeKey(task) === key,
|
||||
)
|
||||
const representative = mergedTasks.reduce(
|
||||
(latest, task) => (task.updatedAt > latest.updatedAt ? task : latest),
|
||||
challengeTasks[0],
|
||||
)
|
||||
const metaParts = [
|
||||
`${representative.accountName || '豆包账号'} · ${translatePlatform(representative.platform)}`,
|
||||
mergedTasks.length > 1 ? `已合并 ${mergedTasks.length} 个失败任务` : '',
|
||||
].filter(Boolean)
|
||||
|
||||
return {
|
||||
id: `task-doubao-challenge-${key}`,
|
||||
kind: 'task' as const,
|
||||
title: '豆包需要人工验证',
|
||||
meta: metaParts.join(' · '),
|
||||
detail: DOUBAO_CHALLENGE_DETAIL,
|
||||
badgeLabel: '执行失败',
|
||||
badgeTone: 'danger' as const,
|
||||
at: Math.max(...mergedTasks.map((task) => task.updatedAt)),
|
||||
}
|
||||
})
|
||||
|
||||
const accountIssues = blockedAccounts.value.map((account) => {
|
||||
const doubaoChallenge = isDoubaoChallengeAccount(account)
|
||||
|
||||
return {
|
||||
id: `account-${account.id}`,
|
||||
kind: 'account' as const,
|
||||
title: doubaoChallenge ? '豆包需要人工验证' : account.displayName,
|
||||
meta: doubaoChallenge
|
||||
? `${account.displayName || '豆包账号'} · ${translatePlatform(account.platform)}`
|
||||
: `${translatePlatform(account.platform)} · ${formatUid(account.platformUid)}`,
|
||||
detail: doubaoChallenge
|
||||
? DOUBAO_CHALLENGE_DETAIL
|
||||
: account.authReason === 'risk_control'
|
||||
? '平台触发风控,需要人工验证后恢复任务执行。'
|
||||
: '授权状态不可用,需要重新登录或确认账号状态。',
|
||||
badgeLabel: doubaoChallenge ? '执行失败' : blockedAccountLabel(account),
|
||||
badgeTone: healthTone(account.health) === 'warn' ? ('warn' as const) : ('danger' as const),
|
||||
at: account.lastVerifiedAt ?? account.lastSyncAt,
|
||||
}
|
||||
})
|
||||
|
||||
return [...taskIssues, ...doubaoChallengeTaskIssues, ...accountIssues].sort(
|
||||
(left, right) => right.at - left.at,
|
||||
)
|
||||
})
|
||||
const issueBreakdown = computed(() => {
|
||||
const tasks = issueItems.value.filter((item) => item.kind === 'task').length
|
||||
const accounts = issueItems.value.filter((item) => item.kind === 'account').length
|
||||
|
||||
return {
|
||||
tasks,
|
||||
accounts,
|
||||
total: tasks + accounts,
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -191,7 +299,7 @@ const issueItems = computed<RuntimeIssueItem[]>(() => {
|
||||
<span class="eyebrow">异常干预</span>
|
||||
</div>
|
||||
<div class="metric-body">
|
||||
<strong>{{ snapshot?.summary.issuesOpen ?? 0 }}</strong>
|
||||
<strong>{{ issueBreakdown.total }}</strong>
|
||||
<p>任务异常 {{ issueBreakdown.tasks }} / 账号阻断 {{ issueBreakdown.accounts }}</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
Reference in New Issue
Block a user