fix(auth): 为登录链路增加有界超时
ci / verify (pull_request) Failing after 6m43s

为 PostgreSQL 连接、就绪检查和本地登录设置分层超时,数据库不可用时返回稳定 503 错误码并记录无凭据的连接池统计。

前端登录在 10 秒后取消请求并兼容调用方 AbortSignal,Nginx 登录精确路由限制上游为 15 秒,同时更新 OpenAPI 和回归测试。
This commit is contained in:
2026-07-21 11:14:35 +08:00
parent 15ae2bc4ca
commit e9f287971a
9 changed files with 322 additions and 19 deletions
+30 -15
View File
@@ -121,6 +121,7 @@ export async function loginLocalAccount(input: { account: string; password: stri
auth: false,
body: input,
method: 'POST',
timeoutMs: 10_000,
});
}
@@ -1206,7 +1207,7 @@ export async function deleteFileStorageChannel(token: string, channelId: string)
async function request<T>(
path: string,
options: { token?: string; auth?: boolean; method?: string; body?: unknown; headers?: Record<string, string>; signal?: AbortSignal } = {},
options: { token?: string; auth?: boolean; method?: string; body?: unknown; headers?: Record<string, string>; signal?: AbortSignal; timeoutMs?: number } = {},
): Promise<T> {
const headers: Record<string, string> = { ...(options.headers ?? {}) };
if (options.auth !== false && options.token && options.token !== OIDC_BROWSER_SESSION_CREDENTIAL) {
@@ -1215,21 +1216,35 @@ async function request<T>(
if (options.body !== undefined) {
headers['Content-Type'] = 'application/json';
}
const response = await fetch(`${API_BASE}${path}`, {
method: options.method ?? 'GET',
headers,
body: options.body === undefined ? undefined : JSON.stringify(options.body),
credentials: 'include',
signal: options.signal,
});
if (!response.ok) {
const body = await response.text();
throw new GatewayApiError(parseErrorDetails(body, response.status, `Request failed: ${response.status}`));
const controller = options.timeoutMs ? new AbortController() : undefined;
const timeout = controller ? globalThis.setTimeout(() => controller.abort(), options.timeoutMs) : undefined;
const signal = controller && options.signal
? AbortSignal.any([controller.signal, options.signal])
: controller?.signal ?? options.signal;
try {
const response = await fetch(`${API_BASE}${path}`, {
method: options.method ?? 'GET',
headers,
body: options.body === undefined ? undefined : JSON.stringify(options.body),
credentials: 'include',
signal,
});
if (!response.ok) {
const body = await response.text();
throw new GatewayApiError(parseErrorDetails(body, response.status, `Request failed: ${response.status}`));
}
if (response.status === 204) {
return undefined as T;
}
return response.json() as Promise<T>;
} catch (error) {
if (controller?.signal.aborted) {
throw new GatewayApiError('登录请求超时,请稍后重试');
}
throw error;
} finally {
if (timeout !== undefined) globalThis.clearTimeout(timeout);
}
if (response.status === 204) {
return undefined as T;
}
return response.json() as Promise<T>;
}
function authorizationHeader(token: string): Record<string, string> {