mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
feat(pool,trace): 账号封禁原因细分与请求追踪 attempted_only 过滤
- 将账号封禁原因从笼统的"账号异常"细分为封禁/停用/需要验证三类, 前后端关键词组同步拆分,号池管理页面展示对应分类标签 - trace API 新增 attempted_only 参数,支持仅返回实际尝试过的候选, 前端时间线组件默认启用过滤,排除 available/unused/skipped 记录
This commit is contained in:
@@ -55,8 +55,14 @@ export const requestTraceApi = {
|
||||
/**
|
||||
* 获取特定请求的完整追踪信息
|
||||
*/
|
||||
async getRequestTrace(requestId: string): Promise<RequestTrace> {
|
||||
const response = await apiClient.get<RequestTrace>(`/api/admin/monitoring/trace/${requestId}`)
|
||||
async getRequestTrace(
|
||||
requestId: string,
|
||||
options: { attemptedOnly?: boolean } = {},
|
||||
): Promise<RequestTrace> {
|
||||
const attemptedOnly = options.attemptedOnly ?? true
|
||||
const response = await apiClient.get<RequestTrace>(`/api/admin/monitoring/trace/${requestId}`, {
|
||||
params: { attempted_only: attemptedOnly },
|
||||
})
|
||||
return response.data
|
||||
},
|
||||
|
||||
|
||||
@@ -632,6 +632,19 @@ const makeAttemptKey = (candidateIndex: number, retryIndex: number): string => {
|
||||
return `${candidateIndex}:${retryIndex}`
|
||||
}
|
||||
|
||||
const POOL_UNATTEMPTED_STATUS = new Set<CandidateRecord['status']>([
|
||||
'available',
|
||||
'unused',
|
||||
'skipped',
|
||||
])
|
||||
|
||||
const isPoolAttemptedCandidate = (candidate: CandidateRecord): boolean => {
|
||||
if (POOL_UNATTEMPTED_STATUS.has(candidate.status)) return false
|
||||
// pending 只有开始执行后才算真正进入号池内部尝试
|
||||
if (candidate.status === 'pending' && !candidate.started_at) return false
|
||||
return true
|
||||
}
|
||||
|
||||
const normalizeTimelineStatus = (value: unknown): CandidateRecord['status'] => {
|
||||
if (typeof value !== 'string') return 'failed'
|
||||
const normalized = value.trim().toLowerCase()
|
||||
@@ -677,8 +690,11 @@ const schedulingAudit = computed<Record<string, unknown> | null>(() => {
|
||||
})
|
||||
|
||||
const poolAttemptCandidates = computed<CandidateRecord[]>(() => {
|
||||
// 新链路:优先使用后端写入的 extra_data.pool_group_id。
|
||||
const fromTrace = rawTimeline.value.filter((candidate) => extractPoolGroupId(candidate) !== null)
|
||||
// 新链路:优先使用后端写入的 extra_data.pool_group_id,
|
||||
// 但仅展示实际进入号池执行的 key(排除 available/unused/skipped)。
|
||||
const fromTrace = rawTimeline.value.filter(
|
||||
(candidate) => extractPoolGroupId(candidate) !== null && isPoolAttemptedCandidate(candidate),
|
||||
)
|
||||
if (fromTrace.length > 0) {
|
||||
return fromTrace
|
||||
}
|
||||
|
||||
@@ -1,7 +1,19 @@
|
||||
// 账号级别封禁/异常的关键词匹配(用于判断 oauth_invalid_reason 是否属于账号封禁)
|
||||
const ACCOUNT_BLOCK_REASON_KEYWORDS = [
|
||||
// -- 按原因细分的关键词组 --
|
||||
|
||||
// 封禁类 (suspended / banned)
|
||||
const KEYWORDS_SUSPENDED = [
|
||||
'suspended',
|
||||
'account_block',
|
||||
'account blocked',
|
||||
'封禁',
|
||||
'封号',
|
||||
'被封',
|
||||
'账户已封禁',
|
||||
'账号异常',
|
||||
]
|
||||
|
||||
// 停用类 (disabled / deactivated)
|
||||
const KEYWORDS_DISABLED = [
|
||||
'account has been disabled',
|
||||
'account disabled',
|
||||
'account has been deactivated',
|
||||
@@ -9,19 +21,22 @@ const ACCOUNT_BLOCK_REASON_KEYWORDS = [
|
||||
'account deactivated',
|
||||
'organization has been disabled',
|
||||
'organization_disabled',
|
||||
'deactivated',
|
||||
'访问被禁止',
|
||||
'账户访问被禁止',
|
||||
]
|
||||
|
||||
// 需要验证类
|
||||
const KEYWORDS_VERIFICATION = [
|
||||
'validation_required',
|
||||
'verify your account',
|
||||
'suspended',
|
||||
'deactivated',
|
||||
// Kiro quota refresher 写入的确切文本
|
||||
'账户已封禁',
|
||||
// Antigravity quota refresher 写入的确切文本
|
||||
'账户访问被禁止',
|
||||
'封禁',
|
||||
'封号',
|
||||
'被封',
|
||||
'访问被禁止',
|
||||
'账号异常',
|
||||
]
|
||||
|
||||
// 合并的完整列表
|
||||
const ACCOUNT_BLOCK_REASON_KEYWORDS = [
|
||||
...KEYWORDS_SUSPENDED,
|
||||
...KEYWORDS_DISABLED,
|
||||
...KEYWORDS_VERIFICATION,
|
||||
]
|
||||
|
||||
export function isAccountLevelBlockReason(reason: string | null | undefined): boolean {
|
||||
@@ -33,6 +48,14 @@ export function isAccountLevelBlockReason(reason: string | null | undefined): bo
|
||||
return ACCOUNT_BLOCK_REASON_KEYWORDS.some(keyword => lowered.includes(keyword))
|
||||
}
|
||||
|
||||
export function classifyAccountBlockLabel(reason: string): string {
|
||||
const lowered = reason.toLowerCase()
|
||||
if (KEYWORDS_VERIFICATION.some(kw => lowered.includes(kw))) return '需要验证'
|
||||
if (KEYWORDS_DISABLED.some(kw => lowered.includes(kw))) return '账号停用'
|
||||
if (KEYWORDS_SUSPENDED.some(kw => lowered.includes(kw))) return '账号封禁'
|
||||
return '账号异常'
|
||||
}
|
||||
|
||||
export function cleanAccountBlockReason(reason: string): string {
|
||||
return reason.replace(/^\[ACCOUNT_BLOCK\]\s*/i, '').trim()
|
||||
}
|
||||
|
||||
@@ -1186,7 +1186,7 @@ import KeyFormDialog from '@/features/providers/components/KeyFormDialog.vue'
|
||||
import OAuthKeyEditDialog from '@/features/providers/components/OAuthKeyEditDialog.vue'
|
||||
import OAuthAccountDialog from '@/features/providers/components/OAuthAccountDialog.vue'
|
||||
import ProxyNodeSelect from '@/features/providers/components/ProxyNodeSelect.vue'
|
||||
import { isAccountLevelBlockReason, cleanAccountBlockReason } from '@/utils/accountBlock'
|
||||
import { isAccountLevelBlockReason, classifyAccountBlockLabel, cleanAccountBlockReason } from '@/utils/accountBlock'
|
||||
|
||||
const { success, error: showError, warning: showWarning } = useToast()
|
||||
const { confirm } = useConfirm()
|
||||
@@ -2339,7 +2339,11 @@ function getAccountAlertLabel(key: PoolKeyDetail): string | null {
|
||||
// 后端 _build_account_quota 返回的确切文本: "账号已封禁" / "访问受限"
|
||||
if (quotaText === '账号已封禁' || quotaText === '封禁') result = '账号封禁'
|
||||
else if (quotaText === '访问受限') result = '访问受限'
|
||||
else if (isAccountLevelBlockReason(key.oauth_invalid_reason)) result = '账号异常'
|
||||
else if (isAccountLevelBlockReason(key.oauth_invalid_reason)) {
|
||||
const reason = String(key.oauth_invalid_reason || '').trim()
|
||||
const cleaned = cleanAccountBlockReason(reason)
|
||||
result = classifyAccountBlockLabel(cleaned || reason)
|
||||
}
|
||||
|
||||
_accountAlertCache.set(key, result)
|
||||
return result
|
||||
|
||||
Reference in New Issue
Block a user