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,500 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 数据管理模块,负责功能和接口数据的存储和检索
|
|
3
|
+
*/
|
|
4
|
+
const fs = require('fs-extra');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const storage = require('./storage');
|
|
7
|
+
|
|
8
|
+
// 缓存启用的接口列表
|
|
9
|
+
let enabledInterfacesCache = null;
|
|
10
|
+
let lastCacheTime = 0;
|
|
11
|
+
|
|
12
|
+
// 缓存过期时间(毫秒)
|
|
13
|
+
const CACHE_TTL = 5000; // 5秒
|
|
14
|
+
|
|
15
|
+
const dataManager = {
|
|
16
|
+
/**
|
|
17
|
+
* 初始化数据管理器
|
|
18
|
+
* @param {object} options 配置选项,包含 baseDir 和 log
|
|
19
|
+
*/
|
|
20
|
+
init(options = {}) {
|
|
21
|
+
this.baseDir = options.baseDir || storage.DATA_DIR;
|
|
22
|
+
this.featuresFile = path.join(this.baseDir, 'features.json');
|
|
23
|
+
this.interfacesFile = path.join(this.baseDir, 'interfaces.json');
|
|
24
|
+
this.logsFile = path.join(this.baseDir, 'logs.json');
|
|
25
|
+
this.log = options.log || console.log;
|
|
26
|
+
|
|
27
|
+
this.log(`数据管理器初始化, 基础目录: ${this.baseDir}`);
|
|
28
|
+
this.log(`功能配置文件: ${this.featuresFile}`);
|
|
29
|
+
this.log(`接口配置文件: ${this.interfacesFile}`);
|
|
30
|
+
this.log(`日志文件: ${this.logsFile}`);
|
|
31
|
+
|
|
32
|
+
// 确保目录存在
|
|
33
|
+
try {
|
|
34
|
+
fs.ensureDirSync(this.baseDir);
|
|
35
|
+
this.log(`确保目录存在: ${this.baseDir}`);
|
|
36
|
+
} catch (err) {
|
|
37
|
+
this.log(`创建目录失败: ${err.message}`);
|
|
38
|
+
console.error('创建目录失败:', err);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 初始化文件
|
|
42
|
+
this.ensureFilesExist();
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
// 确保配置文件存在
|
|
46
|
+
ensureFilesExist() {
|
|
47
|
+
try {
|
|
48
|
+
if (!fs.existsSync(this.featuresFile)) {
|
|
49
|
+
fs.writeJsonSync(this.featuresFile, { features: [] }, { spaces: 2 });
|
|
50
|
+
this.log(`创建功能配置文件: ${this.featuresFile}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!fs.existsSync(this.interfacesFile)) {
|
|
54
|
+
fs.writeJsonSync(this.interfacesFile, { interfaces: [] }, { spaces: 2 });
|
|
55
|
+
this.log(`创建接口配置文件: ${this.interfacesFile}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!fs.existsSync(this.logsFile)) {
|
|
59
|
+
fs.writeJsonSync(this.logsFile, { logs: [] }, { spaces: 2 });
|
|
60
|
+
this.log(`创建日志文件: ${this.logsFile}`);
|
|
61
|
+
}
|
|
62
|
+
} catch (err) {
|
|
63
|
+
this.log(`确保文件存在失败: ${err.message}`);
|
|
64
|
+
console.error('确保文件存在失败:', err);
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 记录请求日志
|
|
70
|
+
* @param {object} logData 日志数据
|
|
71
|
+
*/
|
|
72
|
+
logRequest(logData) {
|
|
73
|
+
try {
|
|
74
|
+
// 确保日志文件存在
|
|
75
|
+
if (!fs.existsSync(this.logsFile)) {
|
|
76
|
+
fs.writeJsonSync(this.logsFile, { logs: [] }, { spaces: 2 });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 读取现有日志
|
|
80
|
+
const logsData = fs.readJsonSync(this.logsFile);
|
|
81
|
+
|
|
82
|
+
// 确保日志结构正确
|
|
83
|
+
if (!logsData.logs) {
|
|
84
|
+
logsData.logs = [];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 标准化日志数据,确保同时包含type和eventType字段
|
|
88
|
+
const standardizedLogData = {
|
|
89
|
+
...logData,
|
|
90
|
+
// 确保type和eventType都存在,前端可能使用eventType进行过滤
|
|
91
|
+
eventType: logData.eventType || logData.type || 'unknown',
|
|
92
|
+
type: logData.type || logData.eventType || 'unknown',
|
|
93
|
+
// 如果缺少必要字段,提供默认值,确保前端过滤不出错
|
|
94
|
+
url: logData.url || '',
|
|
95
|
+
method: logData.method || '',
|
|
96
|
+
message: logData.message || '',
|
|
97
|
+
status: logData.status || '',
|
|
98
|
+
pattern: logData.pattern || ''
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// 添加ID和时间戳
|
|
102
|
+
const logEntry = {
|
|
103
|
+
...standardizedLogData,
|
|
104
|
+
id: Date.now().toString(),
|
|
105
|
+
timestamp: logData.timestamp || new Date().toISOString()
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// 将新日志添加到前面
|
|
109
|
+
logsData.logs.unshift(logEntry);
|
|
110
|
+
|
|
111
|
+
// 限制日志数量,保留最新的5000条
|
|
112
|
+
if (logsData.logs.length > 5000) {
|
|
113
|
+
logsData.logs = logsData.logs.slice(0, 5000);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 保存日志
|
|
117
|
+
fs.writeJsonSync(this.logsFile, logsData, { spaces: 2 });
|
|
118
|
+
|
|
119
|
+
this.log(`记录请求日志: ${logEntry.method} ${logEntry.url} (${logEntry.type})`);
|
|
120
|
+
return true;
|
|
121
|
+
} catch (err) {
|
|
122
|
+
this.log(`记录请求日志失败: ${err.message}`);
|
|
123
|
+
console.error('记录请求日志失败:', err);
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* 获取请求日志
|
|
130
|
+
* @param {object} options 查询选项
|
|
131
|
+
* @param {number} options.limit 返回日志的最大数量
|
|
132
|
+
* @param {string} options.type 日志类型
|
|
133
|
+
* @returns {Promise<Array>} 日志列表
|
|
134
|
+
*/
|
|
135
|
+
async getLogs(options = {}) {
|
|
136
|
+
try {
|
|
137
|
+
// 确保日志文件存在
|
|
138
|
+
if (!fs.existsSync(this.logsFile)) {
|
|
139
|
+
fs.writeJsonSync(this.logsFile, { logs: [] }, { spaces: 2 });
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 读取日志
|
|
144
|
+
const logsData = await fs.readJson(this.logsFile);
|
|
145
|
+
|
|
146
|
+
if (!logsData.logs) {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
let logs = logsData.logs;
|
|
151
|
+
|
|
152
|
+
// 根据类型过滤
|
|
153
|
+
if (options.type) {
|
|
154
|
+
logs = logs.filter(log => log.type === options.type);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 限制返回数量
|
|
158
|
+
if (options.limit && options.limit > 0) {
|
|
159
|
+
logs = logs.slice(0, options.limit);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return logs;
|
|
163
|
+
} catch (err) {
|
|
164
|
+
this.log(`获取日志失败: ${err.message}`);
|
|
165
|
+
console.error('获取日志失败:', err);
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* 清空日志
|
|
172
|
+
* @returns {Promise<boolean>} 是否成功
|
|
173
|
+
*/
|
|
174
|
+
async clearLogs() {
|
|
175
|
+
try {
|
|
176
|
+
fs.writeJsonSync(this.logsFile, { logs: [] }, { spaces: 2 });
|
|
177
|
+
this.log('日志已清空');
|
|
178
|
+
return true;
|
|
179
|
+
} catch (err) {
|
|
180
|
+
this.log(`清空日志失败: ${err.message}`);
|
|
181
|
+
console.error('清空日志失败:', err);
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* 获取所有功能列表
|
|
188
|
+
* @returns {Promise<Array>} 功能列表
|
|
189
|
+
*/
|
|
190
|
+
async getFeatures() {
|
|
191
|
+
try {
|
|
192
|
+
this.log('尝试读取功能配置...');
|
|
193
|
+
|
|
194
|
+
// 如果文件不存在,创建
|
|
195
|
+
if (!fs.existsSync(this.featuresFile)) {
|
|
196
|
+
this.log(`功能配置文件不存在,创建空文件`);
|
|
197
|
+
fs.writeJsonSync(this.featuresFile, { features: [] }, { spaces: 2 });
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const data = await fs.readJson(this.featuresFile);
|
|
201
|
+
this.log(`成功读取功能配置, 发现 ${data.features ? data.features.length : 0} 个功能`);
|
|
202
|
+
return data.features || [];
|
|
203
|
+
} catch (err) {
|
|
204
|
+
this.log(`读取功能配置失败: ${err.message}`);
|
|
205
|
+
console.error('读取功能配置失败:', err);
|
|
206
|
+
return [];
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* 获取单个功能
|
|
212
|
+
* @param {string} id 功能ID
|
|
213
|
+
* @returns {Promise<object>} 功能对象
|
|
214
|
+
*/
|
|
215
|
+
async getFeature(id) {
|
|
216
|
+
return new Promise((resolve, reject) => {
|
|
217
|
+
this.db.features.findOne({ _id: id }, (err, doc) => {
|
|
218
|
+
if (err) return reject(err);
|
|
219
|
+
resolve(doc);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* 创建新功能
|
|
226
|
+
* @param {object} feature 功能对象
|
|
227
|
+
* @returns {Promise<object>} 创建后的功能对象
|
|
228
|
+
*/
|
|
229
|
+
async createFeature(feature) {
|
|
230
|
+
const now = new Date();
|
|
231
|
+
const newFeature = {
|
|
232
|
+
...feature,
|
|
233
|
+
createdAt: now,
|
|
234
|
+
updatedAt: now,
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
return new Promise((resolve, reject) => {
|
|
238
|
+
this.db.features.insert(newFeature, (err, doc) => {
|
|
239
|
+
if (err) return reject(err);
|
|
240
|
+
resolve(doc);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* 更新功能
|
|
247
|
+
* @param {string} id 功能ID
|
|
248
|
+
* @param {object} update 要更新的字段
|
|
249
|
+
* @returns {Promise<object>} 更新后的功能对象
|
|
250
|
+
*/
|
|
251
|
+
async updateFeature(id, update) {
|
|
252
|
+
const updateObj = {
|
|
253
|
+
...update,
|
|
254
|
+
updatedAt: new Date(),
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
return new Promise((resolve, reject) => {
|
|
258
|
+
this.db.features.update(
|
|
259
|
+
{ _id: id },
|
|
260
|
+
{ $set: updateObj },
|
|
261
|
+
{ returnUpdatedDocs: true },
|
|
262
|
+
(err, numAffected, affectedDocuments) => {
|
|
263
|
+
if (err) return reject(err);
|
|
264
|
+
resolve(affectedDocuments);
|
|
265
|
+
}
|
|
266
|
+
);
|
|
267
|
+
});
|
|
268
|
+
},
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* 删除功能
|
|
272
|
+
* @param {string} id 功能ID
|
|
273
|
+
* @returns {Promise<number>} 影响的行数
|
|
274
|
+
*/
|
|
275
|
+
async deleteFeature(id) {
|
|
276
|
+
// 首先删除该功能下的所有接口
|
|
277
|
+
await this.deleteInterfacesByFeature(id);
|
|
278
|
+
|
|
279
|
+
return new Promise((resolve, reject) => {
|
|
280
|
+
this.db.features.remove({ _id: id }, {}, (err, numRemoved) => {
|
|
281
|
+
if (err) return reject(err);
|
|
282
|
+
resolve(numRemoved);
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
},
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* 获取功能下的所有接口
|
|
289
|
+
* @param {string} featureId 功能ID
|
|
290
|
+
* @returns {Promise<Array>} 接口列表
|
|
291
|
+
*/
|
|
292
|
+
async getInterfaces(featureId) {
|
|
293
|
+
return new Promise((resolve, reject) => {
|
|
294
|
+
this.db.interfaces.find({ featureId }).sort({ updatedAt: -1 }).exec((err, docs) => {
|
|
295
|
+
if (err) return reject(err);
|
|
296
|
+
resolve(docs);
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
},
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* 获取所有启用的接口
|
|
303
|
+
* @returns {Promise<Array>} 启用状态的接口列表
|
|
304
|
+
*/
|
|
305
|
+
async getEnabledInterfaces() {
|
|
306
|
+
const now = Date.now();
|
|
307
|
+
|
|
308
|
+
// 如果缓存存在且未过期,直接返回缓存
|
|
309
|
+
if (enabledInterfacesCache && (now - lastCacheTime < CACHE_TTL)) {
|
|
310
|
+
return enabledInterfacesCache;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
// 读取所有接口数据
|
|
315
|
+
const data = await this.getInterfaces();
|
|
316
|
+
this.log(`读取到接口数据: ${JSON.stringify(data.filter(item => item.enabled === true || item.active === true))}`);
|
|
317
|
+
// 过滤出启用状态的接口
|
|
318
|
+
const enabledInterfaces = data.filter(item => item.enabled === true || item.active === true);
|
|
319
|
+
|
|
320
|
+
// 更新缓存
|
|
321
|
+
enabledInterfacesCache = enabledInterfaces;
|
|
322
|
+
lastCacheTime = now;
|
|
323
|
+
|
|
324
|
+
return enabledInterfaces;
|
|
325
|
+
} catch (err) {
|
|
326
|
+
console.error('获取启用接口失败:', err);
|
|
327
|
+
return [];
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* 使缓存失效,强制下次请求重新加载
|
|
333
|
+
*/
|
|
334
|
+
invalidateCache() {
|
|
335
|
+
enabledInterfacesCache = null;
|
|
336
|
+
lastCacheTime = 0;
|
|
337
|
+
},
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* 获取单个接口
|
|
341
|
+
* @param {string} id 接口ID
|
|
342
|
+
* @returns {Promise<object>} 接口对象
|
|
343
|
+
*/
|
|
344
|
+
async getInterface(id) {
|
|
345
|
+
return new Promise((resolve, reject) => {
|
|
346
|
+
this.db.interfaces.findOne({ _id: id }, (err, doc) => {
|
|
347
|
+
if (err) return reject(err);
|
|
348
|
+
resolve(doc);
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
},
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* 创建新接口
|
|
355
|
+
* @param {object} interfaceObj 接口对象
|
|
356
|
+
* @returns {Promise<object>} 创建后的接口对象
|
|
357
|
+
*/
|
|
358
|
+
async createInterface(interfaceObj) {
|
|
359
|
+
const now = new Date();
|
|
360
|
+
const newInterface = {
|
|
361
|
+
...interfaceObj,
|
|
362
|
+
createdAt: now,
|
|
363
|
+
updatedAt: now,
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
return new Promise((resolve, reject) => {
|
|
367
|
+
this.db.interfaces.insert(newInterface, (err, doc) => {
|
|
368
|
+
if (err) return reject(err);
|
|
369
|
+
resolve(doc);
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
},
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* 更新接口
|
|
376
|
+
* @param {string} id 接口ID
|
|
377
|
+
* @param {object} update 要更新的字段
|
|
378
|
+
* @returns {Promise<object>} 更新后的接口对象
|
|
379
|
+
*/
|
|
380
|
+
async updateInterface(id, update) {
|
|
381
|
+
const updateObj = {
|
|
382
|
+
...update,
|
|
383
|
+
updatedAt: new Date(),
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
return new Promise((resolve, reject) => {
|
|
387
|
+
this.db.interfaces.update(
|
|
388
|
+
{ _id: id },
|
|
389
|
+
{ $set: updateObj },
|
|
390
|
+
{ returnUpdatedDocs: true },
|
|
391
|
+
(err, numAffected, affectedDocuments) => {
|
|
392
|
+
if (err) return reject(err);
|
|
393
|
+
resolve(affectedDocuments);
|
|
394
|
+
}
|
|
395
|
+
);
|
|
396
|
+
});
|
|
397
|
+
},
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* 删除接口
|
|
401
|
+
* @param {string} id 接口ID
|
|
402
|
+
* @returns {Promise<number>} 影响的行数
|
|
403
|
+
*/
|
|
404
|
+
async deleteInterface(id) {
|
|
405
|
+
return new Promise((resolve, reject) => {
|
|
406
|
+
this.db.interfaces.remove({ _id: id }, {}, (err, numRemoved) => {
|
|
407
|
+
if (err) return reject(err);
|
|
408
|
+
resolve(numRemoved);
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
},
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* 删除功能下的所有接口
|
|
415
|
+
* @param {string} featureId 功能ID
|
|
416
|
+
* @returns {Promise<number>} 影响的行数
|
|
417
|
+
*/
|
|
418
|
+
async deleteInterfacesByFeature(featureId) {
|
|
419
|
+
return new Promise((resolve, reject) => {
|
|
420
|
+
this.db.interfaces.remove({ featureId }, { multi: true }, (err, numRemoved) => {
|
|
421
|
+
if (err) return reject(err);
|
|
422
|
+
resolve(numRemoved);
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
},
|
|
426
|
+
|
|
427
|
+
// 保存所有特性
|
|
428
|
+
async saveFeatures(features) {
|
|
429
|
+
try {
|
|
430
|
+
this.log(`保存功能配置, 共 ${features.length} 个功能`);
|
|
431
|
+
await fs.writeJson(this.featuresFile, { features }, { spaces: 2 });
|
|
432
|
+
this.log('功能配置保存成功');
|
|
433
|
+
return true;
|
|
434
|
+
} catch (err) {
|
|
435
|
+
this.log(`保存功能配置失败: ${err.message}`);
|
|
436
|
+
console.error('保存功能配置失败:', err);
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
},
|
|
440
|
+
|
|
441
|
+
// 获取所有接口
|
|
442
|
+
async getInterfaces() {
|
|
443
|
+
try {
|
|
444
|
+
this.log('尝试读取接口配置...');
|
|
445
|
+
|
|
446
|
+
// 如果文件不存在,创建
|
|
447
|
+
if (!fs.existsSync(this.interfacesFile)) {
|
|
448
|
+
this.log(`接口配置文件不存在,创建空文件`);
|
|
449
|
+
fs.writeJsonSync(this.interfacesFile, { interfaces: [] }, { spaces: 2 });
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const data = await fs.readJson(this.interfacesFile);
|
|
453
|
+
this.log(`成功读取接口配置, 发现 ${data.interfaces ? data.interfaces.length : 0} 个接口`);
|
|
454
|
+
return data.interfaces || [];
|
|
455
|
+
} catch (err) {
|
|
456
|
+
this.log(`读取接口配置失败: ${err.message}`);
|
|
457
|
+
console.error('读取接口配置失败:', err);
|
|
458
|
+
return [];
|
|
459
|
+
}
|
|
460
|
+
},
|
|
461
|
+
|
|
462
|
+
// 保存所有接口
|
|
463
|
+
async saveInterfaces(interfaces) {
|
|
464
|
+
try {
|
|
465
|
+
this.log(`保存接口配置, 共 ${interfaces.length} 个接口`);
|
|
466
|
+
await fs.writeJson(this.interfacesFile, { interfaces }, { spaces: 2 });
|
|
467
|
+
this.log('接口配置保存成功');
|
|
468
|
+
return true;
|
|
469
|
+
} catch (err) {
|
|
470
|
+
this.log(`保存接口配置失败: ${err.message}`);
|
|
471
|
+
console.error('保存接口配置失败:', err);
|
|
472
|
+
return false;
|
|
473
|
+
}
|
|
474
|
+
},
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* 获取所有启用的特性
|
|
478
|
+
* @returns {Promise<Array>} 启用的特性列表
|
|
479
|
+
*/
|
|
480
|
+
async getEnabledFeatures() {
|
|
481
|
+
try {
|
|
482
|
+
const features = await this.getFeatures();
|
|
483
|
+
// 确保features是数组
|
|
484
|
+
if (!Array.isArray(features)) {
|
|
485
|
+
this.log(`获取启用的功能失败: features不是数组`);
|
|
486
|
+
return [];
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const enabledFeatures = features.filter(feature => feature && typeof feature === 'object' && feature.active);
|
|
490
|
+
this.log(`获取启用的功能, 共 ${enabledFeatures.length} 个`);
|
|
491
|
+
return enabledFeatures;
|
|
492
|
+
} catch (err) {
|
|
493
|
+
this.log(`获取启用的功能失败: ${err.message}`);
|
|
494
|
+
console.error('获取启用的功能失败:', err);
|
|
495
|
+
return [];
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
module.exports = dataManager;
|
package/lib/index.js
ADDED
package/lib/models.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file 数据模型定义文件
|
|
3
|
+
* @description 定义了 whistle.mock-plugin 使用的数据模型
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 功能模块数据模型
|
|
8
|
+
* @typedef {Object} Feature
|
|
9
|
+
* @property {string} id - 功能模块唯一标识符
|
|
10
|
+
* @property {string} name - 功能模块名称
|
|
11
|
+
* @property {string} description - 功能模块描述
|
|
12
|
+
* @property {number} interfaceCount - 该功能模块下的接口数量
|
|
13
|
+
* @property {boolean} active - 功能模块是否启用
|
|
14
|
+
* @property {string} createdAt - 创建时间,ISO 字符串格式
|
|
15
|
+
* @property {string} updatedAt - 最后更新时间,ISO 字符串格式
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 接口数据模型
|
|
20
|
+
* @typedef {Object} Interface
|
|
21
|
+
* @property {string} id - 接口唯一标识符
|
|
22
|
+
* @property {string} featureId - 所属功能模块 ID
|
|
23
|
+
* @property {string} name - 接口名称
|
|
24
|
+
* @property {string} urlPattern - URL 匹配规则,支持精确匹配、通配符和正则表达式
|
|
25
|
+
* @property {string} proxyType - 代理类型,可选值: 'response'(数据模板), 'redirect'(URL重定向), 'file'(文件代理)
|
|
26
|
+
* @property {string} responseContent - 响应内容,使用 Mock.js 语法
|
|
27
|
+
* @property {string} targetUrl - 重定向目标 URL,仅在 proxyType='redirect' 时有效
|
|
28
|
+
* @property {string} filePath - 文件路径,仅在 proxyType='file' 时有效
|
|
29
|
+
* @property {number} httpStatus - HTTP 状态码
|
|
30
|
+
* @property {number} responseDelay - 响应延迟,单位毫秒
|
|
31
|
+
* @property {string} httpMethod - HTTP 方法,可选值: 'GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS', 'ALL'
|
|
32
|
+
* @property {boolean} active - 接口是否启用
|
|
33
|
+
* @property {string} contentType - 响应内容类型
|
|
34
|
+
* @property {Object|null} headers - 自定义响应头
|
|
35
|
+
* @property {string} createdAt - 创建时间,ISO 字符串格式
|
|
36
|
+
* @property {string} updatedAt - 最后更新时间,ISO 字符串格式
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 接口测试请求数据模型
|
|
41
|
+
* @typedef {Object} InterfaceTestRequest
|
|
42
|
+
* @property {string} url - 测试 URL
|
|
43
|
+
* @property {string} interfaceId - 接口 ID
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 接口测试响应数据模型
|
|
48
|
+
* @typedef {Object} InterfaceTestResponse
|
|
49
|
+
* @property {number} statusCode - HTTP 状态码
|
|
50
|
+
* @property {string} contentType - 响应内容类型
|
|
51
|
+
* @property {string} responseBody - 响应内容
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* API 响应数据模型
|
|
56
|
+
* @typedef {Object} ApiResponse
|
|
57
|
+
* @property {number} code - 状态码,0 表示成功,其他值表示错误
|
|
58
|
+
* @property {string} message - 状态消息
|
|
59
|
+
* @property {*} data - 响应数据
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
module.exports = {
|
|
63
|
+
// 这里没有实际导出,仅用于文档说明
|
|
64
|
+
};
|