whistle.mock-plugins 1.0.5 → 1.0.6-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/cgi-bin/test-interface.js +264 -261
- package/app/public/js/index.js +1 -1
- package/lib/rules-server.js +246 -140
- package/package.json +1 -1
- package/ui/src/pages/InterfaceManagement.js +69 -29
- package/ui/src/pages/MockData.js +32 -0
|
@@ -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
|
+
|
|
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,114 @@ 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
|
-
}
|
|
237
|
+
// 检查接口URL是否与测试URL匹配
|
|
238
|
+
const pattern = targetInterface.pattern;
|
|
239
|
+
const proxyType = targetInterface.proxyType || 'response';
|
|
86
240
|
|
|
87
|
-
//
|
|
88
|
-
|
|
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
|
-
}
|
|
241
|
+
// 使用URL匹配检查
|
|
242
|
+
const isMatch = isUrlMatchPattern(url, pattern, proxyType);
|
|
105
243
|
|
|
106
|
-
|
|
244
|
+
// 如果存在method配置,检查HTTP方法是否匹配
|
|
245
|
+
const methodMatch = !targetInterface.method ||
|
|
246
|
+
targetInterface.method === 'all' ||
|
|
247
|
+
targetInterface.method.toLowerCase() === 'all' ||
|
|
248
|
+
req.method.toLowerCase() === targetInterface.method.toLowerCase();
|
|
107
249
|
|
|
108
|
-
//
|
|
109
|
-
|
|
250
|
+
// 确定匹配结果
|
|
251
|
+
const matchResult = isMatch && methodMatch;
|
|
110
252
|
|
|
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;
|
|
253
|
+
// 构建测试结果
|
|
254
|
+
const result = {
|
|
255
|
+
interfaceId: targetInterface.id,
|
|
256
|
+
interfaceName: targetInterface.name,
|
|
257
|
+
proxyType: targetInterface.proxyType,
|
|
258
|
+
pattern: targetInterface.pattern,
|
|
259
|
+
url: url,
|
|
260
|
+
isMatch: matchResult,
|
|
261
|
+
matchDetail: {
|
|
262
|
+
urlMatch: isMatch,
|
|
263
|
+
methodMatch: methodMatch
|
|
147
264
|
}
|
|
148
265
|
};
|
|
149
266
|
|
|
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
|
-
});
|
|
267
|
+
// 根据代理类型处理额外信息
|
|
268
|
+
if (matchResult) {
|
|
269
|
+
if (proxyType === 'response') {
|
|
270
|
+
// 添加响应数据(如果有)
|
|
271
|
+
if (targetInterface.response) {
|
|
272
|
+
result.responseData = targetInterface.response;
|
|
205
273
|
}
|
|
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()
|
|
274
|
+
} else if (proxyType === 'redirect' || proxyType === 'url_redirect') {
|
|
275
|
+
// 添加重定向信息(如果有)
|
|
276
|
+
if (targetInterface.targetUrl) {
|
|
277
|
+
// 处理动态生成的目标URL
|
|
278
|
+
const targetUrl = generateRandomValue(targetInterface.targetUrl);
|
|
279
|
+
result.redirectInfo = {
|
|
280
|
+
targetUrl: targetUrl,
|
|
281
|
+
targetFormatted: targetUrl
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
// 添加自定义请求头(如果有)
|
|
285
|
+
if (targetInterface.customHeaders) {
|
|
286
|
+
try {
|
|
287
|
+
result.redirectInfo.customHeaders = targetInterface.customHeaders;
|
|
288
|
+
} catch (e) {
|
|
289
|
+
console.error('解析自定义请求头失败:', e);
|
|
290
|
+
}
|
|
251
291
|
}
|
|
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
292
|
}
|
|
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
|
-
});
|
|
293
|
+
}
|
|
323
294
|
}
|
|
324
295
|
|
|
296
|
+
// 返回测试结果
|
|
325
297
|
return res.json({
|
|
326
|
-
code:
|
|
327
|
-
message: '
|
|
328
|
-
data:
|
|
298
|
+
code: 200,
|
|
299
|
+
message: matchResult ? '接口匹配成功' : '接口匹配失败',
|
|
300
|
+
data: result
|
|
329
301
|
});
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// 处理缓存刷新请求
|
|
305
|
+
if (req.path === '/_flush_cache') {
|
|
306
|
+
// 清空缓存
|
|
307
|
+
enabledInterfacesCache = null;
|
|
308
|
+
cacheTime = 0;
|
|
309
|
+
|
|
310
|
+
return res.json({
|
|
311
|
+
code: 200,
|
|
312
|
+
message: '缓存已刷新',
|
|
335
313
|
data: null
|
|
336
314
|
});
|
|
337
315
|
}
|
|
316
|
+
|
|
317
|
+
// 处理缓存状态请求
|
|
318
|
+
if (req.path === '/_cache_status') {
|
|
319
|
+
return res.json({
|
|
320
|
+
code: 200,
|
|
321
|
+
message: '缓存状态',
|
|
322
|
+
data: {
|
|
323
|
+
enabledInterfaces: {
|
|
324
|
+
cached: !!enabledInterfacesCache,
|
|
325
|
+
lastUpdate: cacheTime,
|
|
326
|
+
count: enabledInterfacesCache ? enabledInterfacesCache.length : 0
|
|
327
|
+
},
|
|
328
|
+
cacheInterval: CONFIG.CACHE_INTERVAL
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// 其他请求返回405
|
|
334
|
+
return res.status(405).json({
|
|
335
|
+
code: 405,
|
|
336
|
+
message: '方法不允许',
|
|
337
|
+
data: null
|
|
338
|
+
});
|
|
338
339
|
} catch (err) {
|
|
340
|
+
console.error('测试接口处理错误:', err);
|
|
341
|
+
|
|
339
342
|
return res.status(500).json({
|
|
340
343
|
code: 500,
|
|
341
|
-
message: '服务器内部错误: ' + err.message,
|
|
344
|
+
message: '服务器内部错误: ' + (err.message || err),
|
|
342
345
|
data: null
|
|
343
346
|
});
|
|
344
347
|
}
|