背景
目前我们公司都是前后端分离的开发模式,前后端在明确需求之后,后端会和前端商量后会先定义好接口(包括接口请求路径,需要的参数及对应的类型,返回的数据结构等等)然后再开始开发。这些接口协议数据我们是用一个叫 rap2 的可视化接口管理平台(不知道有没有人用过,可能用 YApi 的比较多吧)统一管理。
公司目前语言基本都是 typescript(在我的推广下),所以在开发新需求时,接口协议定好以后,我们需要写对应的接口请求函数以及对应的 ts 声明代码。写这些代码都是比较繁琐无聊的事情,于是我便开发了一个 vscode 插件(rap2-to-ts),可以根据 rap2 上的数据自动生成对应的接口请求函数以及ts 声明文件。
可行性分析
鉴权
首先是鉴权,由于我司的 rap2 的登录每次都需要图形验证码,所以无法通过给插件配置账号密码数据来实现鉴权。
通过研究 rap2 的 network 请求发现其接口鉴权通过 cookie 中存储的 koa.sid 和 koa.sid.sig 两条数据,于是只能麻烦一点,需要先在网页上登录 rap2 ,然后右击检查打开开发者工具,前往 Application,找到 cookies 中存储的 koa.sid 和 koa.sid.sig 两条数据。然后把他复制并配置到 vscode 插件,配置数据是通过配置文件的方式,插件会获取当前项目的根目录下的 rapToTs.config.js文件,获取文件中的数据。
获取接口数据
要自动生成接口的请求函数及类型声明,那肯定得拿到接口的对应具体数据。
首先我司 rap2 的接口管理分类层级是
组织 ——> 仓库 ——> 模块 ——> 具体的接口
rap2-to-ts 插件的实现也会根据这个层级来选择生成的接口数据,不过如果一个接口一个接口的点击生成就太慢了,所以,插件是选择一整个模块的生成数据,也就是说插件的思路是先选择组织,然后选择组织下仓库,然后选择仓库下的某个模块,接着插件根据这个模块获取对应的数据去生成该模块下的接口的请求文件及类型声明文件。
rap2分类层级以及接口数据通过查看 rap2 的请求接口说明,把请求的接口路径拿过来,在插件中做对应请求就可以获取对应的数据。
ts类型文件的生成
ts类型文件的生成统一通过接口的 JSON Schema 数据来生成,rap2 也提供了接口的来获取接口的 JSON Schema,都是我发现获取到的 JSON Schema 格式不规范,导致生成类型文件时会错乱。
于是我需要结合接口返回的 JSON Schema 数据以及返回的接口详情数据做数据格式转换处理,来生成规范的 JSON Schema。主要需要处理:类型为数组或对象的具体定义、类型层级的规范(为了不把所有数据定义在一个类型变量中,把引用类型(数组/对象)的用额外的变量定义)等等,这一块处理算是这个插件开发比较费时的一步了。
最后利用 json-schema-to-typescript 包来将 JSON Schema 转为对应的 ts 类型。
插件使用流程
初版
首先安装好插件后,在插件的左下角会出现一个 rap2ts 按钮
在点击该按钮后,首先会检查当前项目的根目录下有没有插件对应的配置文件rapToTs.config.js,如果没有会自动生成默认的配置文件,大概如下
module.exports = {
// rap2 鉴权cookie
rapKoaSidSig: '',
// rap2 鉴权cookie
rapKoaSiD: '',
// 请求地址
baseUrl: '',
// 输出地址
outDir: './src/services',
// 请求体实例文件路径
requestFilePath: '@/utils/request',
// 可选项,是否只生成接口类型声明文件
onlyTypeFile: true,
// 可选项,返回体属性,有时会封装请求方法,处理统一请求返回,有时候只需返回接口文档中返回格式的某个属性
responseAttr: '',
};
如果有填写正确的配置文件,点击按钮后就会调取接口,首先获取rap2的组织列表,并在vscode上出现选择列表,因为我司的rap2的组织列表只有一个组织,所以调取接口后就默认选中了唯一的组织,拿到组织id后接着请求仓库列表,同样vscode上出现仓库选择列表,类似如下
选中仓库后,接着获取仓库下的模块数据,同样是列表选择的方式
接着点击模块就会在指定的输出地址(配置文件中)生成模块下所有接口的请求函数和类型声明数据
生成的文件代码类似下面这种
-
ts类型声明文件
declare namespace IOrderSystem { namespace Request { /* * 获取订单列表 请求类型 */ export interface IGetList { key: string } //勿删: EndTag request: } namespace Response { /* * 获取订单列表 响应类型 */ export interface IGetList { code?: number message?: string id?: string data?: InterfaceAData } export interface IGetListData { /** * 授权码 */ code?: string } //勿删: EndTag response: } } -
请求函数
import request from '@/utils/request' /* * 获取订单列表 */ export function getList(params: IModuleA.Request.IGetList): Promise<IModuleA.Response.IGetList> { return request({ url: '/xxxxxx/xxxxxx/xxxxxx', params, }) }
生成的文件的布局是,在指定的输出目录下选择的模块会生成一个文件夹,文件夹下就有一个请求函数文件以及一个ts类型声明文件,如果选择模块后发现已经有对应的模块目录以及目录下的文件时,这时会获取现有该目录下的文件数据,通过对比的方式,发现已存在的就不修改覆盖,未存在的就新增,新增的逻辑是请求函数文件会直接在接口后面新增,ts类型声明文件由于有命名空间分区,而且有请求和响应两部分,是通过具体的占位符替换新增的方式,所以可以看到上面代码中类型声明文件有 //勿删: EndTag request: 和 //勿删: EndTag response:两部分。
上面代码中,注释部分是获取接口详情中的接口说明数据的,namespace 、 interface 和 function 的变量命名是获取接口的中文命名然后调翻译的接口翻译成英文并转成大驼峰的格式生成的。
以上就是第一版的设计,第一版发布后,在公司很多前端项目中应用了起来,在使用中也是不断修改完善了许多细节。
然后有同事反馈,有个麻烦的事情,上面提到生成的代码 namespace 、 interface 和 function的命名是通过中文机翻成英文生成的,这就导致有时翻译生成的英文语义不符,而且生成的英文命名会特别长的情况。针对这个问题,便修改了插件,来到了第二版。
第二版
第二版在原先的基础上,左下角增加了一个按钮,也就是现在左下角有两个按钮
新增加的按钮(rap2Json)在点击后,和上面一样,是一个选择 组织 ——> 仓库 ——> 模块 的过程,在选好模块会在当前项目的根目录下生成一个rapToTs.json文件,类似下面这种 Json 格式
{
"7897": {
"name": "接口示例模块一",
"variableName": "moduleA",
"children": {
"63937": {
"name": "接口一",
"url": "/xxxxxx/xxxxxx/xxxxxx",
"variableName": "interfaceA",
"description": "列表请求接口"
},
"63949": {
"name": "接口二",
"url": "/xxxxxx/xxxxxx/xxxxxx",
"variableName": "interfaceB",
"description": "获取详情接口"
},
"6495901": {
"name": "接口三",
"url": "/xxxxxx/xxxxxx/xxxxxx",
"variableName": "interfaceC",
"description": "新增接口"
}
}
}
}
该 JSON 文件的数据是以模块数据的ID作为key值,然后其下面name,variableName,children三个属性,
- name : rap2模块的中文命名
- variableName : 模块的变量命名,这个数据默认是以中文命名的翻译生成,后续生成的类型声明文件的模块命名空间会采用这里的数据,使用者可以修改这里的命名来自定义 namespace 的命名。
- children:是一个对象,包括该模块下的所有接口数据,以接口ID作为键值,接口具体数据为对象value的形式。具体包括
- name :rap2接口的中文命名
- url :接口路径地址
- variableName :接口的变量命名,同样可以修改该数据来自定义后续生成的代码的 interface 和 function的命名
- description:接口详情
其实生成这个 JSON 文件就是为了自定义其中的 variableName。
生成好 JSON 文件后,再点击第二个rap2ts按钮,会根据 JSON 文件的数据生成模块选择列表,以上面的json文件为例:
接着选择想要的模块后就是和初版一样,生成模块下所有接口的请求函数和类型声明数据的过程,只不过这次是根据JSON文件中的 variableName 来为 name,variableName,children 命名。