释放双手,优雅地调用后端接口💗

·  阅读 11501
释放双手,优雅地调用后端接口💗

「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战

大家好,我是速冻鱼🐟,一条水系前端💦,喜欢花里胡哨💐,持续沙雕🌲,是隔壁寒草🌿的好兄弟,刚开始写文章。 如果喜欢我的文章,可以关注➕点赞,为我注入能量,与我一同成长吧~

阅读本文🦀

1.如何自动生成基于TypeScript的API模块

2.如何释放业务同学的双手,让其更专注于业务实现

3.您将了解到什么是Swagger、OpenAPIStopLight

4.您将了解到遇到一个完全没接触过的需求,如何快速圆满完成任务

5.如何编写CLI工具,完成自动化操作

前言🌵

您是否在编写前端API请求模块的时候,一边看文档一边编写前端请求接口呢?编写各种TS泛型类型定义,导致开发效率低下,遇到后端接口改变时,又不得不手动去修改请求接口🕶

如何解决这类工程化的问题呢?我们可以利用定义接口的标准API文档,自动生成前端基于TypeScriptAPI调用模块,这样我们甚至可以不用去阅读API接口文档就能直接上手开发业务,下面一起一探究竟吧~

前置知识🐳

  • OpenAPI

    人话就是一个定义API的规范

    OpenAPI规范是定义REST api结构和语法的标准格式。OpenAPI文档是机器和人类可读的,这使得任何人都可以轻松地确定每个API的工作方式。构建api的工程师可以使用api来规划和设计服务器、生成代码和实现合同测试。

  • Swagger

    img

    人话按照OpenAPI规范生成接口文档的工具

    Swagger是与实现OpenAPI规范的一些最知名、最广泛使用的工具相关的名称。Swagger工具集包括开源、免费和商业工具的组合,可以在API生命周期的不同阶段使用

  • StopLight

    Stoplight Reviews 2021: Details, Pricing, & Features | G2

    人话就是StopLight是和Swagger一样的按照OpenAPI规范生成接口文档的工具,它需要付费,总之就是更强更好用~~

    stopLight首先是一个API设计管理工具,可以使用OpenAPI等行业标准来设计、开发、测试和记录HTTP API,并使用Markdown来记录书面形式的文档。

  • API规范文件

image-20211031000240795

明确需求🐿

  • 编写一个CLI工具让前端只需要执行一行命令就能做到开箱即用的API请求模块
  • 拉取StopLight的规范文件YML或者JSON格式
  • 根据文范文件在node_modules文件夹生成API请求模块,并将生成的文件进行整合,统一导出
  • 可以根据配置文件选择生成不同版本的API请求模块
  • 需要兼容Swagger2.0和OpenAPI3.0规范
  • 编写项目README文档

实现需求🐰

对核心功能进行调研

核心功能就在于如何生成基于TypeScript的API模块?

可以自动动手写模版引擎去生成代码,但是这个工作量太大了,而且我的水平不够,所以只有依赖于第三方库。

通过大量查阅Github和资料,总结出以下的方案

1.使用官方的Swagger-codegen

github.com/swagger-api…

要么生成工具是一个JAR包,要么就是用JS写的库文件很久没人维护了,所以放弃

2.free-swagger

github.com/yeyan1996/f…

国人写的,但是start数太少,生成的代码类型丢失为any,放弃

3.ts-gear

github.com/superwf/ts-…

京东莫大佬写的,但是在我的使用中发现对OpenAPI3支持的不好,放弃

4.swagger-typescript-api

github.com/acacode/swa…

外国友人写的,这个项目有600多start,代码都是使用模版生成的,但是文档写的很烂,但是实现效果还不错,最后还是采用的这个库,不过踩了很多坑

编写CLI入口文件 🍓

指定入口

package.json

{
  //...
  "bin": {
    "heimdall": "bin/heimdall.js"
  }
 //...
}

复制代码

拉取规范文件 🍎

StopLight会将API接口文档托管在Github,因此我们需要在CLI实现拉取规范文件

以下省略了部分代码

//...

//初始化命令行帮助信息,并获取命令行参数

const options = getCommandOptions()

//生成API入口
if (options.generate) {
    const projectName = getProjectName()
    //1.执行下载文件命令
    await gitCloneProject(projectName)
    //2.生成api文件
    //2.1删除之前下载过的API文件
    await removeDir(path.resolve(cwd(), "node_modules/@imf/heimdall-ts/api"))

    await createApi()
    //3.生成入口文件
    await generateMain()
    //4.删除下载的yml所在文件夹
    await removeDir(path.resolve(cwd(), getProjectName()))

} else if (options.log) {
    const projectName = getProjectName()
    //1.执行下载文件命令
    await gitCloneProject(projectName, true)
    //2.执行打印日志的命令
    await showLog()
    //3.删除下载的文件夹
    await removeDir(path.resolve(cwd(), getProjectName()))
}

//...


/**
 * 克隆项目
 */
function gitCloneProject(projectName, isLog = false) {
    return new Promise<void>((resolve, reject) => {
        shell.exec(`git clone https://sudongyu:YUANCxzeJwiVhzQio18v@git.stoplight.io/floozy/${projectName}.git`, {
            cwd: `${cwd()}`
        }, () => {
            const versionCode = getPkgMaifest()?.heimdall?.versionCode
            //如果有versionCode,需要回退版本
            if (!isLog && versionCode) {
                shell.exec(`git checkout ${versionCode}`, {
                    cwd: `${path.resolve(cwd(), getProjectName())}`
                }, () => {
                    resolve()
                })
            } else {
                resolve()
            }
        })
    })
}


复制代码

生成基于TS的API调用模块 🍌

使用 swagger-typescript-api这个库来生成代码,并将其放置在node_modules文件夹下,这样不会影响使用方的代码目录结构

/**
 * 创建api文件
 */
 function createApi() {
     return new Promise<void>(async (resolve, reject) => {
         //V3
         /* NOTE: all fields are optional expect one of `output`, `url`, `spec` */
         const openApi3Array = getOenAPI3YmlFileName(path.resolve(cwd(), `${getProjectName()}`))
         for (let item of openApi3Array) {
             await generateApi({
                 name: `${item.replace('.oas3.yml', '')}Api.ts`,
                 url: null,
                 output: path.resolve(process.cwd(), "node_modules/@imf/heimdall-ts/api"),
                 input: path.resolve(process.cwd(), `${getProjectName()}`, `${item}`),
                 httpClientType: "axios", // or "fetch",
                 unwrapResponseData: true,
                 generateUnionEnums: true,
                 enumNamesAsValues: true,
                 moduleNameFirstTag: false,
                 moduleNameIndex:-1
             })
         }

         //V2
         /* NOTE: all fields are optional expect one of `output`, `url`, `spec` */
         const openApi2Array = getOenAPI2YmlFileName(path.resolve(cwd(), `${getProjectName()}`))
         for (let item of openApi2Array) {
             await generateApi({
                 name: `${item.replace('.oas2.yml', '')}Api.ts`,
                 url: null,
                 output: path.resolve(process.cwd(), "node_modules/@imf/heimdall-ts/api"),
                 input: path.resolve(process.cwd(), `${getProjectName()}`, `${item}`),
                 httpClientType: "axios", // or "fetch",
                 unwrapResponseData: true,//是否包裹response
                 generateUnionEnums: true,
                 enumNamesAsValues: true,
                 moduleNameFirstTag: false,
                 moduleNameIndex:-1 //模块名分割
             })
         }
         if(!openApi3Array.length&&!openApi2Array.length){
             await removeDir(path.resolve(cwd(), getProjectName()))
             reject('no openApi3 or openApi2 resources to generate !!!!!')
         }
            resolve()
     })

}
复制代码

整合并导出 🥝

由于 swagger-typescript-api这个库生成的文件是单个单个的,我们需要将生成的文件整合起来,利用正则表达式和模版字符串生成库入口文件

/**
 * 生成入口文件index.ts
 */
function generateMain() {
    return new Promise<void>((resolve, reject)=>{
        //获取文件名
        const fileNames = getFileName(path.resolve(cwd(), 'node_modules/@imf/heimdall-ts/api'))
        //转换文件名 eg:  main.ts -> MainGameApi
        const transformedFileNames = fileNames.map(item => {
            return transformToCamel(item)
        })
        //编写要写如的内容content
        const content = `
        ${transformedFileNames.map((item, index) => {
                return `import \{Api as ${item}\} from \'\.\/${fileNames[index]}\'\n`
            }).join('')
        }
   
   export {
        ${transformedFileNames.map(item => {
            return `${item}\n`
        })}
    }
   `
        //创建index文件
        generateFile(path.resolve(cwd(),'node_modules/@imf/heimdall-ts/api','index.ts'))
        //写入文件
        writeFile(path.resolve(cwd(),'node_modules/@imf/heimdall-ts/api','index.ts'),content).then(()=>{
            resolve()
        })
    })
}

复制代码

支持版本回退 🍉

本来基本功能都实现了,但是老大又提了新需求,需要支持版本回退,这样调用方就可以方便得管理自己的API模块了

  • 实现查看API文档版本号,这里我的解决方案就是利用git版本管理工具的命令可以查看日志来实现

    /**
     * 打印版本stoplight版本信息
     */
    function showLog(){
        return new Promise<void>((resolve, reject)=>{
            shell.exec('git log --pretty=" %h %ci %s "', {
                    cwd: `${path.resolve(cwd(), getProjectName())}`
                },()=>{
                resolve()
                }
            )
        })
    }
    复制代码
  • 执行git checkout命令切换到对应版本的仓库,然后根据这个文档再生成API模块,这样就实现了版本回退

    function gitCloneProject(projectName, isLog = false) {
        return new Promise<void>((resolve, reject) => {
            shell.exec(`git clone https://sudongyu:YUANCxzeJwiVhzQio18v@git.stoplight.io/floozy/${projectName}.git`, {
                cwd: `${cwd()}`
            }, () => {
                const versionCode = getPkgMaifest()?.heimdall?.versionCode
                //如果有versionCode,需要回退版本
                if (!isLog && versionCode) {
                    shell.exec(`git checkout ${versionCode}`, {
                        cwd: `${path.resolve(cwd(), getProjectName())}`
                    }, () => {
                        resolve()
                    })
                } else {
                    resolve()
                }
            })
        })
    复制代码

编写文档 📃

一个库代码逻辑写好后,最重要的还有文档编写啦😁,写上好看易懂的文档,这个库就算大功告成了

image-20211031143811243

项目效果展示 🪖

  • gif 展示

    auth-example

  • 自动生成的代码

    image-20211031000538089

  • 从自动生成的API模块库中导入要使用的API(智能的提示)

    image-20211031000700511

  • 使用API模块发送请求

    由于生成的TS,所以有很好的代码提示,能直接点出来想调用的接口

image-20211031001059973

所有数据都拥有对应的TS类型

image-20211031001201062

收获🍁

编写这个库最大的收获其实是遇到一个完全不懂的需求,如果去解决问题呢?当使用的第三方库不能满足你的需求怎么办呢。我学到的就是先从结果出发,理清思路,一步步去实现每个步骤,当遇到阻碍的时候,多去看看第三方库的Github上的issue或者阅读源码,或许就能很好的解决问题。当然还学习到了如何优雅地调用后端接口,帮助业务同学提高开发效率😂~

结束语🌞

img

那么我的释放双手,优雅地调用后端接口💗这篇文章就结束了,文章的目的其实很简单,就是对日常工作的总结和输出,输出一些觉得对大家有用的东西,菜不菜不重要,但是热爱🔥,希望通过文章认识更多志同道合的朋友,如果你也喜欢折腾,欢迎加我好友,一起沙雕,一起进步

github🤖:sudongyu

个人博客👨‍💻:速冻鱼blog

vx👦:sudongyuer

写在最后

伙伴们,如果喜欢我的口水话给🐟🐟点一个赞👍或者关注➕都是对我最大的支持。

加我微信:sudongyuer,邀你进群,一起学习前端,成为更优秀的工程师~(群二维码在这里->前端要早睡, 二维码过期了的话看链接沸点中的评论,我会把最新的二维码放在评论区,当然也可以加我微信我拉你进群,毕竟我也是有趣的前端,认识我也不赖🌟~

分类:
前端
分类:
前端