feishu-mcp
Version:
Model Context Protocol server for Feishu integration
205 lines (204 loc) • 7.17 kB
JavaScript
import axios, { AxiosError } from 'axios';
import { Logger } from '../utils/logger.js';
import { formatErrorMessage } from '../utils/error.js';
/**
* API服务基类
* 提供通用的HTTP请求处理和认证功能
*/
export class BaseApiService {
constructor() {
Object.defineProperty(this, "accessToken", {
enumerable: true,
configurable: true,
writable: true,
value: ''
});
Object.defineProperty(this, "tokenExpireTime", {
enumerable: true,
configurable: true,
writable: true,
value: null
});
}
/**
* 检查访问令牌是否过期
* @returns 是否过期
*/
isTokenExpired() {
if (!this.accessToken || !this.tokenExpireTime)
return true;
// 预留5分钟的缓冲时间
return Date.now() >= (this.tokenExpireTime - 5 * 60 * 1000);
}
/**
* 处理API错误
* @param error 错误对象
* @param message 错误上下文消息
* @throws 标准化的API错误
*/
handleApiError(error, message) {
Logger.error(`${message}:`, error);
// 如果已经是格式化的API错误,直接重新抛出
if (error && typeof error === 'object' && 'status' in error && 'err' in error) {
throw error;
}
// 处理Axios错误
if (error instanceof AxiosError && error.response) {
const responseData = error.response.data;
const apiError = {
status: error.response.status,
err: formatErrorMessage(error, message),
apiError: responseData,
logId: responseData?.log_id
};
throw apiError;
}
// 处理其他类型的错误
const errorMessage = error instanceof Error
? error.message
: (typeof error === 'string' ? error : '未知错误');
throw {
status: 500,
err: formatErrorMessage(error, message),
apiError: {
code: -1,
msg: errorMessage,
error
}
};
}
/**
* 执行API请求
* @param endpoint 请求端点
* @param method 请求方法
* @param data 请求数据
* @param needsAuth 是否需要认证
* @param additionalHeaders 附加请求头
* @returns 响应数据
*/
async request(endpoint, method = 'GET', data, needsAuth = true, additionalHeaders) {
try {
// 构建请求URL
const url = `${this.getBaseUrl()}${endpoint}`;
// 准备请求头
const headers = {
'Content-Type': 'application/json',
...additionalHeaders
};
// 添加认证令牌
if (needsAuth) {
const accessToken = await this.getAccessToken();
headers['Authorization'] = `Bearer ${accessToken}`;
}
// 记录请求信息
Logger.debug('准备发送请求:');
Logger.debug(`请求URL: ${url}`);
Logger.debug(`请求方法: ${method}`);
if (data) {
Logger.debug(`请求数据:`, data);
}
// 构建请求配置
const config = {
method,
url,
headers,
data: method !== 'GET' ? data : undefined,
params: method === 'GET' ? data : undefined
};
// 发送请求
const response = await axios(config);
// 记录响应信息
Logger.debug('收到响应:');
Logger.debug(`响应状态码: ${response.status}`);
Logger.debug(`响应头:`, response.headers);
Logger.debug(`响应数据:`, response.data);
// 检查API错误
if (response.data && typeof response.data.code === 'number' && response.data.code !== 0) {
Logger.error(`API返回错误码: ${response.data.code}, 错误消息: ${response.data.msg}`);
throw {
status: response.status,
err: response.data.msg || 'API返回错误码',
apiError: response.data,
logId: response.data.log_id
};
}
// 返回数据
return response.data.data;
}
catch (error) {
// 处理401错误,可能是令牌过期
if (error instanceof AxiosError && error.response?.status === 401) {
// 清除当前令牌,下次请求会重新获取
this.accessToken = '';
this.tokenExpireTime = null;
Logger.warn('访问令牌可能已过期,已清除缓存的令牌');
// 如果这是重试请求,避免无限循环
if (error.isRetry) {
this.handleApiError(error, `API请求失败 (${endpoint})`);
}
// 重试请求
Logger.info('重试请求...');
try {
return await this.request(endpoint, method, data, needsAuth, additionalHeaders);
}
catch (retryError) {
// 标记为重试请求
retryError.isRetry = true;
this.handleApiError(retryError, `重试API请求失败 (${endpoint})`);
}
}
// 处理其他错误
this.handleApiError(error, `API请求失败 (${endpoint})`);
}
}
/**
* GET请求
* @param endpoint 请求端点
* @param params 请求参数
* @param needsAuth 是否需要认证
* @returns 响应数据
*/
async get(endpoint, params, needsAuth = true) {
return this.request(endpoint, 'GET', params, needsAuth);
}
/**
* POST请求
* @param endpoint 请求端点
* @param data 请求数据
* @param needsAuth 是否需要认证
* @returns 响应数据
*/
async post(endpoint, data, needsAuth = true) {
return this.request(endpoint, 'POST', data, needsAuth);
}
/**
* PUT请求
* @param endpoint 请求端点
* @param data 请求数据
* @param needsAuth 是否需要认证
* @returns 响应数据
*/
async put(endpoint, data, needsAuth = true) {
return this.request(endpoint, 'PUT', data, needsAuth);
}
/**
* PATCH请求
* @param endpoint 请求端点
* @param data 请求数据
* @param needsAuth 是否需要认证
* @returns 响应数据
*/
async patch(endpoint, data, needsAuth = true) {
return this.request(endpoint, 'PATCH', data, needsAuth);
}
/**
* DELETE请求
* @param endpoint 请求端点
* @param data 请求数据
* @param needsAuth 是否需要认证
* @returns 响应数据
*/
async delete(endpoint, data, needsAuth = true) {
return this.request(endpoint, 'DELETE', data, needsAuth);
}
}