@liangshanli/mcp-server-project-standards 1.2.2 → 2.0.1
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/README.md +15 -0
- package/bin/cli.js +1 -1
- package/package.json +8 -4
- package/src/server-final.js +445 -268
- package/src/utils/api_common.js +137 -0
- package/src/utils/api_config.js +217 -0
- package/src/utils/api_debug.js +145 -604
- package/src/utils/api_login.js +165 -0
package/src/utils/api_debug.js
CHANGED
|
@@ -1,646 +1,187 @@
|
|
|
1
|
-
const
|
|
2
|
-
const path = require('path');
|
|
3
|
-
|
|
4
|
-
// Get allowed methods from environment variable
|
|
5
|
-
const getAllowedMethods = () => {
|
|
6
|
-
const allowedMethods = process.env.API_DEBUG_ALLOWED_METHODS || 'GET';
|
|
7
|
-
return allowedMethods.split(',').map(method => method.trim().toUpperCase());
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
// Get login URL from environment variable
|
|
11
|
-
const getLoginUrl = () => {
|
|
12
|
-
return process.env.API_DEBUG_LOGIN_URL || '/api/login';
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
// Get login method from environment variable
|
|
16
|
-
const getLoginMethod = () => {
|
|
17
|
-
return (process.env.API_DEBUG_LOGIN_METHOD || 'POST').toUpperCase();
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
// Get login body from environment variable
|
|
21
|
-
const getLoginBody = () => {
|
|
22
|
-
return process.env.API_DEBUG_LOGIN_BODY || '{"username":"","password":""}';
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
// Get login description from environment variable
|
|
26
|
-
const getLoginDescription = () => {
|
|
27
|
-
return process.env.API_DEBUG_LOGIN_DESCRIPTION || 'Save returned token to common headers in debug tool, field name Authorization, field value Bearer token';
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
// Get API debug config file path
|
|
31
|
-
const getApiConfigPath = () => {
|
|
32
|
-
const configDir = process.env.CONFIG_DIR || './.setting';
|
|
33
|
-
return path.join(configDir, 'api.json');
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
// Load API debug config
|
|
37
|
-
const loadApiConfig = () => {
|
|
38
|
-
const apiConfigPath = getApiConfigPath();
|
|
39
|
-
try {
|
|
40
|
-
if (fs.existsSync(apiConfigPath)) {
|
|
41
|
-
const configData = fs.readFileSync(apiConfigPath, 'utf8');
|
|
42
|
-
return JSON.parse(configData);
|
|
43
|
-
}
|
|
44
|
-
} catch (err) {
|
|
45
|
-
console.error('Failed to read API config file:', err.message);
|
|
46
|
-
}
|
|
47
|
-
return {
|
|
48
|
-
baseUrl: '',
|
|
49
|
-
headers: {
|
|
50
|
-
'Content-Type': 'application/json',
|
|
51
|
-
'Accept': 'application/json'
|
|
52
|
-
},
|
|
53
|
-
list: []
|
|
54
|
-
};
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
// Save API debug config
|
|
58
|
-
const saveApiConfig = (apiConfig) => {
|
|
59
|
-
const configDir = process.env.CONFIG_DIR || './.setting';
|
|
60
|
-
const apiConfigPath = path.join(configDir, 'api.json');
|
|
61
|
-
|
|
62
|
-
try {
|
|
63
|
-
if (!fs.existsSync(configDir)) {
|
|
64
|
-
fs.mkdirSync(configDir, { recursive: true });
|
|
65
|
-
}
|
|
66
|
-
fs.writeFileSync(apiConfigPath, JSON.stringify(apiConfig, null, 2), 'utf8');
|
|
67
|
-
return true;
|
|
68
|
-
} catch (err) {
|
|
69
|
-
console.error('Failed to save API config file:', err.message);
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
// Detect content type based on body content
|
|
75
|
-
const detectContentType = (body) => {
|
|
76
|
-
if (typeof body !== 'string') {
|
|
77
|
-
return 'application/json';
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const trimmedBody = body.trim();
|
|
81
|
-
|
|
82
|
-
// Check for XML
|
|
83
|
-
if (trimmedBody.startsWith('<') && trimmedBody.endsWith('>')) {
|
|
84
|
-
return 'application/xml';
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Check for HTML
|
|
88
|
-
if (trimmedBody.includes('<html') || trimmedBody.includes('<!DOCTYPE html')) {
|
|
89
|
-
return 'text/html';
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Check for JSON
|
|
93
|
-
try {
|
|
94
|
-
JSON.parse(trimmedBody);
|
|
95
|
-
return 'application/json';
|
|
96
|
-
} catch {
|
|
97
|
-
// Not JSON, continue checking
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Check for URL encoded format
|
|
101
|
-
if (trimmedBody.includes('=') && trimmedBody.includes('&')) {
|
|
102
|
-
return 'application/x-www-form-urlencoded';
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Check for plain text patterns
|
|
106
|
-
if (trimmedBody.includes('\n') || trimmedBody.includes('\r')) {
|
|
107
|
-
return 'text/plain';
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Default to text/plain
|
|
111
|
-
return 'text/plain';
|
|
112
|
-
};
|
|
1
|
+
const { getAllowedMethods, loadApiConfig, saveApiConfig, detectContentType } = require('./api_common');
|
|
113
2
|
|
|
114
3
|
/**
|
|
115
|
-
* API 调试工具 -
|
|
4
|
+
* API 调试工具 - 直接执行API请求
|
|
5
|
+
*
|
|
6
|
+
* 支持的请求体格式:
|
|
7
|
+
* 1. JSON对象: {"username": "admin", "password": "123456"}
|
|
8
|
+
* 2. 表单数据: "username=admin&password=123456"
|
|
9
|
+
* 3. 纯文本: "Hello World"
|
|
10
|
+
* 4. XML: "<user><name>John</name><email>john@example.com</email></user>"
|
|
11
|
+
* 5. HTML: "<html><body>Content</body></html>"
|
|
12
|
+
*
|
|
13
|
+
* 自动内容类型检测:
|
|
14
|
+
* - JSON对象 → application/json
|
|
15
|
+
* - 表单数据 → application/x-www-form-urlencoded
|
|
16
|
+
* - XML → application/xml
|
|
17
|
+
* - HTML → text/html
|
|
18
|
+
* - 纯文本 → text/plain
|
|
19
|
+
*
|
|
116
20
|
* @param {Object} params - 参数
|
|
117
|
-
* @param {string} params.
|
|
118
|
-
* @param {
|
|
119
|
-
* @param {
|
|
120
|
-
* @param {Object} params.
|
|
121
|
-
* @param {
|
|
122
|
-
* @param {string} params.
|
|
123
|
-
* @param {string} params.baseUrl - 新的基础 URL(updateBaseUrl 时必需)
|
|
124
|
-
* @param {Object} params.headers - 新的公共请求头(updateHeaders 时必需)
|
|
125
|
-
* @param {string} params.headerName - 要删除的请求头名称(deleteHeader 时必需)
|
|
126
|
-
* @param {string} params.keyword - 搜索关键词(search 时必需)
|
|
21
|
+
* @param {string} params.url - 要执行的接口URL(必需)
|
|
22
|
+
* @param {string} params.method - HTTP方法(可选,默认GET)
|
|
23
|
+
* @param {Object} params.headers - 额外请求头(可选)
|
|
24
|
+
* @param {Object} params.query - 查询参数(可选)
|
|
25
|
+
* @param {*} params.body - 请求体(可选,支持多种格式)
|
|
26
|
+
* @param {string} params.contentType - 内容类型(可选,会自动检测)
|
|
127
27
|
* @param {Object} config - 服务器配置
|
|
128
28
|
* @param {Function} saveConfig - 保存配置函数
|
|
129
|
-
* @returns {Object} API
|
|
29
|
+
* @returns {Object} API调试结果
|
|
130
30
|
*/
|
|
131
31
|
async function api_debug(params, config, saveConfig) {
|
|
132
|
-
const {
|
|
32
|
+
const { url, method = 'GET', headers = {}, query, body, contentType } = params || {};
|
|
133
33
|
|
|
134
|
-
if (!
|
|
135
|
-
throw new Error('Missing
|
|
34
|
+
if (!url) {
|
|
35
|
+
throw new Error('Missing url parameter');
|
|
136
36
|
}
|
|
137
37
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const apiDebugConfig = loadApiConfig();
|
|
142
|
-
return apiDebugConfig;
|
|
143
|
-
} catch (err) {
|
|
144
|
-
throw new Error(`Failed to get API debug config: ${err.message}`);
|
|
145
|
-
}
|
|
146
|
-
} else if (action === 'set') {
|
|
147
|
-
if (!apiConfig) {
|
|
148
|
-
throw new Error('Missing config parameter for set action');
|
|
149
|
-
}
|
|
38
|
+
try {
|
|
39
|
+
// 加载当前配置
|
|
40
|
+
const apiDebugConfig = loadApiConfig();
|
|
150
41
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
// 合并配置,保持 baseUrl 和 headers
|
|
156
|
-
const mergedConfig = {
|
|
157
|
-
baseUrl: apiConfig.baseUrl || existingConfig.baseUrl || '',
|
|
158
|
-
headers: { ...existingConfig.headers, ...apiConfig.headers },
|
|
159
|
-
list: []
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
// 去重处理:相同的 URL 只保留一份
|
|
163
|
-
const urlMap = new Map();
|
|
164
|
-
|
|
165
|
-
// 先添加现有列表中的项目
|
|
166
|
-
if (existingConfig.list && Array.isArray(existingConfig.list)) {
|
|
167
|
-
existingConfig.list.forEach(item => {
|
|
168
|
-
if (item.url) {
|
|
169
|
-
urlMap.set(item.url, item);
|
|
170
|
-
}
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// 再添加新配置中的项目(会覆盖相同 URL 的项目)
|
|
175
|
-
if (apiConfig.list && Array.isArray(apiConfig.list)) {
|
|
176
|
-
apiConfig.list.forEach(item => {
|
|
177
|
-
if (item.url) {
|
|
178
|
-
urlMap.set(item.url, item);
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// 转换为数组
|
|
184
|
-
mergedConfig.list = Array.from(urlMap.values());
|
|
185
|
-
|
|
186
|
-
// 保存到 api.json 文件
|
|
187
|
-
const saved = saveApiConfig(mergedConfig);
|
|
188
|
-
if (!saved) {
|
|
189
|
-
throw new Error('Failed to save API configuration');
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return {
|
|
193
|
-
success: true,
|
|
194
|
-
message: `Successfully updated API debug config. Total APIs: ${mergedConfig.list.length}`,
|
|
195
|
-
totalApis: mergedConfig.list.length,
|
|
196
|
-
timestamp: new Date().toISOString()
|
|
197
|
-
};
|
|
198
|
-
} catch (err) {
|
|
199
|
-
throw new Error(`Failed to update API debug config: ${err.message}`);
|
|
200
|
-
}
|
|
201
|
-
} else if (action === 'delete') {
|
|
202
|
-
if (!url) {
|
|
203
|
-
throw new Error('Missing url parameter for delete action');
|
|
204
|
-
}
|
|
42
|
+
// 验证请求方法是否被允许
|
|
43
|
+
const allowedMethods = getAllowedMethods();
|
|
44
|
+
const requestMethod = method.toUpperCase();
|
|
205
45
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
const apiDebugConfig = loadApiConfig();
|
|
209
|
-
|
|
210
|
-
if (!apiDebugConfig.list) {
|
|
211
|
-
apiDebugConfig.list = [];
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// 查找并删除指定URL的接口
|
|
215
|
-
const originalLength = apiDebugConfig.list.length;
|
|
216
|
-
apiDebugConfig.list = apiDebugConfig.list.filter(item => item.url !== url);
|
|
217
|
-
|
|
218
|
-
if (apiDebugConfig.list.length === originalLength) {
|
|
219
|
-
return {
|
|
220
|
-
success: false,
|
|
221
|
-
message: `API with URL "${url}" not found`,
|
|
222
|
-
timestamp: new Date().toISOString()
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// 保存配置
|
|
227
|
-
const saved = saveApiConfig(apiDebugConfig);
|
|
228
|
-
if (!saved) {
|
|
229
|
-
throw new Error('Failed to save API configuration');
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
return {
|
|
233
|
-
success: true,
|
|
234
|
-
message: `Successfully deleted API with URL: ${url}`,
|
|
235
|
-
deletedUrl: url,
|
|
236
|
-
timestamp: new Date().toISOString()
|
|
237
|
-
};
|
|
238
|
-
} catch (err) {
|
|
239
|
-
throw new Error(`Failed to delete API: ${err.message}`);
|
|
240
|
-
}
|
|
241
|
-
} else if (action === 'execute') {
|
|
242
|
-
if (!url) {
|
|
243
|
-
throw new Error('Missing url parameter for execute action');
|
|
46
|
+
if (!allowedMethods.includes(requestMethod)) {
|
|
47
|
+
throw new Error(`Method ${requestMethod} is not allowed. Allowed methods: ${allowedMethods.join(', ')}`);
|
|
244
48
|
}
|
|
245
49
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
apiDebugConfig.list = [];
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// 查找指定URL的接口,如果不存在则创建一个新的
|
|
255
|
-
let apiItem = apiDebugConfig.list.find(item => item.url === url);
|
|
256
|
-
let itemIndex = apiDebugConfig.list.findIndex(item => item.url === url);
|
|
257
|
-
|
|
258
|
-
if (!apiItem) {
|
|
259
|
-
// 如果接口不存在,创建一个新的接口配置
|
|
260
|
-
apiItem = {
|
|
261
|
-
url: url,
|
|
262
|
-
method: 'GET',
|
|
263
|
-
description: `API: ${url}`
|
|
264
|
-
};
|
|
265
|
-
apiDebugConfig.list.push(apiItem);
|
|
266
|
-
itemIndex = apiDebugConfig.list.length - 1;
|
|
267
|
-
}
|
|
50
|
+
// 构建完整 URL
|
|
51
|
+
let fullUrl;
|
|
52
|
+
if (url.startsWith('http://') || url.startsWith('https://')) {
|
|
53
|
+
fullUrl = url;
|
|
54
|
+
} else {
|
|
268
55
|
const baseUrl = apiDebugConfig.baseUrl || '';
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
// 匹配完整路径或去掉前导斜杠的路径
|
|
287
|
-
return apiUrl === loginPath || apiUrl === loginPath.replace(/^\//, '') || apiUrl === loginUrlPattern;
|
|
288
|
-
} catch (e) {
|
|
289
|
-
// 如果 URL 解析失败,回退到字符串匹配
|
|
290
|
-
return apiUrl === loginUrlPattern;
|
|
291
|
-
}
|
|
56
|
+
fullUrl = baseUrl + url;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 合并请求头
|
|
60
|
+
const finalHeaders = {
|
|
61
|
+
...apiDebugConfig.headers,
|
|
62
|
+
...headers
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// 处理请求体
|
|
66
|
+
let requestBody = null;
|
|
67
|
+
if (body && (requestMethod === 'POST' || requestMethod === 'PUT' || requestMethod === 'PATCH')) {
|
|
68
|
+
if (typeof body === 'string') {
|
|
69
|
+
// 检查是否指定了 Content-Type
|
|
70
|
+
if (contentType) {
|
|
71
|
+
finalHeaders['Content-Type'] = contentType;
|
|
72
|
+
requestBody = body;
|
|
292
73
|
} else {
|
|
293
|
-
//
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
apiUrl.endsWith(loginUrlPattern.replace(/^\//, ''));
|
|
298
|
-
}
|
|
299
|
-
})();
|
|
300
|
-
|
|
301
|
-
if (!allowedMethods.includes(requestMethod)) {
|
|
302
|
-
// 如果是登录接口,继续执行;否则返回错误
|
|
303
|
-
if (!isLoginRequest) {
|
|
304
|
-
throw new Error(`Method ${requestMethod} is not allowed. Allowed methods: ${allowedMethods.join(', ')}`);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// 构建完整 URL - 如果用户提供的是完整URL,直接使用;否则拼接baseUrl
|
|
309
|
-
let fullUrl;
|
|
310
|
-
if (apiItem.url.startsWith('http://') || apiItem.url.startsWith('https://')) {
|
|
311
|
-
fullUrl = apiItem.url;
|
|
312
|
-
} else {
|
|
313
|
-
fullUrl = baseUrl + apiItem.url;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// 合并请求头
|
|
317
|
-
const headers = {
|
|
318
|
-
...commonHeaders,
|
|
319
|
-
...apiItem.header
|
|
320
|
-
};
|
|
321
|
-
|
|
322
|
-
// 处理请求体
|
|
323
|
-
let body = null;
|
|
324
|
-
if (apiItem.body && (apiItem.method === 'POST' || apiItem.method === 'PUT' || apiItem.method === 'PATCH')) {
|
|
325
|
-
if (typeof apiItem.body === 'string') {
|
|
326
|
-
// 检查是否指定了 Content-Type
|
|
327
|
-
if (apiItem.contentType) {
|
|
328
|
-
headers['Content-Type'] = apiItem.contentType;
|
|
329
|
-
body = apiItem.body;
|
|
330
|
-
} else {
|
|
331
|
-
// 自动判断 Content-Type
|
|
332
|
-
const detectedType = detectContentType(apiItem.body);
|
|
333
|
-
headers['Content-Type'] = detectedType;
|
|
334
|
-
body = apiItem.body;
|
|
335
|
-
}
|
|
336
|
-
} else if (typeof apiItem.body === 'object') {
|
|
337
|
-
// 检查是否指定了 Content-Type
|
|
338
|
-
if (apiItem.contentType) {
|
|
339
|
-
if (apiItem.contentType === 'application/x-www-form-urlencoded') {
|
|
340
|
-
// 转换为 URL 编码格式
|
|
341
|
-
const formData = new URLSearchParams();
|
|
342
|
-
Object.entries(apiItem.body).forEach(([key, value]) => {
|
|
343
|
-
if (value !== null && value !== undefined) {
|
|
344
|
-
formData.append(key, value);
|
|
345
|
-
}
|
|
346
|
-
});
|
|
347
|
-
headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
|
348
|
-
body = formData.toString();
|
|
349
|
-
} else {
|
|
350
|
-
// 其他指定类型,直接序列化
|
|
351
|
-
headers['Content-Type'] = apiItem.contentType;
|
|
352
|
-
body = JSON.stringify(apiItem.body);
|
|
353
|
-
}
|
|
354
|
-
} else {
|
|
355
|
-
// 自动判断:对象默认使用 JSON 格式
|
|
356
|
-
headers['Content-Type'] = 'application/json';
|
|
357
|
-
body = JSON.stringify(apiItem.body);
|
|
358
|
-
}
|
|
74
|
+
// 自动判断 Content-Type
|
|
75
|
+
const detectedType = detectContentType(body);
|
|
76
|
+
finalHeaders['Content-Type'] = detectedType;
|
|
77
|
+
requestBody = body;
|
|
359
78
|
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
});
|
|
371
|
-
queryString = queryParams.toString();
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
const finalUrl = queryString ? `${fullUrl}?${queryString}` : fullUrl;
|
|
375
|
-
|
|
376
|
-
let success = true;
|
|
377
|
-
let response;
|
|
378
|
-
let responseData;
|
|
379
|
-
let error = null;
|
|
380
|
-
|
|
381
|
-
// 请求处理部分 - 只有这部分出错才会保存到 api.json
|
|
382
|
-
try {
|
|
383
|
-
|
|
384
|
-
// 如果是登录接口且方法不被允许,使用环境变量中的登录配置
|
|
385
|
-
if (isLoginRequest && !allowedMethods.includes(requestMethod)) {
|
|
386
|
-
const loginMethod = getLoginMethod();
|
|
387
|
-
const loginBody = getLoginBody();
|
|
388
|
-
|
|
389
|
-
// 更新请求方法和请求体
|
|
390
|
-
apiItem.method = loginMethod;
|
|
391
|
-
if (loginBody) {
|
|
392
|
-
apiItem.body = loginBody;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// 更新最终 URL 和方法
|
|
396
|
-
const updatedFinalUrl = queryString ? `${fullUrl}?${queryString}` : fullUrl;
|
|
397
|
-
|
|
398
|
-
try {
|
|
399
|
-
response = await fetch(updatedFinalUrl, {
|
|
400
|
-
method: loginMethod,
|
|
401
|
-
headers: headers,
|
|
402
|
-
body: loginBody
|
|
79
|
+
} else if (typeof body === 'object') {
|
|
80
|
+
// 检查是否指定了 Content-Type
|
|
81
|
+
if (contentType) {
|
|
82
|
+
if (contentType === 'application/x-www-form-urlencoded') {
|
|
83
|
+
// 转换为 URL 编码格式
|
|
84
|
+
const formData = new URLSearchParams();
|
|
85
|
+
Object.entries(body).forEach(([key, value]) => {
|
|
86
|
+
if (value !== null && value !== undefined) {
|
|
87
|
+
formData.append(key, value);
|
|
88
|
+
}
|
|
403
89
|
});
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
success = false;
|
|
407
|
-
response = null;
|
|
408
|
-
}
|
|
409
|
-
} else {
|
|
410
|
-
try {
|
|
411
|
-
// 执行请求
|
|
412
|
-
response = await fetch(finalUrl, {
|
|
413
|
-
method: apiItem.method || 'GET',
|
|
414
|
-
headers: headers,
|
|
415
|
-
body: body
|
|
416
|
-
});
|
|
417
|
-
} catch (fetchError) {
|
|
418
|
-
error = fetchError.message;
|
|
419
|
-
success = false;
|
|
420
|
-
response = null;
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
if (response) {
|
|
425
|
-
// 获取响应数据
|
|
426
|
-
const contentType = response.headers.get('content-type');
|
|
427
|
-
|
|
428
|
-
if (contentType && contentType.includes('application/json')) {
|
|
429
|
-
responseData = await response.json();
|
|
90
|
+
finalHeaders['Content-Type'] = 'application/x-www-form-urlencoded';
|
|
91
|
+
requestBody = formData.toString();
|
|
430
92
|
} else {
|
|
431
|
-
|
|
93
|
+
// 其他指定类型,直接序列化
|
|
94
|
+
finalHeaders['Content-Type'] = contentType;
|
|
95
|
+
requestBody = JSON.stringify(body);
|
|
432
96
|
}
|
|
433
|
-
|
|
434
|
-
// 判断请求是否成功(HTTP 状态码 200-299 为成功)
|
|
435
|
-
const isHttpSuccess = response.status >= 200 && response.status < 300;
|
|
436
|
-
success = isHttpSuccess;
|
|
437
|
-
|
|
438
|
-
// 记录响应信息
|
|
439
|
-
apiDebugConfig.list[itemIndex].data = responseData;
|
|
440
|
-
apiDebugConfig.list[itemIndex].status = response.status;
|
|
441
|
-
apiDebugConfig.list[itemIndex].statusText = response.statusText;
|
|
442
|
-
apiDebugConfig.list[itemIndex].responseHeaders = Object.fromEntries(response.headers.entries());
|
|
443
|
-
apiDebugConfig.list[itemIndex].lastExecuted = new Date().toISOString();
|
|
444
|
-
apiDebugConfig.list[itemIndex].success = isHttpSuccess;
|
|
445
|
-
apiDebugConfig.list[itemIndex].error = isHttpSuccess ? null : `HTTP ${response.status}: ${response.statusText}`;
|
|
446
97
|
} else {
|
|
447
|
-
//
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
apiDebugConfig.list[itemIndex].status = null;
|
|
451
|
-
apiDebugConfig.list[itemIndex].statusText = null;
|
|
452
|
-
apiDebugConfig.list[itemIndex].responseHeaders = null;
|
|
453
|
-
apiDebugConfig.list[itemIndex].lastExecuted = new Date().toISOString();
|
|
454
|
-
apiDebugConfig.list[itemIndex].success = false;
|
|
455
|
-
apiDebugConfig.list[itemIndex].error = error;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
// 去重处理:相同的 URL 只保留一份(保留最新的执行结果)
|
|
459
|
-
const urlMap = new Map();
|
|
460
|
-
apiDebugConfig.list.forEach(item => {
|
|
461
|
-
if (item.url) {
|
|
462
|
-
urlMap.set(item.url, item);
|
|
463
|
-
}
|
|
464
|
-
});
|
|
465
|
-
apiDebugConfig.list = Array.from(urlMap.values());
|
|
466
|
-
|
|
467
|
-
// 无论成功还是失败,都保存配置
|
|
468
|
-
saveApiConfig(apiDebugConfig);
|
|
469
|
-
|
|
470
|
-
} catch (requestError) {
|
|
471
|
-
// 只有请求相关的错误才保存到 api.json
|
|
472
|
-
try {
|
|
473
|
-
apiDebugConfig.list[itemIndex].lastExecuted = new Date().toISOString();
|
|
474
|
-
apiDebugConfig.list[itemIndex].success = false;
|
|
475
|
-
apiDebugConfig.list[itemIndex].error = requestError.message;
|
|
476
|
-
saveApiConfig(apiDebugConfig);
|
|
477
|
-
} catch (saveErr) {
|
|
478
|
-
console.error('Failed to save request error information:', saveErr.message);
|
|
98
|
+
// 自动判断:对象默认使用 JSON 格式
|
|
99
|
+
finalHeaders['Content-Type'] = 'application/json';
|
|
100
|
+
requestBody = JSON.stringify(body);
|
|
479
101
|
}
|
|
480
102
|
}
|
|
481
|
-
|
|
482
|
-
if (success && response) {
|
|
483
|
-
return {
|
|
484
|
-
success: true,
|
|
485
|
-
message: `Successfully executed API: ${apiItem.description || apiItem.url}`,
|
|
486
|
-
request: {
|
|
487
|
-
url: finalUrl,
|
|
488
|
-
method: apiItem.method || 'GET',
|
|
489
|
-
headers: headers,
|
|
490
|
-
body: body
|
|
491
|
-
},
|
|
492
|
-
response: {
|
|
493
|
-
status: response.status,
|
|
494
|
-
statusText: response.statusText,
|
|
495
|
-
headers: Object.fromEntries(response.headers.entries()),
|
|
496
|
-
data: responseData
|
|
497
|
-
},
|
|
498
|
-
timestamp: new Date().toISOString()
|
|
499
|
-
};
|
|
500
|
-
} else {
|
|
501
|
-
return {
|
|
502
|
-
success: false,
|
|
503
|
-
message: `Failed to execute API: ${apiItem.description || apiItem.url}`,
|
|
504
|
-
request: {
|
|
505
|
-
url: finalUrl,
|
|
506
|
-
method: apiItem.method || 'GET',
|
|
507
|
-
headers: headers,
|
|
508
|
-
body: body
|
|
509
|
-
},
|
|
510
|
-
error: error || (response ? `HTTP ${response.status}: ${response.statusText}` : 'Request failed'),
|
|
511
|
-
timestamp: new Date().toISOString()
|
|
512
|
-
};
|
|
513
|
-
}
|
|
514
|
-
} catch (err) {
|
|
515
|
-
// 如果是在保存配置时出错,也要尝试记录错误信息
|
|
516
|
-
try {
|
|
517
|
-
const apiDebugConfig = loadApiConfig();
|
|
518
|
-
const itemIndex = apiDebugConfig.list.findIndex(item => item.url === url);
|
|
519
|
-
if (apiDebugConfig.list && itemIndex >= 0) {
|
|
520
|
-
apiDebugConfig.list[itemIndex].lastExecuted = new Date().toISOString();
|
|
521
|
-
apiDebugConfig.list[itemIndex].success = false;
|
|
522
|
-
apiDebugConfig.list[itemIndex].error = err.message;
|
|
523
|
-
saveApiConfig(apiDebugConfig);
|
|
524
|
-
}
|
|
525
|
-
} catch (saveErr) {
|
|
526
|
-
// 如果连保存都失败了,记录到控制台
|
|
527
|
-
console.error('Failed to save error information:', saveErr.message);
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
throw new Error(`Failed to execute API: ${err.message}`);
|
|
531
103
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
104
|
+
|
|
105
|
+
// 处理查询参数
|
|
106
|
+
let queryString = '';
|
|
107
|
+
if (query && Object.keys(query).length > 0) {
|
|
108
|
+
const queryParams = new URLSearchParams();
|
|
109
|
+
Object.entries(query).forEach(([key, value]) => {
|
|
110
|
+
if (value !== null && value !== undefined) {
|
|
111
|
+
queryParams.append(key, value);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
queryString = queryParams.toString();
|
|
536
115
|
}
|
|
537
116
|
|
|
117
|
+
const finalRequestUrl = queryString ? `${fullUrl}?${queryString}` : fullUrl;
|
|
118
|
+
|
|
119
|
+
let success = true;
|
|
120
|
+
let response;
|
|
121
|
+
let responseData;
|
|
122
|
+
let error = null;
|
|
123
|
+
|
|
538
124
|
try {
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
125
|
+
// 执行请求
|
|
126
|
+
response = await fetch(finalRequestUrl, {
|
|
127
|
+
method: requestMethod,
|
|
128
|
+
headers: finalHeaders,
|
|
129
|
+
body: requestBody
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// 获取响应数据
|
|
133
|
+
const responseContentType = response.headers.get('content-type');
|
|
134
|
+
|
|
135
|
+
if (responseContentType && responseContentType.includes('application/json')) {
|
|
136
|
+
responseData = await response.json();
|
|
137
|
+
} else {
|
|
138
|
+
responseData = await response.text();
|
|
544
139
|
}
|
|
545
140
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
throw new Error(`Failed to update base URL: ${err.message}`);
|
|
554
|
-
}
|
|
555
|
-
} else if (action === 'updateHeaders') {
|
|
556
|
-
const { headers } = params;
|
|
557
|
-
if (!headers || typeof headers !== 'object') {
|
|
558
|
-
throw new Error('Missing or invalid headers parameter for updateHeaders action');
|
|
141
|
+
// 判断请求是否成功(HTTP 状态码 200-299 为成功)
|
|
142
|
+
const isHttpSuccess = response.status >= 200 && response.status < 300;
|
|
143
|
+
success = isHttpSuccess;
|
|
144
|
+
|
|
145
|
+
} catch (requestError) {
|
|
146
|
+
error = requestError.message;
|
|
147
|
+
success = false;
|
|
559
148
|
}
|
|
560
149
|
|
|
561
|
-
|
|
562
|
-
const apiDebugConfig = loadApiConfig();
|
|
563
|
-
apiDebugConfig.headers = { ...apiDebugConfig.headers, ...headers };
|
|
564
|
-
const saved = saveApiConfig(apiDebugConfig);
|
|
565
|
-
if (!saved) {
|
|
566
|
-
throw new Error('Failed to save API configuration');
|
|
567
|
-
}
|
|
568
|
-
|
|
150
|
+
if (success && response) {
|
|
569
151
|
return {
|
|
570
152
|
success: true,
|
|
571
|
-
message:
|
|
572
|
-
|
|
153
|
+
message: `Successfully executed API: ${url}`,
|
|
154
|
+
request: {
|
|
155
|
+
url: finalRequestUrl,
|
|
156
|
+
method: requestMethod,
|
|
157
|
+
headers: finalHeaders,
|
|
158
|
+
body: requestBody
|
|
159
|
+
},
|
|
160
|
+
response: {
|
|
161
|
+
status: response.status,
|
|
162
|
+
statusText: response.statusText,
|
|
163
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
164
|
+
data: responseData
|
|
165
|
+
},
|
|
573
166
|
timestamp: new Date().toISOString()
|
|
574
167
|
};
|
|
575
|
-
}
|
|
576
|
-
throw new Error(`Failed to update headers: ${err.message}`);
|
|
577
|
-
}
|
|
578
|
-
} else if (action === 'deleteHeader') {
|
|
579
|
-
const { headerName } = params;
|
|
580
|
-
if (!headerName) {
|
|
581
|
-
throw new Error('Missing headerName parameter for deleteHeader action');
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
try {
|
|
585
|
-
const apiDebugConfig = loadApiConfig();
|
|
586
|
-
if (apiDebugConfig.headers && apiDebugConfig.headers[headerName]) {
|
|
587
|
-
delete apiDebugConfig.headers[headerName];
|
|
588
|
-
const saved = saveApiConfig(apiDebugConfig);
|
|
589
|
-
if (!saved) {
|
|
590
|
-
throw new Error('Failed to save API configuration');
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
return {
|
|
594
|
-
success: true,
|
|
595
|
-
message: `Successfully deleted header: ${headerName}`,
|
|
596
|
-
headers: apiDebugConfig.headers,
|
|
597
|
-
timestamp: new Date().toISOString()
|
|
598
|
-
};
|
|
599
|
-
} else {
|
|
600
|
-
return {
|
|
601
|
-
success: false,
|
|
602
|
-
message: `Header '${headerName}' not found`,
|
|
603
|
-
headers: apiDebugConfig.headers,
|
|
604
|
-
timestamp: new Date().toISOString()
|
|
605
|
-
};
|
|
606
|
-
}
|
|
607
|
-
} catch (err) {
|
|
608
|
-
throw new Error(`Failed to delete header: ${err.message}`);
|
|
609
|
-
}
|
|
610
|
-
} else if (action === 'search') {
|
|
611
|
-
const { keyword } = params;
|
|
612
|
-
if (!keyword) {
|
|
613
|
-
throw new Error('Missing keyword parameter for search action');
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
try {
|
|
617
|
-
const apiDebugConfig = loadApiConfig();
|
|
618
|
-
let searchResults = [];
|
|
619
|
-
|
|
620
|
-
if (apiDebugConfig.list && Array.isArray(apiDebugConfig.list)) {
|
|
621
|
-
searchResults = apiDebugConfig.list.filter((item) => {
|
|
622
|
-
return item.url.toLowerCase().includes(keyword.toLowerCase()) || item.description.toLowerCase().includes(keyword.toLowerCase());
|
|
623
|
-
});
|
|
624
|
-
}
|
|
625
|
-
|
|
168
|
+
} else {
|
|
626
169
|
return {
|
|
627
|
-
success:
|
|
628
|
-
message: `
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
170
|
+
success: false,
|
|
171
|
+
message: `Failed to execute API: ${url}`,
|
|
172
|
+
request: {
|
|
173
|
+
url: finalRequestUrl,
|
|
174
|
+
method: requestMethod,
|
|
175
|
+
headers: finalHeaders,
|
|
176
|
+
body: requestBody
|
|
177
|
+
},
|
|
178
|
+
error: error || (response ? `HTTP ${response.status}: ${response.statusText}` : 'Request failed'),
|
|
632
179
|
timestamp: new Date().toISOString()
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
} catch (err) {
|
|
638
|
-
throw new Error(`Failed to search APIs: ${err.message}`);
|
|
180
|
+
};
|
|
639
181
|
}
|
|
640
|
-
}
|
|
641
|
-
throw new Error(
|
|
182
|
+
} catch (err) {
|
|
183
|
+
throw new Error(`Failed to execute API: ${err.message}`);
|
|
642
184
|
}
|
|
643
185
|
}
|
|
644
186
|
|
|
645
|
-
|
|
646
187
|
module.exports = api_debug;
|