import axios from "axios"; import NodeCache from 'node-cache'; import fs from 'fs' const APIFOX_API_BASE = "https://apifox.gzmiyuan.com/api/v1"; export class ApifoxService { constructor(config = {}) { const { projectId, token, baseUrl } = config; this.projectId = projectId; this.branchId = null; this.apiTreeList = null; this.http = axios.create({ baseURL: baseUrl || APIFOX_API_BASE, headers: { "X-Client-Mode": "web", "X-Client-Version": "2.7.19", "X-Device-Id": "bd94aea0-7b65-48ba-9274-4b1177897954", "Authorization": `Bearer ${token}` } }); // 初始化node-cache,设置默认5分钟过期时间 this.cache = new NodeCache({ stdTTL: 5 * 60, // 5分钟,单位秒 checkperiod: 60, // 每分钟检查一次过期缓存 useClones: false // 创建缓存对象的克隆以避免引用问题 }); this.init(); } /** * 清除特定缓存 * @param {string} cacheKey - 缓存键名 */ clearCache(cacheKey) { if (cacheKey) { this.cache.del(cacheKey); } else { // 清除所有缓存 this.cache.flushAll(); } } /** * 初始化服务,获取branchId * @returns {Promise} */ async init() { try { this.branchId = await this.getBranchId(); console.log('branchId', this.branchId); } catch (error) { console.error("Error initializing branch ID:", error); throw error; } } /** * 发送GET请求 * @param {string} url - 请求的URL路径 * @param {object} config - axios配置对象 * @returns {Promise} 返回响应数据中的data字段 */ async get(url, config = {}) { try { const response = await this.http.get(url, { ...config, headers: { ...config.headers, "X-Project-Id": this.projectId, "X-Branch-Id": this.branchId, }, }); return response.data; } catch (error) { throw error; } } /** * 发送POST请求 * @param {string} url - 请求的URL路径 * @param {object} data - 请求体数据 * @param {object} config - axios配置对象 * @returns {Promise} 返回响应数据中的data字段 */ async post(url, data = {}, config = {}) { try { const response = await this.http.post(url, data, { ...config, headers: { ...config.headers, "X-Project-Id": this.projectId, "X-Branch-Id": this.branchId, }, }); return response.data; } catch (error) { throw error; } } async getBranchId() { const branches = await this.get(`/projects/${this.projectId}/api-versions`); return branches.data[0].id; } async getApiTreeList() { const apiTree = await this.get(`/projects/${this.projectId}/api-tree-list`); console.log('-- get api tree list success', 'length', apiTree.length); this.apiTreeList = apiTree; return apiTree; } async getFolderList() { // 定义缓存键 const cacheKey = 'folderList'; // 检查缓存是否有效 const cachedData = this.cache.get(cacheKey); if (cachedData) { console.log('Using cached folder list', 'length', cachedData.length); return cachedData; } const {data: folderList} = await this.get(`/projects/${this.projectId}/api-detail-folders`); console.log('--get folder success', 'length', folderList.length); // 设置缓存 this.cache.set(cacheKey, folderList); return folderList; } async getFolderByName(folderName) { const folderList = await this.getFolderList(); const folder = folderList.find((item) => item.name === folderName); if (!folder) { throw new Error(`Folder with name ${folderName} not found`); } return folder; } async getDataSchema() { // 定义缓存键 const cacheKey = 'dataSchema'; // 检查缓存是否有效 const cachedData = this.cache.get(cacheKey); if (cachedData) { console.log('Using cached data schema'); return cachedData; } const {data:schemasTreeList} = await this.get(`/projects/${this.projectId}/data-schemas?locale=zh-CN`); console.log('--get data schema success', 'length', schemasTreeList.length); // 设置缓存 this.cache.set(cacheKey, schemasTreeList); return schemasTreeList; } async getApiDetails() { // 定义缓存键 const cacheKey = 'apiDetails'; // 检查缓存是否有效 const cachedData = this.cache.get(cacheKey); if (cachedData) { console.log('Using cached API details'); return cachedData; } const {data:apiDetails} = await this.get(`/api-details?locale=zh-CN`, {}); console.log('--get api details success', 'length', apiDetails.length); // 设置缓存 this.cache.set(cacheKey, apiDetails); return apiDetails; } getSchemaRefId(obj) { if (!obj || !obj.$ref) { console.log('no schemaid', obj) return; } const schemaName = obj.$ref.split('/').pop(); return schemaName; } /** * 根据schema ID获取完整的schema定义,递归替换所有$ref引用 * @param {number|string} schemaId - schema的ID * @param {Array} allSchemas - 所有schema数据数组(可选,用于递归) * @param {Set} visitedIds - 已访问的ID集合,用于防止循环引用 * @returns {Promise} 返回完整解析后的schema对象 */ async getSchemaById(schemaId, allSchemas = null, visitedIds = new Set()) { if (!schemaId) { return null; } // 如果没有传入allSchemas,则获取所有schema数据 if (!allSchemas) { allSchemas = await this.getDataSchema(); } // 查找对应ID的schema const schema = allSchemas.find((item) => item.id.toString() === schemaId.toString()); if (!schema) { throw new Error(`Schema with id ${schemaId} not found`); } // 防止循环引用 if (visitedIds.has(schemaId.toString())) { return { type: 'object', description: `循环引用: ${schema.name}` }; } visitedIds.add(schemaId.toString()); // 深拷贝schema以避免修改原始数据 const resolvedSchema = JSON.parse(JSON.stringify(schema)); // 递归替换jsonSchema中的所有$ref resolvedSchema.jsonSchema = await this.resolveRefs( resolvedSchema.jsonSchema, allSchemas, new Set(visitedIds) ); // 移除不需要的字段 const { description, folderId, projectId, moduleId, ordering, creatorId, editorId, createdAt, updatedAt, visibility, ...cleanSchema } = resolvedSchema; return cleanSchema; } /** * 递归解析对象中的所有$ref引用 * @param {object} obj - 要解析的对象 * @param {Array} allSchemas - 所有schema数据数组 * @param {Set} visitedIds - 已访问的ID集合 * @returns {Promise} 返回解析后的对象 */ async resolveRefs(obj, allSchemas, visitedIds) { if (!obj || typeof obj !== 'object') { return obj; } // 如果是数组,递归处理每个元素 if (Array.isArray(obj)) { const resolved = []; for (const item of obj) { resolved.push(await this.resolveRefs(item, allSchemas, visitedIds)); } return resolved; } // 如果对象包含$ref,替换为实际的schema定义 if (obj.$ref) { const refId = this.getSchemaRefId(obj); const referencedSchema = await this.getSchemaById(refId, allSchemas, visitedIds); // 返回引用的jsonSchema内容 return referencedSchema.jsonSchema; } // 递归处理对象的所有属性 const resolved = {}; for (const [key, value] of Object.entries(obj)) { resolved[key] = await this.resolveRefs(value, allSchemas, visitedIds); } return resolved; } async getApiDetailResp(apiDetail) { const dataSchema = await this.getDataSchema(); const requestSchemaId = this.getSchemaRefId(apiDetail.requestBody.jsonSchema); const responseSchema = Array.isArray(apiDetail.responses) ? apiDetail.responses[0].jsonSchema : apiDetail.responses.jsonSchema; const responseSchemaId = this.getSchemaRefId(responseSchema); console.log('responseSchemaId', responseSchemaId); const respDataSchemaId = this.getSchemaRefId(dataSchema.find((item) => item.id.toString() === responseSchemaId).jsonSchema.properties.data) console.log('respDataSchemaId', respDataSchemaId); return { path: apiDetail.path, name: apiDetail.name, method: apiDetail.method, url: apiDetail.url, requestSchema: await this.getSchemaById(requestSchemaId), responseSchema: await this.getSchemaById(respDataSchemaId), }; } async getApiDetailByPath(path) { const apiDetails = await this.getApiDetails(); const apiDetail = apiDetails.find((item) => item.path === path); if (!apiDetail) { throw new Error(`API with path ${path} not found`); } return this.getApiDetailResp(apiDetail); } async getApiDetailsById(apiId) { const apiDetails = await this.getApiDetails(); const apiDetail = apiDetails.find((item) => item.id === apiId); if (!apiDetail) { throw new Error(`API with id ${id} not found`); } return this.getApiDetailResp(apiDetail); } // TODO: async getApiListByFolderName(folderName) { const folder = await this.getFolderByName(folderName); const apiDetails = await this.getApiDetails(); const apiList = apiDetails.filter((item) => item.folderId === folder.id); console.log('--getApiListByFolderName success', 'length', apiList.length); return Promise.all(apiList.map((api) => this.getApiDetailResp(api))); } } const apifoxService = new ApifoxService(345161); apifoxService.init().then(() => { apifoxService.getApiListByFolderName('数据中心统计数据') .then(res => { fs.writeFileSync('apifox-ret.json', JSON.stringify(res, null, 2)); }) }).catch((error) => { console.error("Error initializing ApifoxService:", error); });