whistle.mock-plugins 1.0.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/API.md +247 -0
- package/README.md +91 -0
- package/app/cgi-bin/features.js +285 -0
- package/app/cgi-bin/index.js +31 -0
- package/app/cgi-bin/interfaces.js +409 -0
- package/app/cgi-bin/logs.js +151 -0
- package/app/cgi-bin/test-interface.js +152 -0
- package/app/public/index.html +1 -0
- package/app/public/js/index.js +2 -0
- package/app/public/js/index.js.LICENSE.txt +150 -0
- package/app/util/index.js +24 -0
- package/index.html +52 -0
- package/initialize.js +32 -0
- package/lib/dataManager.js +500 -0
- package/lib/index.js +3 -0
- package/lib/models.js +64 -0
- package/lib/routes/features.js +213 -0
- package/lib/routes/interfaces.js +304 -0
- package/lib/routes/proxy.js +239 -0
- package/lib/ruleManager.js +551 -0
- package/lib/rules-server.js +267 -0
- package/lib/server.js +805 -0
- package/lib/storage.js +16 -0
- package/lib/ui-server.js +68 -0
- package/package.json +51 -0
- package/public/bundle.js +2 -0
- package/public/bundle.js.LICENSE.txt +38 -0
- package/public/index.html +1 -0
- package/public/plugin.mock-plugin.html +14 -0
- package/rules.txt +22 -0
- package/test/rulesServer.test.js +120 -0
- package/ui/src/App.css +173 -0
- package/ui/src/App.js +24 -0
- package/ui/src/components/AppLayout.js +149 -0
- package/ui/src/components/FeatureCard.js +53 -0
- package/ui/src/components/Layout.js +94 -0
- package/ui/src/components/MainNav.js +42 -0
- package/ui/src/components/PageContainer.js +16 -0
- package/ui/src/index.html +11 -0
- package/ui/src/index.js +43 -0
- package/ui/src/pages/FileProxy.js +235 -0
- package/ui/src/pages/FilesPage.js +292 -0
- package/ui/src/pages/Home.js +128 -0
- package/ui/src/pages/HomePage.js +195 -0
- package/ui/src/pages/InterfaceManagement.js +829 -0
- package/ui/src/pages/InterfacePage.js +342 -0
- package/ui/src/pages/MockData.js +472 -0
- package/ui/src/pages/Settings.js +192 -0
- package/ui/src/pages/SettingsPage.js +165 -0
- package/ui/src/pages/UrlRedirect.js +225 -0
- package/ui/src/pages/Welcome.js +155 -0
- package/ui/src/services/api.js +186 -0
- package/ui/src/styles/app-layout.css +337 -0
- package/ui/src/styles/feature-card.css +172 -0
- package/ui/src/styles/file-proxy.css +301 -0
- package/ui/src/styles/global.css +224 -0
- package/ui/src/styles/home.css +123 -0
- package/ui/src/styles/index.css +419 -0
- package/ui/src/styles/interface-management.css +694 -0
- package/ui/src/styles/layout.css +159 -0
- package/ui/src/styles/logs-page.css +427 -0
- package/ui/src/styles/main-nav.css +52 -0
- package/ui/src/styles/mock-data.css +850 -0
- package/ui/src/styles/page-container.css +11 -0
- package/ui/src/styles/settings.css +208 -0
- package/ui/src/styles/url-redirect.css +279 -0
- package/ui/src/styles/welcome.css +282 -0
- package/webpack.config.js +49 -0
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
module.exports = function(req, res) {
|
|
5
|
+
const dataDir = this.dataDir;
|
|
6
|
+
const interfacesFile = path.join(dataDir, 'interfaces.json');
|
|
7
|
+
const featuresFile = path.join(dataDir, 'features.json');
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
// 确保配置文件存在并包含有效结构
|
|
11
|
+
if (!fs.existsSync(interfacesFile)) {
|
|
12
|
+
fs.writeJsonSync(interfacesFile, { interfaces: [] }, { spaces: 2 });
|
|
13
|
+
} else {
|
|
14
|
+
// 验证文件结构
|
|
15
|
+
try {
|
|
16
|
+
const fileData = fs.readJsonSync(interfacesFile);
|
|
17
|
+
if (!fileData || !fileData.interfaces) {
|
|
18
|
+
// 如果文件存在但结构无效,重新初始化
|
|
19
|
+
fs.writeJsonSync(interfacesFile, { interfaces: [] }, { spaces: 2 });
|
|
20
|
+
console.log('修复了损坏的interfaces文件结构');
|
|
21
|
+
}
|
|
22
|
+
} catch (e) {
|
|
23
|
+
// JSON解析错误时重新初始化文件
|
|
24
|
+
fs.writeJsonSync(interfacesFile, { interfaces: [] }, { spaces: 2 });
|
|
25
|
+
console.log('修复了无法解析的interfaces文件');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 处理GET请求 - 获取接口列表
|
|
30
|
+
if (req.method === 'GET') {
|
|
31
|
+
const { featureId } = req.query;
|
|
32
|
+
|
|
33
|
+
if (!featureId) {
|
|
34
|
+
return res.status(400).json({
|
|
35
|
+
code: 400,
|
|
36
|
+
message: '功能ID不能为空',
|
|
37
|
+
data: null
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 检查功能是否存在
|
|
42
|
+
let featureExists = false;
|
|
43
|
+
if (fs.existsSync(featuresFile)) {
|
|
44
|
+
try {
|
|
45
|
+
const featuresData = fs.readJsonSync(featuresFile);
|
|
46
|
+
if (featuresData && featuresData.features) {
|
|
47
|
+
const feature = featuresData.features.find(item => item.id === featureId);
|
|
48
|
+
featureExists = !!feature;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!featureExists) {
|
|
52
|
+
return res.status(404).json({
|
|
53
|
+
code: 404,
|
|
54
|
+
message: '功能模块不存在',
|
|
55
|
+
data: null
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
} catch (err) {
|
|
59
|
+
console.error('读取功能文件错误:', err);
|
|
60
|
+
// 出错时继续处理
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 读取接口数据
|
|
65
|
+
let interfacesData;
|
|
66
|
+
try {
|
|
67
|
+
interfacesData = fs.readJsonSync(interfacesFile);
|
|
68
|
+
if (!interfacesData || !interfacesData.interfaces) {
|
|
69
|
+
interfacesData = { interfaces: [] };
|
|
70
|
+
}
|
|
71
|
+
} catch (err) {
|
|
72
|
+
console.error('读取接口列表错误:', err);
|
|
73
|
+
interfacesData = { interfaces: [] };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const filteredInterfaces = interfacesData.interfaces.filter(item => item.featureId === featureId);
|
|
77
|
+
|
|
78
|
+
return res.json({
|
|
79
|
+
code: 0,
|
|
80
|
+
message: '成功',
|
|
81
|
+
data: filteredInterfaces
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 处理POST请求 - 添加或更新接口
|
|
86
|
+
if (req.method === 'POST') {
|
|
87
|
+
const interfaceData = req.body;
|
|
88
|
+
|
|
89
|
+
// 参数验证
|
|
90
|
+
if (!interfaceData.featureId) {
|
|
91
|
+
return res.status(400).json({
|
|
92
|
+
code: 400,
|
|
93
|
+
message: '功能ID不能为空',
|
|
94
|
+
data: null
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!interfaceData.name) {
|
|
99
|
+
return res.status(400).json({
|
|
100
|
+
code: 400,
|
|
101
|
+
message: '接口名称不能为空',
|
|
102
|
+
data: null
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!interfaceData.urlPattern) {
|
|
107
|
+
return res.status(400).json({
|
|
108
|
+
code: 400,
|
|
109
|
+
message: 'URL匹配规则不能为空',
|
|
110
|
+
data: null
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 检查是否响应内容过大,超过100MB时给予特殊处理
|
|
115
|
+
if (interfaceData.responseContent && Buffer.byteLength(interfaceData.responseContent, 'utf8') > 100 * 1024 * 1024) {
|
|
116
|
+
return res.status(413).json({
|
|
117
|
+
code: 413,
|
|
118
|
+
message: '响应内容过大,超过100MB限制',
|
|
119
|
+
data: null
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 检查功能是否存在
|
|
124
|
+
let featureExists = false;
|
|
125
|
+
if (fs.existsSync(featuresFile)) {
|
|
126
|
+
try {
|
|
127
|
+
const featuresData = fs.readJsonSync(featuresFile);
|
|
128
|
+
if (featuresData && featuresData.features) {
|
|
129
|
+
const feature = featuresData.features.find(item => item.id === interfaceData.featureId);
|
|
130
|
+
featureExists = !!feature;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (!featureExists) {
|
|
134
|
+
return res.status(404).json({
|
|
135
|
+
code: 404,
|
|
136
|
+
message: '功能模块不存在',
|
|
137
|
+
data: null
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
} catch (err) {
|
|
141
|
+
console.error('读取功能文件错误:', err);
|
|
142
|
+
// 忽略错误继续处理
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 读取接口数据
|
|
147
|
+
let interfacesData;
|
|
148
|
+
try {
|
|
149
|
+
interfacesData = fs.readJsonSync(interfacesFile);
|
|
150
|
+
if (!interfacesData || !interfacesData.interfaces) {
|
|
151
|
+
interfacesData = { interfaces: [] };
|
|
152
|
+
}
|
|
153
|
+
} catch (err) {
|
|
154
|
+
console.error('读取接口列表错误:', err);
|
|
155
|
+
interfacesData = { interfaces: [] };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 如果有ID则更新,否则创建新接口
|
|
159
|
+
if (interfaceData.id) {
|
|
160
|
+
// 更新现有接口
|
|
161
|
+
const index = interfacesData.interfaces.findIndex(item => item.id === interfaceData.id);
|
|
162
|
+
|
|
163
|
+
if (index === -1) {
|
|
164
|
+
return res.status(404).json({
|
|
165
|
+
code: 404,
|
|
166
|
+
message: '接口不存在',
|
|
167
|
+
data: null
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// 更新接口
|
|
172
|
+
interfacesData.interfaces[index] = {
|
|
173
|
+
...interfacesData.interfaces[index],
|
|
174
|
+
name: interfaceData.name,
|
|
175
|
+
urlPattern: interfaceData.urlPattern,
|
|
176
|
+
proxyType: interfaceData.proxyType,
|
|
177
|
+
responseContent: interfaceData.responseContent,
|
|
178
|
+
targetUrl: interfaceData.targetUrl,
|
|
179
|
+
filePath: interfaceData.filePath,
|
|
180
|
+
httpStatus: interfaceData.httpStatus || 200,
|
|
181
|
+
responseDelay: interfaceData.responseDelay || 0,
|
|
182
|
+
httpMethod: interfaceData.httpMethod || 'ALL',
|
|
183
|
+
active: interfaceData.active !== false
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
fs.writeJsonSync(interfacesFile, interfacesData, { spaces: 2 });
|
|
187
|
+
|
|
188
|
+
return res.json({
|
|
189
|
+
code: 0,
|
|
190
|
+
message: '接口更新成功',
|
|
191
|
+
data: interfacesData.interfaces[index]
|
|
192
|
+
});
|
|
193
|
+
} else {
|
|
194
|
+
// 创建新接口
|
|
195
|
+
const newInterface = {
|
|
196
|
+
id: Date.now().toString(),
|
|
197
|
+
featureId: interfaceData.featureId,
|
|
198
|
+
name: interfaceData.name,
|
|
199
|
+
urlPattern: interfaceData.urlPattern,
|
|
200
|
+
proxyType: interfaceData.proxyType || 'response',
|
|
201
|
+
responseContent: interfaceData.responseContent || '',
|
|
202
|
+
targetUrl: interfaceData.targetUrl || '',
|
|
203
|
+
filePath: interfaceData.filePath || '',
|
|
204
|
+
httpStatus: interfaceData.httpStatus || 200,
|
|
205
|
+
responseDelay: interfaceData.responseDelay || 0,
|
|
206
|
+
httpMethod: interfaceData.httpMethod || 'ALL',
|
|
207
|
+
active: interfaceData.active !== false,
|
|
208
|
+
contentType: interfaceData.contentType || 'text/plain'
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// 确保interfaces数组存在
|
|
212
|
+
if (!Array.isArray(interfacesData.interfaces)) {
|
|
213
|
+
interfacesData.interfaces = [];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
interfacesData.interfaces.push(newInterface);
|
|
217
|
+
fs.writeJsonSync(interfacesFile, interfacesData, { spaces: 2 });
|
|
218
|
+
|
|
219
|
+
return res.json({
|
|
220
|
+
code: 0,
|
|
221
|
+
message: '接口创建成功',
|
|
222
|
+
data: newInterface
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// 处理DELETE请求 - 删除接口
|
|
228
|
+
if (req.method === 'DELETE') {
|
|
229
|
+
const { id } = req.query;
|
|
230
|
+
|
|
231
|
+
if (!id) {
|
|
232
|
+
return res.status(400).json({
|
|
233
|
+
code: 400,
|
|
234
|
+
message: '接口ID不能为空',
|
|
235
|
+
data: null
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
let interfacesData;
|
|
240
|
+
try {
|
|
241
|
+
interfacesData = fs.readJsonSync(interfacesFile);
|
|
242
|
+
if (!interfacesData || !interfacesData.interfaces) {
|
|
243
|
+
interfacesData = { interfaces: [] };
|
|
244
|
+
}
|
|
245
|
+
} catch (err) {
|
|
246
|
+
console.error('读取接口列表错误:', err);
|
|
247
|
+
interfacesData = { interfaces: [] };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const index = interfacesData.interfaces.findIndex(item => item.id === id);
|
|
251
|
+
|
|
252
|
+
if (index === -1) {
|
|
253
|
+
return res.status(404).json({
|
|
254
|
+
code: 404,
|
|
255
|
+
message: '接口不存在',
|
|
256
|
+
data: null
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// 删除接口
|
|
261
|
+
const deletedInterface = interfacesData.interfaces.splice(index, 1)[0];
|
|
262
|
+
fs.writeJsonSync(interfacesFile, interfacesData, { spaces: 2 });
|
|
263
|
+
|
|
264
|
+
return res.json({
|
|
265
|
+
code: 0,
|
|
266
|
+
message: '接口删除成功',
|
|
267
|
+
data: deletedInterface
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// 处理 PUT 请求 - 更新接口
|
|
272
|
+
if (req.method === 'PUT') {
|
|
273
|
+
const { id } = req.query;
|
|
274
|
+
const interfaceData = req.body;
|
|
275
|
+
|
|
276
|
+
if (!id) {
|
|
277
|
+
return res.status(400).json({
|
|
278
|
+
code: 400,
|
|
279
|
+
message: '接口ID不能为空',
|
|
280
|
+
data: null
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// 读取接口数据
|
|
285
|
+
let interfacesData;
|
|
286
|
+
try {
|
|
287
|
+
interfacesData = fs.readJsonSync(interfacesFile);
|
|
288
|
+
if (!interfacesData || !interfacesData.interfaces) {
|
|
289
|
+
interfacesData = { interfaces: [] };
|
|
290
|
+
}
|
|
291
|
+
} catch (err) {
|
|
292
|
+
console.error('读取接口列表错误:', err);
|
|
293
|
+
return res.status(500).json({
|
|
294
|
+
code: 500,
|
|
295
|
+
message: '读取接口数据失败',
|
|
296
|
+
data: null
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// 查找并更新接口
|
|
301
|
+
const index = interfacesData.interfaces.findIndex(item => item.id === id);
|
|
302
|
+
|
|
303
|
+
if (index === -1) {
|
|
304
|
+
return res.status(404).json({
|
|
305
|
+
code: 404,
|
|
306
|
+
message: '接口不存在',
|
|
307
|
+
data: null
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// 更新接口数据
|
|
312
|
+
const updatedInterface = {
|
|
313
|
+
...interfacesData.interfaces[index],
|
|
314
|
+
name: interfaceData.name || interfacesData.interfaces[index].name,
|
|
315
|
+
urlPattern: interfaceData.urlPattern || interfacesData.interfaces[index].urlPattern,
|
|
316
|
+
proxyType: interfaceData.proxyType || interfacesData.interfaces[index].proxyType,
|
|
317
|
+
responseContent: interfaceData.responseContent !== undefined ? interfaceData.responseContent : interfacesData.interfaces[index].responseContent,
|
|
318
|
+
targetUrl: interfaceData.targetUrl !== undefined ? interfaceData.targetUrl : interfacesData.interfaces[index].targetUrl,
|
|
319
|
+
filePath: interfaceData.filePath !== undefined ? interfaceData.filePath : interfacesData.interfaces[index].filePath,
|
|
320
|
+
httpStatus: interfaceData.httpStatus || interfacesData.interfaces[index].httpStatus,
|
|
321
|
+
responseDelay: interfaceData.responseDelay !== undefined ? interfaceData.responseDelay : interfacesData.interfaces[index].responseDelay,
|
|
322
|
+
httpMethod: interfaceData.httpMethod || interfacesData.interfaces[index].httpMethod,
|
|
323
|
+
active: interfaceData.active !== undefined ? interfaceData.active : interfacesData.interfaces[index].active,
|
|
324
|
+
contentType: interfaceData.contentType || interfacesData.interfaces[index].contentType,
|
|
325
|
+
updatedAt: new Date().toISOString()
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
interfacesData.interfaces[index] = updatedInterface;
|
|
329
|
+
fs.writeJsonSync(interfacesFile, interfacesData, { spaces: 2 });
|
|
330
|
+
|
|
331
|
+
return res.json({
|
|
332
|
+
code: 0,
|
|
333
|
+
message: '接口更新成功',
|
|
334
|
+
data: updatedInterface
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// 处理 PATCH 请求 - 部分更新接口(如启用/禁用状态)
|
|
339
|
+
if (req.method === 'PATCH') {
|
|
340
|
+
const { id } = req.query;
|
|
341
|
+
const patchData = req.body;
|
|
342
|
+
|
|
343
|
+
if (!id) {
|
|
344
|
+
return res.status(400).json({
|
|
345
|
+
code: 400,
|
|
346
|
+
message: '接口ID不能为空',
|
|
347
|
+
data: null
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// 读取接口数据
|
|
352
|
+
let interfacesData;
|
|
353
|
+
try {
|
|
354
|
+
interfacesData = fs.readJsonSync(interfacesFile);
|
|
355
|
+
if (!interfacesData || !interfacesData.interfaces) {
|
|
356
|
+
interfacesData = { interfaces: [] };
|
|
357
|
+
}
|
|
358
|
+
} catch (err) {
|
|
359
|
+
console.error('读取接口列表错误:', err);
|
|
360
|
+
return res.status(500).json({
|
|
361
|
+
code: 500,
|
|
362
|
+
message: '读取接口数据失败',
|
|
363
|
+
data: null
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// 查找接口
|
|
368
|
+
const index = interfacesData.interfaces.findIndex(item => item.id === id);
|
|
369
|
+
|
|
370
|
+
if (index === -1) {
|
|
371
|
+
return res.status(404).json({
|
|
372
|
+
code: 404,
|
|
373
|
+
message: '接口不存在',
|
|
374
|
+
data: null
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// 部分更新接口数据
|
|
379
|
+
const updatedInterface = {
|
|
380
|
+
...interfacesData.interfaces[index],
|
|
381
|
+
...patchData,
|
|
382
|
+
updatedAt: new Date().toISOString()
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
interfacesData.interfaces[index] = updatedInterface;
|
|
386
|
+
fs.writeJsonSync(interfacesFile, interfacesData, { spaces: 2 });
|
|
387
|
+
|
|
388
|
+
return res.json({
|
|
389
|
+
code: 0,
|
|
390
|
+
message: '接口更新成功',
|
|
391
|
+
data: updatedInterface
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// 其他请求方法
|
|
396
|
+
return res.status(405).json({
|
|
397
|
+
code: 405,
|
|
398
|
+
message: '不支持的请求方法',
|
|
399
|
+
data: null
|
|
400
|
+
});
|
|
401
|
+
} catch (err) {
|
|
402
|
+
console.error('处理接口请求出错:', err);
|
|
403
|
+
return res.status(500).json({
|
|
404
|
+
code: 500,
|
|
405
|
+
message: '服务器内部错误: ' + err.message,
|
|
406
|
+
data: null
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
module.exports = function(req, res) {
|
|
5
|
+
const dataDir = this.dataDir;
|
|
6
|
+
const logsFile = path.join(dataDir, 'logs.json');
|
|
7
|
+
|
|
8
|
+
// 处理GET请求 - 获取日志
|
|
9
|
+
if (req.method === 'GET') {
|
|
10
|
+
try {
|
|
11
|
+
// 解析查询参数
|
|
12
|
+
const limit = parseInt(req.query.limit) || 100;
|
|
13
|
+
const type = req.query.type || '';
|
|
14
|
+
const keyword = req.query.keyword || '';
|
|
15
|
+
const page = parseInt(req.query.page) || 1;
|
|
16
|
+
const pageSize = parseInt(req.query.pageSize) || 20;
|
|
17
|
+
|
|
18
|
+
// 确保日志文件存在
|
|
19
|
+
if (!fs.existsSync(logsFile)) {
|
|
20
|
+
fs.writeJsonSync(logsFile, { logs: [] }, { spaces: 2 });
|
|
21
|
+
return res.json({
|
|
22
|
+
code: 0,
|
|
23
|
+
message: '成功',
|
|
24
|
+
data: {
|
|
25
|
+
logs: [],
|
|
26
|
+
total: 0,
|
|
27
|
+
page: 1,
|
|
28
|
+
pageSize: pageSize,
|
|
29
|
+
totalPages: 0
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 读取日志
|
|
35
|
+
const logsData = fs.readJsonSync(logsFile);
|
|
36
|
+
|
|
37
|
+
// 确保日志结构正确
|
|
38
|
+
if (!logsData || !logsData.logs || !Array.isArray(logsData.logs)) {
|
|
39
|
+
logsData.logs = [];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 标准化日志格式,确保每个日志项都有正确的字段
|
|
43
|
+
let logs = logsData.logs.map(log => {
|
|
44
|
+
if (!log) return null;
|
|
45
|
+
|
|
46
|
+
// 标准化事件类型字段,确保兼容前端过滤
|
|
47
|
+
// 将type转换为eventType确保前端能够正确过滤
|
|
48
|
+
return {
|
|
49
|
+
id: log.id || Date.now().toString(),
|
|
50
|
+
timestamp: log.timestamp || new Date().toISOString(),
|
|
51
|
+
eventType: log.eventType || log.type || 'unknown',
|
|
52
|
+
type: log.type || log.eventType || 'unknown',
|
|
53
|
+
method: log.method || '',
|
|
54
|
+
url: log.url || '',
|
|
55
|
+
status: log.status || '',
|
|
56
|
+
message: log.message || '',
|
|
57
|
+
pattern: log.pattern || '',
|
|
58
|
+
...log // 保留原有其他字段
|
|
59
|
+
};
|
|
60
|
+
}).filter(Boolean); // 过滤掉null
|
|
61
|
+
|
|
62
|
+
// 根据类型过滤
|
|
63
|
+
if (type && type !== 'all') {
|
|
64
|
+
try {
|
|
65
|
+
logs = logs.filter(log =>
|
|
66
|
+
log && (log.eventType === type || log.type === type)
|
|
67
|
+
);
|
|
68
|
+
} catch (err) {
|
|
69
|
+
console.error('按类型过滤日志失败:', err);
|
|
70
|
+
// 出错时不过滤
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 根据关键词过滤
|
|
75
|
+
if (keyword) {
|
|
76
|
+
try {
|
|
77
|
+
logs = logs.filter(log => {
|
|
78
|
+
if (!log) return false;
|
|
79
|
+
|
|
80
|
+
// 检查关键字段是否包含关键词
|
|
81
|
+
const url = (log.url || '').toLowerCase();
|
|
82
|
+
const pattern = (log.pattern || '').toLowerCase();
|
|
83
|
+
const message = (log.message || '').toLowerCase();
|
|
84
|
+
const lowercaseKeyword = keyword.toLowerCase();
|
|
85
|
+
|
|
86
|
+
return url.includes(lowercaseKeyword) ||
|
|
87
|
+
pattern.includes(lowercaseKeyword) ||
|
|
88
|
+
message.includes(lowercaseKeyword);
|
|
89
|
+
});
|
|
90
|
+
} catch (err) {
|
|
91
|
+
console.error('按关键词过滤日志失败:', err);
|
|
92
|
+
// 出错时不过滤
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 计算总记录数和总页数
|
|
97
|
+
const total = logs.length;
|
|
98
|
+
const totalPages = Math.ceil(total / pageSize);
|
|
99
|
+
|
|
100
|
+
// 分页
|
|
101
|
+
const startIndex = (page - 1) * pageSize;
|
|
102
|
+
const pagedLogs = logs.slice(startIndex, startIndex + pageSize);
|
|
103
|
+
|
|
104
|
+
return res.json({
|
|
105
|
+
code: 0,
|
|
106
|
+
message: '成功',
|
|
107
|
+
data: {
|
|
108
|
+
logs: pagedLogs,
|
|
109
|
+
total: total,
|
|
110
|
+
page: page,
|
|
111
|
+
pageSize: pageSize,
|
|
112
|
+
totalPages: totalPages
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
} catch (err) {
|
|
116
|
+
console.error('获取日志失败:', err);
|
|
117
|
+
return res.status(500).json({
|
|
118
|
+
code: 500,
|
|
119
|
+
message: '获取日志失败: ' + err.message,
|
|
120
|
+
data: null
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 处理DELETE请求 - 清空日志
|
|
126
|
+
if (req.method === 'DELETE') {
|
|
127
|
+
try {
|
|
128
|
+
fs.writeJsonSync(logsFile, { logs: [] }, { spaces: 2 });
|
|
129
|
+
|
|
130
|
+
return res.json({
|
|
131
|
+
code: 0,
|
|
132
|
+
message: '日志已清空',
|
|
133
|
+
data: null
|
|
134
|
+
});
|
|
135
|
+
} catch (err) {
|
|
136
|
+
console.error('清空日志失败:', err);
|
|
137
|
+
return res.status(500).json({
|
|
138
|
+
code: 500,
|
|
139
|
+
message: '清空日志失败: ' + err.message,
|
|
140
|
+
data: null
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 其他请求方法
|
|
146
|
+
return res.status(405).json({
|
|
147
|
+
code: 405,
|
|
148
|
+
message: '不支持的请求方法',
|
|
149
|
+
data: null
|
|
150
|
+
});
|
|
151
|
+
};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const Mock = require('mockjs');
|
|
4
|
+
|
|
5
|
+
module.exports = async function(req, res) {
|
|
6
|
+
const dataDir = this.dataDir;
|
|
7
|
+
const interfacesFile = path.join(dataDir, 'interfaces.json');
|
|
8
|
+
const featuresFile = path.join(dataDir, 'features.json');
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
// 只处理 POST 请求
|
|
12
|
+
if (req.method === 'POST') {
|
|
13
|
+
const { url, interfaceId } = req.body;
|
|
14
|
+
|
|
15
|
+
// 参数验证
|
|
16
|
+
if (!url) {
|
|
17
|
+
return res.status(400).json({
|
|
18
|
+
code: 400,
|
|
19
|
+
message: '测试URL不能为空',
|
|
20
|
+
data: null
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!interfaceId) {
|
|
25
|
+
return res.status(400).json({
|
|
26
|
+
code: 400,
|
|
27
|
+
message: '接口ID不能为空',
|
|
28
|
+
data: null
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 读取接口数据
|
|
33
|
+
let interfacesData;
|
|
34
|
+
try {
|
|
35
|
+
interfacesData = fs.readJsonSync(interfacesFile);
|
|
36
|
+
if (!interfacesData || !interfacesData.interfaces) {
|
|
37
|
+
return res.status(404).json({
|
|
38
|
+
code: 404,
|
|
39
|
+
message: '未找到接口数据',
|
|
40
|
+
data: null
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
} catch (err) {
|
|
44
|
+
return res.status(500).json({
|
|
45
|
+
code: 500,
|
|
46
|
+
message: '读取接口数据失败',
|
|
47
|
+
data: null
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 查找指定接口
|
|
52
|
+
const targetInterface = interfacesData.interfaces.find(item => item.id === interfaceId);
|
|
53
|
+
|
|
54
|
+
if (!targetInterface) {
|
|
55
|
+
return res.status(404).json({
|
|
56
|
+
code: 404,
|
|
57
|
+
message: '指定的接口不存在',
|
|
58
|
+
data: null
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 检查接口是否已禁用
|
|
63
|
+
if (targetInterface.active === false) {
|
|
64
|
+
return res.status(400).json({
|
|
65
|
+
code: 400,
|
|
66
|
+
message: '接口已禁用,无法测试',
|
|
67
|
+
data: null
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 检查功能模块是否已禁用
|
|
72
|
+
try {
|
|
73
|
+
const featuresData = fs.readJsonSync(featuresFile);
|
|
74
|
+
|
|
75
|
+
if (featuresData && featuresData.features) {
|
|
76
|
+
const featureItem = featuresData.features.find(item => item.id === targetInterface.featureId);
|
|
77
|
+
|
|
78
|
+
if (featureItem && featureItem.active === false) {
|
|
79
|
+
return res.status(400).json({
|
|
80
|
+
code: 400,
|
|
81
|
+
message: '所属功能模块已禁用,接口无法使用',
|
|
82
|
+
data: null
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} catch (err) {
|
|
87
|
+
// 忽略读取错误,继续处理
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const startTime = Date.now();
|
|
91
|
+
|
|
92
|
+
// 处理内容,支持 Mock.js 模板语法
|
|
93
|
+
let responseBody = targetInterface.responseContent;
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
// 如果响应内容是JSON,则尝试应用Mock.js模板
|
|
97
|
+
if (targetInterface.contentType && targetInterface.contentType.includes('json')) {
|
|
98
|
+
// 尝试解析JSON
|
|
99
|
+
const jsonTemplate = JSON.parse(responseBody);
|
|
100
|
+
// 使用Mock.js生成随机数据
|
|
101
|
+
const mockData = Mock.mock(jsonTemplate);
|
|
102
|
+
// 将结果转换回字符串
|
|
103
|
+
responseBody = JSON.stringify(mockData);
|
|
104
|
+
}
|
|
105
|
+
} catch (err) {
|
|
106
|
+
// 如果Mock失败,使用原始内容
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 模拟接口处理延迟
|
|
110
|
+
const delay = targetInterface.responseDelay || 0;
|
|
111
|
+
if (delay > 0) {
|
|
112
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const endTime = Date.now();
|
|
116
|
+
const duration = endTime - startTime;
|
|
117
|
+
|
|
118
|
+
// 模拟接口处理
|
|
119
|
+
const responseData = {
|
|
120
|
+
statusCode: targetInterface.httpStatus || 200,
|
|
121
|
+
contentType: targetInterface.contentType || 'application/json; charset=utf-8',
|
|
122
|
+
responseBody: responseBody,
|
|
123
|
+
mockInfo: {
|
|
124
|
+
matchedPattern: targetInterface.urlPattern,
|
|
125
|
+
requestMethod: targetInterface.httpMethod,
|
|
126
|
+
delay: delay,
|
|
127
|
+
duration: duration,
|
|
128
|
+
timestamp: new Date().toISOString()
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
return res.json({
|
|
133
|
+
code: 0,
|
|
134
|
+
message: '成功',
|
|
135
|
+
data: responseData
|
|
136
|
+
});
|
|
137
|
+
} else {
|
|
138
|
+
// 不支持的请求方法
|
|
139
|
+
return res.status(405).json({
|
|
140
|
+
code: 405,
|
|
141
|
+
message: '不支持的请求方法',
|
|
142
|
+
data: null
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
} catch (err) {
|
|
146
|
+
return res.status(500).json({
|
|
147
|
+
code: 500,
|
|
148
|
+
message: '服务器内部错误: ' + err.message,
|
|
149
|
+
data: null
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Whistle Mock 数据工厂</title><script defer="defer" src="/js/index.js"></script></head><body><div id="root"></div></body></html>
|