whistle.mock-plugins 1.2.2-beta.8 → 1.3.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/COLUMN_RESIZE_IMPLEMENTATION.md +162 -0
- package/TEST_GUIDE.md +156 -0
- package/VERSION.md +57 -3
- package/app/cgi-bin/proxy-fetch.js +117 -0
- package/app/cgi-bin/settings.js +37 -0
- package/app/cgi-bin/stats.js +149 -0
- package/app/public/index.html +1 -1
- package/app/public/js/index.js +1 -1
- package/app/public/js/index.js.LICENSE.txt +25 -2
- package/lib/models.js +9 -0
- package/lib/routes/interfaces.js +6 -3
- package/lib/routes/settings.js +51 -0
- package/lib/ruleManager.js +122 -63
- package/lib/server.js +4 -0
- package/lib/ui-server.js +20 -4
- package/package.json +2 -1
- package/ui/src/App.css +1 -1
- package/ui/src/components/AppLayout.js +317 -80
- package/ui/src/components/FeatureCard.js +7 -2
- package/ui/src/components/KeyboardShortcutsHelp.js +58 -0
- package/ui/src/components/VersionModal.js +4 -41
- package/ui/src/components/interface-management/DataManagementDrawer.js +115 -0
- package/ui/src/components/interface-management/PreviewModal.js +4 -6
- package/ui/src/components/interface-management/RealResponseRecorder.js +165 -0
- package/ui/src/components/interface-management/ResponseContentEditor.js +124 -165
- package/ui/src/components/interface-management/index.js +1 -0
- package/ui/src/hooks/useKeyboardShortcuts.js +99 -0
- package/ui/src/index.html +5 -2
- package/ui/src/index.js +12 -2
- package/ui/src/pages/InterfaceManagementV2.js +565 -141
- package/ui/src/pages/MockDataV2.js +21 -13
- package/ui/src/pages/Settings.js +275 -8
- package/ui/src/pages/Sniffer.js +521 -0
- package/ui/src/pages/Stats.js +288 -0
- package/ui/src/styles/cyber-theme.css +1234 -0
- package/ui/src/styles/earth-theme.css +618 -0
- package/ui/src/styles/interface-management.css +37 -1
- package/ui/src/styles/settings.css +45 -1
|
@@ -10,6 +10,13 @@
|
|
|
10
10
|
http://jedwatson.github.io/classnames
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
/*!
|
|
14
|
+
* Determine if an object is a Buffer
|
|
15
|
+
*
|
|
16
|
+
* @author Feross Aboukhadijeh <https://feross.org>
|
|
17
|
+
* @license MIT
|
|
18
|
+
*/
|
|
19
|
+
|
|
13
20
|
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
|
|
14
21
|
|
|
15
22
|
/**
|
|
@@ -72,6 +79,8 @@
|
|
|
72
79
|
|
|
73
80
|
/** */
|
|
74
81
|
|
|
82
|
+
/** */
|
|
83
|
+
|
|
75
84
|
/** */
|
|
76
85
|
|
|
77
86
|
/** */
|
|
@@ -86,6 +95,10 @@
|
|
|
86
95
|
|
|
87
96
|
/** */
|
|
88
97
|
|
|
98
|
+
/** */
|
|
99
|
+
|
|
100
|
+
/** */
|
|
101
|
+
|
|
89
102
|
/** */
|
|
90
103
|
|
|
91
104
|
/** */
|
|
@@ -136,6 +149,8 @@
|
|
|
136
149
|
|
|
137
150
|
/** */
|
|
138
151
|
|
|
152
|
+
/** */
|
|
153
|
+
|
|
139
154
|
/** */
|
|
140
155
|
|
|
141
156
|
/** */
|
|
@@ -152,8 +167,6 @@
|
|
|
152
167
|
|
|
153
168
|
/** */
|
|
154
169
|
|
|
155
|
-
/** */
|
|
156
|
-
|
|
157
170
|
/** */
|
|
158
171
|
|
|
159
172
|
/** */
|
|
@@ -168,10 +181,20 @@
|
|
|
168
181
|
|
|
169
182
|
/** */
|
|
170
183
|
|
|
184
|
+
/** */
|
|
185
|
+
|
|
186
|
+
/** */
|
|
187
|
+
|
|
171
188
|
/** */
|
|
172
189
|
|
|
190
|
+
/** */
|
|
191
|
+
|
|
173
192
|
/** */
|
|
174
193
|
|
|
175
194
|
/** */
|
|
176
195
|
|
|
196
|
+
/** */
|
|
197
|
+
|
|
198
|
+
/** */
|
|
199
|
+
|
|
177
200
|
/** */
|
package/lib/models.js
CHANGED
|
@@ -22,6 +22,15 @@
|
|
|
22
22
|
* @property {string} name - 响应名称
|
|
23
23
|
* @property {string} description - 响应描述
|
|
24
24
|
* @property {string} content - 响应内容,使用 Mock.js 语法
|
|
25
|
+
* @property {Array<ParamMatcher>} paramMatchers - 响应级入参匹配规则,运行时按顺序匹配,第一个满足所有规则的响应被返回;无规则的响应作为默认兜底
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 参数匹配规则数据模型
|
|
30
|
+
* @typedef {Object} ParamMatcher
|
|
31
|
+
* @property {string} paramPath - 参数路径,支持嵌套路径(如 data.user.id)
|
|
32
|
+
* @property {string} paramValue - 期望值
|
|
33
|
+
* @property {string} matchType - 匹配类型,可选值: 'exact'(精确匹配), 'contains'(包含), 'regex'(正则)
|
|
25
34
|
*/
|
|
26
35
|
|
|
27
36
|
/**
|
package/lib/routes/interfaces.js
CHANGED
|
@@ -53,7 +53,8 @@ const processInterfaceData = (data) => {
|
|
|
53
53
|
id: resp.id,
|
|
54
54
|
name: resp.name,
|
|
55
55
|
description: resp.description || '',
|
|
56
|
-
content: resp.content || '{}'
|
|
56
|
+
content: resp.content || '{}',
|
|
57
|
+
paramMatchers: Array.isArray(resp.paramMatchers) ? resp.paramMatchers : []
|
|
57
58
|
};
|
|
58
59
|
|
|
59
60
|
console.log(`处理后的响应 ${index}:`, processedResp);
|
|
@@ -86,7 +87,8 @@ const processInterfaceData = (data) => {
|
|
|
86
87
|
id: Math.random().toString(36).substring(2, 15),
|
|
87
88
|
name: '默认响应',
|
|
88
89
|
description: '',
|
|
89
|
-
content: result.responseContent
|
|
90
|
+
content: result.responseContent,
|
|
91
|
+
paramMatchers: []
|
|
90
92
|
}];
|
|
91
93
|
result.activeResponseId = result.responses[0].id;
|
|
92
94
|
console.log('创建的默认响应:', result.responses[0]);
|
|
@@ -98,7 +100,8 @@ const processInterfaceData = (data) => {
|
|
|
98
100
|
id: Math.random().toString(36).substring(2, 15),
|
|
99
101
|
name: '默认响应',
|
|
100
102
|
description: '',
|
|
101
|
-
content: '{}'
|
|
103
|
+
content: '{}',
|
|
104
|
+
paramMatchers: []
|
|
102
105
|
}];
|
|
103
106
|
result.activeResponseId = result.responses[0].id;
|
|
104
107
|
result.responseContent = '{}';
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const storage = require('../storage');
|
|
5
|
+
|
|
6
|
+
const DATA_DIR = storage.DATA_DIR;
|
|
7
|
+
const SETTINGS_FILE = path.join(DATA_DIR, 'settings.json');
|
|
8
|
+
|
|
9
|
+
fs.ensureDirSync(DATA_DIR);
|
|
10
|
+
|
|
11
|
+
const DEFAULT_SETTINGS = {
|
|
12
|
+
interfaceParamMatcherEnabled: true,
|
|
13
|
+
responseParamMatcherEnabled: true,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const getSettings = () => {
|
|
17
|
+
try {
|
|
18
|
+
if (!fs.existsSync(SETTINGS_FILE)) return { ...DEFAULT_SETTINGS };
|
|
19
|
+
return { ...DEFAULT_SETTINGS, ...fs.readJsonSync(SETTINGS_FILE) };
|
|
20
|
+
} catch (err) {
|
|
21
|
+
return { ...DEFAULT_SETTINGS };
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const saveSettings = (settings) => {
|
|
26
|
+
fs.writeJsonSync(SETTINGS_FILE, settings, { spaces: 2 });
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const initRouter = () => {
|
|
30
|
+
const router = express.Router();
|
|
31
|
+
router.use(express.json());
|
|
32
|
+
|
|
33
|
+
router.get('/settings', (req, res) => {
|
|
34
|
+
res.json(getSettings());
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
router.put('/settings', (req, res) => {
|
|
38
|
+
try {
|
|
39
|
+
const current = getSettings();
|
|
40
|
+
const updated = { ...current, ...req.body };
|
|
41
|
+
saveSettings(updated);
|
|
42
|
+
res.json(updated);
|
|
43
|
+
} catch (err) {
|
|
44
|
+
res.status(500).json({ error: '保存设置失败' });
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return router;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
module.exports = { initRouter, getSettings };
|
package/lib/ruleManager.js
CHANGED
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
|
+
const fse = require('fs-extra');
|
|
2
3
|
const path = require('path');
|
|
3
4
|
const url = require('url');
|
|
4
5
|
const Mock = require('mockjs');
|
|
6
|
+
const storage = require('./storage');
|
|
7
|
+
|
|
8
|
+
const SETTINGS_FILE = path.join(storage.DATA_DIR, 'settings.json');
|
|
9
|
+
|
|
10
|
+
const getSettings = () => {
|
|
11
|
+
try {
|
|
12
|
+
if (!fse.existsSync(SETTINGS_FILE)) return { interfaceParamMatcherEnabled: true, responseParamMatcherEnabled: true };
|
|
13
|
+
const s = fse.readJsonSync(SETTINGS_FILE);
|
|
14
|
+
return {
|
|
15
|
+
interfaceParamMatcherEnabled: s.interfaceParamMatcherEnabled !== false,
|
|
16
|
+
responseParamMatcherEnabled: s.responseParamMatcherEnabled !== false,
|
|
17
|
+
};
|
|
18
|
+
} catch (e) {
|
|
19
|
+
return { interfaceParamMatcherEnabled: true, responseParamMatcherEnabled: true };
|
|
20
|
+
}
|
|
21
|
+
};
|
|
5
22
|
|
|
6
23
|
// 获取嵌套对象属性值的工具函数
|
|
7
24
|
const getNestedValue = (obj, path) => {
|
|
@@ -268,46 +285,34 @@ const ruleManager = {
|
|
|
268
285
|
return null;
|
|
269
286
|
}
|
|
270
287
|
|
|
271
|
-
// 在URL
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
288
|
+
// 在URL匹配的接口中,查找参数也匹配的接口(受接口入参匹配开关控制)
|
|
289
|
+
const settings = getSettings();
|
|
290
|
+
if (settings.interfaceParamMatcherEnabled) {
|
|
291
|
+
for (const intf of urlMatchedInterfaces) {
|
|
292
|
+
if (isParamMatch(req, intf.paramMatchers, this.log)) {
|
|
293
|
+
this.log(`[规则处理器] 找到完全匹配的接口: ${intf.name} (URL + 参数匹配)`);
|
|
294
|
+
return intf;
|
|
295
|
+
} else {
|
|
296
|
+
this.log(`[规则处理器] 接口 ${intf.name} URL匹配但参数不匹配,继续查找...`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
282
299
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
300
|
+
// 如果没有参数匹配的接口,返回第一个没有参数匹配规则的接口
|
|
301
|
+
const fallbackInterface = urlMatchedInterfaces.find(intf =>
|
|
302
|
+
!intf.paramMatchers || !Array.isArray(intf.paramMatchers) || intf.paramMatchers.length === 0
|
|
303
|
+
);
|
|
288
304
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
this.log(`[规则处理器] 找到完全匹配的接口: ${intf.name} (URL + 接口级参数匹配)`);
|
|
293
|
-
return intf;
|
|
294
|
-
} else {
|
|
295
|
-
this.log(`[规则处理器] 接口 ${intf.name} URL匹配但参数不匹配,继续查找...`);
|
|
305
|
+
if (fallbackInterface) {
|
|
306
|
+
this.log(`[规则处理器] 使用回退接口: ${fallbackInterface.name} (无参数匹配规则)`);
|
|
307
|
+
return fallbackInterface;
|
|
296
308
|
}
|
|
309
|
+
|
|
310
|
+
this.log(`[规则处理器] 所有URL匹配的接口都有参数匹配规则且都不满足,未找到合适的接口`);
|
|
311
|
+
return null;
|
|
312
|
+
} else {
|
|
313
|
+
this.log('[规则处理器] 接口入参匹配已关闭,直接返回第一个URL匹配的接口');
|
|
314
|
+
return urlMatchedInterfaces[0];
|
|
297
315
|
}
|
|
298
|
-
|
|
299
|
-
// 旧数据兜底:返回第一个没有接口级参数匹配规则的接口
|
|
300
|
-
const fallbackInterface = legacyInterfaces.find(intf =>
|
|
301
|
-
!intf.paramMatchers || !Array.isArray(intf.paramMatchers) || intf.paramMatchers.length === 0
|
|
302
|
-
);
|
|
303
|
-
|
|
304
|
-
if (fallbackInterface) {
|
|
305
|
-
this.log(`[规则处理器] 使用回退接口: ${fallbackInterface.name} (无参数匹配规则)`);
|
|
306
|
-
return fallbackInterface;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
this.log(`[规则处理器] 所有URL匹配的接口都有参数匹配规则且都不满足,未找到合适的接口`);
|
|
310
|
-
return null;
|
|
311
316
|
},
|
|
312
317
|
|
|
313
318
|
/**
|
|
@@ -454,9 +459,9 @@ const ruleManager = {
|
|
|
454
459
|
}
|
|
455
460
|
}
|
|
456
461
|
|
|
457
|
-
//
|
|
458
|
-
if (config.responses && config.
|
|
459
|
-
const activeResponse = config.responses.find(r => r.id === config.activeResponseId);
|
|
462
|
+
// 对于有多个响应的接口,添加当前响应标识(优先展示实际命中的响应,在 handleDataTemplate 前此处只能用 activeResponseId 作为预估)
|
|
463
|
+
if (config.responses && config.responses.length > 0) {
|
|
464
|
+
const activeResponse = config.responses.find(r => r.id === config.activeResponseId) || config.responses[0];
|
|
460
465
|
if (activeResponse && activeResponse.name) {
|
|
461
466
|
res.setHeader('X-Whistle-Mock-Response', activeResponse.name);
|
|
462
467
|
}
|
|
@@ -524,6 +529,31 @@ const ruleManager = {
|
|
|
524
529
|
if (this.dataManager && typeof this.dataManager.logRequest === 'function') {
|
|
525
530
|
const parsedUrl = url.parse(req.url);
|
|
526
531
|
try {
|
|
532
|
+
// 获取功能模块名称
|
|
533
|
+
let featureName = '';
|
|
534
|
+
if (interfaceObj.featureId) {
|
|
535
|
+
try {
|
|
536
|
+
const features = await this.dataManager.getFeatures();
|
|
537
|
+
const feature = features.find(f => f.id === interfaceObj.featureId);
|
|
538
|
+
if (feature && feature.name) {
|
|
539
|
+
featureName = feature.name;
|
|
540
|
+
}
|
|
541
|
+
} catch (err) {
|
|
542
|
+
this.log(`[规则处理器] 获取功能模块信息失败: ${err.message}`);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// 获取实际命中的响应名称
|
|
547
|
+
let responseName = '';
|
|
548
|
+
if (result && result.mockInfo && result.mockInfo.responseName) {
|
|
549
|
+
responseName = result.mockInfo.responseName;
|
|
550
|
+
} else {
|
|
551
|
+
const headerResponse = res.getHeader('X-Whistle-Mock-Response');
|
|
552
|
+
if (headerResponse) {
|
|
553
|
+
responseName = headerResponse;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
527
557
|
this.dataManager.logRequest({
|
|
528
558
|
type: 'mock_hit',
|
|
529
559
|
eventType: 'mock_hit',
|
|
@@ -536,6 +566,8 @@ const ruleManager = {
|
|
|
536
566
|
proxyType: proxyType,
|
|
537
567
|
interfaceName: interfaceObj.name,
|
|
538
568
|
interfaceId: interfaceObj.id,
|
|
569
|
+
featureName: featureName,
|
|
570
|
+
responseName: responseName,
|
|
539
571
|
responseData: result,
|
|
540
572
|
responseTime: new Date().toISOString(),
|
|
541
573
|
contentType: res.getHeader('Content-Type') || config.contentType
|
|
@@ -768,38 +800,63 @@ const ruleManager = {
|
|
|
768
800
|
|
|
769
801
|
// 支持多响应系统
|
|
770
802
|
let template = '';
|
|
771
|
-
let
|
|
803
|
+
let hitResponse = null;
|
|
772
804
|
|
|
773
805
|
// 检查是否有多响应配置
|
|
774
806
|
if (config.responses && Array.isArray(config.responses) && config.responses.length > 0) {
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
807
|
+
let selectedResponse = null;
|
|
808
|
+
const settings = getSettings();
|
|
809
|
+
|
|
810
|
+
// 1. 优先检查接口级 paramMatchers 中带 targetResponseId 的规则(受开关控制)
|
|
811
|
+
if (settings.interfaceParamMatcherEnabled) {
|
|
812
|
+
const interfaceMatchers = config.paramMatchers;
|
|
813
|
+
if (interfaceMatchers && Array.isArray(interfaceMatchers) && interfaceMatchers.length > 0) {
|
|
814
|
+
for (const matcher of interfaceMatchers) {
|
|
815
|
+
if (!matcher.targetResponseId) continue;
|
|
816
|
+
if (isParamMatch(req, [matcher], this.log)) {
|
|
817
|
+
const target = config.responses.find(r => r.id === matcher.targetResponseId);
|
|
818
|
+
if (target) {
|
|
819
|
+
selectedResponse = target;
|
|
820
|
+
this.log(`[规则处理器] 接口级规则指定响应匹配成功,使用响应: ${target.name || '未命名'}`);
|
|
821
|
+
break;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
782
824
|
}
|
|
783
825
|
}
|
|
826
|
+
} else {
|
|
827
|
+
this.log('[规则处理器] 接口入参匹配已关闭,跳过接口级匹配');
|
|
784
828
|
}
|
|
785
829
|
|
|
786
|
-
//
|
|
787
|
-
if (!
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
830
|
+
// 2. 按顺序查找第一个响应级入参匹配的响应(受开关控制)
|
|
831
|
+
if (!selectedResponse) {
|
|
832
|
+
if (settings.responseParamMatcherEnabled) {
|
|
833
|
+
for (const resp of config.responses) {
|
|
834
|
+
if (resp.paramMatchers && Array.isArray(resp.paramMatchers) && resp.paramMatchers.length > 0) {
|
|
835
|
+
if (isParamMatch(req, resp.paramMatchers, this.log)) {
|
|
836
|
+
selectedResponse = resp;
|
|
837
|
+
this.log(`[规则处理器] 响应级入参匹配成功,使用响应: ${resp.name || '未命名'}`);
|
|
838
|
+
break;
|
|
839
|
+
} else {
|
|
840
|
+
this.log(`[规则处理器] 响应 "${resp.name || '未命名'}" 入参不匹配,继续检查下一个`);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
795
844
|
} else {
|
|
796
|
-
|
|
797
|
-
this.log(`[规则处理器] 使用第一个响应: ${matchedResponse?.name || '未命名'}`);
|
|
845
|
+
this.log('[规则处理器] 响应入参匹配已关闭,跳过响应级匹配');
|
|
798
846
|
}
|
|
799
847
|
}
|
|
800
848
|
|
|
801
|
-
|
|
802
|
-
|
|
849
|
+
// 3. 无匹配则回退到 activeResponseId 指定的默认响应
|
|
850
|
+
if (!selectedResponse) {
|
|
851
|
+
const activeResponseId = config.activeResponseId;
|
|
852
|
+
selectedResponse = (activeResponseId && config.responses.find(r => r.id === activeResponseId))
|
|
853
|
+
|| config.responses[0];
|
|
854
|
+
this.log(`[规则处理器] 无响应级匹配,使用默认响应: ${selectedResponse ? (selectedResponse.name || '未命名') : '无'}`);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
if (selectedResponse) {
|
|
858
|
+
hitResponse = selectedResponse;
|
|
859
|
+
template = selectedResponse.content;
|
|
803
860
|
}
|
|
804
861
|
}
|
|
805
862
|
|
|
@@ -827,8 +884,10 @@ const ruleManager = {
|
|
|
827
884
|
const enableMockHeaders = process.env.WHISTLE_MOCK_HEADERS !== 'false';
|
|
828
885
|
if (enableMockHeaders) {
|
|
829
886
|
res.setHeader('X-Whistle-Mock-Data-Type', 'template');
|
|
830
|
-
if (
|
|
831
|
-
res.setHeader('X-Whistle-Mock-Template',
|
|
887
|
+
if (hitResponse) {
|
|
888
|
+
res.setHeader('X-Whistle-Mock-Template', hitResponse.name || 'unnamed');
|
|
889
|
+
// 覆盖 processRequest 中预设的响应头,确保反映实际命中的响应
|
|
890
|
+
res.setHeader('X-Whistle-Mock-Response', hitResponse.name || 'unnamed');
|
|
832
891
|
}
|
|
833
892
|
}
|
|
834
893
|
|
|
@@ -841,7 +900,7 @@ const ruleManager = {
|
|
|
841
900
|
mockInfo: {
|
|
842
901
|
delay: parseInt(config.responseDelay || config.delay || 0, 10),
|
|
843
902
|
timestamp: new Date().toISOString(),
|
|
844
|
-
responseName:
|
|
903
|
+
responseName: hitResponse ? (hitResponse.name || '未命名') : '默认响应'
|
|
845
904
|
}
|
|
846
905
|
};
|
|
847
906
|
} catch (err) {
|
package/lib/server.js
CHANGED
|
@@ -270,6 +270,10 @@ const app = express();
|
|
|
270
270
|
app.use(express.json({limit: '100mb'}));
|
|
271
271
|
app.use(express.urlencoded({ extended: true, limit: '100mb'}));
|
|
272
272
|
|
|
273
|
+
// 注册设置路由
|
|
274
|
+
const settingsRouter = require('./routes/settings');
|
|
275
|
+
app.use('/api', settingsRouter.initRouter());
|
|
276
|
+
|
|
273
277
|
// 加载功能和接口配置
|
|
274
278
|
const loadConfigurations = () => {
|
|
275
279
|
try {
|
package/lib/ui-server.js
CHANGED
|
@@ -32,7 +32,17 @@ app.use(bodyParser.urlencoded({ extended: true, limit: '100mb'}));
|
|
|
32
32
|
app.use(bodyParser.json({ limit: '100mb' }));
|
|
33
33
|
|
|
34
34
|
// 处理CGI请求
|
|
35
|
-
app.all('/cgi-bin/*', function(req, res) {
|
|
35
|
+
app.all('/cgi-bin/*', async function(req, res) {
|
|
36
|
+
// CORS 预检处理
|
|
37
|
+
if (req.method === 'OPTIONS') {
|
|
38
|
+
res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*');
|
|
39
|
+
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
40
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');
|
|
41
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With');
|
|
42
|
+
res.status(204).end();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
36
46
|
if (req.headers.origin) {
|
|
37
47
|
res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
|
|
38
48
|
res.setHeader('Access-Control-Allow-Credentials', true);
|
|
@@ -63,10 +73,16 @@ app.all('/cgi-bin/*', function(req, res) {
|
|
|
63
73
|
}
|
|
64
74
|
|
|
65
75
|
try {
|
|
66
|
-
// 提供数据目录给CGI
|
|
67
|
-
controller.call({ dataDir: DATA_DIR }, req, res);
|
|
76
|
+
// 提供数据目录给CGI处理程序(支持 async controller)
|
|
77
|
+
const result = controller.call({ dataDir: DATA_DIR }, req, res);
|
|
78
|
+
if (result && typeof result.then === 'function') {
|
|
79
|
+
await result;
|
|
80
|
+
}
|
|
68
81
|
} catch(e) {
|
|
69
|
-
|
|
82
|
+
console.error('[ui-server] CGI 执行错误:', e.message || e);
|
|
83
|
+
if (!res.headersSent) {
|
|
84
|
+
res.status(500).json({ code: 500, message: e.message || 'Internal Server Error' });
|
|
85
|
+
}
|
|
70
86
|
}
|
|
71
87
|
});
|
|
72
88
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whistle.mock-plugins",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Whistle 插件,用于快速创建 API 模拟数据",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"jest": "^29.7.0",
|
|
40
40
|
"react": "^18.2.0",
|
|
41
41
|
"react-dom": "^18.2.0",
|
|
42
|
+
"react-resizable": "^3.0.5",
|
|
42
43
|
"react-sortablejs": "^6.1.4",
|
|
43
44
|
"sortablejs": "^1.15.3",
|
|
44
45
|
"react-markdown": "^8.0.7",
|
package/ui/src/App.css
CHANGED