- Add package.json and package-lock.json for Node.js project setup - Add .gitignore to exclude node_modules directory - Add apifox.js as main entry point for API processing - Add index.js for application initialization - Add api-details.json containing comprehensive API endpoint definitions - Add apifox.json, apifox-ret.json for Apifox configuration and responses - Add data-schemas.json for API data schema definitions - Add schema.json for JSON schema validation - Add folderList.json for API folder structure organization - Initialize project structure for Apifox API documentation management
334 lines
9.9 KiB
JavaScript
334 lines
9.9 KiB
JavaScript
import axios from "axios";
|
|
import NodeCache from 'node-cache';
|
|
import fs from 'fs'
|
|
|
|
|
|
const APIFOX_API_BASE = "https://apifox.gzmiyuan.com/api/v1";
|
|
|
|
process.env.APIFOX_TOKEN = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MzQ1MTIwLCJ0cyI6IjUyOGZiZDMwNGM3MjU2NDMiLCJpYXQiOjE3NjQxNTQyODEzMzB9.IZvoCb3pN5AUl375c4Dhpur1GdYBqyHWVkJ94bDQ6BQ';
|
|
|
|
const http = axios.create({
|
|
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 ${process.env.APIFOX_TOKEN || ''}`
|
|
}
|
|
});
|
|
|
|
|
|
export class ApifoxService {
|
|
constructor(projectId) {
|
|
this.projectId = projectId;
|
|
this.branchId = null;
|
|
this.apiTreeList = null;
|
|
// 初始化node-cache,设置默认5分钟过期时间
|
|
this.cache = new NodeCache({
|
|
stdTTL: 5 * 60, // 5分钟,单位秒
|
|
checkperiod: 60, // 每分钟检查一次过期缓存
|
|
useClones: false // 创建缓存对象的克隆以避免引用问题
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 清除特定缓存
|
|
* @param {string} cacheKey - 缓存键名
|
|
*/
|
|
clearCache(cacheKey) {
|
|
if (cacheKey) {
|
|
this.cache.del(cacheKey);
|
|
} else {
|
|
// 清除所有缓存
|
|
this.cache.flushAll();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 初始化服务,获取branchId
|
|
* @returns {Promise<void>}
|
|
*/
|
|
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 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 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<object>} 返回完整解析后的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<object>} 返回解析后的对象
|
|
*/
|
|
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);
|
|
});
|