feishu-mcp 0.0.6 → 0.0.8

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.
@@ -0,0 +1,363 @@
1
+ import { config as loadDotEnv } from 'dotenv';
2
+ import { hideBin } from 'yargs/helpers';
3
+ import yargs from 'yargs';
4
+ import { Logger, LogLevel } from './logger.js';
5
+ /**
6
+ * 配置来源枚举
7
+ */
8
+ export var ConfigSource;
9
+ (function (ConfigSource) {
10
+ ConfigSource["DEFAULT"] = "default";
11
+ ConfigSource["ENV"] = "env";
12
+ ConfigSource["CLI"] = "cli";
13
+ ConfigSource["FILE"] = "file";
14
+ })(ConfigSource || (ConfigSource = {}));
15
+ /**
16
+ * 应用配置管理类
17
+ * 统一管理所有配置,支持环境变量、命令行参数和默认值
18
+ */
19
+ export class Config {
20
+ /**
21
+ * 私有构造函数,用于单例模式
22
+ */
23
+ constructor() {
24
+ Object.defineProperty(this, "server", {
25
+ enumerable: true,
26
+ configurable: true,
27
+ writable: true,
28
+ value: void 0
29
+ });
30
+ Object.defineProperty(this, "feishu", {
31
+ enumerable: true,
32
+ configurable: true,
33
+ writable: true,
34
+ value: void 0
35
+ });
36
+ Object.defineProperty(this, "log", {
37
+ enumerable: true,
38
+ configurable: true,
39
+ writable: true,
40
+ value: void 0
41
+ });
42
+ Object.defineProperty(this, "cache", {
43
+ enumerable: true,
44
+ configurable: true,
45
+ writable: true,
46
+ value: void 0
47
+ });
48
+ Object.defineProperty(this, "configSources", {
49
+ enumerable: true,
50
+ configurable: true,
51
+ writable: true,
52
+ value: void 0
53
+ });
54
+ // 确保在任何配置读取前加载.env文件
55
+ loadDotEnv();
56
+ // 解析命令行参数
57
+ const argv = this.parseCommandLineArgs();
58
+ // 初始化配置来源记录
59
+ this.configSources = {};
60
+ // 配置服务器
61
+ this.server = this.initServerConfig(argv);
62
+ // 配置飞书
63
+ this.feishu = this.initFeishuConfig(argv);
64
+ // 配置日志
65
+ this.log = this.initLogConfig(argv);
66
+ // 配置缓存
67
+ this.cache = this.initCacheConfig(argv);
68
+ }
69
+ /**
70
+ * 获取配置单例
71
+ * @returns 配置实例
72
+ */
73
+ static getInstance() {
74
+ if (!Config.instance) {
75
+ Config.instance = new Config();
76
+ }
77
+ return Config.instance;
78
+ }
79
+ /**
80
+ * 解析命令行参数
81
+ * @returns 解析后的参数对象
82
+ */
83
+ parseCommandLineArgs() {
84
+ return yargs(hideBin(process.argv))
85
+ .options({
86
+ port: {
87
+ type: 'number',
88
+ description: '服务器监听端口'
89
+ },
90
+ 'log-level': {
91
+ type: 'string',
92
+ description: '日志级别 (debug, info, log, warn, error, none)'
93
+ },
94
+ 'feishu-app-id': {
95
+ type: 'string',
96
+ description: '飞书应用ID'
97
+ },
98
+ 'feishu-app-secret': {
99
+ type: 'string',
100
+ description: '飞书应用密钥'
101
+ },
102
+ 'feishu-base-url': {
103
+ type: 'string',
104
+ description: '飞书API基础URL'
105
+ },
106
+ 'cache-enabled': {
107
+ type: 'boolean',
108
+ description: '是否启用缓存'
109
+ },
110
+ 'cache-ttl': {
111
+ type: 'number',
112
+ description: '缓存生存时间(秒)'
113
+ }
114
+ })
115
+ .help()
116
+ .parseSync();
117
+ }
118
+ /**
119
+ * 初始化服务器配置
120
+ * @param argv 命令行参数
121
+ * @returns 服务器配置
122
+ */
123
+ initServerConfig(argv) {
124
+ const serverConfig = {
125
+ port: 3333,
126
+ };
127
+ // 处理PORT
128
+ if (argv.port) {
129
+ serverConfig.port = argv.port;
130
+ this.configSources['server.port'] = ConfigSource.CLI;
131
+ }
132
+ else if (process.env.PORT) {
133
+ serverConfig.port = parseInt(process.env.PORT, 10);
134
+ this.configSources['server.port'] = ConfigSource.ENV;
135
+ }
136
+ else {
137
+ this.configSources['server.port'] = ConfigSource.DEFAULT;
138
+ }
139
+ return serverConfig;
140
+ }
141
+ /**
142
+ * 初始化飞书配置
143
+ * @param argv 命令行参数
144
+ * @returns 飞书配置
145
+ */
146
+ initFeishuConfig(argv) {
147
+ const feishuConfig = {
148
+ appId: '',
149
+ appSecret: '',
150
+ baseUrl: 'https://open.feishu.cn/open-apis',
151
+ tokenLifetime: 7200000 // 2小时,单位:毫秒
152
+ };
153
+ // 处理App ID
154
+ if (argv['feishu-app-id']) {
155
+ feishuConfig.appId = argv['feishu-app-id'];
156
+ this.configSources['feishu.appId'] = ConfigSource.CLI;
157
+ }
158
+ else if (process.env.FEISHU_APP_ID) {
159
+ feishuConfig.appId = process.env.FEISHU_APP_ID;
160
+ this.configSources['feishu.appId'] = ConfigSource.ENV;
161
+ }
162
+ // 处理App Secret
163
+ if (argv['feishu-app-secret']) {
164
+ feishuConfig.appSecret = argv['feishu-app-secret'];
165
+ this.configSources['feishu.appSecret'] = ConfigSource.CLI;
166
+ }
167
+ else if (process.env.FEISHU_APP_SECRET) {
168
+ feishuConfig.appSecret = process.env.FEISHU_APP_SECRET;
169
+ this.configSources['feishu.appSecret'] = ConfigSource.ENV;
170
+ }
171
+ // 处理Base URL
172
+ if (argv['feishu-base-url']) {
173
+ feishuConfig.baseUrl = argv['feishu-base-url'];
174
+ this.configSources['feishu.baseUrl'] = ConfigSource.CLI;
175
+ }
176
+ else if (process.env.FEISHU_BASE_URL) {
177
+ feishuConfig.baseUrl = process.env.FEISHU_BASE_URL;
178
+ this.configSources['feishu.baseUrl'] = ConfigSource.ENV;
179
+ }
180
+ else {
181
+ this.configSources['feishu.baseUrl'] = ConfigSource.DEFAULT;
182
+ }
183
+ // 处理token生命周期
184
+ if (process.env.FEISHU_TOKEN_LIFETIME) {
185
+ feishuConfig.tokenLifetime = parseInt(process.env.FEISHU_TOKEN_LIFETIME, 10) * 1000;
186
+ this.configSources['feishu.tokenLifetime'] = ConfigSource.ENV;
187
+ }
188
+ else {
189
+ this.configSources['feishu.tokenLifetime'] = ConfigSource.DEFAULT;
190
+ }
191
+ return feishuConfig;
192
+ }
193
+ /**
194
+ * 初始化日志配置
195
+ * @param argv 命令行参数
196
+ * @returns 日志配置
197
+ */
198
+ initLogConfig(argv) {
199
+ const logConfig = {
200
+ level: LogLevel.INFO,
201
+ showTimestamp: true,
202
+ showLevel: true,
203
+ timestampFormat: 'yyyy-MM-dd HH:mm:ss.SSS'
204
+ };
205
+ // 处理日志级别
206
+ if (argv['log-level']) {
207
+ logConfig.level = this.getLogLevelFromString(argv['log-level']);
208
+ this.configSources['log.level'] = ConfigSource.CLI;
209
+ }
210
+ else if (process.env.LOG_LEVEL) {
211
+ logConfig.level = this.getLogLevelFromString(process.env.LOG_LEVEL);
212
+ this.configSources['log.level'] = ConfigSource.ENV;
213
+ }
214
+ else {
215
+ this.configSources['log.level'] = ConfigSource.DEFAULT;
216
+ }
217
+ // 处理时间戳显示
218
+ if (process.env.LOG_SHOW_TIMESTAMP) {
219
+ logConfig.showTimestamp = process.env.LOG_SHOW_TIMESTAMP.toLowerCase() === 'true';
220
+ this.configSources['log.showTimestamp'] = ConfigSource.ENV;
221
+ }
222
+ else {
223
+ this.configSources['log.showTimestamp'] = ConfigSource.DEFAULT;
224
+ }
225
+ // 处理级别显示
226
+ if (process.env.LOG_SHOW_LEVEL) {
227
+ logConfig.showLevel = process.env.LOG_SHOW_LEVEL.toLowerCase() === 'true';
228
+ this.configSources['log.showLevel'] = ConfigSource.ENV;
229
+ }
230
+ else {
231
+ this.configSources['log.showLevel'] = ConfigSource.DEFAULT;
232
+ }
233
+ // 处理时间戳格式
234
+ if (process.env.LOG_TIMESTAMP_FORMAT) {
235
+ logConfig.timestampFormat = process.env.LOG_TIMESTAMP_FORMAT;
236
+ this.configSources['log.timestampFormat'] = ConfigSource.ENV;
237
+ }
238
+ else {
239
+ this.configSources['log.timestampFormat'] = ConfigSource.DEFAULT;
240
+ }
241
+ return logConfig;
242
+ }
243
+ /**
244
+ * 初始化缓存配置
245
+ * @param argv 命令行参数
246
+ * @returns 缓存配置
247
+ */
248
+ initCacheConfig(argv) {
249
+ const cacheConfig = {
250
+ enabled: true,
251
+ ttl: 300, // 5分钟,单位:秒
252
+ maxSize: 100
253
+ };
254
+ // 处理缓存启用
255
+ if (argv['cache-enabled'] !== undefined) {
256
+ cacheConfig.enabled = argv['cache-enabled'];
257
+ this.configSources['cache.enabled'] = ConfigSource.CLI;
258
+ }
259
+ else if (process.env.CACHE_ENABLED) {
260
+ cacheConfig.enabled = process.env.CACHE_ENABLED.toLowerCase() === 'true';
261
+ this.configSources['cache.enabled'] = ConfigSource.ENV;
262
+ }
263
+ else {
264
+ this.configSources['cache.enabled'] = ConfigSource.DEFAULT;
265
+ }
266
+ // 处理TTL
267
+ if (argv['cache-ttl']) {
268
+ cacheConfig.ttl = argv['cache-ttl'];
269
+ this.configSources['cache.ttl'] = ConfigSource.CLI;
270
+ }
271
+ else if (process.env.CACHE_TTL) {
272
+ cacheConfig.ttl = parseInt(process.env.CACHE_TTL, 10);
273
+ this.configSources['cache.ttl'] = ConfigSource.ENV;
274
+ }
275
+ else {
276
+ this.configSources['cache.ttl'] = ConfigSource.DEFAULT;
277
+ }
278
+ // 处理最大缓存大小
279
+ if (process.env.CACHE_MAX_SIZE) {
280
+ cacheConfig.maxSize = parseInt(process.env.CACHE_MAX_SIZE, 10);
281
+ this.configSources['cache.maxSize'] = ConfigSource.ENV;
282
+ }
283
+ else {
284
+ this.configSources['cache.maxSize'] = ConfigSource.DEFAULT;
285
+ }
286
+ return cacheConfig;
287
+ }
288
+ /**
289
+ * 从字符串获取日志级别
290
+ * @param levelStr 日志级别字符串
291
+ * @returns 日志级别枚举值
292
+ */
293
+ getLogLevelFromString(levelStr) {
294
+ switch (levelStr.toLowerCase()) {
295
+ case 'debug': return LogLevel.DEBUG;
296
+ case 'info': return LogLevel.INFO;
297
+ case 'log': return LogLevel.LOG;
298
+ case 'warn': return LogLevel.WARN;
299
+ case 'error': return LogLevel.ERROR;
300
+ case 'none': return LogLevel.NONE;
301
+ default: return LogLevel.INFO;
302
+ }
303
+ }
304
+ /**
305
+ * 打印当前配置信息
306
+ * @param isStdioMode 是否在stdio模式下
307
+ */
308
+ printConfig(isStdioMode = false) {
309
+ if (isStdioMode)
310
+ return;
311
+ Logger.info('当前配置:');
312
+ Logger.info('服务器配置:');
313
+ Logger.info(`- 端口: ${this.server.port} (来源: ${this.configSources['server.port']})`);
314
+ Logger.info('飞书配置:');
315
+ if (this.feishu.appId) {
316
+ Logger.info(`- App ID: ${this.maskApiKey(this.feishu.appId)} (来源: ${this.configSources['feishu.appId']})`);
317
+ }
318
+ if (this.feishu.appSecret) {
319
+ Logger.info(`- App Secret: ${this.maskApiKey(this.feishu.appSecret)} (来源: ${this.configSources['feishu.appSecret']})`);
320
+ }
321
+ Logger.info(`- API URL: ${this.feishu.baseUrl} (来源: ${this.configSources['feishu.baseUrl']})`);
322
+ Logger.info(`- Token生命周期: ${this.feishu.tokenLifetime / 1000}秒 (来源: ${this.configSources['feishu.tokenLifetime']})`);
323
+ Logger.info('日志配置:');
324
+ Logger.info(`- 日志级别: ${LogLevel[this.log.level]} (来源: ${this.configSources['log.level']})`);
325
+ Logger.info(`- 显示时间戳: ${this.log.showTimestamp} (来源: ${this.configSources['log.showTimestamp']})`);
326
+ Logger.info(`- 显示日志级别: ${this.log.showLevel} (来源: ${this.configSources['log.showLevel']})`);
327
+ Logger.info('缓存配置:');
328
+ Logger.info(`- 启用缓存: ${this.cache.enabled} (来源: ${this.configSources['cache.enabled']})`);
329
+ Logger.info(`- 缓存TTL: ${this.cache.ttl}秒 (来源: ${this.configSources['cache.ttl']})`);
330
+ Logger.info(`- 最大缓存条目: ${this.cache.maxSize} (来源: ${this.configSources['cache.maxSize']})`);
331
+ }
332
+ /**
333
+ * 掩盖API密钥
334
+ * @param key API密钥
335
+ * @returns 掩盖后的密钥字符串
336
+ */
337
+ maskApiKey(key) {
338
+ if (!key || key.length <= 4)
339
+ return '****';
340
+ return `${key.substring(0, 2)}****${key.substring(key.length - 2)}`;
341
+ }
342
+ /**
343
+ * 验证配置是否完整有效
344
+ * @returns 是否验证成功
345
+ */
346
+ validate() {
347
+ // 验证服务器配置
348
+ if (!this.server.port || this.server.port <= 0) {
349
+ Logger.error('无效的服务器端口配置');
350
+ return false;
351
+ }
352
+ // 验证飞书配置
353
+ if (!this.feishu.appId) {
354
+ Logger.error('缺少飞书应用ID,请通过环境变量FEISHU_APP_ID或命令行参数--feishu-app-id提供');
355
+ return false;
356
+ }
357
+ if (!this.feishu.appSecret) {
358
+ Logger.error('缺少飞书应用Secret,请通过环境变量FEISHU_APP_SECRET或命令行参数--feishu-app-secret提供');
359
+ return false;
360
+ }
361
+ return true;
362
+ }
363
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * 从URL或ID中提取飞书文档ID
3
+ * 支持多种格式:
4
+ * 1. 标准文档URL: https://xxx.feishu.cn/docs/xxx 或 https://xxx.feishu.cn/docx/xxx
5
+ * 2. API URL: https://open.feishu.cn/open-apis/docx/v1/documents/xxx
6
+ * 3. 直接ID: JcKbdlokYoPIe0xDzJ1cduRXnRf
7
+ *
8
+ * @param input 文档URL或ID
9
+ * @returns 提取的文档ID或null
10
+ */
11
+ export function extractDocumentId(input) {
12
+ // 移除首尾空白
13
+ input = input.trim();
14
+ // 处理各种URL格式
15
+ const docxMatch = input.match(/\/docx\/([a-zA-Z0-9_-]+)/i);
16
+ const docsMatch = input.match(/\/docs\/([a-zA-Z0-9_-]+)/i);
17
+ const apiMatch = input.match(/\/documents\/([a-zA-Z0-9_-]+)/i);
18
+ const directIdMatch = input.match(/^([a-zA-Z0-9_-]{10,})$/); // 假设ID至少10个字符
19
+ // 按优先级返回匹配结果
20
+ const match = docxMatch || docsMatch || apiMatch || directIdMatch;
21
+ return match ? match[1] : null;
22
+ }
23
+ /**
24
+ * 从URL或Token中提取Wiki节点ID
25
+ * 支持多种格式:
26
+ * 1. Wiki URL: https://xxx.feishu.cn/wiki/xxx
27
+ * 2. 直接Token: xxx
28
+ *
29
+ * @param input Wiki URL或Token
30
+ * @returns 提取的Wiki Token或null
31
+ */
32
+ export function extractWikiToken(input) {
33
+ // 移除首尾空白
34
+ input = input.trim();
35
+ // 处理Wiki URL格式
36
+ const wikiMatch = input.match(/\/wiki\/([a-zA-Z0-9_-]+)/i);
37
+ const directMatch = input.match(/^([a-zA-Z0-9_-]{10,})$/); // 假设Token至少10个字符
38
+ // 提取Token,如果存在查询参数,去掉它们
39
+ let token = wikiMatch ? wikiMatch[1] : (directMatch ? directMatch[1] : null);
40
+ if (token && token.includes('?')) {
41
+ token = token.split('?')[0];
42
+ }
43
+ return token;
44
+ }
45
+ /**
46
+ * 规范化文档ID
47
+ * 提取输入中的文档ID,如果提取失败则返回原输入
48
+ *
49
+ * @param input 文档URL或ID
50
+ * @returns 规范化的文档ID
51
+ * @throws 如果无法提取有效ID则抛出错误
52
+ */
53
+ export function normalizeDocumentId(input) {
54
+ const id = extractDocumentId(input);
55
+ if (!id) {
56
+ throw new Error(`无法从 "${input}" 提取有效的文档ID`);
57
+ }
58
+ return id;
59
+ }
60
+ /**
61
+ * 规范化Wiki Token
62
+ * 提取输入中的Wiki Token,如果提取失败则返回原输入
63
+ *
64
+ * @param input Wiki URL或Token
65
+ * @returns 规范化的Wiki Token
66
+ * @throws 如果无法提取有效Token则抛出错误
67
+ */
68
+ export function normalizeWikiToken(input) {
69
+ const token = extractWikiToken(input);
70
+ if (!token) {
71
+ throw new Error(`无法从 "${input}" 提取有效的Wiki Token`);
72
+ }
73
+ return token;
74
+ }
75
+ /**
76
+ * 根据图片二进制数据检测MIME类型
77
+ * @param buffer 图片二进制数据
78
+ * @returns MIME类型字符串
79
+ */
80
+ export function detectMimeType(buffer) {
81
+ // 简单的图片格式检测,根据文件头进行判断
82
+ if (buffer.length < 4) {
83
+ return 'application/octet-stream';
84
+ }
85
+ // JPEG格式
86
+ if (buffer[0] === 0xFF && buffer[1] === 0xD8 && buffer[2] === 0xFF) {
87
+ return 'image/jpeg';
88
+ }
89
+ // PNG格式
90
+ else if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4E && buffer[3] === 0x47) {
91
+ return 'image/png';
92
+ }
93
+ // GIF格式
94
+ else if (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46) {
95
+ return 'image/gif';
96
+ }
97
+ // SVG格式 - 检查字符串前缀
98
+ else if (buffer.length > 5 && buffer.toString('ascii', 0, 5).toLowerCase() === '<?xml' ||
99
+ buffer.toString('ascii', 0, 4).toLowerCase() === '<svg') {
100
+ return 'image/svg+xml';
101
+ }
102
+ // WebP格式
103
+ else if (buffer.length > 12 &&
104
+ buffer[0] === 0x52 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x46 &&
105
+ buffer[8] === 0x57 && buffer[9] === 0x45 && buffer[10] === 0x42 && buffer[11] === 0x50) {
106
+ return 'image/webp';
107
+ }
108
+ // 默认二进制流
109
+ else {
110
+ return 'application/octet-stream';
111
+ }
112
+ }
@@ -0,0 +1,154 @@
1
+ import { Logger } from './logger.js';
2
+ /**
3
+ * 错误排查指南映射
4
+ */
5
+ const errorGuides = {
6
+ // 飞书API标准错误码
7
+ '1770002': '资源未找到。请检查文档ID/块ID是否正确,并确保您有权限访问该资源。',
8
+ '1770001': '权限不足。请确保应用有足够的权限访问此资源。',
9
+ '1770003': '内部服务错误。请稍后重试。',
10
+ '1770004': '参数格式错误。请检查API请求参数是否正确。',
11
+ '1770005': '请求频率限制。请减少请求频率后重试。',
12
+ '1770006': '操作冲突。可能有其他用户正在编辑同一资源。',
13
+ '1770007': '资源已被删除。请检查资源是否存在。',
14
+ '1770008': '资源已被归档。请检查资源状态。',
15
+ '1770015': '文档或文件夹已被移动。请使用新的位置访问。',
16
+ // 身份验证和通用错误
17
+ '99991671': '飞书应用身份验证失败。请检查App ID和App Secret是否正确,或者重新注册飞书应用。',
18
+ '99991663': '权限不足。请确保:\n1. 应用已获得正确的权限范围\n2. 文档已与应用共享\n3. 您有访问该文档的权限',
19
+ '99991672': '请求频率超过限制。请稍后再试或优化代码减少请求次数。',
20
+ '99991661': '资源不存在。请检查文档ID/块ID是否正确,并确保资源仍然存在。',
21
+ '99991648': '文档ID格式不正确。请检查ID格式,应为标准飞书文档ID、URL或Token。',
22
+ 'token_invalid': '访问令牌无效。请尝试刷新访问令牌。',
23
+ 'invalid_token': '访问令牌无效。请尝试刷新访问令牌。',
24
+ '404': '资源未找到。请检查URL或ID是否正确。',
25
+ '403': '访问被拒绝。请检查权限设置并确保您有足够的访问权限。',
26
+ '401': '未授权。请检查认证凭据或尝试重新获取访问令牌。',
27
+ '400': '请求参数有误。请检查提供的参数格式和值是否正确。',
28
+ '500': '服务器内部错误。请稍后重试或联系飞书支持团队。'
29
+ };
30
+ /**
31
+ * 格式化错误消息
32
+ * 对飞书API各种错误响应格式进行统一处理
33
+ *
34
+ * @param error 原始错误
35
+ * @param context 错误上下文(可选)
36
+ * @returns 格式化的错误消息
37
+ */
38
+ export function formatErrorMessage(error, context) {
39
+ try {
40
+ // 预处理错误对象
41
+ if (!error) {
42
+ return '发生未知错误';
43
+ }
44
+ // 确定错误类型
45
+ let errorCode;
46
+ let errorMsg = '';
47
+ let fieldViolations = [];
48
+ let troubleshooter = '';
49
+ let logId = '';
50
+ // 处理飞书API标准错误格式
51
+ if (error.apiError) {
52
+ const apiError = error.apiError;
53
+ errorCode = apiError.code;
54
+ errorMsg = apiError.msg || '';
55
+ if (apiError.error) {
56
+ fieldViolations = apiError.error.field_violations || [];
57
+ troubleshooter = apiError.error.troubleshooter || '';
58
+ logId = apiError.error.log_id || '';
59
+ }
60
+ }
61
+ // 处理直接包含code和msg的格式
62
+ else if (error.code !== undefined && error.msg !== undefined) {
63
+ errorCode = error.code;
64
+ errorMsg = error.msg;
65
+ if (error.error) {
66
+ fieldViolations = error.error.field_violations || [];
67
+ troubleshooter = error.error.troubleshooter || '';
68
+ logId = error.error.log_id || '';
69
+ }
70
+ }
71
+ // 处理HTTP类错误
72
+ else if (error.status) {
73
+ errorCode = error.status;
74
+ errorMsg = error.statusText || error.err || '请求失败';
75
+ }
76
+ // 处理标准Error对象
77
+ else if (error instanceof Error) {
78
+ errorMsg = error.message;
79
+ }
80
+ // 处理字符串错误
81
+ else if (typeof error === 'string') {
82
+ errorMsg = error;
83
+ }
84
+ // 处理其他对象类型的错误
85
+ else if (typeof error === 'object') {
86
+ errorMsg = error.message || error.error || JSON.stringify(error);
87
+ }
88
+ // 构建基本错误消息
89
+ let formattedMessage = '';
90
+ if (context) {
91
+ formattedMessage += `${context}: `;
92
+ }
93
+ if (errorCode !== undefined) {
94
+ formattedMessage += `${errorMsg} (错误码: ${errorCode})`;
95
+ }
96
+ else {
97
+ formattedMessage += errorMsg;
98
+ }
99
+ // 添加日志ID
100
+ if (logId) {
101
+ formattedMessage += `\n日志ID: ${logId}`;
102
+ }
103
+ // 添加字段验证错误信息
104
+ if (fieldViolations && fieldViolations.length > 0) {
105
+ formattedMessage += '\n字段验证错误:';
106
+ fieldViolations.forEach((violation) => {
107
+ let detail = `\n - ${violation.field}`;
108
+ if (violation.description) {
109
+ detail += `: ${violation.description}`;
110
+ }
111
+ if (violation.value !== undefined) {
112
+ detail += `,提供的值: ${violation.value}`;
113
+ }
114
+ formattedMessage += detail;
115
+ });
116
+ }
117
+ // 添加排查建议
118
+ if (troubleshooter) {
119
+ formattedMessage += `\n\n排查建议:\n${troubleshooter}`;
120
+ }
121
+ else {
122
+ // 尝试添加预定义的错误指南
123
+ const errorCodeStr = String(errorCode);
124
+ if (errorGuides[errorCodeStr]) {
125
+ formattedMessage += `\n\n排查建议:\n${errorGuides[errorCodeStr]}`;
126
+ }
127
+ else {
128
+ // 如果没有精确匹配,尝试通过错误消息内容模糊匹配
129
+ for (const [key, guide] of Object.entries(errorGuides)) {
130
+ if (errorMsg.toLowerCase().includes(key.toLowerCase())) {
131
+ formattedMessage += `\n\n排查建议:\n${guide}`;
132
+ break;
133
+ }
134
+ }
135
+ }
136
+ }
137
+ return formattedMessage;
138
+ }
139
+ catch (e) {
140
+ Logger.error("格式化错误消息时发生错误:", e);
141
+ return typeof error === 'string' ? error : '发生未知错误';
142
+ }
143
+ }
144
+ /**
145
+ * 包装错误为标准格式
146
+ *
147
+ * @param message 错误消息前缀
148
+ * @param originalError 原始错误
149
+ * @returns 包装后的错误对象
150
+ */
151
+ export function wrapError(message, originalError) {
152
+ const errorMessage = formatErrorMessage(originalError);
153
+ return new Error(`${message}: ${errorMessage}`);
154
+ }