whistle.mock-plugins 1.0.6-beta.4 → 1.0.6-beta.6

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.
@@ -231,17 +231,6 @@ const ruleManager = {
231
231
  // 只记录关键信息
232
232
  this.log(`[规则处理器] 处理请求,接口: ${interfaceObj.name}, 代理类型: ${proxyType}`);
233
233
 
234
- // 添加CORS支持 - 设置跨域相关的响应头
235
- res.setHeader('Access-Control-Allow-Origin', '*');
236
- res.setHeader('Access-Control-Allow-Credentials', 'true');
237
-
238
- // 处理预检请求
239
- if (req.method === 'OPTIONS') {
240
- res.statusCode = 204; // No Content
241
- res.end();
242
- return { handled: true };
243
- }
244
-
245
234
  // 设置响应头
246
235
  let headers = config.headers;
247
236
  if (typeof headers === 'string') {
@@ -260,6 +249,10 @@ const ruleManager = {
260
249
  if (config.contentType && !res.getHeader('Content-Type')) {
261
250
  res.setHeader('Content-Type', config.contentType);
262
251
  }
252
+
253
+ // 添加CORS支持 - 设置跨域相关的响应头
254
+ res.setHeader('Access-Control-Allow-Origin', '*');
255
+ res.setHeader('Access-Control-Allow-Credentials', 'true');
263
256
 
264
257
  // 添加延迟
265
258
  const delay = parseInt(config.responseDelay || config.delay || 0, 10);
@@ -1,284 +1,547 @@
1
- // 引入必要的模块
2
1
  const fs = require('fs-extra');
3
2
  const path = require('path');
4
3
  const url = require('url');
4
+ const storage = require('./storage');
5
5
 
6
6
  // 数据存储目录
7
- const DATA_DIR = process.env.WHISTLE_PLUGIN_DATA_DIR || path.join(process.env.HOME || process.env.USERPROFILE, '.whistle.mock-plugin');
8
- const FEATURES_FILE = path.join(DATA_DIR, 'features.json');
7
+ const DATA_DIR = path.join(process.env.WHISTLE_PLUGIN_DATA_DIR || storage.DATA_DIR);
8
+ // 接口配置文件
9
9
  const INTERFACES_FILE = path.join(DATA_DIR, 'interfaces.json');
10
+ // 添加功能模块配置文件
11
+ const FEATURES_FILE = path.join(DATA_DIR, 'features.json');
10
12
 
11
- // 缓存控制
13
+ // 配置项
12
14
  const CONFIG = {
15
+ // 日志级别:0=NONE, 1=ERROR, 2=WARN, 3=INFO, 4=DEBUG
16
+ LOG_LEVEL: process.env.MOCK_PLUGIN_LOG_LEVEL || 3,
13
17
  // 缓存有效期(毫秒),默认 60 秒
14
- CACHE_INTERVAL: process.env.MOCK_PLUGIN_CACHE_INTERVAL || 60000
18
+ CACHE_INTERVAL: process.env.MOCK_PLUGIN_CACHE_INTERVAL || 60000,
19
+ // 是否在控制台输出详细日志
20
+ VERBOSE_CONSOLE: process.env.MOCK_PLUGIN_VERBOSE === 'true'
15
21
  };
16
22
 
17
23
  // 缓存控制变量
18
24
  let enabledInterfacesCache = null; // 仅缓存启用状态的接口
19
25
  let cacheTime = 0;
26
+ let cacheVersion = 1;
20
27
 
21
- /**
22
- * 生成随机值
23
- * @param {string} pattern 模式
24
- * @returns {string} 生成的值
25
- */
26
- const generateRandomValue = (pattern) => {
27
- // 如果不是以@开头的模式,直接返回原值
28
- if (!pattern || !pattern.startsWith('@')) {
29
- return pattern;
28
+ // 正则表达式缓存
29
+ const regexCache = new Map();
30
+
31
+ // 记录日志
32
+ const log = (message, level = 3) => {
33
+ // 只记录小于等于当前日志级别的日志
34
+ if (level <= CONFIG.LOG_LEVEL) {
35
+ const prefix = level === 1 ? '[ERROR] ' :
36
+ level === 2 ? '[WARN] ' :
37
+ level === 4 ? '[DEBUG] ' : '';
38
+
39
+ console.log(`[mock-plugin:rulesServer] ${prefix}${message}`);
30
40
  }
31
-
32
- const formatPattern = pattern.substring(1); // 去掉@前缀
33
- // 使用单次正则替换优化性能
34
- return formatPattern.replace(/x/g, () => {
35
- const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
36
- return chars.charAt(Math.floor(Math.random() * chars.length));
37
- });
38
41
  };
39
42
 
40
- /**
41
- * 验证目标URL是否有效
42
- * @param {string} url 要验证的URL
43
- * @returns {boolean} URL是否有效
44
- */
45
- const isValidTargetUrl = (url) => {
46
- if (!url) return false;
47
-
43
+ // 错误日志
44
+ const logError = (message) => log(message, 1);
45
+
46
+ // 警告日志
47
+ const logWarn = (message) => log(message, 2);
48
+
49
+ // 信息日志
50
+ const logInfo = (message) => log(message, 3);
51
+
52
+ // 调试日志
53
+ const logDebug = (message) => log(message, 4);
54
+
55
+ // 获取文件最后修改时间
56
+ const getFileModTime = (filePath) => {
48
57
  try {
49
- // 检查URL是否以http://或https://开头
50
- return url.startsWith('http://') || url.startsWith('https://');
58
+ const stats = fs.statSync(filePath);
59
+ return stats.mtimeMs;
51
60
  } catch (err) {
52
- console.error('[isValidTargetUrl] 验证URL出错:', err);
53
- return false;
61
+ return 0;
62
+ }
63
+ };
64
+
65
+ // 加载所有启用的接口(整合了功能模块和接口的加载逻辑)
66
+ const loadEnabledInterfaces = () => {
67
+ try {
68
+ const currentTime = Date.now();
69
+ const interfacesFileModTime = getFileModTime(INTERFACES_FILE);
70
+ const featuresFileModTime = getFileModTime(FEATURES_FILE);
71
+
72
+ // 取两个文件的最后修改时间的最大值
73
+ const lastModTime = Math.max(interfacesFileModTime, featuresFileModTime);
74
+
75
+ // 如果缓存有效且文件未被修改,直接返回缓存
76
+ if (enabledInterfacesCache &&
77
+ currentTime - cacheTime < CONFIG.CACHE_INTERVAL &&
78
+ lastModTime <= cacheTime) {
79
+ logDebug('使用已启用接口的缓存');
80
+ return enabledInterfacesCache;
81
+ }
82
+
83
+ logDebug('缓存无效或文件已修改,重新加载接口');
84
+
85
+ // 1. 读取并解析所有功能模块
86
+ let enabledFeatureIds = [];
87
+ if (fs.existsSync(FEATURES_FILE)) {
88
+ try {
89
+ const featuresData = fs.readJsonSync(FEATURES_FILE);
90
+ let features = [];
91
+
92
+ if (featuresData && Array.isArray(featuresData)) {
93
+ features = featuresData;
94
+ } else if (featuresData && featuresData.features && Array.isArray(featuresData.features)) {
95
+ features = featuresData.features;
96
+ }
97
+
98
+ // 过滤出启用状态的功能ID
99
+ enabledFeatureIds = features
100
+ .filter(feature => feature.active === true)
101
+ .map(feature => feature.id);
102
+
103
+ logDebug(`找到 ${enabledFeatureIds.length} 个启用的功能模块`);
104
+ } catch (err) {
105
+ logError(`读取功能模块失败: ${err.message}`);
106
+ }
107
+ }
108
+
109
+ // 2. 读取并解析所有接口
110
+ let enabledInterfaces = [];
111
+ if (fs.existsSync(INTERFACES_FILE)) {
112
+ try {
113
+ const interfacesData = fs.readJsonSync(INTERFACES_FILE);
114
+ let interfaces = [];
115
+
116
+ if (interfacesData && Array.isArray(interfacesData)) {
117
+ interfaces = interfacesData;
118
+ } else if (interfacesData && interfacesData.interfaces && Array.isArray(interfacesData.interfaces)) {
119
+ interfaces = interfacesData.interfaces;
120
+ }
121
+
122
+ // 过滤出启用状态的接口
123
+ enabledInterfaces = interfaces.filter(intf => {
124
+ // 首先检查接口本身是否启用
125
+ const isInterfaceEnabled = intf.active !== false; // 默认为true
126
+
127
+ if (!isInterfaceEnabled) {
128
+ return false;
129
+ }
130
+
131
+ // 如果接口有关联功能,检查功能是否启用
132
+ if (intf.featureId) {
133
+ return enabledFeatureIds.includes(intf.featureId);
134
+ }
135
+
136
+ // 如果接口没有关联功能,只要接口本身启用就可以
137
+ return true;
138
+ });
139
+
140
+ logInfo(`找到 ${enabledInterfaces.length} 个启用的接口`);
141
+ } catch (err) {
142
+ logError(`读取接口配置失败: ${err.message}`);
143
+ }
144
+ }
145
+
146
+ // 更新缓存
147
+ enabledInterfacesCache = enabledInterfaces;
148
+ cacheTime = currentTime;
149
+
150
+ return enabledInterfaces;
151
+ } catch (err) {
152
+ logError(`加载启用接口失败: ${err.message}`);
153
+ return enabledInterfacesCache || [];
54
154
  }
55
155
  };
56
156
 
57
- /**
58
- * 检查URL是否匹配模式
59
- * @param {string} url 要检查的URL
60
- * @param {string} pattern 匹配模式
61
- * @param {string} proxyType 代理类型
62
- * @returns {boolean} 是否匹配
63
- */
64
- const isUrlMatch = (url, pattern, proxyType) => {
65
- if (!url || !pattern) return false;
157
+ // 匹配URL是否符合模式
158
+ const isUrlMatch = (url, pattern, proxyType, fullUrl) => {
159
+ if (!pattern) return false;
66
160
 
67
161
  try {
68
- // 对于url_redirect类型,需要完全匹配
162
+ // 针对不同的代理类型采用不同的匹配策略
163
+
164
+ // 对于url_redirect类型,需要完全匹配fullPath才命中
69
165
  if (proxyType === 'url_redirect') {
70
- return url === pattern;
166
+ // 精确匹配完整路径,这是最快的比较方式
167
+ return pattern === fullUrl;
71
168
  }
72
169
 
73
170
  // 对于redirect类型,只要url以pattern开头即可命中(前缀匹配)
74
171
  if (proxyType === 'redirect') {
75
- return url.startsWith(pattern);
172
+ // 使用字符串原生的startsWith方法更高效
173
+ return fullUrl.startsWith(pattern);
76
174
  }
77
175
 
78
- // 默认的匹配逻辑(用于response类型等)
79
- // 精确匹配
176
+ // 以下是默认的匹配逻辑(用于response类型等)
177
+
178
+ // 精确匹配 - 最高效的匹配方式
80
179
  if (pattern === url) {
81
180
  return true;
82
181
  }
83
-
84
- // 正则表达式
85
- if (pattern.startsWith('/') && pattern.length > 2 && pattern.endsWith('/')) {
86
- const regex = new RegExp(pattern.slice(1, -1));
182
+
183
+ // 通配符匹配
184
+ if (pattern.includes('*')) {
185
+ // 使用缓存的正则表达式
186
+ const cacheKey = `wildcard:${pattern}`;
187
+ let regex = regexCache.get(cacheKey);
188
+
189
+ if (!regex) {
190
+ // 需要转换为正则表达式,处理特殊字符
191
+ const regexPattern = pattern
192
+ .replace(/[-\/\\^$+?.()|[\]{}]/g, '\\$&') // 转义特殊字符
193
+ .replace(/\*/g, '.*'); // 将 * 替换为 .*
194
+
195
+ regex = new RegExp('^' + regexPattern + '$');
196
+ regexCache.set(cacheKey, regex);
197
+ }
198
+
87
199
  return regex.test(url);
88
200
  }
89
-
90
- // 通配符模式
91
- if (pattern.includes('*')) {
92
- const regexPattern = pattern
93
- .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
94
- .replace(/\*/g, '.*');
95
- const regex = new RegExp(`^${regexPattern}$`);
201
+
202
+ // 正则表达式匹配
203
+ if (pattern.startsWith('/') && pattern.length > 2 && pattern.endsWith('/')) {
204
+ // 使用缓存的正则表达式
205
+ const cacheKey = `regex:${pattern}`;
206
+ let regex = regexCache.get(cacheKey);
207
+
208
+ if (!regex) {
209
+ const regexStr = pattern.slice(1, -1);
210
+ regex = new RegExp(regexStr);
211
+ regexCache.set(cacheKey, regex);
212
+ }
213
+
96
214
  return regex.test(url);
97
215
  }
98
-
99
- return false;
100
216
  } catch (e) {
101
- console.error('URL匹配检查失败:', e);
102
- return false;
217
+ log(`正则表达式匹配错误: ${e.message}`);
103
218
  }
219
+
220
+ return false;
104
221
  };
105
222
 
106
- /**
107
- * 加载启用的接口数据
108
- * @returns {Promise<Array>} 启用的接口列表
109
- */
110
- const loadEnabledInterfaces = async () => {
223
+ // 验证目标URL是否有效
224
+ const isValidTargetUrl = (url) => {
225
+ if (!url) return false;
226
+
111
227
  try {
112
- const currentTime = Date.now();
113
-
114
- // 使用缓存
115
- if (enabledInterfacesCache &&
116
- currentTime - cacheTime < CONFIG.CACHE_INTERVAL) {
117
- return enabledInterfacesCache;
118
- }
119
-
120
- // 从数据管理器获取启用的接口
121
- const dataManager = require('./dataManager');
122
- const enabledInterfaces = await dataManager.getEnabledInterfaces();
123
-
124
- // 更新缓存
125
- enabledInterfacesCache = enabledInterfaces;
126
- cacheTime = currentTime;
127
-
128
- return enabledInterfaces;
129
- } catch (err) {
130
- console.error('[loadEnabledInterfaces] 加载启用接口失败:', err);
131
- return enabledInterfacesCache || [];
228
+ // 检查是否是合法的URL格式
229
+ new URL(url);
230
+ return true;
231
+ } catch (e) {
232
+ log(`URL验证错误: ${e.message}`);
233
+ return false;
132
234
  }
133
235
  };
134
236
 
135
- /**
136
- * 查找匹配的接口
137
- * @param {Array} interfaces 接口列表
138
- * @param {string} url 请求URL
139
- * @returns {object|null} 匹配的接口,如果没有匹配则返回null
140
- */
141
- const findMatchingInterface = (interfaces, url) => {
142
- if (!interfaces || !Array.isArray(interfaces) || interfaces.length === 0 || !url) {
143
- return null;
237
+ // 验证重定向规则的有效性
238
+ const validateRedirectRule = (interface) => {
239
+ if (!interface) return false;
240
+
241
+ const { proxyType, targetUrl } = interface;
242
+
243
+ // 对于redirect和url_redirect类型,必须有有效的目标URL
244
+ if ((proxyType === 'redirect' || proxyType === 'url_redirect') && !isValidTargetUrl(targetUrl)) {
245
+ log(`接口 ${interface.id || interface.name || '未知'} 的目标URL无效: ${targetUrl}`);
246
+ return false;
144
247
  }
145
248
 
146
- try {
147
- // 尝试查找匹配的接口
148
- for (const intf of interfaces) {
149
- const { pattern, proxyType } = intf;
150
-
151
- // 检查URL是否匹配
152
- if (isUrlMatch(url, pattern, proxyType)) {
153
- return intf;
154
- }
155
- }
156
-
157
- // 没有找到匹配的接口
158
- return null;
159
- } catch (err) {
160
- console.error('[findMatchingInterface] 查找匹配接口出错:', err);
161
- return null;
249
+ return true;
250
+ };
251
+
252
+ // 生成随机数工具函数
253
+ const generateRandomValue = (pattern) => {
254
+ // 如果不是以@开头的模式,直接返回原值
255
+ if (!pattern || !pattern.startsWith('@')) {
256
+ return pattern;
162
257
  }
258
+
259
+ const formatPattern = pattern.substring(1); // 去掉@前缀
260
+ // 使用单次正则替换代替每个字符的遍历
261
+ return formatPattern.replace(/x/g, () => {
262
+ // 使用一个固定的字符集,避免生成不兼容的字符
263
+ const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
264
+ return chars.charAt(Math.floor(Math.random() * chars.length));
265
+ });
163
266
  };
164
267
 
165
- /**
166
- * 获取Whistle规则
167
- * @param {string} fullUrl 完整URL
168
- * @param {string} pattern URL匹配模式
169
- * @param {string} proxyType 代理类型
170
- * @param {object} options 选项
171
- * @returns {string} Whistle规则
172
- */
173
- const getWhistleRules = (fullUrl, pattern, proxyType, options = {}) => {
174
- // 记录调用参数
175
- console.log(`[getWhistleRules] fullUrl=${fullUrl}, pattern=${pattern}, proxyType=${proxyType}`);
268
+ // 构建重定向规则
269
+ const buildRedirectRule = (fullUrl, targetUrl, customHeaders, pattern, isUrlRedirect = 0) => {
270
+ // 构建目标URL
271
+ let finalTargetUrl;
272
+ if(!isUrlRedirect){
273
+ finalTargetUrl = fullUrl.replace(pattern, targetUrl);
274
+ }else{
275
+ finalTargetUrl = `redirect://${targetUrl}`;
276
+ }
277
+
278
+ // 定义结果数组,避免多次字符串拼接
279
+ const rules = [`${fullUrl} ${finalTargetUrl}`];
176
280
 
177
- // 验证参数
178
- if (!fullUrl || !pattern || !proxyType) {
179
- console.log('[getWhistleRules] 参数无效,返回空规则');
180
- return '';
281
+ // 如果有自定义请求头,添加到规则中
282
+ if (customHeaders && Object.keys(customHeaders).length > 0) {
283
+ // 为每个请求头创建一个规则行
284
+ Object.entries(customHeaders).forEach(([key, value]) => {
285
+ // 处理随机值
286
+ const processedValue = generateRandomValue(value);
287
+
288
+ // 添加到规则数组
289
+ rules.push(`${fullUrl} headerReplace://req.${key}:/(.*)/=${processedValue}`);
290
+ });
181
291
  }
182
292
 
183
- try {
184
- // 根据代理类型生成不同的规则
185
- switch (proxyType) {
186
- case 'response':
187
- // 使用插件处理响应
188
- return `${fullUrl} whistle.mock-plugin://\n${fullUrl} resCors://enable`;
189
-
190
- case 'redirect':
191
- // 验证目标URL
192
- const targetUrl = options.targetUrl;
193
- if (!isValidTargetUrl(targetUrl)) {
194
- console.log(`[getWhistleRules] 无效的目标URL: ${targetUrl}`);
195
- return '';
196
- }
197
-
198
- // 自定义请求头
199
- const customHeaders = options.customHeaders || {};
200
-
201
- // 构建重定向规则,包括自定义请求头
202
- return `${fullUrl} ${targetUrl}\n${fullUrl} resCors://enable`;
203
-
204
- case 'url_redirect':
205
- // 验证目标URL
206
- const urlRedirectTarget = options.targetUrl;
207
- if (!isValidTargetUrl(urlRedirectTarget)) {
208
- console.log(`[getWhistleRules] 无效的URL重定向目标: ${urlRedirectTarget}`);
209
- return '';
210
- }
211
-
212
- // 自定义请求头
213
- const urlRedirectHeaders = options.customHeaders || {};
214
-
215
- // 对于url_redirect,使用redirect://前缀
216
- return `${fullUrl} redirect://${urlRedirectTarget}\n${fullUrl} resCors://enable`;
217
-
218
- default:
219
- console.log(`[getWhistleRules] 不支持的代理类型: ${proxyType}`);
220
- return '';
221
- }
222
- } catch (err) {
223
- console.error('[getWhistleRules] 生成规则时出错:', err);
224
- return '';
293
+ // 最后才将所有规则合并为一个字符串,减少字符串操作
294
+ return rules.join('\n');
295
+ };
296
+
297
+ // 缓存管理接口 - 简化版本
298
+ const cacheManager = {
299
+ // 清除缓存
300
+ clearCache: () => {
301
+ logInfo('主动清除接口缓存');
302
+ enabledInterfacesCache = null;
303
+ cacheTime = 0;
304
+ cacheVersion++;
305
+ },
306
+
307
+ // 获取当前缓存状态
308
+ getStatus: () => {
309
+ return {
310
+ enabled: {
311
+ cached: !!enabledInterfacesCache,
312
+ cacheTime: cacheTime,
313
+ itemCount: enabledInterfacesCache ? enabledInterfacesCache.length : 0
314
+ },
315
+ cacheVersion,
316
+ configInterval: CONFIG.CACHE_INTERVAL
317
+ };
225
318
  }
226
319
  };
227
320
 
228
- /**
229
- * 处理请求
230
- * @param {object} ctx 上下文
231
- * @returns {Promise<void>}
232
- */
233
- const handleRequest = async (ctx) => {
234
- const { req, res } = ctx;
321
+ // Whistle 规则服务器实现
322
+ module.exports = (server, options) => {
323
+ // 优化:缓存常用路径匹配
324
+ const debugConfigPath = '/_debug/config';
325
+ const debugEnabledInterfacesPath = '/_debug/enabled-interfaces';
326
+ const clearCachePath = '/_flush_cache';
235
327
 
236
- try {
237
- // 获取请求URL
238
- const fullUrl = req.url;
328
+ server.on('request', (req, res) => {
329
+ const oReq = req.originalReq;
239
330
 
240
- // 记录请求
241
- console.log(`[规则服务器] 收到请求: ${fullUrl}`);
331
+ // 提取请求相关信息
332
+ const fullUrl = oReq.url;
333
+ logDebug(`原始URL: ${fullUrl}`);
242
334
 
243
- // 加载启用的接口
244
- const enabledInterfaces = await loadEnabledInterfaces();
335
+ // 处理缓存刷新请求
336
+ if (fullUrl.startsWith(clearCachePath)) {
337
+ cacheManager.clearCache();
338
+
339
+ res.writeHead(200, { 'Content-Type': 'application/json' });
340
+ res.end(JSON.stringify({
341
+ success: true,
342
+ message: '缓存已刷新',
343
+ cacheStatus: cacheManager.getStatus()
344
+ }));
345
+ return;
346
+ }
245
347
 
246
- if (!enabledInterfaces || enabledInterfaces.length === 0) {
247
- console.log('[规则服务器] 没有启用的接口');
248
- res.end('');
348
+ // 特殊调试路径,查看缓存状态
349
+ if (fullUrl.endsWith('/_cache_status')) {
350
+ res.writeHead(200, { 'Content-Type': 'application/json' });
351
+ res.end(JSON.stringify(cacheManager.getStatus()));
249
352
  return;
250
353
  }
251
354
 
252
- // 查找匹配的接口
253
- const matchedInterface = findMatchingInterface(enabledInterfaces, fullUrl);
355
+ // 特殊调试路径,使用更高效的匹配
356
+ if (fullUrl.endsWith(debugConfigPath)) {
357
+ try {
358
+ const configs = {
359
+ interfaces: fs.existsSync(INTERFACES_FILE) ? fs.readJsonSync(INTERFACES_FILE) : null,
360
+ features: fs.existsSync(FEATURES_FILE) ? fs.readJsonSync(FEATURES_FILE) : null
361
+ };
362
+ res.end(JSON.stringify(configs, null, 2));
363
+ return;
364
+ } catch (err) {
365
+ logError(`读取配置文件失败: ${err.message}`);
366
+ res.end(`读取配置文件失败: ${err.message}`);
367
+ return;
368
+ }
369
+ }
370
+
371
+ // 调试路径,查看启用的接口
372
+ if (fullUrl.endsWith(debugEnabledInterfacesPath)) {
373
+ try {
374
+ const enabledInterfaces = loadEnabledInterfaces();
375
+ res.end(JSON.stringify({
376
+ count: enabledInterfaces.length,
377
+ interfaces: enabledInterfaces
378
+ }, null, 2));
379
+ return;
380
+ } catch (err) {
381
+ logError(`获取启用接口失败: ${err.message}`);
382
+ res.end(`获取启用接口失败: ${err.message}`);
383
+ return;
384
+ }
385
+ }
254
386
 
255
- if (!matchedInterface) {
256
- console.log(`[规则服务器] 没有找到匹配的接口: ${fullUrl}`);
387
+ // 解析URL
388
+ let parsedUrl;
389
+ try {
390
+ parsedUrl = url.parse(fullUrl);
391
+ } catch (err) {
392
+ logError(`解析URL失败: ${err.message}`);
393
+ // 解析失败时,不处理请求
257
394
  res.end('');
258
395
  return;
259
396
  }
260
397
 
261
- // 获取接口信息
262
- const { pattern, proxyType, targetUrl, customHeaders } = matchedInterface;
398
+ // 尝试获取 pathname 或 path,用于匹配
399
+ const method = oReq.method;
400
+ const path = parsedUrl.pathname || '';
263
401
 
264
- // 生成Whistle规则
265
- const rule = getWhistleRules(fullUrl, pattern, proxyType, {
266
- targetUrl,
267
- customHeaders
268
- });
402
+ logDebug(`请求方法: ${method}, 请求路径: ${path}`);
269
403
 
270
- // 记录生成的规则
271
- console.log(`[规则服务器] 生成规则: ${rule}`);
404
+ // 加载启用的接口配置(使用缓存优化)
405
+ const enabledInterfaces = loadEnabledInterfaces();
272
406
 
273
- // 返回规则
274
- res.end(rule);
275
- } catch (err) {
276
- console.error('[规则服务器] 处理请求出错:', err);
277
- res.end('');
278
- }
279
- };
280
-
281
- // 导出规则服务器
282
- module.exports = {
283
- handleRequest
407
+ if (enabledInterfaces.length === 0) {
408
+ logDebug('没有找到启用的接口配置,跳过处理');
409
+ res.end('');
410
+ return;
411
+ }
412
+
413
+ // 优化:使用 find 查找匹配的接口,只遍历一次数组
414
+ const matchedInterface = enabledInterfaces.find(intf => {
415
+ // 获取URL模式
416
+ const pattern = intf.urlPattern || '';
417
+ const proxyType = intf.proxyType || 'response';
418
+
419
+ if (!pattern) {
420
+ logDebug(`接口 ${intf.id || intf.name || '未知'} 缺少URL模式`);
421
+ return false;
422
+ }
423
+
424
+ // 检查URL模式匹配,根据proxyType选择适当的匹配策略
425
+ const urlMatches = isUrlMatch(path, pattern, proxyType, fullUrl);
426
+
427
+ if (!urlMatches) {
428
+ // 不记录每个失败的匹配,以减少日志量
429
+ return false;
430
+ }
431
+
432
+ logDebug(`URL模式匹配成功: ${fullUrl} 匹配 ${pattern} (${proxyType}模式)`);
433
+
434
+ // 检查HTTP方法匹配
435
+ const methodField = intf.httpMethod || intf.method || '';
436
+ const methodValue = methodField.toUpperCase();
437
+ const methodMatches = !methodValue ||
438
+ methodValue === 'ALL' ||
439
+ methodValue === 'ANY' ||
440
+ methodValue === method;
441
+
442
+ if (!methodMatches) {
443
+ logDebug(`方法不匹配: 请求方法 ${method}, 接口方法 ${methodValue}`);
444
+ return false;
445
+ }
446
+
447
+ logDebug(`方法匹配成功: ${method} 匹配 ${methodValue || 'ALL'}`);
448
+
449
+ return true;
450
+ });
451
+
452
+ // 如果找到匹配的接口,处理对应的规则
453
+ if (matchedInterface) {
454
+ logInfo(`找到匹配接口 "${matchedInterface.name || matchedInterface.id || '未知'}" 用于 ${method} ${path}`);
455
+
456
+ // 处理不同的代理类型
457
+ const proxyType = matchedInterface.proxyType || 'response';
458
+
459
+ // 优先级判断:首先处理 response 类型
460
+ if (proxyType === 'response') {
461
+ // 返回指向插件服务的规则
462
+ logDebug(`接口 "${matchedInterface.name}" 使用模拟响应模式`);
463
+ res.end(`${fullUrl} mock-plugins://`);
464
+ return;
465
+ }
466
+
467
+ // 对于redirect和url_redirect类型,验证规则有效性
468
+ if ((proxyType === 'redirect' || proxyType === 'url_redirect') && !validateRedirectRule(matchedInterface)) {
469
+ logWarn(`接口 "${matchedInterface.name}" 的重定向规则无效,跳过处理`);
470
+ res.end('');
471
+ return;
472
+ }
473
+
474
+ // 处理 redirect 类型:将请求的 url 中的源 url 部分替换为目标 url
475
+ if (proxyType === 'redirect') {
476
+ const targetUrl = matchedInterface.targetUrl.trim();
477
+ const pattern = matchedInterface.urlPattern.trim();
478
+ logDebug(`接口 "${matchedInterface.name}" 使用重定向模式,模式: ${pattern}, 目标URL: ${targetUrl}`);
479
+
480
+ // 确保目标URL合法
481
+ if (!isValidTargetUrl(targetUrl)) {
482
+ logWarn(`目标URL不合法: ${targetUrl},跳过处理`);
483
+ res.end('');
484
+ return;
485
+ }
486
+
487
+ // 执行重定向:将fullUrl中匹配的pattern替换为targetUrl
488
+ // 使用startsWith优化匹配效率
489
+ if (fullUrl.startsWith(pattern)) {
490
+ // 获取自定义请求头
491
+ const customHeaders = matchedInterface.customHeaders || {};
492
+
493
+ // 构建重定向规则,包括自定义请求头
494
+ const redirectRule = buildRedirectRule(fullUrl, targetUrl, customHeaders, pattern, 0);
495
+
496
+ // 返回规则
497
+ res.end(redirectRule);
498
+ logDebug(`重定向规则: ${redirectRule}`);
499
+ return;
500
+ } else {
501
+ logDebug(`URL ${fullUrl} 不以 ${pattern} 开头,跳过处理`);
502
+ res.end('');
503
+ return;
504
+ }
505
+ }
506
+
507
+ // 处理 url_redirect 类型:完全匹配 fullPath 并直接返回目标URL
508
+ if (proxyType === 'url_redirect') {
509
+ const targetUrl = matchedInterface.targetUrl.trim();
510
+ logDebug(`接口 "${matchedInterface.name}" 使用URL重定向模式,目标URL: ${targetUrl}`);
511
+
512
+ // 确保目标URL合法
513
+ if (!isValidTargetUrl(targetUrl)) {
514
+ logWarn(`目标URL不合法: ${targetUrl},跳过处理`);
515
+ res.end('');
516
+ return;
517
+ }
518
+
519
+ // 对于url_redirect,我们需要完全匹配
520
+ if (fullUrl === matchedInterface.urlPattern) {
521
+ // 获取自定义请求头
522
+ const customHeaders = matchedInterface.customHeaders || {};
523
+
524
+ // 构建重定向规则,包括自定义请求头
525
+ const redirectRule = buildRedirectRule(fullUrl, targetUrl, customHeaders, pattern, 1);
526
+
527
+ // 返回规则
528
+ res.end(redirectRule);
529
+ logDebug(`URL重定向规则: ${redirectRule}`);
530
+ return;
531
+ } else {
532
+ logDebug(`URL ${fullUrl} 不完全匹配 ${matchedInterface.urlPattern},跳过处理`);
533
+ res.end('');
534
+ return;
535
+ }
536
+ }
537
+
538
+ // 默认情况下使用mock-plugins处理
539
+ logDebug(`接口 "${matchedInterface.name}" 使用默认处理模式`);
540
+ res.end(`${fullUrl} mock-plugins://`);
541
+ } else {
542
+ // 如果没有匹配的接口,返回空字符串,Whistle 会继续处理下一个规则
543
+ logDebug(`未找到匹配接口,跳过处理 ${method} ${path}`);
544
+ res.end('');
545
+ }
546
+ });
284
547
  };
package/lib/server.js CHANGED
@@ -410,20 +410,6 @@ const handleLegacyRequest = (req, res, next) => {
410
410
  const https = require('https');
411
411
  const { Readable } = require('stream');
412
412
 
413
- // 添加CORS支持 - 设置跨域相关的响应头
414
- res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*');
415
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS, PATCH');
416
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With, Accept');
417
- res.setHeader('Access-Control-Allow-Credentials', 'true');
418
- res.setHeader('Access-Control-Max-Age', '86400'); // 24小时
419
-
420
- // 处理预检请求
421
- if (req.method === 'OPTIONS') {
422
- res.statusCode = 204; // No Content
423
- res.end();
424
- return;
425
- }
426
-
427
413
  // 获取原始完整URL的所有可能来源
428
414
  let originalUrl = null;
429
415
 
@@ -519,20 +505,6 @@ const handleLegacyRequest = (req, res, next) => {
519
505
  res.setHeader(key, proxyRes.headers[key]);
520
506
  });
521
507
 
522
- // 确保设置CORS头,即使原始响应没有
523
- if (!res.getHeader('Access-Control-Allow-Origin')) {
524
- res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*');
525
- }
526
- if (!res.getHeader('Access-Control-Allow-Methods')) {
527
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS, PATCH');
528
- }
529
- if (!res.getHeader('Access-Control-Allow-Headers')) {
530
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With, Accept');
531
- }
532
- if (!res.getHeader('Access-Control-Allow-Credentials')) {
533
- res.setHeader('Access-Control-Allow-Credentials', 'true');
534
- }
535
-
536
508
  // 设置状态码
537
509
  res.statusCode = proxyRes.statusCode;
538
510
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whistle.mock-plugins",
3
- "version": "1.0.6-beta.4",
3
+ "version": "1.0.6-beta.6",
4
4
  "description": "Whistle 插件,用于快速创建 API 模拟数据",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {