工具 | 自动生成api接口

99 阅读1分钟

这是一个将swagger接口文档自动生成TypeScript的api接口以及interface定义。

说明

  • 该示例的url是apifox接口文档导出的url

  • 命名:接口分组的目录需要是对应的tag

  • 参数说明:在接口请求参数中,需要按照实际规定来写,不管是在params还是body中,如果是必填项,请设置必填,这样会得到 required:true , 那么在 interface 中就没有可选,否则会得到 required:false,那么在interface中会有可选操作符

  • 结果示例:

使用

  • 直接node这个文件,即可生成api文件

代码

/**
 * 说明:此文件是基于apifox创建,如果是swagger也应该是适用的,api获取是从项目设置的中的打开url获取,我这里是选择的是swagger2.0
 * fun api函数名称
 * funArr api定义的约束数组
 * oldTag 上一组tag
 * srcFolder 生产的api文件夹
 * url 需要生产api的链接
 */
const fs = require("fs");
const path = require("path");
const http = require("http");

let fun = "";
let funArr = [];
let oldTag = "";
const srcFolder = "./src/server";
const typeFolder = "./types/server";
const url = "http://127.0.0.1:4523/export/openapi?projectId=603885&version=2.0";

const mkdirsSync = (dirname) => {
  if (fs.existsSync(dirname)) {
    return true;
  } else {
    if (mkdirsSync(path.dirname(dirname))) {
      fs.mkdirSync(dirname);
      return true;
    }
  }
};

const getPath = (pathUrl) => {
  return path.resolve(__dirname, pathUrl);
};

const generateTemplate = (tagName) => {
  let str = "";
  funArr.forEach((v) => {
    str += `${v}Type,`;
  });
  return `import request from '../../request'
import {${str} } from '../../types/server/${tagName}.types'
`;
};
const generateFunc = (url, summary, type = "post") => {
  const arr = url.slice(1).split("/");
  fun = arr[arr.length - 1].replace(/^\S/, (s) => s.toUpperCase());
  funArr.push(fun);
  return `
// ${summary || ""}
export const  ${fun} = (${
    type === "get" ? `params:${fun}Type` : `data:${fun}Type`
  }):Promise<any> =>{
  return request({
    url:'${url}',
    method: '${type}',
    ${type === "get" ? "params" : "data"},
  })
}
`;
};

const generateTypeFunc = (typeName, str) => {
  return `
export interface ${typeName}Type {
${str}
}
`;
};

const httpgetJson = (url) => {
  return new Promise((resolve, reject) => {
    http
      .get(url, (res) => {
        const { statusCode } = res;
        const contentType = res.headers["content-type"];
        let error;
        if (statusCode !== 200) {
          error = new Error("请求失败。" + `状态码: ${statusCode}`);
        } else if (!contentType.includes("application/json")) {
          error = new Error(
            "无效的 content-type." +
              `期望 application/json 但获取的是 ${contentType}`
          );
        }
        if (error) {
          console.error(error.message);
          // 消耗响应数据以释放内存
          res.resume();
          return;
        }

        res.setEncoding("utf8");
        let rawData = "";
        res.on("data", (chunk) => {
          rawData += chunk;
        });
        res.on("end", () => {
          try {
            const parsedData = JSON.parse(rawData);
            resolve(parsedData);
          } catch (e) {
            reject(`错误: ${e.message}`);
          }
        });
      })
      .on("error", (e) => {
        reject(`错误: ${e.message}`);
      });
  });
};

const main = async () => {
  console.log("正在获取apifox文件...");
  const { paths } = await httpgetJson(url);
  console.log("获取成功,正在生成api文件...");
  const obj = {};
  const typeObj = {};
  for (const name in paths) {
    const path = paths[name];
    let folder = "";
    if (path.post) {
      const tag = path.post.tags[0];
      if (!tag) continue;
      const urlArray = name.slice(1).split("/");
      const typeName = urlArray[1] ? urlArray[1] : urlArray[0];
      if (name.slice(1).split("/").length === 4) {
        folder = urlArray[1];
      } else {
        if (name.slice(1).split("/")[0] !== tag) continue;
      }

      if (obj[path.post.tags[0]]) {
        obj[path.post.tags[0]].push({
          summary: path.post.summary,
          tag,
          name,
          type: "post",
          folder,
        });
      } else {
        obj[path.post.tags[0]] = [
          { summary: path.post.summary, tag, name, type: "post", folder },
        ];
      }
      if (typeObj[path.post.tags[0]]) {
        path.post.parameters.forEach((v) => {
          typeObj[path.post.tags[0]].push({
            ...v,
            tag: tag.replace(/^\S/, (s) => s.toUpperCase()),
            folder: urlArray[0],
            typeName: urlArray[1] ? urlArray[1] : urlArray[0],
          });
        });
      } else {
        typeObj[path.post.tags[0]] = [];
        path.post.parameters.forEach((v) => {
          typeObj[path.post.tags[0]].push({
            ...v,
            tag: tag.replace(/^\S/, (s) => s.toUpperCase()),
            folder: urlArray[0],
            typeName: urlArray[1] ? urlArray[1] : urlArray[0],
          });
        });
      }
    } else if (path.get) {
      const tag = path.get.tags[0];

      if (!tag) continue;
      const urlArray = name.slice(1).split("/");
      const typeName = urlArray[1] ? urlArray[1] : urlArray[0];
      if (name.slice(1).split("/").length === 4) {
        folder = urlArray[1];
      } else {
        if (name.slice(1).split("/")[0] !== tag) continue;
      }

      if (obj[path.get.tags[0]]) {
        obj[path.get.tags[0]].push({
          summary: path.get.summary,
          tag,
          name,
          type: "get",
          folder,
        });
      } else {
        obj[path.get.tags[0]] = [
          { summary: path.get.summary, tag, name, type: "get", folder },
        ];
      }
      if (typeObj[path.get.tags[0]]) {
        path.get.parameters.forEach((v) => {
          typeObj[path.get.tags[0]].push({
            ...v,
            tag: tag.replace(/^\S/, (s) => s.toUpperCase()),
            folder: urlArray[0],
            typeName: urlArray[1] ? urlArray[1] : urlArray[0],
          });
        });
      } else {
        typeObj[path.get.tags[0]] = [];
        path.get.parameters.forEach((v) => {
          typeObj[path.get.tags[0]].push({
            ...v,
            tag: tag.replace(/^\S/, (s) => s.toUpperCase()),
            folder: urlArray[0],
            typeName: urlArray[1] ? urlArray[1] : urlArray[0],
          });
        });
      }
    }
  }
  // 创建api文件
  for (const tagName in obj) {
    let jsString = "";
    const requestTypes = [];
    let folder = "";
    for (const item of obj[tagName]) {
      if (item.tag !== oldTag) {
        funArr = [];
      }
      oldTag = item.tag;
      const requestType = requestTypes.filter((o) => o === item.type);
      if (requestType.length === 0) requestTypes.push(item.type);
      jsString += generateFunc(item.name, item.summary, item.type);
      folder = item.folder;
    }
    jsString = generateTemplate(tagName) + jsString;
    mkdirsSync(getPath(`${srcFolder}/${folder}`));
    fs.writeFileSync(getPath(`${srcFolder}/${folder}/${tagName}.ts`), jsString);
  }
  // 创建types文件
  mkdirsSync(getPath(`${typeFolder}`));
  for (const typeName in typeObj) {
    let typeStr = "";
    let typeNameStr = "";
    let index = 0;
    let beginIndex = 0;
    let endIndex = 0;
    const currentTypeItemNameList = typeObj[typeName].map((v) => {
      return v.typeName;
    });
    for (const item of typeObj[typeName]) {
      console.log("item", item.name);
      typeStr += `${item.name}${item.required ? "" : "?"}:${item.type},`;
      index++;
      if (index === typeObj[typeName].length) {
        const newStrList = typeStr.split(",");
        currentTypeItemNameList.forEach((v, i) => {
          if (v != currentTypeItemNameList[i + 1]) {
            endIndex = i + 1;
            typeNameStr += generateTypeFunc(
              v.replace(/^\S/, (s) => s.toUpperCase()),
              newStrList.slice(beginIndex, endIndex).join(",\n ")
            );
            beginIndex = i + 1;
          }
        });
      }
    }

    fs.writeFileSync(
      getPath(`${typeFolder}/${typeName}.types.ts`),
      typeNameStr
    );
  }
  console.log("api文件生成完毕!");
};

main();

注:思路参考网络,并加上在自己的思考以及功能需求,最终形成这样的版本代码!

本文由mdnice多平台发布