whistle.mock-plugins 1.0.6-beta.0 → 1.0.6-beta.2

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,6 +231,20 @@ const ruleManager = {
231
231
  // 只记录关键信息
232
232
  this.log(`[规则处理器] 处理请求,接口: ${interfaceObj.name}, 代理类型: ${proxyType}`);
233
233
 
234
+ // 添加CORS支持 - 设置跨域相关的响应头
235
+ res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*');
236
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS, PATCH');
237
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With, Accept');
238
+ res.setHeader('Access-Control-Allow-Credentials', 'true');
239
+ res.setHeader('Access-Control-Max-Age', '86400'); // 24小时
240
+
241
+ // 处理预检请求
242
+ if (req.method === 'OPTIONS') {
243
+ res.statusCode = 204; // No Content
244
+ res.end();
245
+ return { handled: true };
246
+ }
247
+
234
248
  // 设置响应头
235
249
  let headers = config.headers;
236
250
  if (typeof headers === 'string') {
@@ -1,547 +1,284 @@
1
+ // 引入必要的模块
1
2
  const fs = require('fs-extra');
2
3
  const path = require('path');
3
4
  const url = require('url');
4
- const storage = require('./storage');
5
5
 
6
6
  // 数据存储目录
7
- const DATA_DIR = path.join(process.env.WHISTLE_PLUGIN_DATA_DIR || storage.DATA_DIR);
8
- // 接口配置文件
9
- const INTERFACES_FILE = path.join(DATA_DIR, 'interfaces.json');
10
- // 添加功能模块配置文件
7
+ const DATA_DIR = process.env.WHISTLE_PLUGIN_DATA_DIR || path.join(process.env.HOME || process.env.USERPROFILE, '.whistle.mock-plugin');
11
8
  const FEATURES_FILE = path.join(DATA_DIR, 'features.json');
9
+ const INTERFACES_FILE = path.join(DATA_DIR, 'interfaces.json');
12
10
 
13
- // 配置项
11
+ // 缓存控制
14
12
  const CONFIG = {
15
- // 日志级别:0=NONE, 1=ERROR, 2=WARN, 3=INFO, 4=DEBUG
16
- LOG_LEVEL: process.env.MOCK_PLUGIN_LOG_LEVEL || 3,
17
13
  // 缓存有效期(毫秒),默认 60 秒
18
- CACHE_INTERVAL: process.env.MOCK_PLUGIN_CACHE_INTERVAL || 60000,
19
- // 是否在控制台输出详细日志
20
- VERBOSE_CONSOLE: process.env.MOCK_PLUGIN_VERBOSE === 'true'
14
+ CACHE_INTERVAL: process.env.MOCK_PLUGIN_CACHE_INTERVAL || 60000
21
15
  };
22
16
 
23
17
  // 缓存控制变量
24
18
  let enabledInterfacesCache = null; // 仅缓存启用状态的接口
25
19
  let cacheTime = 0;
26
- let cacheVersion = 1;
27
-
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}`);
40
- }
41
- };
42
-
43
- // 错误日志
44
- const logError = (message) => log(message, 1);
45
-
46
- // 警告日志
47
- const logWarn = (message) => log(message, 2);
48
20
 
49
- // 信息日志
50
- const logInfo = (message) => log(message, 3);
51
-
52
- // 调试日志
53
- const logDebug = (message) => log(message, 4);
54
-
55
- // 获取文件最后修改时间
56
- const getFileModTime = (filePath) => {
57
- try {
58
- const stats = fs.statSync(filePath);
59
- return stats.mtimeMs;
60
- } catch (err) {
61
- return 0;
21
+ /**
22
+ * 生成随机值
23
+ * @param {string} pattern 模式
24
+ * @returns {string} 生成的值
25
+ */
26
+ const generateRandomValue = (pattern) => {
27
+ // 如果不是以@开头的模式,直接返回原值
28
+ if (!pattern || !pattern.startsWith('@')) {
29
+ return pattern;
62
30
  }
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
+ });
63
38
  };
64
39
 
65
- // 加载所有启用的接口(整合了功能模块和接口的加载逻辑)
66
- const loadEnabledInterfaces = () => {
40
+ /**
41
+ * 验证目标URL是否有效
42
+ * @param {string} url 要验证的URL
43
+ * @returns {boolean} URL是否有效
44
+ */
45
+ const isValidTargetUrl = (url) => {
46
+ if (!url) return false;
47
+
67
48
  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;
49
+ // 检查URL是否以http://或https://开头
50
+ return url.startsWith('http://') || url.startsWith('https://');
151
51
  } catch (err) {
152
- logError(`加载启用接口失败: ${err.message}`);
153
- return enabledInterfacesCache || [];
52
+ console.error('[isValidTargetUrl] 验证URL出错:', err);
53
+ return false;
154
54
  }
155
55
  };
156
56
 
157
- // 匹配URL是否符合模式
158
- const isUrlMatch = (url, pattern, proxyType, fullUrl) => {
159
- if (!pattern) return false;
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;
160
66
 
161
67
  try {
162
- // 针对不同的代理类型采用不同的匹配策略
163
-
164
- // 对于url_redirect类型,需要完全匹配fullPath才命中
68
+ // 对于url_redirect类型,需要完全匹配
165
69
  if (proxyType === 'url_redirect') {
166
- // 精确匹配完整路径,这是最快的比较方式
167
- return pattern === fullUrl;
70
+ return url === pattern;
168
71
  }
169
72
 
170
73
  // 对于redirect类型,只要url以pattern开头即可命中(前缀匹配)
171
74
  if (proxyType === 'redirect') {
172
- // 使用字符串原生的startsWith方法更高效
173
- return fullUrl.startsWith(pattern);
75
+ return url.startsWith(pattern);
174
76
  }
175
77
 
176
- // 以下是默认的匹配逻辑(用于response类型等)
177
-
178
- // 精确匹配 - 最高效的匹配方式
78
+ // 默认的匹配逻辑(用于response类型等)
79
+ // 精确匹配
179
80
  if (pattern === url) {
180
81
  return true;
181
82
  }
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
-
83
+
84
+ // 正则表达式
85
+ if (pattern.startsWith('/') && pattern.length > 2 && pattern.endsWith('/')) {
86
+ const regex = new RegExp(pattern.slice(1, -1));
199
87
  return regex.test(url);
200
88
  }
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
-
89
+
90
+ // 通配符模式
91
+ if (pattern.includes('*')) {
92
+ const regexPattern = pattern
93
+ .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
94
+ .replace(/\*/g, '.*');
95
+ const regex = new RegExp(`^${regexPattern}$`);
214
96
  return regex.test(url);
215
97
  }
98
+
99
+ return false;
216
100
  } catch (e) {
217
- log(`正则表达式匹配错误: ${e.message}`);
101
+ console.error('URL匹配检查失败:', e);
102
+ return false;
218
103
  }
219
-
220
- return false;
221
104
  };
222
105
 
223
- // 验证目标URL是否有效
224
- const isValidTargetUrl = (url) => {
225
- if (!url) return false;
226
-
106
+ /**
107
+ * 加载启用的接口数据
108
+ * @returns {Promise<Array>} 启用的接口列表
109
+ */
110
+ const loadEnabledInterfaces = async () => {
227
111
  try {
228
- // 检查是否是合法的URL格式
229
- new URL(url);
230
- return true;
231
- } catch (e) {
232
- log(`URL验证错误: ${e.message}`);
233
- return false;
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 || [];
234
132
  }
235
133
  };
236
134
 
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;
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;
247
144
  }
248
145
 
249
- return true;
250
- };
251
-
252
- // 生成随机数工具函数
253
- const generateRandomValue = (pattern) => {
254
- // 如果不是以@开头的模式,直接返回原值
255
- if (!pattern || !pattern.startsWith('@')) {
256
- return pattern;
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;
257
162
  }
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
- });
266
163
  };
267
164
 
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}`];
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}`);
280
176
 
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
- });
177
+ // 验证参数
178
+ if (!fullUrl || !pattern || !proxyType) {
179
+ console.log('[getWhistleRules] 参数无效,返回空规则');
180
+ return '';
291
181
  }
292
182
 
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
- };
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 '';
318
225
  }
319
226
  };
320
227
 
321
- // Whistle 规则服务器实现
322
- module.exports = (server, options) => {
323
- // 优化:缓存常用路径匹配
324
- const debugConfigPath = '/_debug/config';
325
- const debugEnabledInterfacesPath = '/_debug/enabled-interfaces';
326
- const clearCachePath = '/_flush_cache';
228
+ /**
229
+ * 处理请求
230
+ * @param {object} ctx 上下文
231
+ * @returns {Promise<void>}
232
+ */
233
+ const handleRequest = async (ctx) => {
234
+ const { req, res } = ctx;
327
235
 
328
- server.on('request', (req, res) => {
329
- const oReq = req.originalReq;
330
-
331
- // 提取请求相关信息
332
- const fullUrl = oReq.url;
333
- logDebug(`原始URL: ${fullUrl}`);
334
-
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
- }
347
-
348
- // 特殊调试路径,查看缓存状态
349
- if (fullUrl.endsWith('/_cache_status')) {
350
- res.writeHead(200, { 'Content-Type': 'application/json' });
351
- res.end(JSON.stringify(cacheManager.getStatus()));
352
- return;
353
- }
236
+ try {
237
+ // 获取请求URL
238
+ const fullUrl = req.url;
354
239
 
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
- }
240
+ // 记录请求
241
+ console.log(`[规则服务器] 收到请求: ${fullUrl}`);
370
242
 
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
- }
243
+ // 加载启用的接口
244
+ const enabledInterfaces = await loadEnabledInterfaces();
386
245
 
387
- // 解析URL
388
- let parsedUrl;
389
- try {
390
- parsedUrl = url.parse(fullUrl);
391
- } catch (err) {
392
- logError(`解析URL失败: ${err.message}`);
393
- // 解析失败时,不处理请求
246
+ if (!enabledInterfaces || enabledInterfaces.length === 0) {
247
+ console.log('[规则服务器] 没有启用的接口');
394
248
  res.end('');
395
249
  return;
396
250
  }
397
251
 
398
- // 尝试获取 pathname 或 path,用于匹配
399
- const method = oReq.method;
400
- const path = parsedUrl.pathname || '';
401
-
402
- logDebug(`请求方法: ${method}, 请求路径: ${path}`);
252
+ // 查找匹配的接口
253
+ const matchedInterface = findMatchingInterface(enabledInterfaces, fullUrl);
403
254
 
404
- // 加载启用的接口配置(使用缓存优化)
405
- const enabledInterfaces = loadEnabledInterfaces();
406
-
407
- if (enabledInterfaces.length === 0) {
408
- logDebug('没有找到启用的接口配置,跳过处理');
255
+ if (!matchedInterface) {
256
+ console.log(`[规则服务器] 没有找到匹配的接口: ${fullUrl}`);
409
257
  res.end('');
410
258
  return;
411
259
  }
412
260
 
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;
261
+ // 获取接口信息
262
+ const { pattern, proxyType, targetUrl, customHeaders } = matchedInterface;
263
+
264
+ // 生成Whistle规则
265
+ const rule = getWhistleRules(fullUrl, pattern, proxyType, {
266
+ targetUrl,
267
+ customHeaders
450
268
  });
451
269
 
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
- });
270
+ // 记录生成的规则
271
+ console.log(`[规则服务器] 生成规则: ${rule}`);
272
+
273
+ // 返回规则
274
+ res.end(rule);
275
+ } catch (err) {
276
+ console.error('[规则服务器] 处理请求出错:', err);
277
+ res.end('');
278
+ }
279
+ };
280
+
281
+ // 导出规则服务器
282
+ module.exports = {
283
+ handleRequest
547
284
  };
package/lib/server.js CHANGED
@@ -410,6 +410,20 @@ 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
+
413
427
  // 获取原始完整URL的所有可能来源
414
428
  let originalUrl = null;
415
429
 
@@ -505,6 +519,20 @@ const handleLegacyRequest = (req, res, next) => {
505
519
  res.setHeader(key, proxyRes.headers[key]);
506
520
  });
507
521
 
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
+
508
536
  // 设置状态码
509
537
  res.statusCode = proxyRes.statusCode;
510
538
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whistle.mock-plugins",
3
- "version": "1.0.6-beta.0",
3
+ "version": "1.0.6-beta.2",
4
4
  "description": "Whistle 插件,用于快速创建 API 模拟数据",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {