-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapi-service.js
More file actions
373 lines (326 loc) · 12 KB
/
api-service.js
File metadata and controls
373 lines (326 loc) · 12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
const axios = require('axios');
const cheerio = require('cheerio');
const fs = require('fs');
const path = require('path');
const https = require('https');
/**
* API服务类 - 负责与中转服务器API通信
* 参考ccusage实现逻辑,提供高效的数据获取、缓存管理和错误处理功能
*/
class ApiService {
constructor() {
this.baseConfig = {
timeout: 15000, // 15秒超时
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Accept-Encoding': 'gzip, deflate, br',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1'
},
// 忽略SSL证书验证(用于自签名证书)
httpsAgent: new https.Agent({
rejectUnauthorized: false
})
};
// 缓存数据 - 改进为Map结构支持多个API端点
this.cache = new Map();
this.cacheTimeout = 30000; // 30秒缓存
this.offlineMode = false;
// 增强的缓存路径策略 - 优先使用用户主目录,支持全局安装
this.persistentCachePath = this.getPersistentCachePath();
// 重试配置
this.retryConfig = {
maxRetries: 3,
retryDelay: 2000, // 2秒
backoffMultiplier: 1.5
};
// 加载持久化缓存
this.loadPersistentCache();
}
// 获取持久化缓存路径 - 支持全局安装场景
getPersistentCachePath() {
const os = require('os');
const path = require('path');
// 缓存路径优先级:
// 1. 环境变量指定的路径
if (process.env.CC_CACHE_DIR) {
return path.join(process.env.CC_CACHE_DIR, 'cache.json');
}
// 2. 用户主目录的.claude目录(全局配置目录)
const globalClaudeDir = path.join(os.homedir(), '.claude');
try {
// 确保目录存在
const fs = require('fs');
fs.mkdirSync(globalClaudeDir, { recursive: true });
return path.join(globalClaudeDir, 'statusbar-cache.json');
} catch (e) {
// 如果无法创建全局目录,回退到临时目录
if (process.env.CC_DEBUG) {
console.error(`[DEBUG] 无法创建全局缓存目录,回退到临时目录: ${e.message}`);
}
}
// 3. 系统临时目录作为fallback
const tmpDir = os.tmpdir();
return path.join(tmpDir, 'claude-statusbar-cache.json');
}
/**
* 加载持久化缓存
*/
loadPersistentCache() {
try {
if (fs.existsSync(this.persistentCachePath)) {
const cacheData = JSON.parse(fs.readFileSync(this.persistentCachePath, 'utf8'));
// 恢复缓存数据
Object.entries(cacheData).forEach(([key, value]) => {
this.cache.set(key, value);
});
if (process.env.CC_DEBUG) {
console.error(`[DEBUG] 已加载持久化缓存: ${this.persistentCachePath}, 条目数: ${Object.keys(cacheData).length}`);
}
} else {
if (process.env.CC_DEBUG) {
console.error(`[DEBUG] 持久化缓存文件不存在: ${this.persistentCachePath}`);
}
}
} catch (error) {
// 忽略缓存加载错误,但在调试模式下记录
if (process.env.CC_DEBUG) {
console.error(`[DEBUG] 缓存加载失败 ${this.persistentCachePath}: ${error.message}`);
}
}
}
/**
* 保存持久化缓存
*/
savePersistentCache() {
try {
const path = require('path');
const fs = require('fs');
// 确保缓存目录存在
const cacheDir = path.dirname(this.persistentCachePath);
fs.mkdirSync(cacheDir, { recursive: true });
// 转换Map为普通对象进行序列化
const cacheData = Object.fromEntries(this.cache);
fs.writeFileSync(this.persistentCachePath, JSON.stringify(cacheData, null, 2));
if (process.env.CC_DEBUG) {
console.error(`[DEBUG] 已保存持久化缓存: ${this.persistentCachePath}, 条目数: ${Object.keys(cacheData).length}`);
}
} catch (error) {
// 忽略缓存保存错误,但在调试模式下记录
if (process.env.CC_DEBUG) {
console.error(`[DEBUG] 缓存保存失败 ${this.persistentCachePath}: ${error.message}`);
}
}
}
/**
* 获取服务器状态数据
* @param {string} apiUrl - API接口地址
* @param {boolean} useCache - 是否使用缓存
* @returns {Promise<string>} HTML响应数据
*/
async fetchServerStatus(apiUrl, useCache = true) {
const cacheKey = `status_${apiUrl}`;
// 检查内存缓存
if (useCache && this.isCacheValid(cacheKey)) {
console.log('使用内存缓存数据');
return this.cache.get(cacheKey).data;
}
// 离线模式检查
if (this.offlineMode && this.cache.has(cacheKey)) {
// 离线模式:使用缓存数据(已移除调试输出)
return this.cache.get(cacheKey).data;
}
let lastError = null;
// 重试机制
for (let attempt = 1; attempt <= this.retryConfig.maxRetries; attempt++) {
try {
console.log(`尝试获取数据 (第${attempt}次)...`);
const response = await this.makeRequest(apiUrl);
if (response.status === 200 && response.data) {
// 更新缓存
this.updateCache(cacheKey, response.data);
this.savePersistentCache();
console.log('数据获取成功');
return response.data;
}
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
} catch (error) {
lastError = error;
console.warn(`第${attempt}次尝试失败:`, error.message);
// 如果不是最后一次尝试,等待后重试
if (attempt < this.retryConfig.maxRetries) {
const delay = this.retryConfig.retryDelay * Math.pow(this.retryConfig.backoffMultiplier, attempt - 1);
console.log(`等待${delay}ms后重试...`);
await this.sleep(delay);
}
}
}
// 所有重试都失败,检查是否有缓存数据
if (this.cache.has(cacheKey)) {
console.log('使用过期缓存数据');
return this.cache.get(cacheKey).data;
}
// 抛出最后一个错误
throw new Error(`获取数据失败 (${this.retryConfig.maxRetries}次重试): ${lastError.message}`);
}
/**
* 发起HTTP请求
* @param {string} url - 请求地址
* @returns {Promise<Object>} axios响应对象
*/
async makeRequest(url) {
const config = {
...this.baseConfig,
method: 'GET',
url: url,
validateStatus: (status) => status < 500 // 只有5xx错误才抛出异常
};
return await axios(config);
}
/**
* 检查缓存是否有效
* @param {string} cacheKey - 缓存键
* @returns {boolean}
*/
isCacheValid(cacheKey) {
if (!this.cache.has(cacheKey)) {
return false;
}
const cacheItem = this.cache.get(cacheKey);
if (!cacheItem.data || !cacheItem.timestamp) {
return false;
}
const now = Date.now();
return (now - cacheItem.timestamp) < this.cacheTimeout;
}
/**
* 更新缓存
* @param {string} cacheKey - 缓存键
* @param {string} data - 要缓存的数据
*/
updateCache(cacheKey, data) {
this.cache.set(cacheKey, {
data: data,
timestamp: Date.now()
});
}
/**
* 清除缓存
* @param {string} cacheKey - 缓存键(可选,不传则清除所有缓存)
*/
clearCache(cacheKey = null) {
if (cacheKey) {
this.cache.delete(cacheKey);
} else {
this.cache.clear();
}
this.savePersistentCache();
}
/**
* 设置缓存TTL
* @param {number} ttl - 缓存时间(毫秒)
*/
setCacheTTL(ttl) {
this.cacheTimeout = ttl;
}
/**
* 设置离线模式
* @param {boolean} offline - 是否启用离线模式
*/
setOfflineMode(offline) {
this.offlineMode = offline;
}
/**
* 测试API连接
* @param {string} apiUrl - API接口地址
* @returns {Promise<Object>} 测试结果
*/
async testConnection(apiUrl) {
const startTime = Date.now();
try {
const response = await this.makeRequest(apiUrl);
const endTime = Date.now();
const responseTime = endTime - startTime;
return {
success: true,
status: response.status,
responseTime: responseTime,
dataSize: response.data ? response.data.length : 0,
message: `连接成功 (${responseTime}ms)`
};
} catch (error) {
const endTime = Date.now();
const responseTime = endTime - startTime;
return {
success: false,
status: error.response ? error.response.status : 0,
responseTime: responseTime,
dataSize: 0,
message: `连接失败: ${error.message}`,
error: error.message
};
}
}
/**
* 获取网络状态信息
* @param {string} cacheKey - 缓存键(可选)
* @returns {Object} 网络状态
*/
getNetworkInfo(cacheKey = null) {
if (cacheKey && this.cache.has(cacheKey)) {
const cacheItem = this.cache.get(cacheKey);
return {
cacheValid: this.isCacheValid(cacheKey),
cacheAge: cacheItem.timestamp ? Date.now() - cacheItem.timestamp : null,
cacheTTL: this.cacheTimeout,
hasData: !!cacheItem.data,
offlineMode: this.offlineMode
};
}
return {
totalCacheItems: this.cache.size,
cacheTTL: this.cacheTimeout,
offlineMode: this.offlineMode,
cacheKeys: Array.from(this.cache.keys())
};
}
/**
* 睡眠函数
* @param {number} ms - 睡眠时间(毫秒)
* @returns {Promise<void>}
*/
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* 设置请求超时时间
* @param {number} timeout - 超时时间(毫秒)
*/
setTimeout(timeout) {
this.baseConfig.timeout = timeout;
}
/**
* 设置重试配置
* @param {Object} config - 重试配置
*/
setRetryConfig(config) {
this.retryConfig = { ...this.retryConfig, ...config };
}
/**
* 获取统计信息
* @returns {Object} 统计信息
*/
getStats() {
return {
cache: this.getNetworkInfo(),
config: {
timeout: this.baseConfig.timeout,
maxRetries: this.retryConfig.maxRetries,
retryDelay: this.retryConfig.retryDelay
}
};
}
}
module.exports = ApiService;