koishi-plugin-imx 2.2.1 → 2.2.2
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/lib/modules/bilibili.js +17 -14
- package/lib/modules/github.js +4 -3
- package/lib/modules/mx-space.d.ts +0 -1
- package/lib/modules/mx-space.js +87 -54
- package/lib/utils/axios-error.d.ts +35 -0
- package/lib/utils/axios-error.js +111 -0
- package/lib/utils/mx-api.js +8 -4
- package/package.json +1 -1
package/lib/modules/bilibili.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.Config = exports.name = void 0;
|
|
|
7
7
|
exports.apply = apply;
|
|
8
8
|
const koishi_1 = require("koishi");
|
|
9
9
|
const axios_1 = __importDefault(require("axios"));
|
|
10
|
+
const axios_error_1 = require("../utils/axios-error");
|
|
10
11
|
exports.name = 'bilibili';
|
|
11
12
|
exports.Config = koishi_1.Schema.object({
|
|
12
13
|
enabled: koishi_1.Schema.boolean().description('启用 Bilibili 直播监控').default(false),
|
|
@@ -38,11 +39,11 @@ function apply(ctx, config) {
|
|
|
38
39
|
}
|
|
39
40
|
const statusList = [];
|
|
40
41
|
for (const roomId of config.roomIds) {
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
const isLive = await (0, axios_error_1.axiosRequestWithLog)(logger, () => getRoomLiveStatus(roomId), `获取房间 ${roomId} 状态`);
|
|
43
|
+
if (isLive !== null) {
|
|
43
44
|
statusList.push(`房间 ${roomId}: ${isLive ? '🔴 直播中' : '⚫ 未直播'}`);
|
|
44
45
|
}
|
|
45
|
-
|
|
46
|
+
else {
|
|
46
47
|
statusList.push(`房间 ${roomId}: ❌ 获取失败`);
|
|
47
48
|
}
|
|
48
49
|
}
|
|
@@ -52,20 +53,21 @@ function apply(ctx, config) {
|
|
|
52
53
|
}
|
|
53
54
|
async function checkLiveStatus(ctx, config, logger) {
|
|
54
55
|
for (const roomId of config.roomIds) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
const isLive = await (0, axios_error_1.axiosRequestWithLog)(logger, () => getRoomLiveStatus(roomId), `检查房间 ${roomId} 直播状态`);
|
|
57
|
+
if (isLive === null) {
|
|
58
|
+
// 请求失败,跳过此次检查
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const wasLive = liveStatusCache.get(roomId) || false;
|
|
62
|
+
if (isLive && !wasLive) {
|
|
63
|
+
// 开播通知
|
|
64
|
+
const roomInfo = await (0, axios_error_1.axiosRequestWithLog)(logger, () => getRoomInfo(roomId), `获取房间 ${roomId} 信息`);
|
|
65
|
+
if (roomInfo) {
|
|
61
66
|
const message = formatLiveMessage(roomInfo);
|
|
62
67
|
await sendToChannels(ctx, config.watchChannels, message, logger);
|
|
63
68
|
}
|
|
64
|
-
liveStatusCache.set(roomId, isLive);
|
|
65
|
-
}
|
|
66
|
-
catch (error) {
|
|
67
|
-
logger.error(`检查房间 ${roomId} 状态失败:`, error);
|
|
68
69
|
}
|
|
70
|
+
liveStatusCache.set(roomId, isLive);
|
|
69
71
|
}
|
|
70
72
|
}
|
|
71
73
|
async function getRoomLiveStatus(roomId) {
|
|
@@ -90,7 +92,8 @@ async function sendToChannels(ctx, channels, message, logger) {
|
|
|
90
92
|
await ctx.broadcast([channelId], message);
|
|
91
93
|
}
|
|
92
94
|
catch (error) {
|
|
93
|
-
|
|
95
|
+
const simplified = (0, axios_error_1.simplifyAxiosError)(error, `发送消息到频道 ${channelId}`);
|
|
96
|
+
logger.warn(simplified.message);
|
|
94
97
|
}
|
|
95
98
|
}
|
|
96
99
|
}
|
package/lib/modules/github.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.Config = exports.name = void 0;
|
|
|
7
7
|
exports.apply = apply;
|
|
8
8
|
const koishi_1 = require("koishi");
|
|
9
9
|
const axios_1 = __importDefault(require("axios"));
|
|
10
|
+
const axios_error_1 = require("../utils/axios-error");
|
|
10
11
|
exports.name = 'github';
|
|
11
12
|
exports.Config = koishi_1.Schema.object({
|
|
12
13
|
enabled: koishi_1.Schema.boolean().description('启用 GitHub 功能').default(false),
|
|
@@ -32,11 +33,11 @@ function apply(ctx, config) {
|
|
|
32
33
|
}
|
|
33
34
|
const statusList = [];
|
|
34
35
|
for (const repo of config.repositories) {
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
const repoInfo = await (0, axios_error_1.axiosRequestWithLog)(logger, () => getRepoInfo(repo), `获取仓库 ${repo} 信息`);
|
|
37
|
+
if (repoInfo) {
|
|
37
38
|
statusList.push(`${repo}: ⭐ ${repoInfo.stargazers_count} | 🍴 ${repoInfo.forks_count}`);
|
|
38
39
|
}
|
|
39
|
-
|
|
40
|
+
else {
|
|
40
41
|
statusList.push(`${repo}: ❌ 获取失败`);
|
|
41
42
|
}
|
|
42
43
|
}
|
package/lib/modules/mx-space.js
CHANGED
|
@@ -48,6 +48,7 @@ const hitokoto_1 = require("../utils/hitokoto");
|
|
|
48
48
|
const mx_api_1 = require("../utils/mx-api");
|
|
49
49
|
const mx_url_builder_1 = require("../utils/mx-url-builder");
|
|
50
50
|
const mx_event_handler_1 = require("../utils/mx-event-handler");
|
|
51
|
+
const axios_error_1 = require("../utils/axios-error");
|
|
51
52
|
dayjs_1.default.extend(relativeTime_1.default);
|
|
52
53
|
exports.name = 'mx-space';
|
|
53
54
|
exports.inject = ['server'];
|
|
@@ -58,7 +59,6 @@ exports.Config = koishi_1.Schema.object({
|
|
|
58
59
|
secret: koishi_1.Schema.string().description('MX Space Webhook Secret').role('secret'),
|
|
59
60
|
path: koishi_1.Schema.string().description('Webhook 路径').default('/mx-space/webhook'),
|
|
60
61
|
watchChannels: koishi_1.Schema.array(koishi_1.Schema.string()).description('监听的频道ID列表').default([]),
|
|
61
|
-
requireSignature: koishi_1.Schema.boolean().description('是否要求签名验证(禁用可允许健康检查等请求)').default(true),
|
|
62
62
|
}).description('Webhook 配置'),
|
|
63
63
|
greeting: koishi_1.Schema.object({
|
|
64
64
|
enabled: koishi_1.Schema.boolean().description('启用问候功能').default(true),
|
|
@@ -91,7 +91,7 @@ function apply(ctx, config) {
|
|
|
91
91
|
memoChatId: null,
|
|
92
92
|
};
|
|
93
93
|
// 设置 Webhook 处理器
|
|
94
|
-
if (config.webhook && ctx.server) {
|
|
94
|
+
if (config.webhook?.secret && ctx.server) {
|
|
95
95
|
setupWebhook(ctx, config, logger);
|
|
96
96
|
}
|
|
97
97
|
// 设置问候功能
|
|
@@ -113,11 +113,11 @@ function apply(ctx, config) {
|
|
|
113
113
|
logger.info('MX Space 模块已启动');
|
|
114
114
|
}
|
|
115
115
|
function setupWebhook(ctx, config, logger) {
|
|
116
|
-
if (!ctx.server) {
|
|
117
|
-
logger.warn('
|
|
116
|
+
if (!config.webhook?.secret || !ctx.server) {
|
|
117
|
+
logger.warn('Webhook 配置不完整或 server 插件未启用');
|
|
118
118
|
return;
|
|
119
119
|
}
|
|
120
|
-
const webhookPath = config.webhook
|
|
120
|
+
const webhookPath = config.webhook.path || '/mx-space/webhook';
|
|
121
121
|
ctx.server.post(webhookPath, async (koaCtx) => {
|
|
122
122
|
try {
|
|
123
123
|
logger.debug('收到 webhook 请求:', {
|
|
@@ -127,8 +127,17 @@ function setupWebhook(ctx, config, logger) {
|
|
|
127
127
|
body: koaCtx.request.body
|
|
128
128
|
});
|
|
129
129
|
const body = koaCtx.request.body;
|
|
130
|
-
const
|
|
131
|
-
|
|
130
|
+
const headers = koaCtx.request.headers;
|
|
131
|
+
// 兼容多种签名头格式
|
|
132
|
+
// GitHub: x-hub-signature-256
|
|
133
|
+
// MX Space: X-Webhook-Signature (SHA1), X-Webhook-Signature256 (SHA256)
|
|
134
|
+
const signature = headers['x-hub-signature-256'] ||
|
|
135
|
+
headers['x-webhook-signature256'] ||
|
|
136
|
+
headers['x-webhook-signature'];
|
|
137
|
+
// 获取事件类型和其他 MX Space 专用头
|
|
138
|
+
const eventType = headers['x-webhook-event'];
|
|
139
|
+
const webhookId = headers['x-webhook-id'];
|
|
140
|
+
const timestamp = headers['x-webhook-timestamp'];
|
|
132
141
|
// 检查请求体是否存在
|
|
133
142
|
if (!body) {
|
|
134
143
|
logger.warn('Webhook 请求体为空');
|
|
@@ -136,75 +145,94 @@ function setupWebhook(ctx, config, logger) {
|
|
|
136
145
|
koaCtx.body = { error: 'Request body is empty' };
|
|
137
146
|
return;
|
|
138
147
|
}
|
|
139
|
-
//
|
|
140
|
-
|
|
141
|
-
// 健康检查等系统事件允许无签名通过
|
|
142
|
-
const isSystemEvent = ['health_check', 'ping'].includes(eventType);
|
|
143
|
-
// 检查是否需要签名验证
|
|
144
|
-
const requireSignature = config.webhook?.requireSignature !== false; // 默认为true
|
|
145
|
-
// 验证签名(系统事件可以跳过)
|
|
146
|
-
if (requireSignature && config.webhook?.secret && !isSystemEvent) {
|
|
147
|
-
if (!signature) {
|
|
148
|
-
logger.warn(`事件 ${eventType} 缺少签名头,但配置要求签名验证`);
|
|
149
|
-
koaCtx.status = 401;
|
|
150
|
-
koaCtx.body = { error: 'Missing signature' };
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
148
|
+
// 验证签名
|
|
149
|
+
if (config.webhook?.secret && signature) {
|
|
153
150
|
const crypto = await Promise.resolve().then(() => __importStar(require('crypto')));
|
|
154
151
|
const payload = JSON.stringify(body);
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
152
|
+
let isValidSignature = false;
|
|
153
|
+
// 判断签名算法并验证
|
|
154
|
+
if (signature.startsWith('sha256=') || headers['x-webhook-signature256']) {
|
|
155
|
+
// SHA256 签名验证
|
|
156
|
+
const hmac = crypto.createHmac('sha256', config.webhook.secret);
|
|
157
|
+
hmac.update(payload);
|
|
158
|
+
const expectedSignature = signature.startsWith('sha256=')
|
|
159
|
+
? 'sha256=' + hmac.digest('hex')
|
|
160
|
+
: hmac.digest('hex');
|
|
161
|
+
isValidSignature = signature === expectedSignature;
|
|
162
|
+
}
|
|
163
|
+
else if (headers['x-webhook-signature']) {
|
|
164
|
+
// SHA1 签名验证(MX Space 默认)
|
|
165
|
+
const hmac = crypto.createHmac('sha1', config.webhook.secret);
|
|
166
|
+
hmac.update(payload);
|
|
167
|
+
const expectedSignature = hmac.digest('hex');
|
|
168
|
+
isValidSignature = signature === expectedSignature;
|
|
169
|
+
}
|
|
158
170
|
logger.debug('签名验证:', {
|
|
159
|
-
event: eventType,
|
|
160
171
|
received: signature,
|
|
161
|
-
|
|
172
|
+
algorithm: signature.startsWith('sha256=') ? 'SHA256' : (headers['x-webhook-signature256'] ? 'SHA256' : 'SHA1'),
|
|
173
|
+
isValid: isValidSignature,
|
|
174
|
+
eventType,
|
|
175
|
+
webhookId,
|
|
176
|
+
timestamp
|
|
162
177
|
});
|
|
163
|
-
if (
|
|
164
|
-
logger.warn(
|
|
178
|
+
if (!isValidSignature) {
|
|
179
|
+
logger.warn('Webhook 签名验证失败');
|
|
165
180
|
koaCtx.status = 401;
|
|
166
181
|
koaCtx.body = { error: 'Invalid signature' };
|
|
167
182
|
return;
|
|
168
183
|
}
|
|
169
184
|
}
|
|
170
|
-
else if (!
|
|
171
|
-
logger.
|
|
185
|
+
else if (config.webhook?.secret && !signature) {
|
|
186
|
+
logger.warn('配置了签名但请求中没有签名头');
|
|
187
|
+
koaCtx.status = 401;
|
|
188
|
+
koaCtx.body = { error: 'Missing signature' };
|
|
189
|
+
return;
|
|
172
190
|
}
|
|
173
|
-
|
|
174
|
-
|
|
191
|
+
// 检查请求体格式
|
|
192
|
+
// 兼容多种格式:
|
|
193
|
+
// 1. GitHub 格式: { type, data }
|
|
194
|
+
// 2. MX Space 格式: 直接的事件数据,事件类型在 X-Webhook-Event 头中
|
|
195
|
+
let eventTypeToProcess;
|
|
196
|
+
let eventData;
|
|
197
|
+
if (eventType) {
|
|
198
|
+
// MX Space 格式:事件类型在头部,数据在请求体
|
|
199
|
+
eventTypeToProcess = eventType;
|
|
200
|
+
eventData = body;
|
|
175
201
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
koaCtx.body = { message: 'Health check successful' };
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
202
|
+
else if (body.type && body.data) {
|
|
203
|
+
// GitHub 格式:事件类型和数据都在请求体
|
|
204
|
+
eventTypeToProcess = body.type;
|
|
205
|
+
eventData = body.data;
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
185
208
|
logger.warn('Webhook 请求体格式错误:', body);
|
|
186
209
|
koaCtx.status = 400;
|
|
187
210
|
koaCtx.body = {
|
|
188
211
|
error: 'Invalid webhook payload',
|
|
189
|
-
details: 'Missing required fields: type or data',
|
|
190
|
-
received: body
|
|
212
|
+
details: 'Missing required fields: event type or data',
|
|
213
|
+
received: body,
|
|
214
|
+
headers: { eventType, webhookId, timestamp }
|
|
191
215
|
};
|
|
192
216
|
return;
|
|
193
217
|
}
|
|
194
|
-
logger.info(`处理 MX Space 事件: ${
|
|
218
|
+
logger.info(`处理 MX Space 事件: ${eventTypeToProcess}`, {
|
|
219
|
+
webhookId,
|
|
220
|
+
timestamp,
|
|
221
|
+
format: eventType ? 'mx-space' : 'github'
|
|
222
|
+
});
|
|
195
223
|
// 处理事件
|
|
196
|
-
await (0, mx_event_handler_1.handleMxSpaceEvent)(ctx, config,
|
|
224
|
+
await (0, mx_event_handler_1.handleMxSpaceEvent)(ctx, config, eventTypeToProcess, eventData, logger);
|
|
197
225
|
koaCtx.status = 200;
|
|
198
226
|
koaCtx.body = { message: 'Webhook processed successfully' };
|
|
199
227
|
}
|
|
200
228
|
catch (error) {
|
|
201
|
-
|
|
229
|
+
const simplified = (0, axios_error_1.simplifyAxiosError)(error, '处理 MX Space webhook');
|
|
230
|
+
logger.error(simplified.message);
|
|
202
231
|
koaCtx.status = 500;
|
|
203
|
-
koaCtx.body = { error: 'Internal server error', details:
|
|
232
|
+
koaCtx.body = { error: 'Internal server error', details: simplified.message };
|
|
204
233
|
}
|
|
205
234
|
});
|
|
206
|
-
|
|
207
|
-
logger.info(`MX Space Webhook 已启动,监听路径: ${webhookPath},签名验证: ${signatureStatus}`);
|
|
235
|
+
logger.info(`MX Space Webhook 已启动,监听路径: ${webhookPath}`);
|
|
208
236
|
}
|
|
209
237
|
function setupWelcomeNewMember(ctx, config, logger) {
|
|
210
238
|
if (!config.welcomeNewMember?.channels?.length)
|
|
@@ -220,7 +248,8 @@ function setupWelcomeNewMember(ctx, config, logger) {
|
|
|
220
248
|
await session.send(welcomeMessage);
|
|
221
249
|
}
|
|
222
250
|
catch (error) {
|
|
223
|
-
|
|
251
|
+
const simplified = (0, axios_error_1.simplifyAxiosError)(error, '发送欢迎消息');
|
|
252
|
+
logger.warn(simplified.message);
|
|
224
253
|
}
|
|
225
254
|
});
|
|
226
255
|
logger.info('新成员欢迎功能已启用');
|
|
@@ -250,7 +279,8 @@ function setupCommentReply(ctx, config, logger, globalState) {
|
|
|
250
279
|
globalState.memoChatId = null;
|
|
251
280
|
}
|
|
252
281
|
catch (error) {
|
|
253
|
-
|
|
282
|
+
const simplified = (0, axios_error_1.simplifyAxiosError)(error, '回复评论');
|
|
283
|
+
await session.send(`回复失败!${simplified.message}`);
|
|
254
284
|
globalState.toCommentId = null;
|
|
255
285
|
globalState.memoChatId = null;
|
|
256
286
|
}
|
|
@@ -275,7 +305,8 @@ function setupGreeting(ctx, config, logger) {
|
|
|
275
305
|
await sendToChannels(ctx, config.greeting.channels || [], message, logger);
|
|
276
306
|
}
|
|
277
307
|
catch (error) {
|
|
278
|
-
|
|
308
|
+
const simplified = (0, axios_error_1.simplifyAxiosError)(error, '发送早安消息');
|
|
309
|
+
logger.warn(simplified.message);
|
|
279
310
|
}
|
|
280
311
|
}, null, false, 'Asia/Shanghai');
|
|
281
312
|
// 晚安定时任务
|
|
@@ -294,7 +325,8 @@ function setupGreeting(ctx, config, logger) {
|
|
|
294
325
|
await sendToChannels(ctx, config.greeting.channels || [], message, logger);
|
|
295
326
|
}
|
|
296
327
|
catch (error) {
|
|
297
|
-
|
|
328
|
+
const simplified = (0, axios_error_1.simplifyAxiosError)(error, '发送晚安消息');
|
|
329
|
+
logger.warn(simplified.message);
|
|
298
330
|
}
|
|
299
331
|
}, null, false, 'Asia/Shanghai');
|
|
300
332
|
morningJob.start();
|
|
@@ -319,7 +351,8 @@ function setupCommands(ctx, config, logger) {
|
|
|
319
351
|
return `💭 ${hitokoto}\n\n—— ${from || '未知'}`;
|
|
320
352
|
}
|
|
321
353
|
catch (error) {
|
|
322
|
-
|
|
354
|
+
const simplified = (0, axios_error_1.simplifyAxiosError)(error, '获取一言');
|
|
355
|
+
logger.warn(simplified.message);
|
|
323
356
|
return '获取一言失败';
|
|
324
357
|
}
|
|
325
358
|
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Logger } from 'koishi';
|
|
2
|
+
export interface SimplifiedError {
|
|
3
|
+
message: string;
|
|
4
|
+
status?: number;
|
|
5
|
+
code?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* 简化 axios 错误信息
|
|
9
|
+
* @param error axios 错误对象
|
|
10
|
+
* @param context 错误上下文描述
|
|
11
|
+
* @returns 简化的错误信息
|
|
12
|
+
*/
|
|
13
|
+
export declare function simplifyAxiosError(error: any, context?: string): SimplifiedError;
|
|
14
|
+
/**
|
|
15
|
+
* 记录简化的错误日志
|
|
16
|
+
* @param logger 日志记录器
|
|
17
|
+
* @param error 错误对象
|
|
18
|
+
* @param context 错误上下文
|
|
19
|
+
*/
|
|
20
|
+
export declare function logSimplifiedError(logger: Logger, error: any, context?: string): void;
|
|
21
|
+
/**
|
|
22
|
+
* 安全的 axios 请求包装器
|
|
23
|
+
* @param requestFn axios 请求函数
|
|
24
|
+
* @param context 请求上下文描述
|
|
25
|
+
* @returns Promise<T | null>
|
|
26
|
+
*/
|
|
27
|
+
export declare function safeAxiosRequest<T>(requestFn: () => Promise<T>, context?: string): Promise<T | null>;
|
|
28
|
+
/**
|
|
29
|
+
* 带日志的 axios 请求包装器
|
|
30
|
+
* @param logger 日志记录器
|
|
31
|
+
* @param requestFn axios 请求函数
|
|
32
|
+
* @param context 请求上下文描述
|
|
33
|
+
* @returns Promise<T | null>
|
|
34
|
+
*/
|
|
35
|
+
export declare function axiosRequestWithLog<T>(logger: Logger, requestFn: () => Promise<T>, context?: string): Promise<T | null>;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.simplifyAxiosError = simplifyAxiosError;
|
|
7
|
+
exports.logSimplifiedError = logSimplifiedError;
|
|
8
|
+
exports.safeAxiosRequest = safeAxiosRequest;
|
|
9
|
+
exports.axiosRequestWithLog = axiosRequestWithLog;
|
|
10
|
+
const axios_1 = __importDefault(require("axios"));
|
|
11
|
+
/**
|
|
12
|
+
* 简化 axios 错误信息
|
|
13
|
+
* @param error axios 错误对象
|
|
14
|
+
* @param context 错误上下文描述
|
|
15
|
+
* @returns 简化的错误信息
|
|
16
|
+
*/
|
|
17
|
+
function simplifyAxiosError(error, context = '请求') {
|
|
18
|
+
if (axios_1.default.isAxiosError(error)) {
|
|
19
|
+
const axiosError = error;
|
|
20
|
+
// 网络错误
|
|
21
|
+
if (!axiosError.response) {
|
|
22
|
+
return {
|
|
23
|
+
message: `${context}失败: 网络连接错误`,
|
|
24
|
+
code: axiosError.code || 'NETWORK_ERROR'
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
// HTTP 错误状态码
|
|
28
|
+
const status = axiosError.response.status;
|
|
29
|
+
const statusText = axiosError.response.statusText;
|
|
30
|
+
switch (status) {
|
|
31
|
+
case 400:
|
|
32
|
+
return { message: `${context}失败: 请求参数错误`, status };
|
|
33
|
+
case 401:
|
|
34
|
+
return { message: `${context}失败: 未授权访问`, status };
|
|
35
|
+
case 403:
|
|
36
|
+
return { message: `${context}失败: 访问被拒绝`, status };
|
|
37
|
+
case 404:
|
|
38
|
+
return { message: `${context}失败: 资源不存在`, status };
|
|
39
|
+
case 429:
|
|
40
|
+
return { message: `${context}失败: 请求过于频繁`, status };
|
|
41
|
+
case 500:
|
|
42
|
+
return { message: `${context}失败: 服务器内部错误`, status };
|
|
43
|
+
case 502:
|
|
44
|
+
return { message: `${context}失败: 网关错误`, status };
|
|
45
|
+
case 503:
|
|
46
|
+
return { message: `${context}失败: 服务不可用`, status };
|
|
47
|
+
default:
|
|
48
|
+
return {
|
|
49
|
+
message: `${context}失败: HTTP ${status} ${statusText}`,
|
|
50
|
+
status
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// 其他类型的错误
|
|
55
|
+
return {
|
|
56
|
+
message: `${context}失败: ${error?.message || '未知错误'}`,
|
|
57
|
+
code: 'UNKNOWN_ERROR'
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* 记录简化的错误日志
|
|
62
|
+
* @param logger 日志记录器
|
|
63
|
+
* @param error 错误对象
|
|
64
|
+
* @param context 错误上下文
|
|
65
|
+
*/
|
|
66
|
+
function logSimplifiedError(logger, error, context = '操作') {
|
|
67
|
+
const simplified = simplifyAxiosError(error, context);
|
|
68
|
+
if (simplified.status && simplified.status >= 500) {
|
|
69
|
+
// 服务器错误使用 error 级别
|
|
70
|
+
logger.error(simplified.message);
|
|
71
|
+
}
|
|
72
|
+
else if (simplified.status && simplified.status >= 400) {
|
|
73
|
+
// 客户端错误使用 warn 级别
|
|
74
|
+
logger.warn(simplified.message);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
// 网络错误等使用 error 级别
|
|
78
|
+
logger.error(simplified.message);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* 安全的 axios 请求包装器
|
|
83
|
+
* @param requestFn axios 请求函数
|
|
84
|
+
* @param context 请求上下文描述
|
|
85
|
+
* @returns Promise<T | null>
|
|
86
|
+
*/
|
|
87
|
+
async function safeAxiosRequest(requestFn, context = '请求') {
|
|
88
|
+
try {
|
|
89
|
+
return await requestFn();
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
// 静默处理错误,返回 null
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* 带日志的 axios 请求包装器
|
|
98
|
+
* @param logger 日志记录器
|
|
99
|
+
* @param requestFn axios 请求函数
|
|
100
|
+
* @param context 请求上下文描述
|
|
101
|
+
* @returns Promise<T | null>
|
|
102
|
+
*/
|
|
103
|
+
async function axiosRequestWithLog(logger, requestFn, context = '请求') {
|
|
104
|
+
try {
|
|
105
|
+
return await requestFn();
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
logSimplifiedError(logger, error, context);
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
package/lib/utils/mx-api.js
CHANGED
|
@@ -5,6 +5,7 @@ exports.getMxSpaceAggregateData = getMxSpaceAggregateData;
|
|
|
5
5
|
const api_client_1 = require("@mx-space/api-client");
|
|
6
6
|
const axios_1 = require("@mx-space/api-client/dist/adaptors/axios");
|
|
7
7
|
const constants_1 = require("../constants");
|
|
8
|
+
const axios_error_1 = require("./axios-error");
|
|
8
9
|
let apiClientInstance;
|
|
9
10
|
function getApiClient(ctx, config) {
|
|
10
11
|
if (apiClientInstance) {
|
|
@@ -27,12 +28,15 @@ function getApiClient(ctx, config) {
|
|
|
27
28
|
return res;
|
|
28
29
|
}, (err) => {
|
|
29
30
|
const res = err.response;
|
|
30
|
-
const error = Promise.reject(err);
|
|
31
31
|
if (!res) {
|
|
32
|
-
|
|
32
|
+
// 网络错误等,记录简化日志
|
|
33
|
+
(0, axios_error_1.logSimplifiedError)(logger, err, 'MX Space API 请求');
|
|
33
34
|
}
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
else {
|
|
36
|
+
// HTTP 错误,记录简化日志
|
|
37
|
+
(0, axios_error_1.logSimplifiedError)(logger, err, `MX Space API 请求 ${res.config.url}`);
|
|
38
|
+
}
|
|
39
|
+
return Promise.reject(err);
|
|
36
40
|
});
|
|
37
41
|
const apiClient = (0, api_client_1.createClient)(axios_1.axiosAdaptor)(config.baseUrl, {
|
|
38
42
|
controllers: api_client_1.allControllers,
|