fix(密钥检测): 直连失败重试一次,超时/网络提示中文化

上游 ai.t8star.org 实测约 1/8 单请求完全不应答(主机 A 上 JDK 客户端
HTTP/1.1 与 HTTP/2 均复现),检测只有一次机会时用户会看到
「网络不可达:HttpTimeoutException: request timed out」。

- 传输层失败(超时/网络不可达)对直连最后一跳重试一次(800ms 间隔);
  代理模块不重试——jikip 按提取次数计费,重试会多扣一次
- 失败文案中文化(新增 CODE_TIMEOUT),英文异常串只进服务端日志
- 探测提问改「你好」(最简一次调用)
- 检测面板标明检测对象(配置密钥 sk-**** / 输入值(未保存)),
  检测接口超时单独放宽(客户端 60s / 后台 180s),避免重试期间前端先超时
This commit is contained in:
2026-09-14 00:28:35 +08:00
parent fd614004b0
commit 864c22ffc7
5 changed files with 175 additions and 36 deletions
@@ -71,11 +71,13 @@ export function deleteMyApiSecret(moduleKey: string) {
}
/** value 非空时检测输入值(不落库);为空时检测服务端已存密钥并把结果落库。 */
/** 检测连通性:服务端最多「首查 + 传输层失败重试一次」,最坏约 31s,故单独放宽超时。 */
export function checkMyApiSecret(moduleKey: string, value?: string) {
return unwrapJavaResponse(
post<JavaApiResponse<UserApiSecretCheckResult>, { value?: string }>(
buildJavaUrl(API_ENDPOINTS.userSecret.check.replace('{moduleKey}', encodeURIComponent(moduleKey))),
{ value },
{ timeout: 60000 },
),
)
}
@@ -34,7 +34,7 @@
:disabled="busy || moduleStates[module.moduleKey].checking"
@click="runCheck(module.moduleKey as ApiSecretModuleKey)"
>
{{ moduleStates[module.moduleKey].checking ? '检测中...' : (moduleStates[module.moduleKey].input.trim() ? '检测输入值' : '检测已存密钥') }}
{{ moduleStates[module.moduleKey].checking ? '检测中...' : (moduleStates[module.moduleKey].input.trim() ? '检测输入值(未保存)' : '检测配置密钥') }}
</button>
<span class="check-result" :class="resultClassOf(module.moduleKey)">
{{ statusTextOf(module.moduleKey) }}
@@ -93,7 +93,7 @@
:disabled="!proxyReady || busy || proxyChecking"
@click="runProxyCheck"
>
{{ proxyChecking ? '检测中...' : (proxyUrl.trim() && proxyDirty ? '检测输入地址' : '检测已存代理') }}
{{ proxyChecking ? '检测中...' : (proxyUrl.trim() && proxyDirty ? '检测输入地址(未保存)' : '检测配置代理') }}
</button>
<span class="check-result" :class="proxyResultClass">
{{ proxyStatusText }}
@@ -147,13 +147,15 @@ type ModuleState = {
checking: boolean
result: UserApiSecretCheckResult | null
error: string
/** 最近一次检测的来源:saved=密钥配置里已保存的密钥,input=输入框里的未保存值。 */
lastCheckSource: 'saved' | 'input' | ''
}
const modules = ref(listApiSecretModules())
const moduleStates = reactive<Record<string, ModuleState>>({})
// 首次渲染即需可读:按模块清单预初始化状态(后续 refreshSnapshots 兜底补齐)
for (const module of modules.value) {
moduleStates[module.moduleKey] = { input: '', checking: false, result: null, error: '' }
moduleStates[module.moduleKey] = { input: '', checking: false, result: null, error: '', lastCheckSource: '' }
}
const snapshots = ref<Record<string, ApiSecretSnapshot>>({})
const busy = ref(false)
@@ -240,7 +242,7 @@ const balanceText = computed(() => {
function ensureModuleState(moduleKey: string) {
if (!moduleStates[moduleKey]) {
moduleStates[moduleKey] = { input: '', checking: false, result: null, error: '' }
moduleStates[moduleKey] = { input: '', checking: false, result: null, error: '', lastCheckSource: '' }
}
return moduleStates[moduleKey]
}
@@ -288,13 +290,18 @@ function formatTime(millis: number | null) {
function statusTextOf(moduleKey: string) {
const state = ensureModuleState(moduleKey)
if (state.checking) return '正在检测...'
if (state.checking) return state.input.trim() ? '正在检测输入值...' : '正在检测配置密钥...'
if (state.result) {
const latency = state.result.checkLatencyMs != null ? `${state.result.checkLatencyMs}ms` : ''
const suffix = state.result.viaProxy === true ? ' · 经代理' : ''
if (state.result.checkStatus === 'passed') return `检测通过${latency}${suffix}`
if (state.result.checkCode === 'insufficient_balance') return `欠费:${state.result.checkMessage || '代理服务商余额不足'}${suffix}`
return `${state.result.checkMessage || '检测失败'}${suffix}`
const snapshot = snapshotOf(moduleKey)
// 明确展示本次检测的对象:输入值(未保存)或该用户密钥配置里保存的密钥(脱敏)
const prefix = state.lastCheckSource === 'input'
? '输入值(未保存):'
: `配置密钥${snapshot.masked ? ` ${snapshot.masked}` : ''}`
if (state.result.checkStatus === 'passed') return `${prefix}检测通过${latency}${suffix}`
if (state.result.checkCode === 'insufficient_balance') return `${prefix}欠费:${state.result.checkMessage || '代理服务商余额不足'}${suffix}`
return `${prefix}${state.result.checkMessage || '检测失败'}${suffix}`
}
if (state.error) return state.error
const snapshot = snapshotOf(moduleKey)
@@ -302,7 +309,8 @@ function statusTextOf(moduleKey: string) {
const status = CHECK_STATUS_TEXT[snapshot.checkStatus] || '已保存'
const time = formatTime(snapshot.checkedAt)
const message = snapshot.checkStatus === 'unknown' ? '' : `${snapshot.checkMessage || ''}`
return `${status}${message}${time ? ` · ${time}` : ''}`
const masked = snapshot.masked ? `配置密钥 ${snapshot.masked} · ` : ''
return `${masked}${status}${message}${time ? ` · ${time}` : ''}`
}
function resultClassOf(moduleKey: string) {
@@ -322,7 +330,9 @@ async function runCheck(moduleKey: ApiSecretModuleKey) {
state.error = ''
state.result = null
const inputValue = state.input.trim()
state.lastCheckSource = inputValue ? 'input' : 'saved'
try {
// 不传 override 时服务端检测的是当前登录用户在密钥配置里保存的密钥(按 uid 绑定)
const result = await checkApiSecret(moduleKey, inputValue || undefined)
state.result = result
if (result.checkStatus === 'failed') {
@@ -330,7 +340,7 @@ async function runCheck(moduleKey: ApiSecretModuleKey) {
} else if (result.checkStatus === 'error') {
ElMessage.warning(result.checkMessage || '暂时无法判定密钥有效性')
} else {
ElMessage.success(inputValue ? '输入值检测通过(未保存)' : '密钥检测通过')
ElMessage.success(inputValue ? '输入值检测通过(未保存)' : '配置密钥检测通过')
}
} catch (error) {
state.error = error instanceof Error ? error.message : '检测失败'