首页 前端知识 根据swagger文档导出前端api.ts文件

根据swagger文档导出前端api.ts文件

2025-02-24 13:02:03 前端知识 前端哥 474 252 我要收藏

前言

当前公司开发,后端通过swagger提供接口文档,开发某些模块时,一下子可能生成二三十个接口,前端使用的时候,需要一个一个copy到对应的api文件中,定义类型,引入接口等等,实在是麻烦!于是花了点时间,搞了下自动导出
github项目地址,欢迎提出问题,求个star😁

思路

  1. 通过 swagger-parser这个库,解析swagger文档地址,下面是swagger内容的一些说明,可参考

    // swagger文档
    // info:API的基本信息,包括标题、版本号、描述等。
    // host:API的主机名和端口号。
    // basePath:API的基本路径,通常是API的前缀。
    // schemes:API的协议,通常是http或https。
    // tags:API的标签,可以用于对API进行分类。
    // paths:API的路径和操作,包括HTTP方法、请求参数、响应内容等。
    	// operationId: 用于指定操作的唯一标识符。
    	// summary: 用于提供操作的简要说明。
    	// description: 用于提供操作的详细说明。
    	// tags: 用于对操作进行分类和组织。
    	// parameters: 用于定义在操作中使用的参数,例如查询参数,请求体参数等。
    	// requestBody: 用于定义操作的请求体。
    	// responses: 用于定义操作的响应,包括响应代码和响应消息。
    	// security: 用于指定操作所需的安全方案。
    	// deprecated: 用于指定操作是否已被弃用。
    // parameters:API的公共参数,可以在多个API中共用。
    // responses:API的响应定义,包括响应码、响应内容等。
    // securityDefinitions:API的安全定义,包括认证方式、访问授权等。
    // security:API的安全配置,指定哪些API需要认证或授权。
    
  2. 获取解析中需要的信息,如接口类型、标题、描述、地址、参数等等

  3. 遍历所有接口,根据配置的规则,生成内容,写入文件

核心代码

const fs = require("fs");
const path = require("path");
const chalk = require("chalk");
const beautify = require("js-beautify").js_beautify;
const parse = require("swagger-parser");
const { swaggerUrl, topConfig, apiConfig,dirIdx } = require("./config");
const demoData = require("./demo.json");

// demo or swaggerUrl
let demo = process.env.rundemo;
if (demo) {
  console.log(
    "========================================测试demo========================================"
  );
} else {
  console.log(
    "========================================swaggerUrl========================================"
  );
}

// api接口方法存放目录
const API_PATH = path.resolve(__dirname, "../api");

// 判断目录是否存在
const isExist = (lastPath = "") => {
  const privatePath = `${lastPath ? API_PATH + "/" + lastPath : API_PATH}`;
  const stat = fs.existsSync(privatePath);
  if (!stat) {
    fs.mkdirSync(privatePath);
  }
};

// 清空文件夹
const rmDir = (dir) => {
  if (fs.existsSync(dir)) {
    const files = fs.readdirSync(dir);
    if (files.length > 0) {
      fs.rmdirSync(dir, { recursive: true });
      fs.mkdirSync(dir);
    }
  }
};

// 接口名称处理 getColumnInfosUsingGET-->getColumnInfos  saveProjectUsingPOST-->saveProject
const moduleName = (operationId) => {
  return operationId.replace(/Using(POST|GET)/g, "");
};
// 数据类型
const dataType = (key) => {
  const type = {
    string: "string",
    integer: "number",
    int: "number",
    long: "string",
    Array: "array",
    file: "Blob",
    boolean: "boolean",
  };
  return type[key] ? type[key] : "any";
};

// 获取模块
const getModules = (map) => {
  let moduleList = [];
  map.forEach((value, key) => {
    const module = writeFileApi(key, value);
    moduleList = [...moduleList, ...module];
  });
  console.log(
    chalk.green(
      "---------------------------------------------------------------"
    )
  );
  console.log(chalk.green("导出成功!"));
  return moduleList;
};

// 参数item类型
const interfaceParamsList = (params) => {
  let str = "";
  params.forEach((item) => {
    str = `${str}
      /** ${item.description ? item.description : ""} **/
      ${item.name}${item.required ? "?" : ""}: ${dataType(item.type)}; 
    `;
  });
  return str;
};

// 定义参数类型
const interfaceParamsTpl = (params, interfaceName) => {
  if (!params || params.length === 0) {
    return "";
  } else {
    return `interface ${interfaceName} {
      ${interfaceParamsList(params)}
    }`;
  }
};

// 设置单个接口写入模板
const tplInsertApi = (apiInfo) => {
  const keys = Object.keys(apiInfo);
  const method = keys[0];
  const methodParams = apiInfo[method];
  const parameters = methodParams.parameters; // 接口参数
  const operationId = methodParams.operationId;
  const allPath = apiInfo.allPath; // 接口地址
  const summary = methodParams.summary; // 接口描述
  const requestName = moduleName(operationId); // 接口名称格式化

  let interfaceName = "any"; // 定义接口name
  let interfaceParams = "data?:any"; // 定义参数及类型
  let parametersType = "data"; // 请求类型

  if (parameters && parameters.length > 0) {
    interfaceName = `${requestName}Ife`;
    interfaceParams = `data?: ${interfaceName}`;
  }
  // get请求默认为json传参
  if (method.toLocaleLowerCase() === "get") {
    parametersType = "params";
    interfaceParams = `params?: ${interfaceName}`;
  }

  return apiConfig(
    summary,
    interfaceParamsTpl(parameters, interfaceName),
    requestName,
    interfaceParams,
    method,
    allPath,
    parametersType
  );
};

// 接口名称(使用operationId)
const getModulesName = (apiInfo) => {
  const keys = Object.keys(apiInfo);
  const method = keys[0];
  const methodParams = apiInfo[method];
  const operationId = methodParams.operationId;
  return operationId;
};

// 单个文件顶部配置
const setTopConfig = () => {
  let config = topConfig;
  return config;
};

// 写入文件
const writeFileApi = (fileName, apiData) => {
  // 设置文件顶部配置(如引入axios/定义响应类型等)
  let tplIndex = setTopConfig();
  const apiDataLen = apiData.length;
  let moduleList = [];
  for (let i = 0; i < apiDataLen; i++) {
    const item = apiData[i];
    tplIndex = `${tplIndex}\n${tplInsertApi(item)}\n`;
    moduleList.push(getModulesName(item));
  }
  tplIndex = beautify(tplIndex, {
    indent_size: 2, // 缩进宽度,默认为4
    max_preserve_newlines: 2, // 保留的最大换行符数量,默认为10
    space_in_paren: true, // 圆括号内是否保留空格,默认为false
    preserve_newlines: false, // 是否保留换行符,默认为true
    // jslint_happy: true, // 是否启用JSLint风格,默认为false
  });
  fs.writeFileSync(`${API_PATH}/${fileName}.ts`, tplIndex);
  console.log(
    chalk.blue(
      `${fileName}.ts` +
        chalk.green(" ------------ ") +
        chalk.black("[" + apiDataLen + "]") +
        chalk.green("个接口写入完成")
    )
  );
  return moduleList;
};

const gen = async () => {
  rmDir(API_PATH);
  isExist();
  try {
    let parsed;
    if (demo) {
      parsed = demoData;
    } else {
      // 解析url 获得
      parsed = await parse.parse(swaggerUrl);
    }

    const paths = parsed.paths;
    const pathsKeys = Object.keys(paths);
    const pathsKeysLen = pathsKeys.length;
    console.log(
      chalk.blue("开始解析,总共接口数量:") + chalk.yellow(pathsKeysLen)
    );
    console.log(
      chalk.red(
        "---------------------------------------------------------------"
      )
    );

    const modulesMap = new Map();
    for (let i = 0; i < pathsKeysLen; i++) {
      const item = pathsKeys[i];
      const itemAry = item.split("/");
      const pathsItem = paths[item];
      let fileName = itemAry[dirIdx || 2];
      if (!fileName) continue;
      fileName = fileName.toLowerCase();
      pathsItem.allPath = item;
      if (modulesMap.has(fileName)) {
        // 继续添加到当前 fileName 文件内
        const fileNameAry = modulesMap.get(fileName);
        fileNameAry.push(pathsItem);
        modulesMap.set(fileName, fileNameAry);
      } else {
        modulesMap.set(fileName, [pathsItem]);
      }
    }
    // 获取模块,并写入文件
    getModules(modulesMap);
  } catch (e) {
    console.log(e);
  }
};

module.exports = {
  gen,
};

其他

初次提交,在本公司项目中,使用是完全支持了,但是不可避免,可能存在问题,如有发现,欢迎提出,感谢!(后期有时间的话,打算搞成vscode插件)

转载请注明出处或者链接地址:https://www.qianduange.cn//article/21007.html
标签
评论
发布的文章

C/C | 每日一练 (2)

2025-02-24 13:02:49

Linux性能监控工具汇总

2025-02-24 13:02:48

Python常见面试题的详解16

2025-02-24 13:02:48

QQ登录测试用例报告

2025-02-24 13:02:47

大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!