2.0.11 • Published 2 months ago

plk-api2ts v2.0.11

Weekly downloads
-
License
MIT
Repository
github
Last release
2 months ago

plk-api2ts

plk-api2ts 是一个高效的工程化工具,它可以将 Swagger(v2版本) 文档转换为 TypeScript 文件。这个工具的主要目标是自动化后端接口的类型定义,将其转换为前端代码,从而消除了手动编写类型定义的需求。

通过使用 plk-api2ts,你可以大大提高开发效率,减少错误,并确保前后端接口的类型一致性。这个工具特别适合在大型项目中使用,其中可能包含大量的接口和类型定义。

主要特性

  1. 自动化:只需一次设置,就可以自动将后端的 Swagger 文档转换为 TypeScript 文件。
  2. 准确性:通过直接从 Swagger 文档生成类型定义,可以确保前后端接口的类型一致性。
  3. 高效率:消除了手动编写和更新类型定义的需求,从而大大提高了开发效率。
  4. 便捷性:可以作为命令行工具直接转换swagger数据为 TypeScript 文件,同时支持转换后清除swagger JSON 数据文件。

如何开始

  1. 安装
npm install plk-api2ts -D
  1. 添加配置文件 api2ts.config.js(如果仅做命令行工具转换swagger为TypeScript可以省略)
const path = require("path");

module.exports = () => {
  return {
    output: path.resolve(__dirname, "./autoApi"),
    serviceMap: {
      yourServiceName: "your api path",
    },
  };
};
  1. 配置命令
{
  "scripts": {
    "api2ts": "api2ts",
  }
}
  1. 运行命令, 自动生成接口类型定义

仅更新定义文件

npm run api2ts

配置项

选项名称描述类型默认值
output文件生成目录(完整路径)stringpath.join(process.cwd(), "./api2ts")
serviceMap需要转换的服务Record<string, string>null
serviceNameToPath是否根据服务名称添加子级目录booleanfalse
translate是否启用翻译(自动翻译中文为对应的英文)booleanfalse( --translate=true 修改)
contentTemplate自定义内容模板见下方说明详见下方说明
customContent自定义文件添加内容( data: any, definitionsFile: SourceFile, transFormType: (arg: any) => string ) => Promise详见下方说明
interfacePrefixinterface自定义前缀string'I'
enumPrefixenum自定义定义前缀string'E'
createTsFile是否生成ts文件booleantrue ( --ts=false 修改)
createJsonFile是否生成json文件booleanfalse ( --json=true 修改)
clearJsonFile是否清理json文件booleanfalse ( --type=clear 修改)
newLineKind行尾序列'CRLF'|'LF''LF'( --nlk=CRLF 修改)
sort生成interface时,对成员名称排序(数据内容key顺序不稳定,开启可以防止无效的文件变更)booleanfalse (--sort=true 修改)
transformOriginType自定义swagger内type类型转换(define: swagger) => "string"| "number"|"boolean"|"[]"|"{}"详情见下方说明
pathFilter过滤目标项(用于更新单个接口)(path: string) => boolean() => true

默认 contentTemplate

最终会被 ·customContent·消费

export const contentTemplate = `import { http } from "@/api/http";
/**
* @author <% author %> 
* @desc <% desc %>
* @link <% docUrl %>
*/
export default function fetchMethod(options: <% argumentsDefine %> , extraOptions?: any) {
    return http<<% responseDefine %>>(
        {
            url: "<% path %>",
            method: "<% method %>",
            ...options,
        },
        extraOptions,
    );
}
`;

通过自定义模板可以满足 ·90%·以上的自定义场景

默认 customContent

除非你有 100% 自定义内容的需求,否则更加推荐使用 contentTemplate 配置你的自定义内容

import { SourceFile } from "ts-morph";

export const customContent = async ( data: any, definitionsFile: SourceFile, contentTemplate: string, transFormType: (arg: any) => string ) => { const typeInfoArr: ITypeInfo[] = []; for (let url in data.paths) { const fetchDefines = data.pathsurl; for (let methodStr in fetchDefines) { const methodDefine = fetchDefinesmethodStr; const docUrl = http://${ data.host }/doc.html#/default/${methodDefine.tags?.join("/")}/${ methodDefine.operationId }; const author = methodDefine?."x-author" || ""; const desc = methodDefine?."summary" || ""; const path = url; const method = methodStr.toUpperCase(); const responseDefineSchema = methodDefine.responses?.200?.schema; const responseDefine = responseDefineSchema ? ${transFormType(responseDefineSchema)} : "any"; let bodyDefine: any; let queryDefine: any; let arrayQueryDefineMap: Record<string, any> = {}; methodDefine.parameters?.forEach((paramsDefine: any) => { if (paramsDefine.in === "body") { bodyDefine = paramsDefine; } if (paramsDefine.in === "query") { if (!queryDefine) { queryDefine = { in: "query", schema: { type: "object", properties: {}, }, }; } if (paramsDefine.name.includes("0")) { const keyArr = paramsDefine.name.split("0."); const name = keyArr0; const key = keyArr1; let targetDefine = arrayQueryDefineMapname; if (!targetDefine) { targetDefine = { type: "array", items: { type: "object", properties: {}, }, }; } targetDefine.items.propertieskey = paramsDefine; arrayQueryDefineMapname = targetDefine; } else { queryDefine.schema.propertiesparamsDefine.name = paramsDefine; } } }); if (queryDefine) { queryDefine.schema.properties = { ...queryDefine.schema.properties, ...arrayQueryDefineMap, }; } const defineArr = bodyDefine, queryDefine.filter(Boolean); const argumentsDefine = (() => { let str = "{"; defineArr.forEach((defineItem, index) => { const name = defineItem.in === "body" ? "data" : "params"; if (index) { str += ","; } str += ${name}: ${transFormType(defineItem.schema)}; }); str += "}"; return str; })();

  typeInfoArr.push({
    docUrl,
    author,
    desc,
    argumentsDefine,
    responseDefine,
    path,
    method,
  });
}

} typeInfoArr.forEach((typeInfo) => { let str = contentTemplate; console.log("contentTemplate: ", contentTemplate); Object.keys(typeInfo).forEach((key) => { const target = <% ${key} %>; str = str.replace(target, typeInfokey as keyof ITypeInfo); }); definitionsFile.addStatements(str); }); };

结果示例
```javascript
import { http } from "@/api/http";

/**
* @author 张三
* @link http://yourdocpath/doc.html#/default/exportUsingPOST
*/
export default function fetchMethod(options: { data: IProcessDataSearchVO }, extraOptions?: any) {
    return http<IJSONResultlong>(
        {
            url: "/masterdata-service/allocationOrder/export",
            method: "POST",
            ...options,
        },
        extraOptions,
    );
}
/** 流程数据搜索VO */
export interface IProcessDataSearchVO {
    /** undefined */
    showFieldSerialNoList?: string[];
    /** 全局搜索 */
    allSearch?: string;
    /** 当前页面 */
    pageNo?: number;
    /** 字段搜索 */
    dataSearchList?: IProcessDataDetailsSearchVO[];
    /** 分页大小 */
    pageSize?: number;
    /** 排序字段集 */
    orders?: IPagingSortVO[];
    /** 明细表表code,传值后,将会查询明细表数据 */
    tableColumnCode?: string;
    /** 当前的表单分组 */
    currentFormDataGrouping?: IFormDataGroupingDTO;
    /** 操作员工id */
    opUserId?: string;
    /** 操作角色id集 */
    opRoleIds?: string[];
    /** 操作部门id */
    opDeptId?: string;
}
/** 流程数据明细搜索VO */
export interface IProcessDataDetailsSearchVO {
    /** 列code */
    code: string;
    /** 搜索类型 */
    searchType: EProcessDataDetailsSearchVO_searchType;
    /** 搜索文本 - 针对文本搜索 */
    text?: string;
    /** 搜索起始值 - 针对范围搜索 */
    limitBegin?: Record<string, any>;
    /** 搜索结束值 - 针对范围搜索 */
    limitEnd?: Record<string, any>;
    /** 搜索选项值 - 针对选择搜索 */
    selectors?: Record<string, any>[];
    /** 表格编码 */
    tableCode?: string;
}
/** 分页排序VO */
export interface IPagingSortVO {
    /** undefined */
    column?: string;
    /** undefined */
    isAsc?: EPagingSortVO_isAsc;
}
/** 表单数据分组DTO */
export interface IFormDataGroupingDTO {
    /** 分组字段序列 */
    groupingFieldSerialNo?: string;
    /** 分组字段编码 */
    groupFieldCode?: string;
    /** 分组的值, 如果是关联表单,则是ID */
    groupingValue?: string;
    /** 分组名称 */
    groupingName?: string;
    /** 下级分组 */
    children?: IFormDataGroupingDTO[];
    /** 级联表单数据,  级联表单的上下级关系  - Y, 多字段分组关系 - N */
    cascadeFormData?: EFormDataGroupingDTO_cascadeFormData;
    /** 多级基础数据上级ID */
    treeDataParentId?: string;
}
/** JSONResult«long» */
export interface IJSONResultlong {
    /** 返回码 */
    code?: number;
    /** 返回消息说明 */
    msg?: string;
    /** 响应结果 */
    data?: string;
    /** 服务器结果返回时的 Unix timestamp,单位毫秒 */
    ts?: string;
}

export enum EProcessDataDetailsSearchVO_searchType {
    NONE = "NONE",
    EQ = "EQ",
    LIKE = "LIKE",
    RANGE = "RANGE",
    SELECTOR = "SELECTOR",
    IS_NULL = "IS_NULL",
    NOT_NULL = "NOT_NULL",
    NE = "NE",
    REGEXP = "REGEXP"
}

export enum EPagingSortVO_isAsc {
    Y = "Y",
    N = "N"
}

export enum EFormDataGroupingDTO_cascadeFormData {
    /** 是 */
    Y = "Y",
    /** 否 */
    N = "N"
}

默认 transformOriginType

export const transformOriginType = (define: any): string => {
  const typeName = `${define.type}${define.format ? `(${define.format})` : ""}`;

  const defaultTypeMap = {
    string: "string",
    "string(date-time)": "string",
    "string(date)": "string",
    integer: "number",
    "integer(int64)": "string",
    "integer(int32)": "number",
    number: "number",
    boolean: "boolean",
    array: "[]",
    object: "{}",
  };
  return defaultTypeMap[typeName as keyof typeof defaultTypeMap] as string;
};

CASE

  1. 自定义添加文件生成内容
const path = require("path");

module.exports = () => {
  return {
    output: path.resolve(__dirname, "./autoApi"),
    serviceMap: {
      yourServiceName: "your api path",
    },
    // 满足 90% 以上的自定义内容的需求
    contentTemplate: '你的自定义内容',
    // 仅当你需要完全自定义你的文件内容时使用
    customContent: () => '// 自定义内容'
  };
};
  1. 自动生成的翻译名称不符合要求 修改 translateCache.json 中字典内容,重新执行代码生成逻辑 npm run api2ts

  2. 自定义更新某一个接口

npx api2ts --filter=你的接口请求路径
  1. 需要生成接口的swagger文件
npx api2ts --json=true
  1. 需要清除生成的swagger文件
npx api2ts --type=clear
  1. 当前已有swagger文件,想要转换为ts定义
npx api2ts --type=transform
  1. 指定转换文件夹下的swagger 文件为 TypeScript
npx api2ts --type=transform --target=./your_path
  1. 指定转换文件夹下的swagger 文件为 TypeScript 并且将 interface 名称翻译为英文
npx api2ts --type=transform --target=./your_path --translate=true
  1. 清除转换文件夹下的swagger 文件
npx api2ts --type=clear --target=./your_path

功能清单

功能内容是否支持
获取接口swagger数据
生成接口数据类型定义
接口定义名称翻译
接口定义翻译结果调整
自定义api服务
根据服务名称创建文件夹归类
自定义文件内容模板
自定义生成文件内容
单个接口数据更新
对指定文件夹下的swagger数据文件转换为 TypeScript
清理swagger json 数据文件

:copyright: License

MIT

写在最后

欢迎大家提 issue, 但希望您能提供你的配置,或者给出类型转换有异常的swagger json 数据,描述清楚如何复现问题。我将不定期清理issue。最后希望大家都能愉快coding, 不用再写api相关的ts代码☺

2.0.11

2 months ago

2.0.10

2 months ago

2.0.9

2 months ago

2.0.8

2 months ago

2.0.3

2 months ago

2.0.2

2 months ago

2.0.5

2 months ago

2.0.4

2 months ago

2.0.7

2 months ago

2.0.6

2 months ago

1.0.26

2 months ago

1.0.25

2 months ago

2.0.1

2 months ago

2.0.0

2 months ago

1.0.27

2 months ago

1.0.19

2 months ago

1.0.18

2 months ago

1.0.17

2 months ago

1.0.16

2 months ago

1.0.22

2 months ago

1.0.21

2 months ago

1.0.20

2 months ago

1.0.24

2 months ago

1.0.23

2 months ago

1.0.15

2 months ago

1.0.9

2 months ago

1.0.11

2 months ago

1.0.10

2 months ago

1.0.14

2 months ago

1.0.13

2 months ago

1.0.12

2 months ago

1.0.8

3 months ago

1.0.7

3 months ago

1.0.6

3 months ago

1.0.5

3 months ago

1.0.2

3 months ago

1.0.4

3 months ago

1.0.3

3 months ago

1.0.1

3 months ago

1.0.0

3 months ago