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:
+61
@@ -137,6 +137,67 @@ class UserApiSecretCheckServiceTest {
|
||||
assertThat(outcome.code()).isEqualTo(UserApiSecretCheckService.CODE_SERVER_ERROR);
|
||||
}
|
||||
|
||||
// ===== 传输层失败分类(2026-09-13:面板不再出现英文异常串,超时单列 code)=====
|
||||
|
||||
@Test
|
||||
void transportTimeoutIsClassifiedAsTimeoutWithChineseMessage() {
|
||||
// 生产形态:ResourceAccessException 包装 HttpTimeoutException
|
||||
Exception failure = new org.springframework.web.client.ResourceAccessException(
|
||||
"I/O error on POST request", new java.net.http.HttpTimeoutException("request timed out"));
|
||||
UserApiSecretCheckService.CheckOutcome outcome = service.classifyTransportFailure(failure, 15001, false);
|
||||
|
||||
assertThat(outcome.status()).isEqualTo(UserApiSecretCheckService.STATUS_ERROR);
|
||||
assertThat(outcome.code()).isEqualTo(UserApiSecretCheckService.CODE_TIMEOUT);
|
||||
assertThat(outcome.message()).isEqualTo(UserApiSecretCheckService.TIMEOUT_MESSAGE);
|
||||
assertThat(outcome.message()).doesNotContain("HttpTimeoutException");
|
||||
assertThat(outcome.latencyMs()).isEqualTo(15001);
|
||||
}
|
||||
|
||||
@Test
|
||||
void concurrentTimeoutExceptionAlsoClassifiedAsTimeout() {
|
||||
UserApiSecretCheckService.CheckOutcome outcome = service.classifyTransportFailure(
|
||||
new java.util.concurrent.TimeoutException("timed out"), 100, true);
|
||||
|
||||
assertThat(outcome.code()).isEqualTo(UserApiSecretCheckService.CODE_TIMEOUT);
|
||||
assertThat(outcome.viaProxy()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void transportConnectFailureIsClassifiedAsNetworkError() {
|
||||
UserApiSecretCheckService.CheckOutcome outcome = service.classifyTransportFailure(
|
||||
new java.net.ConnectException("Connection refused"), 300, true);
|
||||
|
||||
assertThat(outcome.status()).isEqualTo(UserApiSecretCheckService.STATUS_ERROR);
|
||||
assertThat(outcome.code()).isEqualTo(UserApiSecretCheckService.CODE_NETWORK_ERROR);
|
||||
assertThat(outcome.message()).isEqualTo(UserApiSecretCheckService.NETWORK_ERROR_MESSAGE);
|
||||
assertThat(outcome.viaProxy()).isTrue();
|
||||
assertThat(outcome.message()).doesNotContain("ConnectException");
|
||||
}
|
||||
|
||||
@Test
|
||||
void transportNestedTimeoutInsideGenericIOExceptionIsTimeout() {
|
||||
Exception failure = new java.io.IOException("boom", new java.util.concurrent.TimeoutException("timed out"));
|
||||
UserApiSecretCheckService.CheckOutcome outcome = service.classifyTransportFailure(failure, 200, false);
|
||||
|
||||
assertThat(outcome.code()).isEqualTo(UserApiSecretCheckService.CODE_TIMEOUT);
|
||||
}
|
||||
|
||||
/** 只有传输层失败值得重试;HTTP 层的 401/429/5xx 重试无意义。 */
|
||||
@Test
|
||||
void onlyTransportFailuresAreRetryable() {
|
||||
assertThat(UserApiSecretCheckService.isTransportFailure(outcome("error", UserApiSecretCheckService.CODE_TIMEOUT))).isTrue();
|
||||
assertThat(UserApiSecretCheckService.isTransportFailure(outcome("error", UserApiSecretCheckService.CODE_NETWORK_ERROR))).isTrue();
|
||||
assertThat(UserApiSecretCheckService.isTransportFailure(outcome("error", UserApiSecretCheckService.CODE_RATE_LIMITED))).isFalse();
|
||||
assertThat(UserApiSecretCheckService.isTransportFailure(outcome("error", UserApiSecretCheckService.CODE_SERVER_ERROR))).isFalse();
|
||||
assertThat(UserApiSecretCheckService.isTransportFailure(outcome("failed", UserApiSecretCheckService.CODE_INVALID_KEY))).isFalse();
|
||||
assertThat(UserApiSecretCheckService.isTransportFailure(outcome("passed", UserApiSecretCheckService.CODE_OK))).isFalse();
|
||||
assertThat(UserApiSecretCheckService.isTransportFailure(null)).isFalse();
|
||||
}
|
||||
|
||||
private static UserApiSecretCheckService.CheckOutcome outcome(String status, String code) {
|
||||
return new UserApiSecretCheckService.CheckOutcome(status, code, "msg", 100, false);
|
||||
}
|
||||
|
||||
// ===== 代理配置探测(提取链接语义;2026-09-13 修复「直连自家站点假通过」)=====
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user