- 修复Qwen API的配额错误识别和速率限制,避免因配额耗尽导致服务中断 - 修正Gemini API服务初始化顺序,确保OAuth2客户端在HTTP代理配置后创建 - 优化提供商数据脱敏逻辑,防止保存时覆盖真实的敏感信息 - 增强前端错误处理,支持国际化错误消息的翻译和显示 - 移除Antigravity中冗余的思考签名修复代码,简化历史记录处理 - 修复服务管理器初始化逻辑,确保提供商池状态正确更新 - 统一日志下载文件名格式,改进文件下载错误处理 - 更新翻译文件,添加缺失的通用错误消息国际化支持
369 lines
No EOL
9.7 KiB
JavaScript
369 lines
No EOL
9.7 KiB
JavaScript
// 认证模块 - 处理token管理和API调用封装
|
||
import { t } from './i18n.js';
|
||
/**
|
||
* 认证管理类
|
||
*/
|
||
class AuthManager {
|
||
constructor() {
|
||
this.tokenKey = 'authToken';
|
||
this.expiryKey = 'authTokenExpiry';
|
||
this.baseURL = window.location.origin;
|
||
}
|
||
|
||
/**
|
||
* 获取存储的token
|
||
*/
|
||
getToken() {
|
||
return localStorage.getItem(this.tokenKey);
|
||
}
|
||
|
||
/**
|
||
* 获取token过期时间
|
||
*/
|
||
getTokenExpiry() {
|
||
const expiry = localStorage.getItem(this.expiryKey);
|
||
return expiry ? parseInt(expiry) : null;
|
||
}
|
||
|
||
/**
|
||
* 检查token是否有效
|
||
*/
|
||
isTokenValid() {
|
||
const token = this.getToken();
|
||
const expiry = this.getTokenExpiry();
|
||
|
||
if (!token) return false;
|
||
|
||
// 如果设置了过期时间,检查是否过期
|
||
if (expiry && Date.now() > expiry) {
|
||
this.clearToken();
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 保存token到本地存储
|
||
*/
|
||
saveToken(token, rememberMe = false) {
|
||
localStorage.setItem(this.tokenKey, token);
|
||
|
||
if (rememberMe) {
|
||
const expiryTime = Date.now() + (7 * 24 * 60 * 60 * 1000); // 7天
|
||
localStorage.setItem(this.expiryKey, expiryTime.toString());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 清除token
|
||
*/
|
||
clearToken() {
|
||
localStorage.removeItem(this.tokenKey);
|
||
localStorage.removeItem(this.expiryKey);
|
||
}
|
||
|
||
/**
|
||
* 登出
|
||
*/
|
||
async logout() {
|
||
this.clearToken();
|
||
window.location.href = '/login.html';
|
||
}
|
||
}
|
||
|
||
/**
|
||
* API调用封装类
|
||
*/
|
||
class ApiClient {
|
||
constructor() {
|
||
this.authManager = new AuthManager();
|
||
this.baseURL = window.location.origin;
|
||
}
|
||
|
||
/**
|
||
* 获取带认证的请求头
|
||
*/
|
||
getAuthHeaders() {
|
||
const token = this.authManager.getToken();
|
||
return token ? {
|
||
'Authorization': `Bearer ${token}`,
|
||
'Content-Type': 'application/json'
|
||
} : {
|
||
'Content-Type': 'application/json'
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 处理401错误重定向到登录页
|
||
*/
|
||
handleUnauthorized() {
|
||
this.authManager.clearToken();
|
||
window.location.href = '/login.html';
|
||
}
|
||
|
||
/**
|
||
* 通用API请求方法
|
||
*/
|
||
async request(endpoint, options = {}) {
|
||
const url = `${this.baseURL}/api${endpoint}`;
|
||
const headers = {
|
||
...this.getAuthHeaders(),
|
||
...options.headers
|
||
};
|
||
|
||
const config = {
|
||
...options,
|
||
headers
|
||
};
|
||
|
||
try {
|
||
const response = await fetch(url, config);
|
||
|
||
// 如果是401错误,重定向到登录页
|
||
if (response.status === 401) {
|
||
this.handleUnauthorized();
|
||
throw new Error(t('common.unauthorized'));
|
||
}
|
||
|
||
const contentType = response.headers.get('content-type');
|
||
let data;
|
||
if (contentType && contentType.includes('application/json')) {
|
||
data = await response.json();
|
||
} else {
|
||
data = await response.text();
|
||
}
|
||
|
||
// 如果响应状态码不是 2xx,抛出错误
|
||
if (!response.ok) {
|
||
let errorMessage;
|
||
if (data && typeof data === 'object') {
|
||
// 优先使用错误代码进行翻译
|
||
const code = (data.error && data.error.messageCode) || data.messageCode;
|
||
if (code) {
|
||
const translated = t(code);
|
||
if (translated !== code) {
|
||
errorMessage = translated;
|
||
}
|
||
}
|
||
|
||
// 如果没有翻译,使用原始错误消息
|
||
if (!errorMessage) {
|
||
errorMessage = (data.error && data.error.message) || data.message;
|
||
}
|
||
}
|
||
|
||
if (!errorMessage) {
|
||
errorMessage = `${t('common.requestFailed')} (${t('common.status')}: ${response.status})`;
|
||
}
|
||
throw new Error(errorMessage);
|
||
}
|
||
|
||
return data;
|
||
} catch (error) {
|
||
if (error.message === t('common.unauthorized')) {
|
||
// 已经在handleUnauthorized中处理了重定向
|
||
throw error;
|
||
}
|
||
console.error('API请求错误:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* GET请求
|
||
*/
|
||
async get(endpoint, params = {}) {
|
||
const queryString = new URLSearchParams(params).toString();
|
||
const url = queryString ? `${endpoint}?${queryString}` : endpoint;
|
||
return this.request(url, { method: 'GET' });
|
||
}
|
||
|
||
/**
|
||
* POST请求
|
||
*/
|
||
async post(endpoint, data = {}) {
|
||
return this.request(endpoint, {
|
||
method: 'POST',
|
||
body: JSON.stringify(data)
|
||
});
|
||
}
|
||
|
||
/**
|
||
* PUT请求
|
||
*/
|
||
async put(endpoint, data = {}) {
|
||
return this.request(endpoint, {
|
||
method: 'PUT',
|
||
body: JSON.stringify(data)
|
||
});
|
||
}
|
||
|
||
/**
|
||
* DELETE请求
|
||
*/
|
||
async delete(endpoint) {
|
||
return this.request(endpoint, { method: 'DELETE' });
|
||
}
|
||
|
||
/**
|
||
* POST请求(支持FormData上传)
|
||
*/
|
||
async upload(endpoint, formData) {
|
||
const url = `${this.baseURL}/api${endpoint}`;
|
||
|
||
// 获取认证token
|
||
const token = this.authManager.getToken();
|
||
const headers = {};
|
||
|
||
// 如果有token,添加Authorization头部
|
||
if (token) {
|
||
headers['Authorization'] = `Bearer ${token}`;
|
||
}
|
||
|
||
// 对于FormData请求,不添加Content-Type头部,让浏览器自动设置
|
||
const config = {
|
||
method: 'POST',
|
||
headers,
|
||
body: formData
|
||
};
|
||
|
||
try {
|
||
const response = await fetch(url, config);
|
||
|
||
// 如果是401错误,重定向到登录页
|
||
if (response.status === 401) {
|
||
this.handleUnauthorized();
|
||
throw new Error(t('common.unauthorized'));
|
||
}
|
||
|
||
const contentType = response.headers.get('content-type');
|
||
let data;
|
||
if (contentType && contentType.includes('application/json')) {
|
||
data = await response.json();
|
||
} else {
|
||
data = await response.text();
|
||
}
|
||
|
||
// 如果响应状态码不是 2xx,抛出错误
|
||
if (!response.ok) {
|
||
const errorMessage = (data && typeof data === 'object' && data.error && data.error.message)
|
||
|| (data && typeof data === 'object' && data.message)
|
||
|| `${t('common.uploadFailed')} (${t('common.status')}: ${response.status})`;
|
||
throw new Error(errorMessage);
|
||
}
|
||
|
||
return data;
|
||
} catch (error) {
|
||
if (error.message === t('common.unauthorized')) {
|
||
// 已经在handleUnauthorized中处理了重定向
|
||
throw error;
|
||
}
|
||
console.error('API请求错误:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 初始化认证检查
|
||
*/
|
||
async function initAuth() {
|
||
const authManager = new AuthManager();
|
||
|
||
// 检查是否已经有有效的token
|
||
if (authManager.isTokenValid()) {
|
||
// 验证token是否仍然有效(发送一个测试请求)
|
||
try {
|
||
const apiClient = new ApiClient();
|
||
await apiClient.get('/health');
|
||
return true;
|
||
} catch (error) {
|
||
// Token无效,清除并重定向到登录页
|
||
authManager.clearToken();
|
||
window.location.href = '/login.html';
|
||
return false;
|
||
}
|
||
} else {
|
||
// 没有有效token,重定向到登录页
|
||
window.location.href = '/login.html';
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 登出函数
|
||
*/
|
||
async function logout() {
|
||
const authManager = new AuthManager();
|
||
await authManager.logout();
|
||
}
|
||
|
||
/**
|
||
* 登录函数(供登录页面使用)
|
||
*/
|
||
async function login(password, rememberMe = false) {
|
||
try {
|
||
const response = await fetch('/api/login', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
password,
|
||
rememberMe
|
||
})
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
// 保存token
|
||
const authManager = new AuthManager();
|
||
authManager.saveToken(data.token, rememberMe);
|
||
return { success: true };
|
||
} else {
|
||
return { success: false, message: data.message };
|
||
}
|
||
} catch (error) {
|
||
console.error('登录错误:', error);
|
||
return { success: false, message: '登录失败,请检查网络连接' };
|
||
}
|
||
}
|
||
|
||
// 创建单例实例
|
||
const authManager = new AuthManager();
|
||
const apiClient = new ApiClient();
|
||
|
||
/**
|
||
* 获取带认证的请求头(便捷函数)
|
||
* @returns {Object} 包含认证信息的请求头
|
||
*/
|
||
function getAuthHeaders() {
|
||
return apiClient.getAuthHeaders();
|
||
}
|
||
|
||
// 导出实例到 window(兼容旧代码)
|
||
window.authManager = authManager;
|
||
window.apiClient = apiClient;
|
||
window.initAuth = initAuth;
|
||
window.logout = logout;
|
||
window.login = login;
|
||
|
||
// 导出认证管理器类和API客户端类供其他模块使用
|
||
window.AuthManager = AuthManager;
|
||
window.ApiClient = ApiClient;
|
||
|
||
// ES6 模块导出
|
||
export {
|
||
AuthManager,
|
||
ApiClient,
|
||
authManager,
|
||
apiClient,
|
||
initAuth,
|
||
logout,
|
||
login,
|
||
getAuthHeaders
|
||
};
|
||
|
||
console.log('认证模块已加载'); |