Files
autoAiWorkSys/app/utils/api.js
张成 e17d5610f5 1
2025-12-22 16:26:59 +08:00

410 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 前端 API 工具类
* 用于在渲染进程Vue中直接调用后端 API
*/
// API 基础 URL从环境变量或配置中获取
/**
* HTTP 请求工具
*/
class ApiClient {
constructor() {
this.token = this.getToken();
// 缓存 electronAPI 可用状态,避免每次检查
this._electronAPIChecked = false;
this._electronAPIAvailable = false;
}
/**
* 检查 window.electronAPI 是否可用(快速检查,不等待)
* @param {boolean} forceCheck 强制重新检查(默认 false
* @returns {boolean} 是否可用
*/
isElectronAPIAvailable(forceCheck = false) {
// 如果已经检查过且不需要强制检查,直接返回缓存结果
if (this._electronAPIChecked && !forceCheck) {
return this._electronAPIAvailable;
}
// 快速检查
const available = !!(window.electronAPI && window.electronAPI.http);
// 缓存结果
this._electronAPIChecked = true;
this._electronAPIAvailable = available;
return available;
}
/**
* 等待 window.electronAPI 可用(仅在首次检查时等待)
* @param {number} maxWaitTime 最大等待时间毫秒默认500ms减少等待时间
* @param {number} checkInterval 检查间隔毫秒默认50ms加快检查频率
* @returns {Promise<boolean>} 是否可用
*/
async waitForElectronAPI(maxWaitTime = 500, checkInterval = 50) {
// 如果已经确认可用,直接返回
if (this.isElectronAPIAvailable()) {
return true;
}
// 如果已经检查过但不可用,直接返回 false不重复等待
if (this._electronAPIChecked && !this._electronAPIAvailable) {
return false;
}
// 首次检查时短暂等待preload 脚本是同步的,通常立即可用)
const startTime = Date.now();
return new Promise((resolve) => {
const check = () => {
if (window.electronAPI && window.electronAPI.http) {
this._electronAPIChecked = true;
this._electronAPIAvailable = true;
resolve(true);
return;
}
const elapsed = Date.now() - startTime;
if (elapsed >= maxWaitTime) {
// 超时后标记为不可用
this._electronAPIChecked = true;
this._electronAPIAvailable = false;
resolve(false);
return;
}
setTimeout(check, checkInterval);
};
check();
});
}
/**
* 使用 electronAPI.http 发送请求
* @param {string} method HTTP 方法
* @param {string} endpoint API 端点
* @param {Object} data 请求数据
* @returns {Promise<Object>}
*/
/**
* 将响应式对象Proxy转换为普通对象以便通过 IPC 传递
* @param {any} obj 要转换的对象
* @returns {any} 普通对象
*/
toPlainObject(obj) {
if (obj === null || obj === undefined) {
return obj;
}
// 如果是基本类型,直接返回
if (typeof obj !== 'object') {
return obj;
}
// 如果是 Date 对象,转换为 ISO 字符串
if (obj instanceof Date) {
return obj.toISOString();
}
// 如果是 RegExp 对象,转换为字符串
if (obj instanceof RegExp) {
return obj.toString();
}
// 尝试使用 JSON 序列化/反序列化(最可靠的方法)
try {
return JSON.parse(JSON.stringify(obj));
} catch (e) {
// 如果 JSON 序列化失败,使用递归方法
console.warn('[API] JSON 序列化失败,使用递归方法:', e);
// 如果是数组,递归处理每个元素
if (Array.isArray(obj)) {
return obj.map(item => this.toPlainObject(item));
}
// 如果是对象,递归处理每个属性
const plainObj = {};
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
try {
plainObj[key] = this.toPlainObject(obj[key]);
} catch (err) {
// 如果无法访问属性,跳过
console.warn(`[API] 无法访问属性 ${key}:`, err);
}
}
}
return plainObj;
}
}
async requestWithElectronAPI(method, endpoint, data = null) {
const fullEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
// 将响应式对象转换为普通对象(避免 Proxy 无法序列化的问题)
const plainData = data ? this.toPlainObject(data) : null;
// 主进程不再缓存 token每次请求都从浏览器端实时获取这里不需要同步
try {
let result;
if (method === 'GET') {
result = await window.electronAPI.http.get(fullEndpoint, plainData || {});
} else {
result = await window.electronAPI.http.post(fullEndpoint, plainData || {});
}
// 输出详细的响应日志
console.log('requestWithElectronAPI result', method, fullEndpoint ,plainData, result);
return result;
} catch (error) {
// 输出详细的错误日志
console.error('========================================');
console.error(`[API错误] ${method}`);
console.error(`[错误信息]`, error.message || error);
console.error(`[错误堆栈]`, error.stack);
console.error('========================================');
throw error;
}
}
/**
* 设置认证 token
* @param {string} token
*/
setToken(token) {
this.token = token;
if (token) {
localStorage.setItem('api_token', token);
// 主进程不再缓存 token每次请求都从浏览器端实时获取
} else {
localStorage.removeItem('api_token');
}
}
/**
* 获取认证 token
* @returns {string|null}
*/
getToken() {
if (!this.token) {
this.token = localStorage.getItem('api_token');
}
return this.token;
}
/**
* 构建请求头
* @returns {Object}
*/
buildHeaders() {
const headers = {
'Content-Type': 'application/json'
};
const token = this.getToken();
if (token) {
headers['applet-token'] = `${token}`;
}
return headers;
}
/**
* 处理错误
* @param {Error|Response} error
* @returns {Promise<Object>}
*/
async handleError(error) {
if (error.response) {
// 服务器返回了错误响应
const { status, data } = error.response;
return {
code: status,
message: data?.message || `请求失败: ${status}`,
data: null
};
} else if (error.request) {
// 请求已发出但没有收到响应
return {
code: -1,
message: '网络错误,请检查网络连接',
data: null
};
} else {
// 其他错误
return {
code: -1,
message: error.message || '未知错误',
data: null
};
}
}
/**
* 显示错误提示
* @param {string} message 错误消息
*/
showError(message) {
// 优先使用 electronAPI 的日志功能
if (window.electronAPI && typeof window.electronAPI.log === 'function') {
try {
window.electronAPI.log('error', `[API错误] ${message}`);
} catch (error) {
console.error('调用 electronAPI.log 失败:', error);
}
}
console.error('[API错误]', message);
}
/**
* 确保 electronAPI 可用并发送请求
* @param {string} method HTTP 方法
* @param {string} endpoint API 端点
* @param {Object} data 请求数据
* @returns {Promise<Object>}
*/
async _ensureElectronAPIAndRequest(method, endpoint, data = null) {
// 检查 electronAPI 是否可用
if (!this.isElectronAPIAvailable()) {
// 首次检查时,短暂等待一次
const electronAPIAvailable = await this.waitForElectronAPI();
if (!electronAPIAvailable) {
const errorMsg = 'electronAPI 不可用,无法发送请求';
this.showError(errorMsg);
throw new Error(errorMsg);
}
}
// 发送请求
const result = await this.requestWithElectronAPI(method, endpoint, data);
// 检查返回结果,如果 code 不为 0显示错误提示
if (result && result.code !== undefined && result.code !== 0) {
const errorMsg = result.message || '请求失败';
this.showError(errorMsg);
}
return result;
}
/**
* 发送 HTTP 请求
* @param {string} method HTTP 方法
* @param {string} endpoint API 端点
* @param {Object} data 请求数据
* @returns {Promise<Object>}
*/
async request(method, endpoint, data = null) {
try {
const res = await this._ensureElectronAPIAndRequest(method, endpoint, data);
return res
} catch (error) {
const errorMsg = error.message || '请求失败';
this.showError(errorMsg);
throw error;
}
}
/**
* GET 请求
* @param {string} endpoint API 端点
* @param {Object} params 查询参数
* @returns {Promise<Object>}
*/
async get(endpoint, params = {}) {
try {
return await this._ensureElectronAPIAndRequest('GET', endpoint, params);
} catch (error) {
const errorMsg = error.message || '请求失败';
this.showError(errorMsg);
throw error;
}
}
/**
* POST 请求
* @param {string} endpoint API 端点
* @param {Object} data 请求数据
* @returns {Promise<Object>}
*/
async post(endpoint, data = {}) {
try {
const result = await this.request('POST', endpoint, data);
return result;
} catch (error) {
console.error(`[ApiClient] POST 请求失败:`, { error: error.message, endpoint, data });
const errorResult = await this.handleError(error);
// 如果 handleError 返回了错误结果,显示错误提示
if (errorResult && errorResult.code !== 0) {
this.showError(errorResult.message || '请求失败');
}
return errorResult;
}
}
}
// 创建默认实例
const apiClient = new ApiClient();
/**
* 用户登录
* @param {string} email 邮箱
* @param {string} password 密码
* @param {string} deviceId 设备ID可选
* @returns {Promise<Object>}
*/
export async function login(email, password, deviceId = null) {
const requestData = {
login_name: email, // 后端使用 login_name 字段
password
};
if (deviceId) {
requestData.device_id = deviceId;
}
const response = await apiClient.post('/user/login', requestData);
// 如果登录成功,保存 token
if (response.code === 0 && response.data && response.data.token) {
apiClient.setToken(response.data.token);
}
return response;
}
/**
* 设置 API token用于其他地方设置的 token
* @param {string} token
*/
export function setToken(token) {
apiClient.setToken(token);
}
/**
* 获取 API token
* @returns {string|null}
*/
export function getToken() {
return apiClient.getToken();
}
/**
* 清除 API token
*/
export function clearToken() {
apiClient.setToken(null);
}
// 导出默认实例,方便直接使用
export default apiClient;