feat: Initialize Apifox API documentation project
- 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
This commit is contained in:
commit
10467c194c
10 files changed
+1655284
No files matched your search
@@ -0,0 +1 @@
|
||||
node_modules
|
||||
+780470
File diff suppressed because it is too large.
Load diff
+413
@@ -0,0 +1,413 @@
|
||||
[
|
||||
{
|
||||
"path": "/shop/data/centre/bigData/statistics/anxuan/live/export",
|
||||
"name": "安选直播大盘分析导出",
|
||||
"method": "post",
|
||||
"requestSchema": null,
|
||||
"responseSchema": {
|
||||
"id": 399189,
|
||||
"name": "LiveStatisticsExportRespDTO",
|
||||
"displayName": "",
|
||||
"jsonSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"taskId": {
|
||||
"type": "string",
|
||||
"description": "导出任务id"
|
||||
}
|
||||
},
|
||||
"x-apifox-orders": [
|
||||
"taskId"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/shop/data/centre/bigData/statistics/anxuan/live/user/export",
|
||||
"name": "安选用户导出",
|
||||
"method": "post",
|
||||
"requestSchema": {
|
||||
"id": 399233,
|
||||
"name": "DataCentreLiveExportReqVO",
|
||||
"displayName": "",
|
||||
"jsonSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"liveId": {
|
||||
"type": "integer",
|
||||
"description": "直播间id"
|
||||
},
|
||||
"type": {
|
||||
"type": "integer",
|
||||
"description": "类型 1安选 2团购"
|
||||
},
|
||||
"scene": {
|
||||
"type": "integer",
|
||||
"description": "导出场景 1大盘分析 2用户导出 3订单导出"
|
||||
}
|
||||
},
|
||||
"x-apifox-orders": [
|
||||
"liveId",
|
||||
"type",
|
||||
"scene"
|
||||
]
|
||||
}
|
||||
},
|
||||
"responseSchema": {
|
||||
"id": 399189,
|
||||
"name": "LiveStatisticsExportRespDTO",
|
||||
"displayName": "",
|
||||
"jsonSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"taskId": {
|
||||
"type": "string",
|
||||
"description": "导出任务id"
|
||||
}
|
||||
},
|
||||
"x-apifox-orders": [
|
||||
"taskId"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/shop/data/centre/bigData/statistics/anxuan/live/order/export",
|
||||
"name": "安选订单导出",
|
||||
"method": "post",
|
||||
"requestSchema": {
|
||||
"id": 399233,
|
||||
"name": "DataCentreLiveExportReqVO",
|
||||
"displayName": "",
|
||||
"jsonSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"liveId": {
|
||||
"type": "integer",
|
||||
"description": "直播间id"
|
||||
},
|
||||
"type": {
|
||||
"type": "integer",
|
||||
"description": "类型 1安选 2团购"
|
||||
},
|
||||
"scene": {
|
||||
"type": "integer",
|
||||
"description": "导出场景 1大盘分析 2用户导出 3订单导出"
|
||||
}
|
||||
},
|
||||
"x-apifox-orders": [
|
||||
"liveId",
|
||||
"type",
|
||||
"scene"
|
||||
]
|
||||
}
|
||||
},
|
||||
"responseSchema": {
|
||||
"id": 399189,
|
||||
"name": "LiveStatisticsExportRespDTO",
|
||||
"displayName": "",
|
||||
"jsonSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"taskId": {
|
||||
"type": "string",
|
||||
"description": "导出任务id"
|
||||
}
|
||||
},
|
||||
"x-apifox-orders": [
|
||||
"taskId"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/shop/data/centre/bigData/statistics/team/live/export",
|
||||
"name": "团购直播大盘分析导出",
|
||||
"method": "post",
|
||||
"requestSchema": null,
|
||||
"responseSchema": {
|
||||
"id": 399189,
|
||||
"name": "LiveStatisticsExportRespDTO",
|
||||
"displayName": "",
|
||||
"jsonSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"taskId": {
|
||||
"type": "string",
|
||||
"description": "导出任务id"
|
||||
}
|
||||
},
|
||||
"x-apifox-orders": [
|
||||
"taskId"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/shop/data/centre/bigData/statistics/team/live/user/export",
|
||||
"name": "团购用户导出",
|
||||
"method": "post",
|
||||
"requestSchema": {
|
||||
"id": 399233,
|
||||
"name": "DataCentreLiveExportReqVO",
|
||||
"displayName": "",
|
||||
"jsonSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"liveId": {
|
||||
"type": "integer",
|
||||
"description": "直播间id"
|
||||
},
|
||||
"type": {
|
||||
"type": "integer",
|
||||
"description": "类型 1安选 2团购"
|
||||
},
|
||||
"scene": {
|
||||
"type": "integer",
|
||||
"description": "导出场景 1大盘分析 2用户导出 3订单导出"
|
||||
}
|
||||
},
|
||||
"x-apifox-orders": [
|
||||
"liveId",
|
||||
"type",
|
||||
"scene"
|
||||
]
|
||||
}
|
||||
},
|
||||
"responseSchema": {
|
||||
"id": 399189,
|
||||
"name": "LiveStatisticsExportRespDTO",
|
||||
"displayName": "",
|
||||
"jsonSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"taskId": {
|
||||
"type": "string",
|
||||
"description": "导出任务id"
|
||||
}
|
||||
},
|
||||
"x-apifox-orders": [
|
||||
"taskId"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/shop/data/centre/bigData/statistics/team/live/order/export",
|
||||
"name": "团购订单导出",
|
||||
"method": "post",
|
||||
"requestSchema": {
|
||||
"id": 399233,
|
||||
"name": "DataCentreLiveExportReqVO",
|
||||
"displayName": "",
|
||||
"jsonSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"liveId": {
|
||||
"type": "integer",
|
||||
"description": "直播间id"
|
||||
},
|
||||
"type": {
|
||||
"type": "integer",
|
||||
"description": "类型 1安选 2团购"
|
||||
},
|
||||
"scene": {
|
||||
"type": "integer",
|
||||
"description": "导出场景 1大盘分析 2用户导出 3订单导出"
|
||||
}
|
||||
},
|
||||
"x-apifox-orders": [
|
||||
"liveId",
|
||||
"type",
|
||||
"scene"
|
||||
]
|
||||
}
|
||||
},
|
||||
"responseSchema": {
|
||||
"id": 399189,
|
||||
"name": "LiveStatisticsExportRespDTO",
|
||||
"displayName": "",
|
||||
"jsonSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"taskId": {
|
||||
"type": "string",
|
||||
"description": "导出任务id"
|
||||
}
|
||||
},
|
||||
"x-apifox-orders": [
|
||||
"taskId"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/shop/data/centre/bigData/statistics/live/export/status",
|
||||
"name": "导出状态查询",
|
||||
"method": "get",
|
||||
"requestSchema": null,
|
||||
"responseSchema": {
|
||||
"id": 391736,
|
||||
"name": "TaskInfoResDTO",
|
||||
"displayName": "TaskInfoResDTO",
|
||||
"jsonSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"description": "",
|
||||
"format": "int64"
|
||||
},
|
||||
"fileName": {
|
||||
"type": "string",
|
||||
"description": ""
|
||||
},
|
||||
"fileUrl": {
|
||||
"type": "string",
|
||||
"description": ""
|
||||
},
|
||||
"uploadUser": {
|
||||
"type": "string",
|
||||
"description": ""
|
||||
},
|
||||
"uploadUserId": {
|
||||
"type": "string",
|
||||
"description": ""
|
||||
},
|
||||
"taskStatus": {
|
||||
"type": "integer",
|
||||
"description": ""
|
||||
},
|
||||
"taskType": {
|
||||
"type": "integer",
|
||||
"description": ""
|
||||
},
|
||||
"excelRows": {
|
||||
"type": "integer",
|
||||
"description": ""
|
||||
},
|
||||
"insertRows": {
|
||||
"type": "integer",
|
||||
"description": ""
|
||||
},
|
||||
"gmtCreate": {
|
||||
"type": "string",
|
||||
"description": "",
|
||||
"x-apifox-mock": "@datetime"
|
||||
},
|
||||
"gmtModified": {
|
||||
"type": "string",
|
||||
"description": "",
|
||||
"x-apifox-mock": "@datetime"
|
||||
},
|
||||
"uploadUserType": {
|
||||
"type": "integer",
|
||||
"description": "上传用户类型(端口)(0-中台 1-供应商 2-营销公司 3-营销公司删除)"
|
||||
},
|
||||
"onDuplicate": {
|
||||
"type": "integer",
|
||||
"description": "重复数据处理方式 1重复数据不更新 2重复数据覆盖 3重复数据更新"
|
||||
},
|
||||
"taskSource": {
|
||||
"type": "integer",
|
||||
"description": ""
|
||||
},
|
||||
"taskName": {
|
||||
"type": "string",
|
||||
"description": ""
|
||||
},
|
||||
"failFileUrl": {
|
||||
"type": "string",
|
||||
"description": ""
|
||||
},
|
||||
"failFileName": {
|
||||
"type": "string",
|
||||
"description": ""
|
||||
},
|
||||
"extra": {
|
||||
"type": "string",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"x-apifox-orders": [
|
||||
"id",
|
||||
"fileName",
|
||||
"fileUrl",
|
||||
"uploadUser",
|
||||
"uploadUserId",
|
||||
"taskStatus",
|
||||
"taskType",
|
||||
"excelRows",
|
||||
"insertRows",
|
||||
"gmtCreate",
|
||||
"gmtModified",
|
||||
"uploadUserType",
|
||||
"onDuplicate",
|
||||
"taskSource",
|
||||
"taskName",
|
||||
"failFileUrl",
|
||||
"failFileName",
|
||||
"extra"
|
||||
],
|
||||
"required": [
|
||||
"onDuplicate"
|
||||
],
|
||||
"title": "TaskInfoResDTO"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/shop/data/centre/bigData/statistics/live/export/cache",
|
||||
"name": "导出缓存查询",
|
||||
"method": "post",
|
||||
"requestSchema": {
|
||||
"id": 399233,
|
||||
"name": "DataCentreLiveExportReqVO",
|
||||
"displayName": "",
|
||||
"jsonSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"liveId": {
|
||||
"type": "integer",
|
||||
"description": "直播间id"
|
||||
},
|
||||
"type": {
|
||||
"type": "integer",
|
||||
"description": "类型 1安选 2团购"
|
||||
},
|
||||
"scene": {
|
||||
"type": "integer",
|
||||
"description": "导出场景 1大盘分析 2用户导出 3订单导出"
|
||||
}
|
||||
},
|
||||
"x-apifox-orders": [
|
||||
"liveId",
|
||||
"type",
|
||||
"scene"
|
||||
]
|
||||
}
|
||||
},
|
||||
"responseSchema": {
|
||||
"id": 399347,
|
||||
"name": "ExportCacheRespDTO",
|
||||
"displayName": "",
|
||||
"jsonSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"fileName": {
|
||||
"type": "string",
|
||||
"description": "文件名称"
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "文件地址"
|
||||
}
|
||||
},
|
||||
"x-apifox-orders": [
|
||||
"fileName",
|
||||
"url"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,333 @@
|
||||
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);
|
||||
});
|
||||
+543
@@ -0,0 +1,543 @@
|
||||
[
|
||||
{
|
||||
"path": "/shop/data/centre/bigData/statistics/anxuan/live/export",
|
||||
"name": "安选直播大盘分析导出",
|
||||
"method": "post",
|
||||
"requestSchema": null,
|
||||
"responseSchema": {
|
||||
"id": 399189,
|
||||
"name": "LiveStatisticsExportRespDTO",
|
||||
"displayName": "",
|
||||
"jsonSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"taskId": {
|
||||
"type": "string",
|
||||
"description": "导出任务id"
|
||||
}
|
||||
},
|
||||
"x-apifox-orders": [
|
||||
"taskId"
|
||||
]
|
||||
},
|
||||
"folderId": 0,
|
||||
"description": "com.meiji.big.data.api.dto.response.live.LiveStatisticsExportRespDTO",
|
||||
"projectId": 345161,
|
||||
"moduleId": 300361,
|
||||
"ordering": 354,
|
||||
"creatorId": 345148,
|
||||
"editorId": 345049,
|
||||
"createdAt": "2025-09-17T12:54:06.000Z",
|
||||
"updatedAt": "2025-09-22T11:48:34.000Z",
|
||||
"visibility": "INHERITED"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/shop/data/centre/bigData/statistics/anxuan/live/user/export",
|
||||
"name": "安选用户导出",
|
||||
"method": "post",
|
||||
"requestSchema": {
|
||||
"id": 399233,
|
||||
"name": "DataCentreLiveExportReqVO",
|
||||
"displayName": "",
|
||||
"jsonSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"liveId": {
|
||||
"type": "integer",
|
||||
"description": "直播间id"
|
||||
},
|
||||
"type": {
|
||||
"type": "integer",
|
||||
"description": "类型 1安选 2团购"
|
||||
},
|
||||
"scene": {
|
||||
"type": "integer",
|
||||
"description": "导出场景 1大盘分析 2用户导出 3订单导出"
|
||||
}
|
||||
},
|
||||
"x-apifox-orders": [
|
||||
"liveId",
|
||||
"type",
|
||||
"scene"
|
||||
]
|
||||
},
|
||||
"folderId": 0,
|
||||
"description": "com.meiji.proxy.shop.vo.request.bigdata.DataCentreLiveExportReqVO",
|
||||
"projectId": 345161,
|
||||
"moduleId": 300361,
|
||||
"ordering": 366,
|
||||
"creatorId": 345148,
|
||||
"editorId": 345049,
|
||||
"createdAt": "2025-09-18T02:44:05.000Z",
|
||||
"updatedAt": "2025-09-22T11:48:34.000Z",
|
||||
"visibility": "INHERITED"
|
||||
},
|
||||
"responseSchema": {
|
||||
"id": 399189,
|
||||
"name": "LiveStatisticsExportRespDTO",
|
||||
"displayName": "",
|
||||
"jsonSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"taskId": {
|
||||
"type": "string",
|
||||
"description": "导出任务id"
|
||||
}
|
||||
},
|
||||
"x-apifox-orders": [
|
||||
"taskId"
|
||||
]
|
||||
},
|
||||
"folderId": 0,
|
||||
"description": "com.meiji.big.data.api.dto.response.live.LiveStatisticsExportRespDTO",
|
||||
"projectId": 345161,
|
||||
"moduleId": 300361,
|
||||
"ordering": 354,
|
||||
"creatorId": 345148,
|
||||
"editorId": 345049,
|
||||
"createdAt": "2025-09-17T12:54:06.000Z",
|
||||
"updatedAt": "2025-09-22T11:48:34.000Z",
|
||||
"visibility": "INHERITED"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/shop/data/centre/bigData/statistics/anxuan/live/order/export",
|
||||
"name": "安选订单导出",
|
||||
"method": "post",
|
||||
"requestSchema": {
|
||||
"id": 399233,
|
||||
"name": "DataCentreLiveExportReqVO",
|
||||
"displayName": "",
|
||||
"jsonSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"liveId": {
|
||||
"type": "integer",
|
||||
"description": "直播间id"
|
||||
},
|
||||
"type": {
|
||||
"type": "integer",
|
||||
"description": "类型 1安选 2团购"
|
||||
},
|
||||
"scene": {
|
||||
"type": "integer",
|
||||
"description": "导出场景 1大盘分析 2用户导出 3订单导出"
|
||||
}
|
||||
},
|
||||
"x-apifox-orders": [
|
||||
"liveId",
|
||||
"type",
|
||||
"scene"
|
||||
]
|
||||
},
|
||||
"folderId": 0,
|
||||
"description": "com.meiji.proxy.shop.vo.request.bigdata.DataCentreLiveExportReqVO",
|
||||
"projectId": 345161,
|
||||
"moduleId": 300361,
|
||||
"ordering": 366,
|
||||
"creatorId": 345148,
|
||||
"editorId": 345049,
|
||||
"createdAt": "2025-09-18T02:44:05.000Z",
|
||||
"updatedAt": "2025-09-22T11:48:34.000Z",
|
||||
"visibility": "INHERITED"
|
||||
},
|
||||
"responseSchema": {
|
||||
"id": 399189,
|
||||
"name": "LiveStatisticsExportRespDTO",
|
||||
"displayName": "",
|
||||
"jsonSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"taskId": {
|
||||
"type": "string",
|
||||
"description": "导出任务id"
|
||||
}
|
||||
},
|
||||
"x-apifox-orders": [
|
||||
"taskId"
|
||||
]
|
||||
},
|
||||
"folderId": 0,
|
||||
"description": "com.meiji.big.data.api.dto.response.live.LiveStatisticsExportRespDTO",
|
||||
"projectId": 345161,
|
||||
"moduleId": 300361,
|
||||
"ordering": 354,
|
||||
"creatorId": 345148,
|
||||
"editorId": 345049,
|
||||
"createdAt": "2025-09-17T12:54:06.000Z",
|
||||
"updatedAt": "2025-09-22T11:48:34.000Z",
|
||||
"visibility": "INHERITED"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/shop/data/centre/bigData/statistics/team/live/export",
|
||||
"name": "团购直播大盘分析导出",
|
||||
"method": "post",
|
||||
"requestSchema": null,
|
||||
"responseSchema": {
|
||||
"id": 399189,
|
||||
"name": "LiveStatisticsExportRespDTO",
|
||||
"displayName": "",
|
||||
"jsonSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"taskId": {
|
||||
"type": "string",
|
||||
"description": "导出任务id"
|
||||
}
|
||||
},
|
||||
"x-apifox-orders": [
|
||||
"taskId"
|
||||
]
|
||||
},
|
||||
"folderId": 0,
|
||||
"description": "com.meiji.big.data.api.dto.response.live.LiveStatisticsExportRespDTO",
|
||||
"projectId": 345161,
|
||||
"moduleId": 300361,
|
||||
"ordering": 354,
|
||||
"creatorId": 345148,
|
||||
"editorId": 345049,
|
||||
"createdAt": "2025-09-17T12:54:06.000Z",
|
||||
"updatedAt": "2025-09-22T11:48:34.000Z",
|
||||
"visibility": "INHERITED"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/shop/data/centre/bigData/statistics/team/live/user/export",
|
||||
"name": "团购用户导出",
|
||||
"method": "post",
|
||||
"requestSchema": {
|
||||
"id": 399233,
|
||||
"name": "DataCentreLiveExportReqVO",
|
||||
"displayName": "",
|
||||
"jsonSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"liveId": {
|
||||
"type": "integer",
|
||||
"description": "直播间id"
|
||||
},
|
||||
"type": {
|
||||
"type": "integer",
|
||||
"description": "类型 1安选 2团购"
|
||||
},
|
||||
"scene": {
|
||||
"type": "integer",
|
||||
"description": "导出场景 1大盘分析 2用户导出 3订单导出"
|
||||
}
|
||||
},
|
||||
"x-apifox-orders": [
|
||||
"liveId",
|
||||
"type",
|
||||
"scene"
|
||||
]
|
||||
},
|
||||
"folderId": 0,
|
||||
"description": "com.meiji.proxy.shop.vo.request.bigdata.DataCentreLiveExportReqVO",
|
||||
"projectId": 345161,
|
||||
"moduleId": 300361,
|
||||
"ordering": 366,
|
||||
"creatorId": 345148,
|
||||
"editorId": 345049,
|
||||
"createdAt": "2025-09-18T02:44:05.000Z",
|
||||
"updatedAt": "2025-09-22T11:48:34.000Z",
|
||||
"visibility": "INHERITED"
|
||||
},
|
||||
"responseSchema": {
|
||||
"id": 399189,
|
||||
"name": "LiveStatisticsExportRespDTO",
|
||||
"displayName": "",
|
||||
"jsonSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"taskId": {
|
||||
"type": "string",
|
||||
"description": "导出任务id"
|
||||
}
|
||||
},
|
||||
"x-apifox-orders": [
|
||||
"taskId"
|
||||
]
|
||||
},
|
||||
"folderId": 0,
|
||||
"description": "com.meiji.big.data.api.dto.response.live.LiveStatisticsExportRespDTO",
|
||||
"projectId": 345161,
|
||||
"moduleId": 300361,
|
||||
"ordering": 354,
|
||||
"creatorId": 345148,
|
||||
"editorId": 345049,
|
||||
"createdAt": "2025-09-17T12:54:06.000Z",
|
||||
"updatedAt": "2025-09-22T11:48:34.000Z",
|
||||
"visibility": "INHERITED"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/shop/data/centre/bigData/statistics/team/live/order/export",
|
||||
"name": "团购订单导出",
|
||||
"method": "post",
|
||||
"requestSchema": {
|
||||
"id": 399233,
|
||||
"name": "DataCentreLiveExportReqVO",
|
||||
"displayName": "",
|
||||
"jsonSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"liveId": {
|
||||
"type": "integer",
|
||||
"description": "直播间id"
|
||||
},
|
||||
"type": {
|
||||
"type": "integer",
|
||||
"description": "类型 1安选 2团购"
|
||||
},
|
||||
"scene": {
|
||||
"type": "integer",
|
||||
"description": "导出场景 1大盘分析 2用户导出 3订单导出"
|
||||
}
|
||||
},
|
||||
"x-apifox-orders": [
|
||||
"liveId",
|
||||
"type",
|
||||
"scene"
|
||||
]
|
||||
},
|
||||
"folderId": 0,
|
||||
"description": "com.meiji.proxy.shop.vo.request.bigdata.DataCentreLiveExportReqVO",
|
||||
"projectId": 345161,
|
||||
"moduleId": 300361,
|
||||
"ordering": 366,
|
||||
"creatorId": 345148,
|
||||
"editorId": 345049,
|
||||
"createdAt": "2025-09-18T02:44:05.000Z",
|
||||
"updatedAt": "2025-09-22T11:48:34.000Z",
|
||||
"visibility": "INHERITED"
|
||||
},
|
||||
"responseSchema": {
|
||||
"id": 399189,
|
||||
"name": "LiveStatisticsExportRespDTO",
|
||||
"displayName": "",
|
||||
"jsonSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"taskId": {
|
||||
"type": "string",
|
||||
"description": "导出任务id"
|
||||
}
|
||||
},
|
||||
"x-apifox-orders": [
|
||||
"taskId"
|
||||
]
|
||||
},
|
||||
"folderId": 0,
|
||||
"description": "com.meiji.big.data.api.dto.response.live.LiveStatisticsExportRespDTO",
|
||||
"projectId": 345161,
|
||||
"moduleId": 300361,
|
||||
"ordering": 354,
|
||||
"creatorId": 345148,
|
||||
"editorId": 345049,
|
||||
"createdAt": "2025-09-17T12:54:06.000Z",
|
||||
"updatedAt": "2025-09-22T11:48:34.000Z",
|
||||
"visibility": "INHERITED"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/shop/data/centre/bigData/statistics/live/export/status",
|
||||
"name": "导出状态查询",
|
||||
"method": "get",
|
||||
"requestSchema": null,
|
||||
"responseSchema": {
|
||||
"id": 391736,
|
||||
"name": "TaskInfoResDTO",
|
||||
"displayName": "TaskInfoResDTO",
|
||||
"jsonSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"description": "",
|
||||
"format": "int64"
|
||||
},
|
||||
"fileName": {
|
||||
"type": "string",
|
||||
"description": ""
|
||||
},
|
||||
"fileUrl": {
|
||||
"type": "string",
|
||||
"description": ""
|
||||
},
|
||||
"uploadUser": {
|
||||
"type": "string",
|
||||
"description": ""
|
||||
},
|
||||
"uploadUserId": {
|
||||
"type": "string",
|
||||
"description": ""
|
||||
},
|
||||
"taskStatus": {
|
||||
"type": "integer",
|
||||
"description": ""
|
||||
},
|
||||
"taskType": {
|
||||
"type": "integer",
|
||||
"description": ""
|
||||
},
|
||||
"excelRows": {
|
||||
"type": "integer",
|
||||
"description": ""
|
||||
},
|
||||
"insertRows": {
|
||||
"type": "integer",
|
||||
"description": ""
|
||||
},
|
||||
"gmtCreate": {
|
||||
"type": "string",
|
||||
"description": "",
|
||||
"x-apifox-mock": "@datetime"
|
||||
},
|
||||
"gmtModified": {
|
||||
"type": "string",
|
||||
"description": "",
|
||||
"x-apifox-mock": "@datetime"
|
||||
},
|
||||
"uploadUserType": {
|
||||
"type": "integer",
|
||||
"description": "上传用户类型(端口)(0-中台 1-供应商 2-营销公司 3-营销公司删除)"
|
||||
},
|
||||
"onDuplicate": {
|
||||
"type": "integer",
|
||||
"description": "重复数据处理方式 1重复数据不更新 2重复数据覆盖 3重复数据更新"
|
||||
},
|
||||
"taskSource": {
|
||||
"type": "integer",
|
||||
"description": ""
|
||||
},
|
||||
"taskName": {
|
||||
"type": "string",
|
||||
"description": ""
|
||||
},
|
||||
"failFileUrl": {
|
||||
"type": "string",
|
||||
"description": ""
|
||||
},
|
||||
"failFileName": {
|
||||
"type": "string",
|
||||
"description": ""
|
||||
},
|
||||
"extra": {
|
||||
"type": "string",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"x-apifox-orders": [
|
||||
"id",
|
||||
"fileName",
|
||||
"fileUrl",
|
||||
"uploadUser",
|
||||
"uploadUserId",
|
||||
"taskStatus",
|
||||
"taskType",
|
||||
"excelRows",
|
||||
"insertRows",
|
||||
"gmtCreate",
|
||||
"gmtModified",
|
||||
"uploadUserType",
|
||||
"onDuplicate",
|
||||
"taskSource",
|
||||
"taskName",
|
||||
"failFileUrl",
|
||||
"failFileName",
|
||||
"extra"
|
||||
],
|
||||
"required": [
|
||||
"onDuplicate"
|
||||
],
|
||||
"title": "TaskInfoResDTO"
|
||||
},
|
||||
"folderId": 0,
|
||||
"description": "com.meiji.excel.platform.dto.response.TaskInfoResDTO",
|
||||
"projectId": 345161,
|
||||
"moduleId": 300361,
|
||||
"ordering": 7962,
|
||||
"creatorId": 345185,
|
||||
"editorId": 345148,
|
||||
"createdAt": "2025-07-10T01:36:33.000Z",
|
||||
"updatedAt": "2025-09-17T12:54:06.000Z",
|
||||
"visibility": "INHERITED"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/shop/data/centre/bigData/statistics/live/export/cache",
|
||||
"name": "导出缓存查询",
|
||||
"method": "post",
|
||||
"requestSchema": {
|
||||
"id": 399233,
|
||||
"name": "DataCentreLiveExportReqVO",
|
||||
"displayName": "",
|
||||
"jsonSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"liveId": {
|
||||
"type": "integer",
|
||||
"description": "直播间id"
|
||||
},
|
||||
"type": {
|
||||
"type": "integer",
|
||||
"description": "类型 1安选 2团购"
|
||||
},
|
||||
"scene": {
|
||||
"type": "integer",
|
||||
"description": "导出场景 1大盘分析 2用户导出 3订单导出"
|
||||
}
|
||||
},
|
||||
"x-apifox-orders": [
|
||||
"liveId",
|
||||
"type",
|
||||
"scene"
|
||||
]
|
||||
},
|
||||
"folderId": 0,
|
||||
"description": "com.meiji.proxy.shop.vo.request.bigdata.DataCentreLiveExportReqVO",
|
||||
"projectId": 345161,
|
||||
"moduleId": 300361,
|
||||
"ordering": 366,
|
||||
"creatorId": 345148,
|
||||
"editorId": 345049,
|
||||
"createdAt": "2025-09-18T02:44:05.000Z",
|
||||
"updatedAt": "2025-09-22T11:48:34.000Z",
|
||||
"visibility": "INHERITED"
|
||||
},
|
||||
"responseSchema": {
|
||||
"id": 399347,
|
||||
"name": "ExportCacheRespDTO",
|
||||
"displayName": "",
|
||||
"jsonSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"fileName": {
|
||||
"type": "string",
|
||||
"description": "文件名称"
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "文件地址"
|
||||
}
|
||||
},
|
||||
"x-apifox-orders": [
|
||||
"fileName",
|
||||
"url"
|
||||
]
|
||||
},
|
||||
"folderId": 0,
|
||||
"description": "com.meiji.big.data.api.dto.response.ExportCacheRespDTO",
|
||||
"projectId": 345161,
|
||||
"moduleId": 300361,
|
||||
"ordering": 468,
|
||||
"creatorId": 345148,
|
||||
"editorId": 345049,
|
||||
"createdAt": "2025-09-20T07:16:42.000Z",
|
||||
"updatedAt": "2025-09-22T11:48:34.000Z",
|
||||
"visibility": "INHERITED"
|
||||
}
|
||||
}
|
||||
]
|
||||
+851932
File diff suppressed because it is too large.
Load diff
+17484
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,399 @@
|
||||
// 引入必要的模块
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import { z } from "zod";
|
||||
|
||||
// 创建 MCP 服务器实例
|
||||
// 为其设置一个名字和版本
|
||||
const server = new McpServer({
|
||||
name: "apifox-mcp-server",
|
||||
version: "1.0.0",
|
||||
});
|
||||
|
||||
/**
|
||||
* GitLab API 客户端配置
|
||||
* 用于连接到GitLab实例并执行操作
|
||||
*/
|
||||
const gitlabApiClient = {
|
||||
baseUrl: process.env.GITLAB_API_BASE_URL || "https://gitlab.com/api/v4",
|
||||
token: process.env.GITLAB_ACCESS_TOKEN,
|
||||
|
||||
async makeRequest(endpoint, options = {}) {
|
||||
if (!this.token) {
|
||||
throw new Error("GITLAB_ACCESS_TOKEN 环境变量未设置");
|
||||
}
|
||||
|
||||
const url = `${this.baseUrl}${endpoint}`;
|
||||
const defaultOptions = {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"PRIVATE-TOKEN": this.token,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
};
|
||||
|
||||
const requestOptions = { ...defaultOptions, ...options };
|
||||
const response = await fetch(url, requestOptions);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`GitLab API请求失败: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
},
|
||||
|
||||
/**
|
||||
* 创建Merge Request
|
||||
*/
|
||||
async createMergeRequest(projectId, sourceBranch, targetBranch, title, description = "", assigneeId, reviewerIds) {
|
||||
const body = {
|
||||
id: projectId,
|
||||
source_branch: sourceBranch,
|
||||
target_branch: targetBranch,
|
||||
title,
|
||||
description,
|
||||
...(assigneeId !== undefined && { assignee_id: assigneeId }),
|
||||
...(reviewerIds !== undefined && { reviewer_ids: reviewerIds }),
|
||||
};
|
||||
|
||||
return await this.makeRequest(
|
||||
`/projects/${projectId}/merge_requests`,
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify(body),
|
||||
headers: {
|
||||
"PRIVATE-TOKEN": this.token,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* 合并Merge Request
|
||||
*/
|
||||
async mergeMergeRequest(projectId, mrIid, mergeCommitMessage = "", shouldRemoveSourceBranch = false, mergeWhenPipelineSucceeds = false) {
|
||||
const body = {
|
||||
id: projectId,
|
||||
merge_request_iid: mrIid,
|
||||
...(mergeCommitMessage && { merge_commit_message: mergeCommitMessage }),
|
||||
remove_source_branch: shouldRemoveSourceBranch,
|
||||
merge_when_pipeline_succeeds: mergeWhenPipelineSucceeds,
|
||||
};
|
||||
|
||||
return await this.makeRequest(
|
||||
`/projects/${projectId}/merge_requests/${mrIid}/merge`,
|
||||
{
|
||||
method: "PUT",
|
||||
body: JSON.stringify(body),
|
||||
headers: {
|
||||
"PRIVATE-TOKEN": this.token,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取Merge Request详情
|
||||
*/
|
||||
async getMergeRequest(projectId, mrIid) {
|
||||
return await this.makeRequest(`/projects/${projectId}/merge_requests/${mrIid}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* 关闭Merge Request
|
||||
*/
|
||||
async closeMergeRequest(projectId, mrIid) {
|
||||
return await this.makeRequest(
|
||||
`/projects/${projectId}/merge_requests/${mrIid}`,
|
||||
{
|
||||
method: "PUT",
|
||||
body: JSON.stringify({ state_event: "close" }),
|
||||
headers: {
|
||||
"PRIVATE-TOKEN": this.token,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建Merge Request工具
|
||||
*/
|
||||
server.registerTool(
|
||||
"create_merge_request",
|
||||
{
|
||||
description: "在指定的GitLab项目中创建一个新的Merge Request",
|
||||
inputSchema: z.object({
|
||||
projectId: z.string().describe("GitLab项目的ID或URL编码的路径"),
|
||||
sourceBranch: z.string().describe("源分支名称"),
|
||||
targetBranch: z.string().describe("目标分支名称"),
|
||||
title: z.string().describe("Merge Request的标题"),
|
||||
description: z.string().optional().describe("Merge Request的描述"),
|
||||
assigneeId: z.number().optional().describe("负责人ID"),
|
||||
reviewerIds: z.array(z.number()).optional().describe("审阅者ID数组"),
|
||||
}),
|
||||
},
|
||||
async ({ projectId, sourceBranch, targetBranch, title, description = "", assigneeId, reviewerIds }) => {
|
||||
try {
|
||||
const result = await gitlabApiClient.createMergeRequest(
|
||||
projectId,
|
||||
sourceBranch,
|
||||
targetBranch,
|
||||
title,
|
||||
description,
|
||||
assigneeId,
|
||||
reviewerIds
|
||||
);
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `成功创建Merge Request: ${result.web_url}\n标题: ${result.title}\n状态: ${result.state}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `创建Merge Request失败: ${error.message}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 合并Merge Request工具
|
||||
*/
|
||||
server.registerTool(
|
||||
"merge_merge_request",
|
||||
{
|
||||
description: "合并指定的GitLab Merge Request",
|
||||
inputSchema: z.object({
|
||||
projectId: z.string().describe("GitLab项目的ID或URL编码的路径"),
|
||||
mrIid: z.number().describe("Merge Request的内部ID"),
|
||||
mergeCommitMessage: z.string().optional().describe("合并提交的消息"),
|
||||
shouldRemoveSourceBranch: z.boolean().optional().describe("是否在合并后删除源分支"),
|
||||
mergeWhenPipelineSucceeds: z.boolean().optional().describe("是否在CI管道成功后合并"),
|
||||
}),
|
||||
},
|
||||
async ({
|
||||
projectId,
|
||||
mrIid,
|
||||
mergeCommitMessage = "",
|
||||
shouldRemoveSourceBranch = false,
|
||||
mergeWhenPipelineSucceeds = false
|
||||
}) => {
|
||||
try {
|
||||
const result = await gitlabApiClient.mergeMergeRequest(
|
||||
projectId,
|
||||
mrIid,
|
||||
mergeCommitMessage,
|
||||
shouldRemoveSourceBranch,
|
||||
mergeWhenPipelineSucceeds
|
||||
);
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `成功合并Merge Request #${mrIid}:\n状态: ${result.state}\n已合并时间: ${result.merged_at}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `合并Merge Request失败: ${error.message}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 获取Merge Request详情工具
|
||||
*/
|
||||
server.registerTool(
|
||||
"get_merge_request",
|
||||
{
|
||||
description: "获取指定Merge Request的详细信息",
|
||||
inputSchema: z.object({
|
||||
projectId: z.string().describe("GitLab项目的ID或URL编码的路径"),
|
||||
mrIid: z.number().describe("Merge Request的内部ID"),
|
||||
}),
|
||||
},
|
||||
async ({ projectId, mrIid }) => {
|
||||
try {
|
||||
const result = await gitlabApiClient.getMergeRequest(projectId, mrIid);
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Merge Request #${mrIid} 详情:\n标题: ${result.title}\n作者: ${result.author.name}\n状态: ${result.state}\n源分支: ${result.source_branch}\n目标分支: ${result.target_branch}\n创建时间: ${result.created_at}\n更新时间: ${result.updated_at}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `获取Merge Request详情失败: ${error.message}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 关闭Merge Request工具
|
||||
*/
|
||||
server.registerTool(
|
||||
"close_merge_request",
|
||||
{
|
||||
description: "关闭指定的GitLab Merge Request而不合并",
|
||||
inputSchema: z.object({
|
||||
projectId: z.string().describe("GitLab项目的ID或URL编码的路径"),
|
||||
mrIid: z.number().describe("Merge Request的内部ID"),
|
||||
}),
|
||||
},
|
||||
async ({ projectId, mrIid }) => {
|
||||
try {
|
||||
const result = await gitlabApiClient.closeMergeRequest(projectId, mrIid);
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `成功关闭Merge Request #${mrIid}:\n标题: ${result.title}\n当前状态: ${result.state}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `关闭Merge Request失败: ${error.message}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
server.registerTool(
|
||||
'merge_to_test',
|
||||
{
|
||||
description: "把源分支名合并到测试分支,并部署到测试环境",
|
||||
inputSchema: z.object({
|
||||
projectId: z.string().describe("GitLab项目projectId"),
|
||||
sourceBranch: z.string().describe("源分支名称,默认请使用git branch --show-current获取当前分支传递"),
|
||||
}),
|
||||
},
|
||||
|
||||
async ({ projectId, sourceBranch, }) => {
|
||||
if (!sourceBranch) {
|
||||
// 获取当前分支,用 git branch --show-current 命令
|
||||
const { execSync } = await import('child_process');
|
||||
try {
|
||||
sourceBranch = execSync('git branch --show-current', { encoding: 'utf-8' }).trim();
|
||||
} catch (error) {
|
||||
console.error('执行 git branch --show-current 命令失败:', error);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `获取当前分支失败: ${error.message}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
try {
|
||||
const result = await gitlabApiClient.createMergeRequest(
|
||||
projectId,
|
||||
sourceBranch,
|
||||
'test',
|
||||
`Merge ${sourceBranch} to test`, // 标题
|
||||
`Automated merge request from ${sourceBranch} to test branch` // 描述
|
||||
);
|
||||
|
||||
const mrIid = result.iid;
|
||||
|
||||
// 等待3秒钟,让GitLab有时间处理新创建的MR
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
|
||||
// 尝试合并MR
|
||||
try {
|
||||
const mergeResult = await gitlabApiClient.mergeMergeRequest(
|
||||
projectId,
|
||||
mrIid
|
||||
);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `成功创建并合并Merge Request: ${mergeResult.web_url}\n标题: ${mergeResult.title}\n状态: ${mergeResult.state}\n合并时间: ${mergeResult.merged_at}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (mergeError) {
|
||||
// 如果合并失败,返回MR创建成功的信息,但提示合并需要审核
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `成功创建Merge Request,但自动合并失败,请手动审核: ${result.web_url}\n标题: ${result.title}\n状态: ${result.state}\n错误信息: ${mergeError.message}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `合并到test分支失败: ${error.message}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
// 启动服务器,使用标准输入/输出(stdio)与客户端通信
|
||||
async function main() {
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
console.error("GitLab MCP 服务器已通过 stdio 启动"); // 日志输出到 stderr
|
||||
}
|
||||
|
||||
// 运行主函数,并捕获可能的错误
|
||||
main().catch((error) => {
|
||||
console.error("服务器运行出现致命错误:", error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
export { server };
|
||||
Generated
+3683
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "apifox-mcp-server",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
"dev": "npx @modelcontextprotocol/inspector node index.js"
|
||||
},
|
||||
"keywords": [
|
||||
"mcp",
|
||||
"apifox"
|
||||
],
|
||||
"author": "Jasery",
|
||||
"license": "ISC",
|
||||
"description": "A Model Context Protocol server for Apifox",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.22.0",
|
||||
"axios": "^1.13.2",
|
||||
"node-cache": "^5.1.2",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@modelcontextprotocol/inspector": "^0.17.2"
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user