whistle.mock-plugins 1.0.6-beta.1 → 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.
- package/lib/rules-server.js +210 -473
- package/package.json +1 -1
package/lib/rules-server.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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
|
-
|
|
69
|
-
|
|
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
|
-
|
|
153
|
-
return
|
|
52
|
+
console.error('[isValidTargetUrl] 验证URL出错:', err);
|
|
53
|
+
return false;
|
|
154
54
|
}
|
|
155
55
|
};
|
|
156
56
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
173
|
-
return fullUrl.startsWith(pattern);
|
|
75
|
+
return url.startsWith(pattern);
|
|
174
76
|
}
|
|
175
77
|
|
|
176
|
-
//
|
|
177
|
-
|
|
178
|
-
// 精确匹配 - 最高效的匹配方式
|
|
78
|
+
// 默认的匹配逻辑(用于response类型等)
|
|
79
|
+
// 精确匹配
|
|
179
80
|
if (pattern === url) {
|
|
180
81
|
return true;
|
|
181
82
|
}
|
|
182
|
-
|
|
183
|
-
//
|
|
184
|
-
if (pattern.
|
|
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.
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
|
|
101
|
+
console.error('URL匹配检查失败:', e);
|
|
102
|
+
return false;
|
|
218
103
|
}
|
|
219
|
-
|
|
220
|
-
return false;
|
|
221
104
|
};
|
|
222
105
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
106
|
+
/**
|
|
107
|
+
* 加载启用的接口数据
|
|
108
|
+
* @returns {Promise<Array>} 启用的接口列表
|
|
109
|
+
*/
|
|
110
|
+
const loadEnabledInterfaces = async () => {
|
|
227
111
|
try {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
if (
|
|
245
|
-
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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 (
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
//
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
228
|
+
/**
|
|
229
|
+
* 处理请求
|
|
230
|
+
* @param {object} ctx 上下文
|
|
231
|
+
* @returns {Promise<void>}
|
|
232
|
+
*/
|
|
233
|
+
const handleRequest = async (ctx) => {
|
|
234
|
+
const { req, res } = ctx;
|
|
327
235
|
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
388
|
-
|
|
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
|
-
//
|
|
399
|
-
const
|
|
400
|
-
const path = parsedUrl.pathname || '';
|
|
401
|
-
|
|
402
|
-
logDebug(`请求方法: ${method}, 请求路径: ${path}`);
|
|
252
|
+
// 查找匹配的接口
|
|
253
|
+
const matchedInterface = findMatchingInterface(enabledInterfaces, fullUrl);
|
|
403
254
|
|
|
404
|
-
|
|
405
|
-
|
|
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
|
-
//
|
|
414
|
-
const
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
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
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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
|
};
|