UNPKG

feishu-mcp

Version:

Model Context Protocol server for Feishu integration

205 lines (204 loc) 7.17 kB
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); } }