feishu-mcp 0.0.5 → 0.0.7
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 +1 -6
- package/dist/config.js +22 -100
- package/dist/index.js +14 -13
- package/dist/server.js +193 -370
- package/dist/services/baseService.js +204 -0
- package/dist/services/blockFactory.js +184 -0
- package/dist/services/feishu.js +16 -148
- package/dist/services/feishuApiService.js +488 -0
- package/dist/services/feishuBlockService.js +179 -0
- package/dist/services/feishuService.js +475 -0
- package/dist/types/feishuSchema.js +97 -0
- package/dist/utils/cache.js +221 -0
- package/dist/utils/config.js +363 -0
- package/dist/utils/document.js +74 -0
- package/dist/utils/error.js +154 -0
- package/dist/utils/logger.js +257 -0
- package/dist/utils/paramUtils.js +193 -0
- package/package.json +1 -1
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 日志级别枚举
|
|
3
|
+
*/
|
|
4
|
+
export var LogLevel;
|
|
5
|
+
(function (LogLevel) {
|
|
6
|
+
LogLevel[LogLevel["DEBUG"] = 0] = "DEBUG";
|
|
7
|
+
LogLevel[LogLevel["INFO"] = 1] = "INFO";
|
|
8
|
+
LogLevel[LogLevel["LOG"] = 2] = "LOG";
|
|
9
|
+
LogLevel[LogLevel["WARN"] = 3] = "WARN";
|
|
10
|
+
LogLevel[LogLevel["ERROR"] = 4] = "ERROR";
|
|
11
|
+
LogLevel[LogLevel["NONE"] = 5] = "NONE";
|
|
12
|
+
})(LogLevel || (LogLevel = {}));
|
|
13
|
+
// 导入文件系统模块
|
|
14
|
+
import * as fs from 'fs';
|
|
15
|
+
import * as path from 'path';
|
|
16
|
+
/**
|
|
17
|
+
* 增强的日志管理器类
|
|
18
|
+
* 提供可配置的日志记录功能,支持不同日志级别和格式化
|
|
19
|
+
*/
|
|
20
|
+
export class Logger {
|
|
21
|
+
/**
|
|
22
|
+
* 配置日志管理器
|
|
23
|
+
* @param config 日志配置项
|
|
24
|
+
*/
|
|
25
|
+
static configure(config) {
|
|
26
|
+
this.config = { ...this.config, ...config };
|
|
27
|
+
// 确保日志目录存在
|
|
28
|
+
if (this.config.logToFile) {
|
|
29
|
+
const logDir = path.dirname(this.config.logFilePath);
|
|
30
|
+
if (!fs.existsSync(logDir)) {
|
|
31
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 格式化日志消息
|
|
37
|
+
* @param level 日志级别
|
|
38
|
+
* @param args 日志参数
|
|
39
|
+
* @returns 格式化后的日志字符串数组
|
|
40
|
+
*/
|
|
41
|
+
static formatLogMessage(level, args) {
|
|
42
|
+
const result = [];
|
|
43
|
+
// 添加时间戳
|
|
44
|
+
if (this.config.showTimestamp) {
|
|
45
|
+
const now = new Date();
|
|
46
|
+
const timestamp = this.formatDate(now, this.config.timestampFormat || 'yyyy-MM-dd HH:mm:ss.SSS');
|
|
47
|
+
result.push(`[${timestamp}]`);
|
|
48
|
+
}
|
|
49
|
+
// 添加日志级别
|
|
50
|
+
if (this.config.showLevel) {
|
|
51
|
+
const levelStr = LogLevel[level].padEnd(5, ' ');
|
|
52
|
+
result.push(`[${levelStr}]`);
|
|
53
|
+
}
|
|
54
|
+
// 添加原始日志内容
|
|
55
|
+
return [...result, ...args];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 将日志写入文件
|
|
59
|
+
* @param logParts 日志内容部分
|
|
60
|
+
*/
|
|
61
|
+
static writeToFile(logParts) {
|
|
62
|
+
if (!this.config.logToFile)
|
|
63
|
+
return;
|
|
64
|
+
try {
|
|
65
|
+
// 将日志内容转换为字符串
|
|
66
|
+
let logString = '';
|
|
67
|
+
for (const part of logParts) {
|
|
68
|
+
if (typeof part === 'object') {
|
|
69
|
+
try {
|
|
70
|
+
// 简化对象序列化
|
|
71
|
+
logString += this.safeStringify(part) + ' ';
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
logString += '[Object] ';
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
logString += part + ' ';
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// 添加换行符
|
|
82
|
+
logString += '\n';
|
|
83
|
+
// 以追加模式写入文件
|
|
84
|
+
fs.appendFileSync(this.config.logFilePath, logString);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
console.error('写入日志文件失败:', error);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* 安全的对象序列化,限制深度和长度
|
|
92
|
+
* @param obj 要序列化的对象
|
|
93
|
+
* @returns 序列化后的字符串
|
|
94
|
+
*/
|
|
95
|
+
static safeStringify(obj) {
|
|
96
|
+
const seen = new Set();
|
|
97
|
+
const stringified = JSON.stringify(obj, (key, value) => {
|
|
98
|
+
// 处理循环引用
|
|
99
|
+
if (typeof value === 'object' && value !== null) {
|
|
100
|
+
if (seen.has(value)) {
|
|
101
|
+
return '[Circular]';
|
|
102
|
+
}
|
|
103
|
+
seen.add(value);
|
|
104
|
+
}
|
|
105
|
+
// 处理请求/响应对象
|
|
106
|
+
if (key === 'request' || key === 'socket' || key === 'agent' ||
|
|
107
|
+
key === '_events' || key === '_eventsCount' || key === '_maxListeners' ||
|
|
108
|
+
key === 'rawHeaders' || key === 'rawTrailers') {
|
|
109
|
+
return '[Object]';
|
|
110
|
+
}
|
|
111
|
+
return value;
|
|
112
|
+
}, 2);
|
|
113
|
+
if (stringified && stringified.length > this.config.maxObjectStringLength) {
|
|
114
|
+
return stringified.substring(0, this.config.maxObjectStringLength) + '... [截断]';
|
|
115
|
+
}
|
|
116
|
+
return stringified;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* 格式化日期
|
|
120
|
+
* @param date 日期对象
|
|
121
|
+
* @param format 格式字符串
|
|
122
|
+
* @returns 格式化后的日期字符串
|
|
123
|
+
*/
|
|
124
|
+
static formatDate(date, format) {
|
|
125
|
+
const year = date.getFullYear().toString();
|
|
126
|
+
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
|
127
|
+
const day = date.getDate().toString().padStart(2, '0');
|
|
128
|
+
const hours = date.getHours().toString().padStart(2, '0');
|
|
129
|
+
const minutes = date.getMinutes().toString().padStart(2, '0');
|
|
130
|
+
const seconds = date.getSeconds().toString().padStart(2, '0');
|
|
131
|
+
const milliseconds = date.getMilliseconds().toString().padStart(3, '0');
|
|
132
|
+
return format
|
|
133
|
+
.replace('yyyy', year)
|
|
134
|
+
.replace('MM', month)
|
|
135
|
+
.replace('dd', day)
|
|
136
|
+
.replace('HH', hours)
|
|
137
|
+
.replace('mm', minutes)
|
|
138
|
+
.replace('ss', seconds)
|
|
139
|
+
.replace('SSS', milliseconds);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* 记录调试级别日志
|
|
143
|
+
* @param args 日志参数
|
|
144
|
+
*/
|
|
145
|
+
static debug(...args) {
|
|
146
|
+
if (this.config.minLevel <= LogLevel.DEBUG) {
|
|
147
|
+
const formattedMessage = this.formatLogMessage(LogLevel.DEBUG, args);
|
|
148
|
+
console.debug(...formattedMessage);
|
|
149
|
+
this.writeToFile(formattedMessage);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* 记录信息级别日志
|
|
154
|
+
* @param args 日志参数
|
|
155
|
+
*/
|
|
156
|
+
static info(...args) {
|
|
157
|
+
if (this.config.minLevel <= LogLevel.INFO) {
|
|
158
|
+
const formattedMessage = this.formatLogMessage(LogLevel.INFO, args);
|
|
159
|
+
console.info(...formattedMessage);
|
|
160
|
+
this.writeToFile(formattedMessage);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* 记录普通级别日志
|
|
165
|
+
* @param args 日志参数
|
|
166
|
+
*/
|
|
167
|
+
static log(...args) {
|
|
168
|
+
if (this.config.minLevel <= LogLevel.LOG) {
|
|
169
|
+
const formattedMessage = this.formatLogMessage(LogLevel.LOG, args);
|
|
170
|
+
console.log(...formattedMessage);
|
|
171
|
+
this.writeToFile(formattedMessage);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* 记录警告级别日志
|
|
176
|
+
* @param args 日志参数
|
|
177
|
+
*/
|
|
178
|
+
static warn(...args) {
|
|
179
|
+
if (this.config.minLevel <= LogLevel.WARN) {
|
|
180
|
+
const formattedMessage = this.formatLogMessage(LogLevel.WARN, args);
|
|
181
|
+
console.warn(...formattedMessage);
|
|
182
|
+
this.writeToFile(formattedMessage);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* 记录错误级别日志
|
|
187
|
+
* @param args 日志参数
|
|
188
|
+
*/
|
|
189
|
+
static error(...args) {
|
|
190
|
+
if (this.config.minLevel <= LogLevel.ERROR) {
|
|
191
|
+
const formattedMessage = this.formatLogMessage(LogLevel.ERROR, args);
|
|
192
|
+
console.error(...formattedMessage);
|
|
193
|
+
this.writeToFile(formattedMessage);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* 记录请求和响应的详细信息
|
|
198
|
+
* @param method 请求方法
|
|
199
|
+
* @param url 请求URL
|
|
200
|
+
* @param data 请求数据
|
|
201
|
+
* @param response 响应数据
|
|
202
|
+
* @param statusCode 响应状态码
|
|
203
|
+
*/
|
|
204
|
+
static logApiCall(method, url, data, response, statusCode) {
|
|
205
|
+
if (this.config.minLevel <= LogLevel.DEBUG) {
|
|
206
|
+
this.debug('API调用详情:');
|
|
207
|
+
this.debug(`请求: ${method} ${url}`);
|
|
208
|
+
// 简化请求数据记录
|
|
209
|
+
if (data) {
|
|
210
|
+
try {
|
|
211
|
+
if (typeof data === 'string') {
|
|
212
|
+
// 尝试解析JSON字符串
|
|
213
|
+
const parsedData = JSON.parse(data);
|
|
214
|
+
this.debug('请求数据:', parsedData);
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
this.debug('请求数据:', data);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
catch (e) {
|
|
221
|
+
this.debug('请求数据:', data);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
this.debug('请求数据: None');
|
|
226
|
+
}
|
|
227
|
+
this.debug(`响应状态: ${statusCode}`);
|
|
228
|
+
// 简化响应数据记录
|
|
229
|
+
if (response) {
|
|
230
|
+
// 只记录关键信息
|
|
231
|
+
const simplifiedResponse = response.data ? { data: response.data } : response;
|
|
232
|
+
this.debug('响应数据:', simplifiedResponse);
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
this.debug('响应数据: None');
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
this.info(`API调用: ${method} ${url} - 状态码: ${statusCode}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
Object.defineProperty(Logger, "config", {
|
|
244
|
+
enumerable: true,
|
|
245
|
+
configurable: true,
|
|
246
|
+
writable: true,
|
|
247
|
+
value: {
|
|
248
|
+
minLevel: LogLevel.DEBUG, // 修改为DEBUG级别,确保捕获所有日志
|
|
249
|
+
showTimestamp: true,
|
|
250
|
+
showLevel: true,
|
|
251
|
+
timestampFormat: 'yyyy-MM-dd HH:mm:ss.SSS',
|
|
252
|
+
logToFile: false,
|
|
253
|
+
logFilePath: 'log/log.txt',
|
|
254
|
+
maxObjectDepth: 2, // 限制对象序列化深度
|
|
255
|
+
maxObjectStringLength: 5000000 // 限制序列化后字符串长度
|
|
256
|
+
}
|
|
257
|
+
});
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { normalizeDocumentId, normalizeWikiToken } from './document.js';
|
|
2
|
+
import { Logger } from './logger.js';
|
|
3
|
+
import { formatErrorMessage } from './error.js';
|
|
4
|
+
/**
|
|
5
|
+
* 参数验证错误
|
|
6
|
+
*/
|
|
7
|
+
export class ParamValidationError extends Error {
|
|
8
|
+
constructor(param, message) {
|
|
9
|
+
super(message);
|
|
10
|
+
Object.defineProperty(this, "param", {
|
|
11
|
+
enumerable: true,
|
|
12
|
+
configurable: true,
|
|
13
|
+
writable: true,
|
|
14
|
+
value: void 0
|
|
15
|
+
});
|
|
16
|
+
this.name = 'ParamValidationError';
|
|
17
|
+
this.param = param;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 参数处理工具类
|
|
22
|
+
* 提供参数验证、转换和处理功能
|
|
23
|
+
*/
|
|
24
|
+
export class ParamUtils {
|
|
25
|
+
/**
|
|
26
|
+
* 处理文档ID参数
|
|
27
|
+
* 验证并规范化文档ID
|
|
28
|
+
*
|
|
29
|
+
* @param documentId 文档ID或URL
|
|
30
|
+
* @returns 规范化的文档ID
|
|
31
|
+
* @throws 如果文档ID无效则抛出错误
|
|
32
|
+
*/
|
|
33
|
+
static processDocumentId(documentId) {
|
|
34
|
+
if (!documentId) {
|
|
35
|
+
throw new ParamValidationError('documentId', '文档ID不能为空');
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
return normalizeDocumentId(documentId);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
throw new ParamValidationError('documentId', formatErrorMessage(error));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 处理Wiki Token参数
|
|
46
|
+
* 验证并规范化Wiki Token
|
|
47
|
+
*
|
|
48
|
+
* @param wikiUrl Wiki URL或Token
|
|
49
|
+
* @returns 规范化的Wiki Token
|
|
50
|
+
* @throws 如果Wiki Token无效则抛出错误
|
|
51
|
+
*/
|
|
52
|
+
static processWikiToken(wikiUrl) {
|
|
53
|
+
if (!wikiUrl) {
|
|
54
|
+
throw new ParamValidationError('wikiUrl', 'Wiki URL不能为空');
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
return normalizeWikiToken(wikiUrl);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
throw new ParamValidationError('wikiUrl', formatErrorMessage(error));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 处理块ID参数
|
|
65
|
+
* 验证块ID是否有效
|
|
66
|
+
*
|
|
67
|
+
* @param blockId 块ID
|
|
68
|
+
* @returns 验证后的块ID
|
|
69
|
+
* @throws 如果块ID无效则抛出错误
|
|
70
|
+
*/
|
|
71
|
+
static processBlockId(blockId) {
|
|
72
|
+
if (!blockId) {
|
|
73
|
+
throw new ParamValidationError('blockId', '块ID不能为空');
|
|
74
|
+
}
|
|
75
|
+
if (!/^[a-zA-Z0-9_-]{5,}$/.test(blockId)) {
|
|
76
|
+
throw new ParamValidationError('blockId', '块ID格式无效');
|
|
77
|
+
}
|
|
78
|
+
return blockId;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* 处理父块ID参数
|
|
82
|
+
* 验证父块ID是否有效
|
|
83
|
+
*
|
|
84
|
+
* @param parentBlockId 父块ID
|
|
85
|
+
* @returns 验证后的父块ID
|
|
86
|
+
* @throws 如果父块ID无效则抛出错误
|
|
87
|
+
*/
|
|
88
|
+
static processParentBlockId(parentBlockId) {
|
|
89
|
+
if (!parentBlockId) {
|
|
90
|
+
throw new ParamValidationError('parentBlockId', '父块ID不能为空');
|
|
91
|
+
}
|
|
92
|
+
if (!/^[a-zA-Z0-9_-]{5,}$/.test(parentBlockId)) {
|
|
93
|
+
throw new ParamValidationError('parentBlockId', '父块ID格式无效');
|
|
94
|
+
}
|
|
95
|
+
return parentBlockId;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* 处理插入位置索引参数
|
|
99
|
+
* 验证并规范化索引值
|
|
100
|
+
*
|
|
101
|
+
* @param index 插入位置索引
|
|
102
|
+
* @returns 验证后的索引值
|
|
103
|
+
* @throws 如果索引无效则抛出错误
|
|
104
|
+
*/
|
|
105
|
+
static processIndex(index) {
|
|
106
|
+
if (index === undefined || index === null) {
|
|
107
|
+
return 0; // 默认值
|
|
108
|
+
}
|
|
109
|
+
if (!Number.isInteger(index) || index < 0) {
|
|
110
|
+
throw new ParamValidationError('index', '索引必须是非负整数');
|
|
111
|
+
}
|
|
112
|
+
return index;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* 处理对齐方式参数
|
|
116
|
+
* 验证并规范化对齐方式值
|
|
117
|
+
*
|
|
118
|
+
* @param align 对齐方式
|
|
119
|
+
* @returns 验证后的对齐方式值
|
|
120
|
+
*/
|
|
121
|
+
static processAlign(align) {
|
|
122
|
+
if (align === undefined || align === null) {
|
|
123
|
+
return 1; // 默认左对齐
|
|
124
|
+
}
|
|
125
|
+
if (![1, 2, 3].includes(align)) {
|
|
126
|
+
Logger.warn(`对齐方式值 ${align} 无效,使用默认值1(左对齐)`);
|
|
127
|
+
return 1;
|
|
128
|
+
}
|
|
129
|
+
return align;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* 处理语言类型参数
|
|
133
|
+
* 验证并规范化语言类型值
|
|
134
|
+
*
|
|
135
|
+
* @param language 语言类型
|
|
136
|
+
* @returns 验证后的语言类型值
|
|
137
|
+
*/
|
|
138
|
+
static processLanguage(language) {
|
|
139
|
+
if (language === undefined || language === null) {
|
|
140
|
+
return 1; // 默认纯文本
|
|
141
|
+
}
|
|
142
|
+
if (!Number.isInteger(language) || language < 1 || language > 71) {
|
|
143
|
+
Logger.warn(`语言类型值 ${language} 无效,使用默认值1(纯文本)`);
|
|
144
|
+
return 1;
|
|
145
|
+
}
|
|
146
|
+
return language;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* 处理标题级别参数
|
|
150
|
+
* 验证并规范化标题级别值
|
|
151
|
+
*
|
|
152
|
+
* @param level 标题级别
|
|
153
|
+
* @returns 验证后的标题级别值
|
|
154
|
+
*/
|
|
155
|
+
static processHeadingLevel(level) {
|
|
156
|
+
if (level === undefined || level === null) {
|
|
157
|
+
return 1; // 默认一级标题
|
|
158
|
+
}
|
|
159
|
+
// 限制在1-9范围内
|
|
160
|
+
return Math.max(1, Math.min(9, level));
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* 批量处理通用参数
|
|
164
|
+
* 验证并规范化常用参数集
|
|
165
|
+
*
|
|
166
|
+
* @param params 通用参数对象
|
|
167
|
+
* @returns 处理后的参数对象
|
|
168
|
+
*/
|
|
169
|
+
static processCommonParams(params) {
|
|
170
|
+
const result = { ...params };
|
|
171
|
+
// 处理文档ID
|
|
172
|
+
if (params.documentId) {
|
|
173
|
+
result.documentId = ParamUtils.processDocumentId(params.documentId);
|
|
174
|
+
}
|
|
175
|
+
// 处理块ID
|
|
176
|
+
if (params.blockId) {
|
|
177
|
+
result.blockId = ParamUtils.processBlockId(params.blockId);
|
|
178
|
+
}
|
|
179
|
+
// 处理父块ID
|
|
180
|
+
if (params.parentBlockId) {
|
|
181
|
+
result.parentBlockId = ParamUtils.processParentBlockId(params.parentBlockId);
|
|
182
|
+
}
|
|
183
|
+
// 处理索引
|
|
184
|
+
if (params.index !== undefined) {
|
|
185
|
+
result.index = ParamUtils.processIndex(params.index);
|
|
186
|
+
}
|
|
187
|
+
// 处理起始索引
|
|
188
|
+
if (params.startIndex !== undefined) {
|
|
189
|
+
result.startIndex = ParamUtils.processIndex(params.startIndex);
|
|
190
|
+
}
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
}
|