Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion src/core/openai/token_refresh.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,35 @@ def _create_session(self) -> cffi_requests.Session:
session = cffi_requests.Session(impersonate="chrome120", proxy=self.proxy_url)
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:
"""
使用 Session Token 刷新
Expand Down Expand Up @@ -167,7 +196,7 @@ def refresh_by_oauth_token(
)

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]}")
return result

Expand Down
19 changes: 18 additions & 1 deletion static/js/accounts.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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`);
Expand All @@ -500,6 +508,8 @@ async function refreshToken(id) {
}
} catch (error) {
toast.error('刷新失败: ' + error.message);
} finally {
refreshingAccountIds.delete(id);
}
}

Expand Down Expand Up @@ -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();
}
}
Expand Down
17 changes: 17 additions & 0 deletions static/js/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => ({}));

Expand All @@ -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);
}
}
}

Expand Down
Loading