smart-image-scraper-mcp 2.1.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smart-image-scraper-mcp",
3
- "version": "2.1.1",
3
+ "version": "2.3.0",
4
4
  "description": "全网智能图片抓取 MCP 服务器 - 支持 Bing/Google 图片搜索、验证和下载",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -26,11 +26,23 @@ export class LRUCache {
26
26
  }
27
27
 
28
28
  /**
29
- * 生成缓存键
29
+ * 生成缓存键 - 优化版本
30
+ * 使用简单字符串拼接代替 JSON.stringify
30
31
  */
31
32
  _generateKey(key) {
32
33
  if (typeof key === 'string') return key;
33
- return JSON.stringify(key);
34
+ if (typeof key === 'object' && key !== null) {
35
+ // 快速生成键:只使用关键字段
36
+ const parts = [];
37
+ for (const k of Object.keys(key).sort()) {
38
+ const v = key[k];
39
+ if (v !== undefined && v !== null && v !== '') {
40
+ parts.push(`${k}:${v}`);
41
+ }
42
+ }
43
+ return parts.join('|');
44
+ }
45
+ return String(key);
34
46
  }
35
47
 
36
48
  /**
@@ -1,19 +1,42 @@
1
1
  /**
2
- * HTTP 客户端封装
3
- * 配置通用的 User-Agent 和 Timeout
2
+ * HTTP 客户端封装 - 高性能版本
3
+ * 配置连接池复用、Keep-Alive、超时控制
4
4
  */
5
5
 
6
6
  import axios from 'axios';
7
+ import http from 'http';
8
+ import https from 'https';
7
9
  import config from '../config/index.js';
8
10
  import logger from './logger.js';
9
11
 
12
+ // HTTP 连接池配置 - 复用 TCP 连接,大幅提升性能
13
+ const httpAgent = new http.Agent({
14
+ keepAlive: true, // 启用 Keep-Alive
15
+ maxSockets: 50, // 最大并发连接数
16
+ maxFreeSockets: 10, // 最大空闲连接数
17
+ timeout: 30000, // 连接超时 30 秒
18
+ scheduling: 'fifo', // 先进先出调度
19
+ });
20
+
21
+ const httpsAgent = new https.Agent({
22
+ keepAlive: true,
23
+ maxSockets: 50,
24
+ maxFreeSockets: 10,
25
+ timeout: 30000,
26
+ scheduling: 'fifo',
27
+ rejectUnauthorized: false, // 允许自签名证书
28
+ });
29
+
10
30
  const httpClient = axios.create({
11
31
  timeout: config.REQUEST_TIMEOUT,
32
+ httpAgent, // ✅ 使用连接池
33
+ httpsAgent, // ✅ 使用连接池
12
34
  headers: {
13
35
  'User-Agent': config.USER_AGENT,
14
36
  'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
15
37
  'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
16
38
  'Accept-Encoding': 'gzip, deflate, br',
39
+ 'Connection': 'keep-alive', // ✅ 保持连接
17
40
  },
18
41
  maxRedirects: 5,
19
42
  validateStatus: (status) => status < 500,
@@ -0,0 +1,291 @@
1
+ /**
2
+ * 请求队列管理器 - 多线程队列机制
3
+ * 管理并发请求,确保资源正确释放
4
+ */
5
+
6
+ import logger from './logger.js';
7
+ import { metrics } from './metrics.js';
8
+
9
+ /**
10
+ * 请求队列管理器
11
+ */
12
+ export class RequestQueue {
13
+ constructor(options = {}) {
14
+ this.maxConcurrent = options.maxConcurrent || 5; // 最大并发数
15
+ this.maxQueueSize = options.maxQueueSize || 20; // 最大队列长度
16
+ this.requestTimeout = options.requestTimeout || 60000; // 请求超时 60 秒
17
+
18
+ this.queue = []; // 等待队列
19
+ this.active = new Map(); // 活跃请求 Map<requestId, requestInfo>
20
+ this.completed = []; // 已完成请求(保留最近 100 个)
21
+ this.maxCompleted = 100;
22
+
23
+ this.stats = {
24
+ totalProcessed: 0,
25
+ totalSuccess: 0,
26
+ totalFailed: 0,
27
+ totalTimeout: 0,
28
+ };
29
+
30
+ // 定期清理超时请求
31
+ this.cleanupInterval = setInterval(() => this._cleanupTimeouts(), 5000);
32
+ }
33
+
34
+ /**
35
+ * 生成请求 ID
36
+ */
37
+ generateRequestId() {
38
+ return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
39
+ }
40
+
41
+ /**
42
+ * 提交请求到队列
43
+ * @param {Function} task - 异步任务函数
44
+ * @param {Object} metadata - 请求元数据
45
+ * @returns {Promise<Object>} - 任务结果
46
+ */
47
+ async submit(task, metadata = {}) {
48
+ const requestId = this.generateRequestId();
49
+
50
+ // 检查队列是否已满
51
+ if (this.queue.length >= this.maxQueueSize) {
52
+ logger.warn(`[Queue] Request rejected: queue full (${this.queue.length}/${this.maxQueueSize})`);
53
+ metrics.recordError('queue_full');
54
+ return {
55
+ success: false,
56
+ error: `队列已满,请稍后重试。当前队列: ${this.queue.length}`,
57
+ requestId,
58
+ queueStatus: this.getStatus(),
59
+ };
60
+ }
61
+
62
+ // 创建请求包装
63
+ const requestInfo = {
64
+ id: requestId,
65
+ metadata,
66
+ createdAt: Date.now(),
67
+ status: 'queued',
68
+ resolve: null,
69
+ reject: null,
70
+ };
71
+
72
+ // 创建 Promise
73
+ const promise = new Promise((resolve, reject) => {
74
+ requestInfo.resolve = resolve;
75
+ requestInfo.reject = reject;
76
+ requestInfo.task = task;
77
+ });
78
+
79
+ // 加入队列
80
+ this.queue.push(requestInfo);
81
+ logger.info(`[Queue] Request queued: ${requestId} (queue: ${this.queue.length}, active: ${this.active.size})`);
82
+
83
+ // 尝试处理队列
84
+ this._processQueue();
85
+
86
+ // 设置超时(修复内存泄漏:保存 timeoutId 以便清理)
87
+ let timeoutId;
88
+ const timeoutPromise = new Promise((_, reject) => {
89
+ timeoutId = setTimeout(() => {
90
+ reject(new Error('Request timeout'));
91
+ }, this.requestTimeout);
92
+ });
93
+
94
+ try {
95
+ const result = await Promise.race([promise, timeoutPromise]);
96
+ clearTimeout(timeoutId); // ✅ 清理超时定时器
97
+ return result;
98
+ } catch (error) {
99
+ clearTimeout(timeoutId); // ✅ 清理超时定时器
100
+ if (error.message === 'Request timeout') {
101
+ this.stats.totalTimeout++;
102
+ this._removeFromActive(requestId);
103
+ this._removeFromQueue(requestId); // ✅ 也从队列中移除
104
+ return {
105
+ success: false,
106
+ error: '请求超时',
107
+ requestId,
108
+ };
109
+ }
110
+ throw error;
111
+ }
112
+ }
113
+
114
+ /**
115
+ * 从队列中移除请求
116
+ */
117
+ _removeFromQueue(requestId) {
118
+ const index = this.queue.findIndex(r => r.id === requestId);
119
+ if (index !== -1) {
120
+ this.queue.splice(index, 1);
121
+ }
122
+ }
123
+
124
+ /**
125
+ * 处理队列中的请求
126
+ */
127
+ _processQueue() {
128
+ while (this.queue.length > 0 && this.active.size < this.maxConcurrent) {
129
+ const requestInfo = this.queue.shift();
130
+ this._executeRequest(requestInfo);
131
+ }
132
+ }
133
+
134
+ /**
135
+ * 执行单个请求
136
+ */
137
+ async _executeRequest(requestInfo) {
138
+ const { id, task, resolve, metadata } = requestInfo;
139
+
140
+ // 移入活跃列表
141
+ requestInfo.status = 'processing';
142
+ requestInfo.startedAt = Date.now();
143
+ this.active.set(id, requestInfo);
144
+
145
+ logger.info(`[Queue] Processing: ${id} (active: ${this.active.size})`);
146
+
147
+ try {
148
+ // 执行任务
149
+ const result = await task();
150
+
151
+ // 标记完成
152
+ requestInfo.status = 'completed';
153
+ requestInfo.completedAt = Date.now();
154
+ requestInfo.duration = requestInfo.completedAt - requestInfo.startedAt;
155
+
156
+ this.stats.totalProcessed++;
157
+ this.stats.totalSuccess++;
158
+
159
+ // 添加请求信息到结果
160
+ result.requestId = id;
161
+ result.processingTime = requestInfo.duration;
162
+
163
+ resolve(result);
164
+
165
+ logger.info(`[Queue] Completed: ${id} in ${requestInfo.duration}ms`);
166
+ } catch (error) {
167
+ requestInfo.status = 'failed';
168
+ requestInfo.error = error.message;
169
+
170
+ this.stats.totalProcessed++;
171
+ this.stats.totalFailed++;
172
+
173
+ resolve({
174
+ success: false,
175
+ error: error.message,
176
+ requestId: id,
177
+ });
178
+
179
+ logger.error(`[Queue] Failed: ${id} - ${error.message}`);
180
+ } finally {
181
+ // 从活跃列表移除
182
+ this._removeFromActive(id);
183
+
184
+ // 保存到已完成列表
185
+ this._addToCompleted(requestInfo);
186
+
187
+ // 继续处理队列
188
+ this._processQueue();
189
+ }
190
+ }
191
+
192
+ /**
193
+ * 从活跃列表移除
194
+ */
195
+ _removeFromActive(requestId) {
196
+ this.active.delete(requestId);
197
+ }
198
+
199
+ /**
200
+ * 添加到已完成列表
201
+ */
202
+ _addToCompleted(requestInfo) {
203
+ this.completed.push({
204
+ id: requestInfo.id,
205
+ status: requestInfo.status,
206
+ duration: requestInfo.duration,
207
+ completedAt: requestInfo.completedAt,
208
+ });
209
+
210
+ // 限制已完成列表大小
211
+ if (this.completed.length > this.maxCompleted) {
212
+ this.completed.shift();
213
+ }
214
+ }
215
+
216
+ /**
217
+ * 清理超时请求(仅作为备份机制,主超时由 Promise.race 处理)
218
+ */
219
+ _cleanupTimeouts() {
220
+ const now = Date.now();
221
+ // 使用更长的超时时间作为备份清理(比主超时多 10 秒)
222
+ const backupTimeout = this.requestTimeout + 10000;
223
+
224
+ for (const [id, info] of this.active.entries()) {
225
+ if (now - info.startedAt > backupTimeout) {
226
+ logger.warn(`[Queue] Backup timeout cleanup: ${id}`);
227
+ // 仅从活跃列表移除,不再调用 reject(避免重复处理)
228
+ this._removeFromActive(id);
229
+ this.stats.totalTimeout++;
230
+ }
231
+ }
232
+
233
+ // 清理队列中等待过久的请求
234
+ const queueTimeout = 30000; // 队列等待超过 30 秒
235
+ this.queue = this.queue.filter(info => {
236
+ if (now - info.createdAt > queueTimeout) {
237
+ logger.warn(`[Queue] Queue timeout: ${info.id}`);
238
+ info.resolve({
239
+ success: false,
240
+ error: '队列等待超时',
241
+ requestId: info.id,
242
+ });
243
+ return false;
244
+ }
245
+ return true;
246
+ });
247
+ }
248
+
249
+ /**
250
+ * 获取队列状态
251
+ */
252
+ getStatus() {
253
+ return {
254
+ queueLength: this.queue.length,
255
+ activeCount: this.active.size,
256
+ maxConcurrent: this.maxConcurrent,
257
+ maxQueueSize: this.maxQueueSize,
258
+ stats: { ...this.stats },
259
+ activeRequests: Array.from(this.active.entries()).map(([id, info]) => ({
260
+ id,
261
+ status: info.status,
262
+ duration: Date.now() - info.startedAt,
263
+ metadata: info.metadata,
264
+ })),
265
+ };
266
+ }
267
+
268
+ /**
269
+ * 清理资源
270
+ */
271
+ cleanup() {
272
+ clearInterval(this.cleanupInterval);
273
+
274
+ // 拒绝所有等待中的请求
275
+ for (const info of this.queue) {
276
+ info.reject(new Error('Queue shutdown'));
277
+ }
278
+ this.queue = [];
279
+
280
+ logger.info('[Queue] Cleanup completed');
281
+ }
282
+ }
283
+
284
+ // 全局请求队列实例
285
+ export const requestQueue = new RequestQueue({
286
+ maxConcurrent: 5,
287
+ maxQueueSize: 20,
288
+ requestTimeout: 60000,
289
+ });
290
+
291
+ export default requestQueue;
@@ -16,7 +16,7 @@ export class BingScraper extends BaseScraper {
16
16
  }
17
17
 
18
18
  /**
19
- * 搜索 Bing 图片
19
+ * 搜索 Bing 图片 - 并行翻页版本
20
20
  * @param {string} keyword - 搜索关键词
21
21
  * @param {number} limit - 需要获取的图片数量
22
22
  * @param {Object} options - 搜索选项
@@ -24,63 +24,55 @@ export class BingScraper extends BaseScraper {
24
24
  */
25
25
  async search(keyword, limit = 10, options = {}) {
26
26
  this.options = options;
27
- const urls = new Set();
28
- let offset = 0;
29
- const pageSize = 35; // Bing 每页大约35张图片
27
+ const pageSize = 35;
28
+
29
+ // 计算需要的页数,并行获取
30
+ const pagesNeeded = Math.min(Math.ceil(limit / pageSize), 5); // 最多 5 页
31
+ const offsets = Array.from({ length: pagesNeeded }, (_, i) => i * pageSize);
32
+
33
+ logger.info(`[Bing] Searching "${keyword}" - ${pagesNeeded} pages in parallel`);
30
34
 
31
35
  try {
32
- while (urls.size < limit) {
33
- const searchUrl = this._buildSearchUrl(keyword, offset);
34
- logger.info(`Searching Bing Images: ${keyword}, offset: ${offset}`);
35
-
36
- const response = await withRetry(
37
- () => httpClient.get(searchUrl, { timeout: 15000 }), // 添加超时
38
- {
39
- maxRetries: 2, // 减少重试次数
40
- retryCondition: isRetryableError,
41
- }
42
- );
43
-
44
- if (response.status !== 200) {
45
- logger.warn(`Bing search failed with status: ${response.status}`);
46
- break;
47
- }
48
-
49
- // 极速模式:最小延迟
50
- await this._delay(50);
51
-
52
- const newUrls = this._parseResponse(response.data);
53
-
54
- if (newUrls.length === 0) {
55
- logger.info(`No more images found for: ${keyword}`);
56
- break;
57
- }
58
-
59
- newUrls.forEach(url => {
60
- if (urls.size < limit) {
61
- urls.add(url);
62
- }
63
- });
64
-
65
- // 如果已经获取足够数量,直接跳出
66
- if (urls.size >= limit) {
67
- break;
68
- }
69
-
70
- offset += pageSize;
71
-
72
- // 防止无限循环
73
- if (offset > 200) { // 减少最大偏移量
74
- logger.warn('Reached maximum offset limit');
75
- break;
36
+ // 并行获取所有页面
37
+ const pagePromises = offsets.map(offset => this._fetchPage(keyword, offset));
38
+ const results = await Promise.allSettled(pagePromises);
39
+
40
+ // 合并结果
41
+ const urls = new Set();
42
+ for (const result of results) {
43
+ if (result.status === 'fulfilled') {
44
+ result.value.forEach(url => {
45
+ if (urls.size < limit) {
46
+ urls.add(url);
47
+ }
48
+ });
76
49
  }
77
50
  }
51
+
52
+ logger.info(`[Bing] Complete: ${urls.size} URLs for "${keyword}"`);
53
+ return Array.from(urls);
78
54
  } catch (error) {
79
55
  logger.error(`Bing search error for "${keyword}"`, { message: error.message });
56
+ return [];
80
57
  }
58
+ }
81
59
 
82
- logger.info(`Bing search complete: found ${urls.size} URLs for "${keyword}"`);
83
- return Array.from(urls);
60
+ /**
61
+ * 获取单页结果
62
+ */
63
+ async _fetchPage(keyword, offset) {
64
+ const searchUrl = this._buildSearchUrl(keyword, offset);
65
+
66
+ const response = await withRetry(
67
+ () => httpClient.get(searchUrl, { timeout: 10000 }),
68
+ { maxRetries: 1, retryCondition: isRetryableError }
69
+ );
70
+
71
+ if (response.status !== 200) {
72
+ return [];
73
+ }
74
+
75
+ return this._parseResponse(response.data);
84
76
  }
85
77
 
86
78
  /**
@@ -24,65 +24,63 @@ export class GoogleScraper extends BaseScraper {
24
24
  */
25
25
  async search(keyword, limit = 10, options = {}) {
26
26
  this.options = options;
27
- const urls = new Set();
28
- let start = 0;
29
27
  const pageSize = 20;
28
+
29
+ // 计算需要的页数,并行获取
30
+ const pagesNeeded = Math.min(Math.ceil(limit / pageSize), 5); // 最多 5 页
31
+ const starts = Array.from({ length: pagesNeeded }, (_, i) => i * pageSize);
32
+
33
+ logger.info(`[Google] Searching "${keyword}" - ${pagesNeeded} pages in parallel`);
30
34
 
31
35
  try {
32
- while (urls.size < limit && start < 100) { // 减少最大偏移
33
- const searchUrl = this._buildSearchUrl(keyword, start);
34
- logger.info(`Searching Google Images: ${keyword}, start: ${start}`);
35
-
36
- const response = await withRetry(
37
- () => httpClient.get(searchUrl, {
38
- timeout: 15000, // 添加超时
39
- headers: {
40
- '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',
41
- 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
42
- 'Accept-Language': 'en-US,en;q=0.5',
43
- 'Referer': 'https://www.google.com/',
44
- },
45
- }),
46
- {
47
- maxRetries: 2, // 减少重试次数
48
- retryCondition: isRetryableError,
49
- }
50
- );
51
-
52
- if (response.status !== 200) {
53
- logger.warn(`Google search failed with status: ${response.status}`);
54
- break;
55
- }
56
-
57
- const newUrls = this._parseResponse(response.data);
58
-
59
- if (newUrls.length === 0) {
60
- logger.info(`No more images found for: ${keyword}`);
61
- break;
62
- }
63
-
64
- newUrls.forEach(url => {
65
- if (urls.size < limit) {
66
- urls.add(url);
67
- }
68
- });
69
-
70
- // 如果已经获取足够数量,直接跳出
71
- if (urls.size >= limit) {
72
- break;
36
+ // 并行获取所有页面
37
+ const pagePromises = starts.map(start => this._fetchPage(keyword, start));
38
+ const results = await Promise.allSettled(pagePromises);
39
+
40
+ // 合并结果
41
+ const urls = new Set();
42
+ for (const result of results) {
43
+ if (result.status === 'fulfilled') {
44
+ result.value.forEach(url => {
45
+ if (urls.size < limit) {
46
+ urls.add(url);
47
+ }
48
+ });
73
49
  }
74
-
75
- start += pageSize;
76
-
77
- // 极速模式:最小延迟
78
- await this._delay(50);
79
50
  }
51
+
52
+ logger.info(`[Google] Complete: ${urls.size} URLs for "${keyword}"`);
53
+ return Array.from(urls);
80
54
  } catch (error) {
81
55
  logger.error(`Google search error for "${keyword}"`, { message: error.message });
56
+ return [];
82
57
  }
58
+ }
83
59
 
84
- logger.info(`Google search complete: found ${urls.size} URLs for "${keyword}"`);
85
- return Array.from(urls);
60
+ /**
61
+ * 获取单页结果
62
+ */
63
+ async _fetchPage(keyword, start) {
64
+ const searchUrl = this._buildSearchUrl(keyword, start);
65
+
66
+ const response = await withRetry(
67
+ () => httpClient.get(searchUrl, {
68
+ timeout: 10000,
69
+ headers: {
70
+ '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',
71
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
72
+ 'Accept-Language': 'en-US,en;q=0.5',
73
+ 'Referer': 'https://www.google.com/',
74
+ },
75
+ }),
76
+ { maxRetries: 1, retryCondition: isRetryableError }
77
+ );
78
+
79
+ if (response.status !== 200) {
80
+ return [];
81
+ }
82
+
83
+ return this._parseResponse(response.data);
86
84
  }
87
85
 
88
86
  /**
@@ -18,19 +18,13 @@ import logger from '../infrastructure/logger.js';
18
18
  import config from '../config/index.js';
19
19
  import { searchCache } from '../infrastructure/cache.js';
20
20
  import { metrics } from '../infrastructure/metrics.js';
21
+ import { requestQueue } from '../infrastructure/requestQueue.js';
21
22
 
22
- // 极速并发配置 - 最大化吹吐量
23
- const MAX_CONCURRENT_REQUESTS = 5; // 同时处理 5 个 MCP 请求
23
+ // 极速并发配置
24
24
  const MAX_CONCURRENT_KEYWORDS = 3; // 每个请求内并行 3 个关键词
25
- const MAX_CONCURRENT_SEARCHES = 5; // 并行搜索 5 个
26
25
 
27
- // 全局共享的并发限制器
28
- const globalRequestLimit = pLimit(MAX_CONCURRENT_REQUESTS);
26
+ // 关键词并发限制器
29
27
  const globalKeywordLimit = pLimit(MAX_CONCURRENT_KEYWORDS);
30
- const globalSearchLimit = pLimit(MAX_CONCURRENT_SEARCHES);
31
-
32
- // 请求状态跟踪
33
- let activeRequests = new Map(); // requestId -> { startTime, query, status }
34
28
 
35
29
  export class Orchestrator {
36
30
  constructor() {
@@ -38,7 +32,6 @@ export class Orchestrator {
38
32
  this.fileManager = new FileManager();
39
33
  this.imageProcessor = new ImageProcessor();
40
34
  this.keywordLimit = globalKeywordLimit;
41
- this.requestLimit = globalRequestLimit;
42
35
  }
43
36
 
44
37
  /**
@@ -256,79 +249,26 @@ export class Orchestrator {
256
249
  }
257
250
 
258
251
  /**
259
- * 执行任务 - 高性能入口
260
- * 支持多个 MCP 请求并行处理
252
+ * 执行任务 - 使用请求队列管理
253
+ * 支持多个 MCP 请求并行处理,自动资源释放
261
254
  * @param {Object} params - 任务参数
262
255
  * @returns {Promise<Object>} - 执行结果
263
256
  */
264
257
  async execute(params) {
265
- const requestId = `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
266
- const pendingCount = this.requestLimit.pendingCount;
267
- const activeCount = this.requestLimit.activeCount;
268
-
269
- // 记录请求指标
270
258
  metrics.recordRequest();
271
259
 
272
- // 检查是否超过最大并发限制
273
- if (activeCount >= MAX_CONCURRENT_REQUESTS && pendingCount >= MAX_CONCURRENT_REQUESTS) {
274
- logger.warn(`[${requestId}] Request rejected: queue full (${activeCount} active, ${pendingCount} pending)`);
275
- metrics.recordError('queue_full');
276
- return {
277
- success: false,
278
- error: `服务繁忙,已达到最大并发数 ${MAX_CONCURRENT_REQUESTS}。请稍后重试。`,
279
- requestId,
280
- activeRequests: activeCount,
281
- pendingRequests: pendingCount,
282
- };
283
- }
284
-
285
- // 记录活跃请求
286
- activeRequests.set(requestId, {
287
- startTime: Date.now(),
288
- query: params.query,
289
- status: 'queued',
290
- });
291
-
292
- logger.info(`[${requestId}] Request queued: ${activeCount} active, ${pendingCount} pending, query="${params.query}"`);
293
-
294
- try {
295
- // 使用并发限制器,支持多个请求并行
296
- const result = await this.requestLimit(async () => {
297
- activeRequests.get(requestId).status = 'processing';
298
- logger.info(`[${requestId}] Processing started`);
299
- return await this._executeInternal(params, requestId);
300
- });
301
-
302
- result.requestId = requestId;
303
- result.processingTime = Date.now() - activeRequests.get(requestId).startTime;
304
- return result;
305
- } catch (error) {
306
- logger.error(`[${requestId}] Request failed: ${error.message}`);
307
- metrics.recordError(error);
308
- return {
309
- success: false,
310
- error: error.message,
311
- requestId,
312
- };
313
- } finally {
314
- activeRequests.delete(requestId);
315
- }
260
+ // 使用请求队列提交任务
261
+ return requestQueue.submit(
262
+ () => this._executeInternal(params),
263
+ { query: params.query, mode: params.mode }
264
+ );
316
265
  }
317
266
 
318
267
  /**
319
268
  * 获取当前请求状态
320
269
  */
321
270
  static getStatus() {
322
- return {
323
- activeRequests: Array.from(activeRequests.entries()).map(([id, info]) => ({
324
- id,
325
- query: info.query,
326
- status: info.status,
327
- duration: Date.now() - info.startTime,
328
- })),
329
- activeCount: activeRequests.size,
330
- maxConcurrent: MAX_CONCURRENT_REQUESTS,
331
- };
271
+ return requestQueue.getStatus();
332
272
  }
333
273
 
334
274
  /**