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.
Files changed (68) hide show
  1. package/API.md +247 -0
  2. package/README.md +91 -0
  3. package/app/cgi-bin/features.js +285 -0
  4. package/app/cgi-bin/index.js +31 -0
  5. package/app/cgi-bin/interfaces.js +409 -0
  6. package/app/cgi-bin/logs.js +151 -0
  7. package/app/cgi-bin/test-interface.js +152 -0
  8. package/app/public/index.html +1 -0
  9. package/app/public/js/index.js +2 -0
  10. package/app/public/js/index.js.LICENSE.txt +150 -0
  11. package/app/util/index.js +24 -0
  12. package/index.html +52 -0
  13. package/initialize.js +32 -0
  14. package/lib/dataManager.js +500 -0
  15. package/lib/index.js +3 -0
  16. package/lib/models.js +64 -0
  17. package/lib/routes/features.js +213 -0
  18. package/lib/routes/interfaces.js +304 -0
  19. package/lib/routes/proxy.js +239 -0
  20. package/lib/ruleManager.js +551 -0
  21. package/lib/rules-server.js +267 -0
  22. package/lib/server.js +805 -0
  23. package/lib/storage.js +16 -0
  24. package/lib/ui-server.js +68 -0
  25. package/package.json +51 -0
  26. package/public/bundle.js +2 -0
  27. package/public/bundle.js.LICENSE.txt +38 -0
  28. package/public/index.html +1 -0
  29. package/public/plugin.mock-plugin.html +14 -0
  30. package/rules.txt +22 -0
  31. package/test/rulesServer.test.js +120 -0
  32. package/ui/src/App.css +173 -0
  33. package/ui/src/App.js +24 -0
  34. package/ui/src/components/AppLayout.js +149 -0
  35. package/ui/src/components/FeatureCard.js +53 -0
  36. package/ui/src/components/Layout.js +94 -0
  37. package/ui/src/components/MainNav.js +42 -0
  38. package/ui/src/components/PageContainer.js +16 -0
  39. package/ui/src/index.html +11 -0
  40. package/ui/src/index.js +43 -0
  41. package/ui/src/pages/FileProxy.js +235 -0
  42. package/ui/src/pages/FilesPage.js +292 -0
  43. package/ui/src/pages/Home.js +128 -0
  44. package/ui/src/pages/HomePage.js +195 -0
  45. package/ui/src/pages/InterfaceManagement.js +829 -0
  46. package/ui/src/pages/InterfacePage.js +342 -0
  47. package/ui/src/pages/MockData.js +472 -0
  48. package/ui/src/pages/Settings.js +192 -0
  49. package/ui/src/pages/SettingsPage.js +165 -0
  50. package/ui/src/pages/UrlRedirect.js +225 -0
  51. package/ui/src/pages/Welcome.js +155 -0
  52. package/ui/src/services/api.js +186 -0
  53. package/ui/src/styles/app-layout.css +337 -0
  54. package/ui/src/styles/feature-card.css +172 -0
  55. package/ui/src/styles/file-proxy.css +301 -0
  56. package/ui/src/styles/global.css +224 -0
  57. package/ui/src/styles/home.css +123 -0
  58. package/ui/src/styles/index.css +419 -0
  59. package/ui/src/styles/interface-management.css +694 -0
  60. package/ui/src/styles/layout.css +159 -0
  61. package/ui/src/styles/logs-page.css +427 -0
  62. package/ui/src/styles/main-nav.css +52 -0
  63. package/ui/src/styles/mock-data.css +850 -0
  64. package/ui/src/styles/page-container.css +11 -0
  65. package/ui/src/styles/settings.css +208 -0
  66. package/ui/src/styles/url-redirect.css +279 -0
  67. package/ui/src/styles/welcome.css +282 -0
  68. package/webpack.config.js +49 -0
@@ -0,0 +1,267 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const url = require('url');
4
+ const storage = require('./storage');
5
+
6
+ // 数据存储目录
7
+ const DATA_DIR = path.join(process.env.WHISTLE_PLUGIN_DATA_DIR || storage.DATA_DIR);
8
+ // 接口配置文件
9
+ const INTERFACES_FILE = path.join(DATA_DIR, 'interfaces.json');
10
+ // 添加功能模块配置文件
11
+ const FEATURES_FILE = path.join(DATA_DIR, 'features.json');
12
+
13
+ // 记录日志
14
+ const log = (message) => {
15
+ console.log(`[mock-plugin:rulesServer] ${message}`);
16
+ };
17
+
18
+ // 加载功能模块配置
19
+ const loadFeatures = () => {
20
+ try {
21
+ if (!fs.existsSync(FEATURES_FILE)) {
22
+ log('功能模块配置文件不存在');
23
+ return [];
24
+ }
25
+
26
+ const data = fs.readJsonSync(FEATURES_FILE);
27
+
28
+ // 处理不同的数据结构
29
+ let features = [];
30
+
31
+ if (data && Array.isArray(data)) {
32
+ // 如果直接是数组
33
+ features = data;
34
+ } else if (data && data.features && Array.isArray(data.features)) {
35
+ // 如果是 {features: [...]} 格式
36
+ features = data.features;
37
+ } else {
38
+ log('未能识别功能模块数据结构');
39
+ return [];
40
+ }
41
+
42
+ log(`总共发现 ${features.length} 个功能模块配置`);
43
+
44
+ // 过滤出启用状态的功能模块
45
+ const enabledFeatures = features.filter(feature => feature.active === true);
46
+
47
+ log(`找到 ${enabledFeatures.length} 个启用的功能模块`);
48
+ return enabledFeatures;
49
+ } catch (err) {
50
+ log(`加载功能模块配置失败: ${err.message}`);
51
+ return [];
52
+ }
53
+ };
54
+
55
+ // 加载接口配置
56
+ const loadInterfaces = () => {
57
+ try {
58
+ if (!fs.existsSync(INTERFACES_FILE)) {
59
+ log('接口配置文件不存在');
60
+ return [];
61
+ }
62
+
63
+ const data = fs.readJsonSync(INTERFACES_FILE);
64
+ // 为了避免日志过长,只打印前100个字符
65
+ const dataPreview = JSON.stringify(data).substring(0, 100) + (JSON.stringify(data).length > 100 ? '...' : '');
66
+ log(`读取到接口配置数据: ${dataPreview}`);
67
+
68
+ // 处理不同的数据结构
69
+ let interfaces = [];
70
+
71
+ if (data && Array.isArray(data)) {
72
+ // 如果直接是数组
73
+ interfaces = data;
74
+ } else if (data && data.interfaces && Array.isArray(data.interfaces)) {
75
+ // 如果是 {interfaces: [...]} 格式
76
+ interfaces = data.interfaces;
77
+ } else {
78
+ log('未能识别接口数据结构');
79
+ return [];
80
+ }
81
+
82
+ log(`总共发现 ${interfaces.length} 个接口配置`);
83
+
84
+ // 加载功能模块配置
85
+ const enabledFeatures = loadFeatures();
86
+ const enabledFeatureIds = enabledFeatures.map(feature => feature.id);
87
+
88
+ // 过滤出启用状态的接口
89
+ // 兼容不同字段: active 或 enabled
90
+ const enabledInterfaces = interfaces.filter(intf => {
91
+ // 首先检查接口本身是否启用
92
+ const isInterfaceEnabled = intf.enabled === true || intf.active === true;
93
+
94
+ // 如果接口未启用,直接排除
95
+ if (!isInterfaceEnabled) {
96
+ return false;
97
+ }
98
+
99
+ // 如果接口有关联功能,检查功能是否启用
100
+ const featureId = intf.featureId;
101
+ if (featureId) {
102
+ return enabledFeatureIds.includes(featureId);
103
+ }
104
+
105
+ // 如果接口没有关联功能,只要接口本身启用就可以
106
+ return true;
107
+ });
108
+
109
+ log(`找到 ${enabledInterfaces.length} 个启用的接口配置`);
110
+ return enabledInterfaces;
111
+ } catch (err) {
112
+ log(`加载接口配置失败: ${err.message}`);
113
+ return [];
114
+ }
115
+ };
116
+
117
+ // 匹配URL是否符合模式
118
+ const isUrlMatch = (url, pattern) => {
119
+ if (!pattern) return false;
120
+
121
+ try {
122
+ // 精确匹配
123
+ if (pattern === url) {
124
+ return true;
125
+ }
126
+
127
+ // 通配符匹配
128
+ if (pattern.includes('*')) {
129
+ // 需要转换为正则表达式,处理特殊字符
130
+ const regexPattern = pattern
131
+ .replace(/[-\/\\^$+?.()|[\]{}]/g, '\\$&') // 转义特殊字符
132
+ .replace(/\*/g, '.*'); // 将 * 替换为 .*
133
+
134
+ const regex = new RegExp('^' + regexPattern + '$');
135
+ return regex.test(url);
136
+ }
137
+
138
+ // 正则表达式匹配
139
+ if (pattern.startsWith('/') && pattern.endsWith('/')) {
140
+ const regexStr = pattern.slice(1, -1);
141
+ const regex = new RegExp(regexStr);
142
+ return regex.test(url);
143
+ }
144
+ } catch (e) {
145
+ log(`正则表达式匹配错误: ${e.message}`);
146
+ }
147
+
148
+ return false;
149
+ };
150
+
151
+ // Whistle 规则服务器实现
152
+ module.exports = (server, options) => {
153
+ server.on('request', (req, res) => {
154
+ const oReq = req.originalReq;
155
+
156
+ // 提取请求相关信息
157
+ const fullUrl = oReq.url;
158
+ log(`原始URL: ${fullUrl}`);
159
+
160
+ // 特殊调试路径,用于查看配置文件内容
161
+ if (fullUrl.endsWith('/_debug/config')) {
162
+ try {
163
+ const configs = {
164
+ interfaces: fs.existsSync(INTERFACES_FILE) ? fs.readJsonSync(INTERFACES_FILE) : null,
165
+ features: fs.existsSync(FEATURES_FILE) ? fs.readJsonSync(FEATURES_FILE) : null
166
+ };
167
+ res.end(JSON.stringify(configs, null, 2));
168
+ return;
169
+ } catch (err) {
170
+ log(`读取配置文件失败: ${err.message}`);
171
+ res.end(`读取配置文件失败: ${err.message}`);
172
+ return;
173
+ }
174
+ }
175
+
176
+ // 调试路径,查看启用的接口
177
+ if (fullUrl.endsWith('/_debug/enabled-interfaces')) {
178
+ try {
179
+ const enabledInterfaces = loadInterfaces();
180
+ res.end(JSON.stringify({
181
+ count: enabledInterfaces.length,
182
+ interfaces: enabledInterfaces
183
+ }, null, 2));
184
+ return;
185
+ } catch (err) {
186
+ log(`获取启用接口失败: ${err.message}`);
187
+ res.end(`获取启用接口失败: ${err.message}`);
188
+ return;
189
+ }
190
+ }
191
+
192
+ // 解析URL
193
+ let parsedUrl;
194
+ try {
195
+ parsedUrl = url.parse(fullUrl);
196
+ } catch (err) {
197
+ log(`解析URL失败: ${err.message}`);
198
+ // 解析失败时,不处理请求
199
+ res.end('');
200
+ return;
201
+ }
202
+
203
+ // 尝试获取 pathname 或 path,用于匹配
204
+ const method = oReq.method;
205
+ const path = parsedUrl.pathname || '';
206
+
207
+ log(`请求方法: ${method}, 请求路径: ${path}`);
208
+
209
+ // 加载启用的接口配置
210
+ const enabledInterfaces = loadInterfaces();
211
+ log(`已加载 ${enabledInterfaces.length} 个接口配置`);
212
+
213
+ if (enabledInterfaces.length === 0) {
214
+ log('没有找到启用的接口配置,跳过处理');
215
+ res.end('');
216
+ return;
217
+ }
218
+
219
+ // 检查是否有匹配的接口
220
+ const matchedInterface = enabledInterfaces.find(intf => {
221
+ // 获取URL模式
222
+ const pattern = intf.urlPattern || '';
223
+
224
+ if (!pattern) {
225
+ log(`接口 ${intf.id || intf.name || '未知'} 缺少URL模式`);
226
+ return false;
227
+ }
228
+
229
+ // 检查URL模式匹配
230
+ const urlMatches = isUrlMatch(path, pattern);
231
+
232
+ if (!urlMatches) {
233
+ // 不记录每个失败的匹配,以减少日志量
234
+ return false;
235
+ }
236
+
237
+ log(`URL模式匹配成功: ${path} 匹配 ${pattern}`);
238
+
239
+ // 检查HTTP方法匹配
240
+ const methodField = intf.httpMethod || intf.method || '';
241
+ const methodValue = methodField.toUpperCase();
242
+ const methodMatches = !methodValue ||
243
+ methodValue === 'ALL' ||
244
+ methodValue === 'ANY' ||
245
+ methodValue === method;
246
+
247
+ if (!methodMatches) {
248
+ log(`方法不匹配: 请求方法 ${method}, 接口方法 ${methodValue}`);
249
+ return false;
250
+ }
251
+
252
+ log(`方法匹配成功: ${method} 匹配 ${methodValue || 'ALL'}`);
253
+
254
+ return true;
255
+ });
256
+
257
+ // 如果找到匹配的接口,返回对应的规则将请求转发到插件
258
+ if (matchedInterface) {
259
+ log(`找到匹配接口 "${matchedInterface.name || matchedInterface.id || '未知'}" 用于 ${method} ${path}`);
260
+ res.end(`${fullUrl} mock-plugin://`);
261
+ } else {
262
+ // 如果没有匹配的接口,返回空字符串,Whistle 会继续处理下一个规则
263
+ log(`未找到匹配接口,跳过处理 ${method} ${path}`);
264
+ res.end('');
265
+ }
266
+ });
267
+ };