mirror of
https://github.com/cnlimiter/codex-register.git
synced 2026-07-09 22:41:21 +08:00
fix: 修复 OAuth token 刷新一次性令牌报错及批量验证卡死问题
- 增强了 OAuth 刷新错误解析,遇到一次性 refresh_token 已失效时返回明确中文指引,合并了多余的 status_code 401 判断逻辑 - 为通用 API 请求增加可选超时与中断能力 (utils.js) - 为前端账号列表的单账号刷新和批量验证增加并发保护及超时控制,避免请求悬挂导致界面卡死 (accounts.js)
This commit is contained in:
@@ -57,6 +57,35 @@ class TokenRefreshManager:
|
|||||||
session = cffi_requests.Session(impersonate="chrome120", proxy=self.proxy_url)
|
session = cffi_requests.Session(impersonate="chrome120", proxy=self.proxy_url)
|
||||||
return session
|
return session
|
||||||
|
|
||||||
|
def _parse_oauth_error(self, response: cffi_requests.Response) -> str:
|
||||||
|
"""解析 OAuth 错误信息"""
|
||||||
|
body_text = (response.text or "").strip()
|
||||||
|
error_message = ""
|
||||||
|
|
||||||
|
try:
|
||||||
|
body = response.json()
|
||||||
|
error_obj = body.get("error") if isinstance(body, dict) else None
|
||||||
|
if isinstance(error_obj, dict):
|
||||||
|
error_message = str(error_obj.get("message") or "").strip()
|
||||||
|
elif isinstance(body, dict):
|
||||||
|
error_message = str(body.get("error_description") or body.get("message") or "").strip()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
error_lower = error_message.lower()
|
||||||
|
if "refresh token has already been used" in error_lower:
|
||||||
|
return "OAuth refresh_token 已失效(一次性令牌已被使用),请重新登录该账号后再上传 CPA"
|
||||||
|
if response.status_code == 401:
|
||||||
|
if error_message:
|
||||||
|
return f"OAuth token 刷新失败: {error_message}"
|
||||||
|
else:
|
||||||
|
return "OAuth token 刷新失败: refresh_token 无效或已过期,请重新登录账号"
|
||||||
|
if error_message:
|
||||||
|
return f"OAuth token 刷新失败: {error_message}"
|
||||||
|
if body_text:
|
||||||
|
return f"OAuth token 刷新失败: HTTP {response.status_code}, 响应: {body_text[:200]}"
|
||||||
|
return f"OAuth token 刷新失败: HTTP {response.status_code}"
|
||||||
|
|
||||||
def refresh_by_session_token(self, session_token: str) -> TokenRefreshResult:
|
def refresh_by_session_token(self, session_token: str) -> TokenRefreshResult:
|
||||||
"""
|
"""
|
||||||
使用 Session Token 刷新
|
使用 Session Token 刷新
|
||||||
@@ -167,7 +196,7 @@ class TokenRefreshManager:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
result.error_message = f"OAuth token 刷新失败: HTTP {response.status_code}"
|
result.error_message = self._parse_oauth_error(response)
|
||||||
logger.warning(f"{result.error_message}, 响应: {response.text[:200]}")
|
logger.warning(f"{result.error_message}, 响应: {response.text[:200]}")
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ let selectedAccounts = new Set();
|
|||||||
let isLoading = false;
|
let isLoading = false;
|
||||||
let selectAllPages = false; // 是否选中了全部页
|
let selectAllPages = false; // 是否选中了全部页
|
||||||
let currentFilters = { status: '', email_service: '', search: '' }; // 当前筛选条件
|
let currentFilters = { status: '', email_service: '', search: '' }; // 当前筛选条件
|
||||||
|
const refreshingAccountIds = new Set();
|
||||||
|
let isBatchValidating = false;
|
||||||
|
|
||||||
// DOM 元素
|
// DOM 元素
|
||||||
const elements = {
|
const elements = {
|
||||||
@@ -488,6 +490,12 @@ function updateBatchButtons() {
|
|||||||
|
|
||||||
// 刷新单个账号Token
|
// 刷新单个账号Token
|
||||||
async function refreshToken(id) {
|
async function refreshToken(id) {
|
||||||
|
if (refreshingAccountIds.has(id)) {
|
||||||
|
toast.info('该账号正在刷新,请稍候...');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
refreshingAccountIds.add(id);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
toast.info('正在刷新Token...');
|
toast.info('正在刷新Token...');
|
||||||
const result = await api.post(`/accounts/${id}/refresh`);
|
const result = await api.post(`/accounts/${id}/refresh`);
|
||||||
@@ -500,6 +508,8 @@ async function refreshToken(id) {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error('刷新失败: ' + error.message);
|
toast.error('刷新失败: ' + error.message);
|
||||||
|
} finally {
|
||||||
|
refreshingAccountIds.delete(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -528,17 +538,24 @@ async function handleBatchRefresh() {
|
|||||||
// 批量验证Token
|
// 批量验证Token
|
||||||
async function handleBatchValidate() {
|
async function handleBatchValidate() {
|
||||||
if (getEffectiveCount() === 0) return;
|
if (getEffectiveCount() === 0) return;
|
||||||
|
if (isBatchValidating) {
|
||||||
|
toast.info('批量验证进行中,请稍候...');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isBatchValidating = true;
|
||||||
|
|
||||||
elements.batchValidateBtn.disabled = true;
|
elements.batchValidateBtn.disabled = true;
|
||||||
elements.batchValidateBtn.textContent = '验证中...';
|
elements.batchValidateBtn.textContent = '验证中...';
|
||||||
|
|
||||||
try {
|
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}`);
|
toast.info(`有效: ${result.valid_count},无效: ${result.invalid_count}`);
|
||||||
loadAccounts();
|
loadAccounts();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error('批量验证失败: ' + error.message);
|
toast.error('批量验证失败: ' + error.message);
|
||||||
} finally {
|
} finally {
|
||||||
|
isBatchValidating = false;
|
||||||
updateBatchButtons();
|
updateBatchButtons();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -187,12 +187,21 @@ class ApiClient {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const finalOptions = { ...defaultOptions, ...options };
|
const finalOptions = { ...defaultOptions, ...options };
|
||||||
|
const timeoutMs = Number(finalOptions.timeoutMs || 0);
|
||||||
|
delete finalOptions.timeoutMs;
|
||||||
|
|
||||||
if (finalOptions.body && typeof finalOptions.body === 'object') {
|
if (finalOptions.body && typeof finalOptions.body === 'object') {
|
||||||
finalOptions.body = JSON.stringify(finalOptions.body);
|
finalOptions.body = JSON.stringify(finalOptions.body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let timeoutId = null;
|
||||||
try {
|
try {
|
||||||
|
if (timeoutMs > 0) {
|
||||||
|
const controller = new AbortController();
|
||||||
|
finalOptions.signal = controller.signal;
|
||||||
|
timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
const response = await fetch(url, finalOptions);
|
const response = await fetch(url, finalOptions);
|
||||||
const data = await response.json().catch(() => ({}));
|
const data = await response.json().catch(() => ({}));
|
||||||
|
|
||||||
@@ -205,11 +214,19 @@ class ApiClient {
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error.name === 'AbortError') {
|
||||||
|
const timeoutError = new Error('请求超时,请稍后重试');
|
||||||
|
throw timeoutError;
|
||||||
|
}
|
||||||
// 网络错误处理
|
// 网络错误处理
|
||||||
if (!error.response) {
|
if (!error.response) {
|
||||||
toast.error('网络连接失败,请检查网络');
|
toast.error('网络连接失败,请检查网络');
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
|
} finally {
|
||||||
|
if (timeoutId) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user