whistle.mock-plugins 1.0.6 → 1.1.1-beta.1
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/app/cgi-bin/interfaces.js +38 -0
- package/app/cgi-bin/test-interface.js +260 -262
- package/app/public/js/index.js +1 -1
- package/app/public/js/index.js.LICENSE.txt +8 -2
- package/lib/models.js +12 -1
- package/lib/routes/interfaces.js +274 -56
- package/lib/ruleManager.js +209 -60
- package/lib/rules-server.js +246 -139
- package/lib/server.js +120 -0
- package/package.json +1 -1
- package/ui/src/components/interface-management/InterfaceTestModal.js +313 -0
- package/ui/src/components/interface-management/PreviewModal.js +50 -0
- package/ui/src/components/interface-management/README.md +184 -0
- package/ui/src/components/interface-management/ResponseContentEditor.js +427 -0
- package/ui/src/components/interface-management/constants.js +36 -0
- package/ui/src/components/interface-management/index.js +6 -0
- package/ui/src/components/interface-management/utils.js +103 -0
- package/ui/src/pages/InterfaceManagement.js +508 -653
- package/ui/src/pages/MockData.js +32 -0
|
@@ -75,6 +75,17 @@ module.exports = function(req, res) {
|
|
|
75
75
|
|
|
76
76
|
const filteredInterfaces = interfacesData.interfaces.filter(item => item.featureId === featureId);
|
|
77
77
|
|
|
78
|
+
console.log('GET获取接口列表:', {
|
|
79
|
+
featureId,
|
|
80
|
+
totalCount: filteredInterfaces.length,
|
|
81
|
+
interfacesWithResponses: filteredInterfaces.map(item => ({
|
|
82
|
+
name: item.name,
|
|
83
|
+
hasResponses: !!item.responses,
|
|
84
|
+
responsesCount: item.responses ? item.responses.length : 0,
|
|
85
|
+
activeResponseId: item.activeResponseId
|
|
86
|
+
}))
|
|
87
|
+
});
|
|
88
|
+
|
|
78
89
|
return res.json({
|
|
79
90
|
code: 0,
|
|
80
91
|
message: '成功',
|
|
@@ -175,8 +186,11 @@ module.exports = function(req, res) {
|
|
|
175
186
|
urlPattern: interfaceData.urlPattern,
|
|
176
187
|
proxyType: interfaceData.proxyType,
|
|
177
188
|
responseContent: interfaceData.responseContent,
|
|
189
|
+
responses: interfaceData.responses || interfacesData.interfaces[index].responses || [],
|
|
190
|
+
activeResponseId: interfaceData.activeResponseId !== undefined ? interfaceData.activeResponseId : interfacesData.interfaces[index].activeResponseId,
|
|
178
191
|
targetUrl: interfaceData.targetUrl,
|
|
179
192
|
customHeaders: interfaceData.customHeaders || {},
|
|
193
|
+
paramMatchers: interfaceData.paramMatchers || [],
|
|
180
194
|
filePath: interfaceData.filePath,
|
|
181
195
|
httpStatus: interfaceData.httpStatus || 200,
|
|
182
196
|
responseDelay: interfaceData.responseDelay || 0,
|
|
@@ -184,6 +198,12 @@ module.exports = function(req, res) {
|
|
|
184
198
|
active: interfaceData.active !== false
|
|
185
199
|
};
|
|
186
200
|
|
|
201
|
+
console.log('更新接口,包含多响应数据:', {
|
|
202
|
+
name: interfacesData.interfaces[index].name,
|
|
203
|
+
responsesCount: interfacesData.interfaces[index].responses.length,
|
|
204
|
+
activeResponseId: interfacesData.interfaces[index].activeResponseId
|
|
205
|
+
});
|
|
206
|
+
|
|
187
207
|
fs.writeJsonSync(interfacesFile, interfacesData, { spaces: 2 });
|
|
188
208
|
|
|
189
209
|
return res.json({
|
|
@@ -200,8 +220,11 @@ module.exports = function(req, res) {
|
|
|
200
220
|
urlPattern: interfaceData.urlPattern,
|
|
201
221
|
proxyType: interfaceData.proxyType || 'response',
|
|
202
222
|
responseContent: interfaceData.responseContent || '',
|
|
223
|
+
responses: interfaceData.responses || [],
|
|
224
|
+
activeResponseId: interfaceData.activeResponseId || null,
|
|
203
225
|
targetUrl: interfaceData.targetUrl || '',
|
|
204
226
|
customHeaders: interfaceData.customHeaders || {},
|
|
227
|
+
paramMatchers: interfaceData.paramMatchers || [],
|
|
205
228
|
filePath: interfaceData.filePath || '',
|
|
206
229
|
httpStatus: interfaceData.httpStatus || 200,
|
|
207
230
|
responseDelay: interfaceData.responseDelay || 0,
|
|
@@ -210,6 +233,12 @@ module.exports = function(req, res) {
|
|
|
210
233
|
contentType: interfaceData.contentType || 'text/plain'
|
|
211
234
|
};
|
|
212
235
|
|
|
236
|
+
console.log('创建新接口,包含多响应数据:', {
|
|
237
|
+
name: newInterface.name,
|
|
238
|
+
responsesCount: newInterface.responses.length,
|
|
239
|
+
activeResponseId: newInterface.activeResponseId
|
|
240
|
+
});
|
|
241
|
+
|
|
213
242
|
// 确保interfaces数组存在
|
|
214
243
|
if (!Array.isArray(interfacesData.interfaces)) {
|
|
215
244
|
interfacesData.interfaces = [];
|
|
@@ -317,8 +346,11 @@ module.exports = function(req, res) {
|
|
|
317
346
|
urlPattern: interfaceData.urlPattern || interfacesData.interfaces[index].urlPattern,
|
|
318
347
|
proxyType: interfaceData.proxyType || interfacesData.interfaces[index].proxyType,
|
|
319
348
|
responseContent: interfaceData.responseContent !== undefined ? interfaceData.responseContent : interfacesData.interfaces[index].responseContent,
|
|
349
|
+
responses: interfaceData.responses !== undefined ? interfaceData.responses : (interfacesData.interfaces[index].responses || []),
|
|
350
|
+
activeResponseId: interfaceData.activeResponseId !== undefined ? interfaceData.activeResponseId : interfacesData.interfaces[index].activeResponseId,
|
|
320
351
|
targetUrl: interfaceData.targetUrl !== undefined ? interfaceData.targetUrl : interfacesData.interfaces[index].targetUrl,
|
|
321
352
|
customHeaders: interfaceData.customHeaders !== undefined ? interfaceData.customHeaders : interfacesData.interfaces[index].customHeaders || {},
|
|
353
|
+
paramMatchers: interfaceData.paramMatchers !== undefined ? interfaceData.paramMatchers : interfacesData.interfaces[index].paramMatchers || [],
|
|
322
354
|
filePath: interfaceData.filePath !== undefined ? interfaceData.filePath : interfacesData.interfaces[index].filePath,
|
|
323
355
|
httpStatus: interfaceData.httpStatus || interfacesData.interfaces[index].httpStatus,
|
|
324
356
|
responseDelay: interfaceData.responseDelay !== undefined ? interfaceData.responseDelay : interfacesData.interfaces[index].responseDelay,
|
|
@@ -328,6 +360,12 @@ module.exports = function(req, res) {
|
|
|
328
360
|
updatedAt: new Date().toISOString()
|
|
329
361
|
};
|
|
330
362
|
|
|
363
|
+
console.log('PUT更新接口,包含多响应数据:', {
|
|
364
|
+
name: updatedInterface.name,
|
|
365
|
+
responsesCount: updatedInterface.responses.length,
|
|
366
|
+
activeResponseId: updatedInterface.activeResponseId
|
|
367
|
+
});
|
|
368
|
+
|
|
331
369
|
interfacesData.interfaces[index] = updatedInterface;
|
|
332
370
|
fs.writeJsonSync(interfacesFile, interfacesData, { spaces: 2 });
|
|
333
371
|
|
|
@@ -3,6 +3,26 @@ const path = require('path');
|
|
|
3
3
|
const Mock = require('mockjs');
|
|
4
4
|
const url = require('url');
|
|
5
5
|
|
|
6
|
+
// 缓存控制
|
|
7
|
+
const CONFIG = {
|
|
8
|
+
// 缓存有效期(毫秒),默认 60 秒
|
|
9
|
+
CACHE_INTERVAL: process.env.MOCK_PLUGIN_CACHE_INTERVAL || 60000
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// 缓存控制变量
|
|
13
|
+
let enabledInterfacesCache = null; // 仅缓存启用状态的接口
|
|
14
|
+
let cacheTime = 0;
|
|
15
|
+
|
|
16
|
+
// 获取文件修改时间
|
|
17
|
+
const getFileModTime = (filePath) => {
|
|
18
|
+
try {
|
|
19
|
+
const stats = fs.statSync(filePath);
|
|
20
|
+
return stats.mtimeMs;
|
|
21
|
+
} catch (err) {
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
6
26
|
// 生成随机数工具函数(与rules-server.js中保持一致)
|
|
7
27
|
const generateRandomValue = (pattern) => {
|
|
8
28
|
// 如果不是以@开头的模式,直接返回原值
|
|
@@ -11,13 +31,160 @@ const generateRandomValue = (pattern) => {
|
|
|
11
31
|
}
|
|
12
32
|
|
|
13
33
|
const formatPattern = pattern.substring(1); // 去掉@前缀
|
|
14
|
-
//
|
|
34
|
+
// 使用单次正则替换优化性能
|
|
15
35
|
return formatPattern.replace(/x/g, () => {
|
|
16
36
|
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
|
17
37
|
return chars.charAt(Math.floor(Math.random() * chars.length));
|
|
18
38
|
});
|
|
19
39
|
};
|
|
20
40
|
|
|
41
|
+
// 正则表达式缓存
|
|
42
|
+
const regexCache = new Map();
|
|
43
|
+
|
|
44
|
+
// URL匹配函数,提取为纯函数减少重复代码
|
|
45
|
+
const isUrlMatchPattern = (url, pattern, proxyType) => {
|
|
46
|
+
if (!url || !pattern) return false;
|
|
47
|
+
console.log(`isUrlMatchPattern ${url} - ${pattern}`);
|
|
48
|
+
try {
|
|
49
|
+
// 对于url_redirect类型,需要完全匹配
|
|
50
|
+
if (proxyType === 'url_redirect') {
|
|
51
|
+
return url === pattern;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 对于redirect类型,只要url以pattern开头即可命中(前缀匹配)
|
|
55
|
+
if (proxyType === 'redirect') {
|
|
56
|
+
return url.startsWith(pattern);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 默认的匹配逻辑(用于response类型等)
|
|
60
|
+
// 精确匹配
|
|
61
|
+
if (pattern === url) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 正则表达式
|
|
66
|
+
if (pattern.startsWith('/') && pattern.length > 2 && pattern.endsWith('/')) {
|
|
67
|
+
// 使用缓存的正则表达式
|
|
68
|
+
const cacheKey = `regex:${pattern}`;
|
|
69
|
+
let regex = regexCache.get(cacheKey);
|
|
70
|
+
|
|
71
|
+
if (!regex) {
|
|
72
|
+
regex = new RegExp(pattern.slice(1, -1));
|
|
73
|
+
regexCache.set(cacheKey, regex);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return regex.test(url);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 通配符模式
|
|
80
|
+
if (pattern.includes('*')) {
|
|
81
|
+
// 使用缓存的正则表达式
|
|
82
|
+
const cacheKey = `wildcard:${pattern}`;
|
|
83
|
+
let regex = regexCache.get(cacheKey);
|
|
84
|
+
|
|
85
|
+
if (!regex) {
|
|
86
|
+
const regexPattern = pattern
|
|
87
|
+
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
88
|
+
.replace(/\*/g, '.*');
|
|
89
|
+
regex = new RegExp(`^${regexPattern}$`);
|
|
90
|
+
regexCache.set(cacheKey, regex);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return regex.test(url);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return false;
|
|
97
|
+
} catch (e) {
|
|
98
|
+
console.error('URL匹配检查失败:', e);
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// 加载启用的接口数据,带缓存
|
|
104
|
+
const loadEnabledInterfaces = (dataDir, interfacesFile, featuresFile) => {
|
|
105
|
+
try {
|
|
106
|
+
const currentTime = Date.now();
|
|
107
|
+
const interfacesFileModTime = getFileModTime(interfacesFile);
|
|
108
|
+
const featuresFileModTime = getFileModTime(featuresFile);
|
|
109
|
+
|
|
110
|
+
// 取两个文件的最后修改时间的最大值
|
|
111
|
+
const lastModTime = Math.max(interfacesFileModTime, featuresFileModTime);
|
|
112
|
+
|
|
113
|
+
// 使用缓存
|
|
114
|
+
if (enabledInterfacesCache &&
|
|
115
|
+
currentTime - cacheTime < CONFIG.CACHE_INTERVAL &&
|
|
116
|
+
lastModTime <= cacheTime) {
|
|
117
|
+
return enabledInterfacesCache;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 1. 读取并解析所有功能模块
|
|
121
|
+
let enabledFeatureIds = [];
|
|
122
|
+
if (fs.existsSync(featuresFile)) {
|
|
123
|
+
try {
|
|
124
|
+
const featuresData = fs.readJsonSync(featuresFile);
|
|
125
|
+
let features = [];
|
|
126
|
+
|
|
127
|
+
if (featuresData && Array.isArray(featuresData)) {
|
|
128
|
+
features = featuresData;
|
|
129
|
+
} else if (featuresData && featuresData.features && Array.isArray(featuresData.features)) {
|
|
130
|
+
features = featuresData.features;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 过滤出启用状态的功能ID
|
|
134
|
+
enabledFeatureIds = features
|
|
135
|
+
.filter(feature => feature.active === true)
|
|
136
|
+
.map(feature => feature.id);
|
|
137
|
+
} catch (err) {
|
|
138
|
+
console.error('读取功能模块失败:', err);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 2. 读取并解析所有接口
|
|
143
|
+
let enabledInterfaces = [];
|
|
144
|
+
if (fs.existsSync(interfacesFile)) {
|
|
145
|
+
try {
|
|
146
|
+
const interfacesData = fs.readJsonSync(interfacesFile);
|
|
147
|
+
let interfaces = [];
|
|
148
|
+
|
|
149
|
+
if (interfacesData && Array.isArray(interfacesData)) {
|
|
150
|
+
interfaces = interfacesData;
|
|
151
|
+
} else if (interfacesData && interfacesData.interfaces && Array.isArray(interfacesData.interfaces)) {
|
|
152
|
+
interfaces = interfacesData.interfaces;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 过滤出启用状态的接口
|
|
156
|
+
enabledInterfaces = interfaces.filter(intf => {
|
|
157
|
+
// 首先检查接口本身是否启用
|
|
158
|
+
const isInterfaceEnabled = intf.active !== false; // 默认为true
|
|
159
|
+
|
|
160
|
+
if (!isInterfaceEnabled) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// 如果接口有关联功能,检查功能是否启用
|
|
165
|
+
if (intf.featureId) {
|
|
166
|
+
return enabledFeatureIds.includes(intf.featureId);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 如果接口没有关联功能,只要接口本身启用就可以
|
|
170
|
+
return true;
|
|
171
|
+
});
|
|
172
|
+
} catch (err) {
|
|
173
|
+
console.error('读取接口配置失败:', err);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 更新缓存
|
|
178
|
+
enabledInterfacesCache = enabledInterfaces;
|
|
179
|
+
cacheTime = currentTime;
|
|
180
|
+
|
|
181
|
+
return enabledInterfaces;
|
|
182
|
+
} catch (err) {
|
|
183
|
+
console.error('加载启用接口失败:', err);
|
|
184
|
+
return enabledInterfacesCache || [];
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
21
188
|
module.exports = async function(req, res) {
|
|
22
189
|
const dataDir = this.dataDir;
|
|
23
190
|
const interfacesFile = path.join(dataDir, 'interfaces.json');
|
|
@@ -45,27 +212,19 @@ module.exports = async function(req, res) {
|
|
|
45
212
|
});
|
|
46
213
|
}
|
|
47
214
|
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
message: '未找到接口数据',
|
|
56
|
-
data: null
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
} catch (err) {
|
|
60
|
-
return res.status(500).json({
|
|
61
|
-
code: 500,
|
|
62
|
-
message: '读取接口数据失败',
|
|
215
|
+
// 读取接口数据(使用缓存)
|
|
216
|
+
const interfaces = loadEnabledInterfaces(dataDir, interfacesFile, featuresFile);
|
|
217
|
+
|
|
218
|
+
if (!interfaces || interfaces.length === 0) {
|
|
219
|
+
return res.status(404).json({
|
|
220
|
+
code: 404,
|
|
221
|
+
message: '未找到接口数据',
|
|
63
222
|
data: null
|
|
64
223
|
});
|
|
65
224
|
}
|
|
66
225
|
|
|
67
226
|
// 查找指定接口
|
|
68
|
-
const targetInterface =
|
|
227
|
+
const targetInterface = interfaces.find(item => item.id === interfaceId);
|
|
69
228
|
|
|
70
229
|
if (!targetInterface) {
|
|
71
230
|
return res.status(404).json({
|
|
@@ -75,270 +234,109 @@ module.exports = async function(req, res) {
|
|
|
75
234
|
});
|
|
76
235
|
}
|
|
77
236
|
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
code: 400,
|
|
82
|
-
message: '接口已禁用,无法测试',
|
|
83
|
-
data: null
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// 检查功能模块是否已禁用
|
|
88
|
-
try {
|
|
89
|
-
const featuresData = fs.readJsonSync(featuresFile);
|
|
90
|
-
|
|
91
|
-
if (featuresData && featuresData.features) {
|
|
92
|
-
const featureItem = featuresData.features.find(item => item.id === targetInterface.featureId);
|
|
93
|
-
|
|
94
|
-
if (featureItem && featureItem.active === false) {
|
|
95
|
-
return res.status(400).json({
|
|
96
|
-
code: 400,
|
|
97
|
-
message: '所属功能模块已禁用,接口无法使用',
|
|
98
|
-
data: null
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
} catch (err) {
|
|
103
|
-
// 忽略读取错误,继续处理
|
|
104
|
-
}
|
|
237
|
+
// 检查接口URL是否与测试URL匹配
|
|
238
|
+
const pattern = targetInterface.pattern;
|
|
239
|
+
const proxyType = targetInterface.proxyType || 'response';
|
|
105
240
|
|
|
106
|
-
|
|
241
|
+
// 使用URL匹配检查
|
|
242
|
+
const isMatch = isUrlMatchPattern(url, pattern, proxyType);
|
|
107
243
|
|
|
108
|
-
//
|
|
109
|
-
|
|
244
|
+
// 测试接口只验证URL模式匹配,不验证HTTP方法
|
|
245
|
+
// 因为这里的req.method是测试请求的POST方法,与接口配置的HTTP方法无关
|
|
246
|
+
const matchResult = isMatch;
|
|
110
247
|
|
|
111
|
-
//
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (proxyType === 'redirect') {
|
|
123
|
-
return url.indexOf(pattern) === 0;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// 默认的匹配逻辑(用于response类型等)
|
|
127
|
-
// 如果是正则表达式
|
|
128
|
-
if (pattern.startsWith('/') && pattern.endsWith('/')) {
|
|
129
|
-
const regex = new RegExp(pattern.slice(1, -1));
|
|
130
|
-
return regex.test(url);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// 如果是通配符模式
|
|
134
|
-
if (pattern.includes('*')) {
|
|
135
|
-
const regexPattern = pattern
|
|
136
|
-
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
137
|
-
.replace(/\*/g, '.*');
|
|
138
|
-
const regex = new RegExp(`^${regexPattern}$`);
|
|
139
|
-
return regex.test(url);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// 精确匹配
|
|
143
|
-
return url === pattern;
|
|
144
|
-
} catch (e) {
|
|
145
|
-
console.error('URL匹配检查失败:', e);
|
|
146
|
-
return false;
|
|
248
|
+
// 构建测试结果
|
|
249
|
+
const result = {
|
|
250
|
+
interfaceId: targetInterface.id,
|
|
251
|
+
interfaceName: targetInterface.name,
|
|
252
|
+
proxyType: targetInterface.proxyType,
|
|
253
|
+
pattern: targetInterface.pattern,
|
|
254
|
+
url: url,
|
|
255
|
+
isMatch: matchResult,
|
|
256
|
+
matchDetail: {
|
|
257
|
+
urlMatch: isMatch,
|
|
258
|
+
note: 'HTTP方法匹配在测试模式下不进行验证,仅验证URL模式匹配'
|
|
147
259
|
}
|
|
148
260
|
};
|
|
149
261
|
|
|
150
|
-
//
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
try {
|
|
157
|
-
// 如果响应内容是JSON,则尝试应用Mock.js模板
|
|
158
|
-
if (targetInterface.contentType && targetInterface.contentType.includes('json')) {
|
|
159
|
-
// 尝试解析JSON
|
|
160
|
-
const jsonTemplate = JSON.parse(responseBody);
|
|
161
|
-
// 使用Mock.js生成随机数据
|
|
162
|
-
const mockData = Mock.mock(jsonTemplate);
|
|
163
|
-
// 将结果转换回字符串
|
|
164
|
-
responseBody = JSON.stringify(mockData);
|
|
165
|
-
}
|
|
166
|
-
} catch (err) {
|
|
167
|
-
// 如果Mock失败,使用原始内容
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// 模拟接口处理延迟
|
|
171
|
-
const delay = targetInterface.responseDelay || 0;
|
|
172
|
-
if (delay > 0) {
|
|
173
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const endTime = Date.now();
|
|
177
|
-
const duration = endTime - startTime;
|
|
178
|
-
|
|
179
|
-
// 模拟接口处理
|
|
180
|
-
responseData = {
|
|
181
|
-
statusCode: targetInterface.httpStatus || 200,
|
|
182
|
-
contentType: targetInterface.contentType || 'application/json; charset=utf-8',
|
|
183
|
-
responseBody: responseBody,
|
|
184
|
-
mockInfo: {
|
|
185
|
-
matchedPattern: targetInterface.urlPattern,
|
|
186
|
-
requestMethod: targetInterface.httpMethod,
|
|
187
|
-
delay: delay,
|
|
188
|
-
duration: duration,
|
|
189
|
-
timestamp: new Date().toISOString()
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
break;
|
|
193
|
-
|
|
194
|
-
case 'redirect':
|
|
195
|
-
// 处理重定向
|
|
196
|
-
const redirectTarget = targetInterface.targetUrl || '';
|
|
197
|
-
|
|
198
|
-
// 验证目标URL是否有效
|
|
199
|
-
if (!redirectTarget) {
|
|
200
|
-
return res.status(400).json({
|
|
201
|
-
code: 400,
|
|
202
|
-
message: '重定向目标URL不能为空',
|
|
203
|
-
data: null
|
|
204
|
-
});
|
|
262
|
+
// 根据代理类型处理额外信息
|
|
263
|
+
if (matchResult) {
|
|
264
|
+
if (proxyType === 'response') {
|
|
265
|
+
// 添加响应数据(如果有)
|
|
266
|
+
if (targetInterface.response) {
|
|
267
|
+
result.responseData = targetInterface.response;
|
|
205
268
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
data: null
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// 处理自定义请求头,包括随机值
|
|
228
|
-
const redirectHeaders = targetInterface.customHeaders || {};
|
|
229
|
-
const processedHeaders = {};
|
|
230
|
-
|
|
231
|
-
// 处理随机值,转换为实际值
|
|
232
|
-
Object.entries(redirectHeaders).forEach(([key, value]) => {
|
|
233
|
-
processedHeaders[key] = generateRandomValue(value);
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
const formattedRedirectHeaders = Object.entries(processedHeaders)
|
|
237
|
-
.map(([key, value]) => `${key}: ${value}`)
|
|
238
|
-
.join('\n');
|
|
239
|
-
|
|
240
|
-
responseData = {
|
|
241
|
-
statusCode: 302,
|
|
242
|
-
contentType: 'application/json; charset=utf-8',
|
|
243
|
-
proxyType: 'redirect',
|
|
244
|
-
targetUrl: redirectTarget,
|
|
245
|
-
customHeaders: processedHeaders,
|
|
246
|
-
formattedHeaders: formattedRedirectHeaders,
|
|
247
|
-
mockInfo: {
|
|
248
|
-
matchedPattern: targetInterface.urlPattern,
|
|
249
|
-
requestMethod: targetInterface.httpMethod,
|
|
250
|
-
timestamp: new Date().toISOString()
|
|
269
|
+
} else if (proxyType === 'redirect' || proxyType === 'url_redirect') {
|
|
270
|
+
// 添加重定向信息(如果有)
|
|
271
|
+
if (targetInterface.targetUrl) {
|
|
272
|
+
// 处理动态生成的目标URL
|
|
273
|
+
const targetUrl = generateRandomValue(targetInterface.targetUrl);
|
|
274
|
+
result.redirectInfo = {
|
|
275
|
+
targetUrl: targetUrl,
|
|
276
|
+
targetFormatted: targetUrl
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
// 添加自定义请求头(如果有)
|
|
280
|
+
if (targetInterface.customHeaders) {
|
|
281
|
+
try {
|
|
282
|
+
result.redirectInfo.customHeaders = targetInterface.customHeaders;
|
|
283
|
+
} catch (e) {
|
|
284
|
+
console.error('解析自定义请求头失败:', e);
|
|
285
|
+
}
|
|
251
286
|
}
|
|
252
|
-
};
|
|
253
|
-
break;
|
|
254
|
-
|
|
255
|
-
case 'url_redirect':
|
|
256
|
-
// 处理URL重定向
|
|
257
|
-
const baseUrl = targetInterface.targetUrl || '';
|
|
258
|
-
|
|
259
|
-
// 验证基础URL是否有效
|
|
260
|
-
if (!baseUrl) {
|
|
261
|
-
return res.status(400).json({
|
|
262
|
-
code: 400,
|
|
263
|
-
message: 'URL重定向目标URL不能为空',
|
|
264
|
-
data: null
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
try {
|
|
269
|
-
// 验证URL格式
|
|
270
|
-
new URL(baseUrl);
|
|
271
|
-
} catch (err) {
|
|
272
|
-
return res.status(400).json({
|
|
273
|
-
code: 400,
|
|
274
|
-
message: 'URL重定向目标URL格式无效,必须包含http://或https://',
|
|
275
|
-
data: null
|
|
276
|
-
});
|
|
277
287
|
}
|
|
278
|
-
|
|
279
|
-
// 验证URL完全匹配
|
|
280
|
-
if (url !== targetInterface.urlPattern) {
|
|
281
|
-
return res.status(400).json({
|
|
282
|
-
code: 400,
|
|
283
|
-
message: `测试URL不匹配URL重定向规则,URL必须完全匹配 ${targetInterface.urlPattern}`,
|
|
284
|
-
data: null
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// 处理自定义请求头
|
|
289
|
-
const urlRedirectHeaders = targetInterface.customHeaders || {};
|
|
290
|
-
const processedUrlRedirectHeaders = {};
|
|
291
|
-
|
|
292
|
-
// 处理随机值,转换为实际值
|
|
293
|
-
Object.entries(urlRedirectHeaders).forEach(([key, value]) => {
|
|
294
|
-
processedUrlRedirectHeaders[key] = generateRandomValue(value);
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
const formattedUrlRedirectHeaders = Object.entries(processedUrlRedirectHeaders)
|
|
298
|
-
.map(([key, value]) => `${key}: ${value}`)
|
|
299
|
-
.join('\n');
|
|
300
|
-
|
|
301
|
-
responseData = {
|
|
302
|
-
statusCode: 302,
|
|
303
|
-
contentType: 'application/json; charset=utf-8',
|
|
304
|
-
proxyType: 'url_redirect',
|
|
305
|
-
targetUrl: baseUrl,
|
|
306
|
-
customHeaders: processedUrlRedirectHeaders,
|
|
307
|
-
formattedHeaders: formattedUrlRedirectHeaders,
|
|
308
|
-
mockInfo: {
|
|
309
|
-
matchedPattern: targetInterface.urlPattern,
|
|
310
|
-
requestMethod: targetInterface.httpMethod,
|
|
311
|
-
timestamp: new Date().toISOString()
|
|
312
|
-
}
|
|
313
|
-
};
|
|
314
|
-
break;
|
|
315
|
-
|
|
316
|
-
default:
|
|
317
|
-
// 不支持的代理类型
|
|
318
|
-
return res.status(400).json({
|
|
319
|
-
code: 400,
|
|
320
|
-
message: `不支持的代理类型: ${targetInterface.proxyType || 'undefined'}`,
|
|
321
|
-
data: null
|
|
322
|
-
});
|
|
288
|
+
}
|
|
323
289
|
}
|
|
324
290
|
|
|
291
|
+
// 返回测试结果
|
|
325
292
|
return res.json({
|
|
326
|
-
code:
|
|
327
|
-
message: '
|
|
328
|
-
data:
|
|
293
|
+
code: 200,
|
|
294
|
+
message: matchResult ? '接口匹配成功' : '接口匹配失败',
|
|
295
|
+
data: result
|
|
329
296
|
});
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// 处理缓存刷新请求
|
|
300
|
+
if (req.path === '/_flush_cache') {
|
|
301
|
+
// 清空缓存
|
|
302
|
+
enabledInterfacesCache = null;
|
|
303
|
+
cacheTime = 0;
|
|
304
|
+
|
|
305
|
+
return res.json({
|
|
306
|
+
code: 200,
|
|
307
|
+
message: '缓存已刷新',
|
|
335
308
|
data: null
|
|
336
309
|
});
|
|
337
310
|
}
|
|
311
|
+
|
|
312
|
+
// 处理缓存状态请求
|
|
313
|
+
if (req.path === '/_cache_status') {
|
|
314
|
+
return res.json({
|
|
315
|
+
code: 200,
|
|
316
|
+
message: '缓存状态',
|
|
317
|
+
data: {
|
|
318
|
+
enabledInterfaces: {
|
|
319
|
+
cached: !!enabledInterfacesCache,
|
|
320
|
+
lastUpdate: cacheTime,
|
|
321
|
+
count: enabledInterfacesCache ? enabledInterfacesCache.length : 0
|
|
322
|
+
},
|
|
323
|
+
cacheInterval: CONFIG.CACHE_INTERVAL
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// 其他请求返回405
|
|
329
|
+
return res.status(405).json({
|
|
330
|
+
code: 405,
|
|
331
|
+
message: '方法不允许',
|
|
332
|
+
data: null
|
|
333
|
+
});
|
|
338
334
|
} catch (err) {
|
|
335
|
+
console.error('测试接口处理错误:', err);
|
|
336
|
+
|
|
339
337
|
return res.status(500).json({
|
|
340
338
|
code: 500,
|
|
341
|
-
message: '服务器内部错误: ' + err.message,
|
|
339
|
+
message: '服务器内部错误: ' + (err.message || err),
|
|
342
340
|
data: null
|
|
343
341
|
});
|
|
344
342
|
}
|