fix: 修复 OAuth token 刷新一次性令牌报错及批量验证卡死问题

- 增强了 OAuth 刷新错误解析,遇到一次性 refresh_token 已失效时返回明确中文指引,合并了多余的 status_code 401 判断逻辑
- 为通用 API 请求增加可选超时与中断能力 (utils.js)
- 为前端账号列表的单账号刷新和批量验证增加并发保护及超时控制,避免请求悬挂导致界面卡死 (accounts.js)
This commit is contained in:
yunxilyf
2026-03-20 23:03:07 +08:00
parent 62c983b9a4
commit 13f9d17dad
3 changed files with 65 additions and 2 deletions

View File

@@ -11,6 +11,8 @@ let selectedAccounts = new Set();
let isLoading = false;
let selectAllPages = false; // 是否选中了全部页
let currentFilters = { status: '', email_service: '', search: '' }; // 当前筛选条件
const refreshingAccountIds = new Set();
let isBatchValidating = false;
// DOM 元素
const elements = {
@@ -488,6 +490,12 @@ function updateBatchButtons() {
// 刷新单个账号Token
async function refreshToken(id) {
if (refreshingAccountIds.has(id)) {
toast.info('该账号正在刷新,请稍候...');
return;
}
refreshingAccountIds.add(id);
try {
toast.info('正在刷新Token...');
const result = await api.post(`/accounts/${id}/refresh`);
@@ -500,6 +508,8 @@ async function refreshToken(id) {
}
} catch (error) {
toast.error('刷新失败: ' + error.message);
} finally {
refreshingAccountIds.delete(id);
}
}
@@ -528,17 +538,24 @@ async function handleBatchRefresh() {
// 批量验证Token
async function handleBatchValidate() {
if (getEffectiveCount() === 0) return;
if (isBatchValidating) {
toast.info('批量验证进行中,请稍候...');
return;
}
isBatchValidating = true;
elements.batchValidateBtn.disabled = true;
elements.batchValidateBtn.textContent = '验证中...';
try {
const result = await api.post('/accounts/batch-validate', buildBatchPayload());
const result = await api.post('/accounts/batch-validate', buildBatchPayload(), { timeoutMs: 120000 });
toast.info(`有效: ${result.valid_count},无效: ${result.invalid_count}`);
loadAccounts();
} catch (error) {
toast.error('批量验证失败: ' + error.message);
} finally {
isBatchValidating = false;
updateBatchButtons();
}
}

View File

@@ -187,12 +187,21 @@ class ApiClient {
};
const finalOptions = { ...defaultOptions, ...options };
const timeoutMs = Number(finalOptions.timeoutMs || 0);
delete finalOptions.timeoutMs;
if (finalOptions.body && typeof finalOptions.body === 'object') {
finalOptions.body = JSON.stringify(finalOptions.body);
}
let timeoutId = null;
try {
if (timeoutMs > 0) {
const controller = new AbortController();
finalOptions.signal = controller.signal;
timeoutId = setTimeout(() => controller.abort(), timeoutMs);
}
const response = await fetch(url, finalOptions);
const data = await response.json().catch(() => ({}));
@@ -205,11 +214,19 @@ class ApiClient {
return data;
} catch (error) {
if (error.name === 'AbortError') {
const timeoutError = new Error('请求超时,请稍后重试');
throw timeoutError;
}
// 网络错误处理
if (!error.response) {
toast.error('网络连接失败,请检查网络');
}
throw error;
} finally {
if (timeoutId) {
clearTimeout(timeoutId);
}
}
}