OpenAI ChatGPT API 文档之 Embedding openapi specification_OpenApi

OpenAPI Specification(OAS)


无论你从事前端开发还是后端开发,或多或少都听说过Swagger

Swagger Specification 是一种 API Specification(API 规范),2015 年,SmartBear Software 将 Swagger Specification 捐赠给 Linux Foundation,并改称为 OpenAPI Specification简称(OAS)

OAS 本质上是一种 RESTful API 的描述方式,允许用户使用这种方式定义所有的API。

参考:OpenAPI-Specification



OAS的意义?


  • API的开发和调试过程中,涉及角色众多(前端、客户端、后端,QA等),需要通过规范来统一思想,提高信息传达的效率;
  • 前端开发人员不需要关心后端开发的语言和平台,也不应该去查看后端的代码或开发文档,真正的做到面向接口文档开发;
  • 接口定义文档可以通过代码生成工具生成各种语言形式的框架代码,避免需求变更时导致的数据不一致问题,提高开发效率;

通过OAS定义API,然后自动生成前后端的代码以及文档,可以一劳永逸的解决上述问题。



如何定义OAS?


基本格式

OAS可以书写为 JSONYAML。很多配置文件(例如Nginx和大部分脚本语言的配置文件)都习惯使用 JSON ,但越来越多的地方(例如 Springboot),推荐使用YAML,YMAL能覆盖JSON的所有语义而且直至更多数据类型,语法更简洁。

参考:https://yaml.org/spec/1.2/spec.pdf

配置项目

OAS需要配置以下内容:

OpenAI ChatGPT API 文档之 Embedding openapi specification_Specification_02

以Github的OpenAPI为例(Github的Restful API 完全兼容OpenAP 3.0规范):

参考:https://github.com/github/rest-api-description

openapi: 3.0.3
info:
  version: 1.1.4
  title: GitHub v3 REST API
  description: GitHub's v3 REST API.
  license:
    name: MIT
    url: https://spdx.org/licenses/MIT
servers:
- url: https://api.github.com
externalDocs:
  description: GitHub v3 REST API
  url: https://docs.github.com/rest/
tags:
- name: apps
  description: Information for integrations and installations.
- name: activity
  description: Activity APIs provide access to notifications, subscriptions, and timelines.
...
paths:
paths:
  "/":
    get:
      summary: GitHub API Root
      tags:
      responses:
        '200':
          ...
  "/app":
    get:
      summary: Get the authenticated app
      tags:
      - apps
      parameters: []
      responses:
        '200':
          ...
 "/feeds":
    get:
      summary: Get feeds
      tags:
      - activity
      parameters: []
      responses:
        '200':
          ...
components:
  schemas:
    simple-user:
      title: Simple User
      description: Simple User
      type: object
      properties:
        login:
          type: string
          example: octocat
        id:
          type: integer
          example: 1
        avatar_url:
          type: string
          format: uri
          example: https://github.com/images/error/octocat_happy.gif
          ...

详细配置可以参考 https://swagger.io/specification/

使用SwaggerEditor编辑OAS

推荐使用SwaggerEditor,用来对yaml进行读取和编辑。右侧可以显示解析后的结果,还可以直接测试API的调用。

OpenAI ChatGPT API 文档之 Embedding openapi specification_Specification_03


OpenAI ChatGPT API 文档之 Embedding openapi specification_Swagger Editor_04



Android客户端代码生成


SwaggerEditor已经提供了Server和Client的代码生成,可以生成JavaBean以及API请求代码。但是都是Java版本的。如果想生成Kotlin代码可以使用

参考: OpenAPI Generator

OpenAPI Generator

OpenAPI Generator使用gardle生成目标代码,

参考: OpenAPI Generator Gradle Plugin

在工程中创建一个专门用于生成api的module,并为之定义build.gradle.kts脚本

val kotlin_version = "1.4.30"

plugins {
    kotlin("jvm")

    // OpenAPI Generator
    id("org.openapi.generator") version "5.0.0-beta2"
    id("kotlinx-serialization")
}


dependencies {
    implementation(fileTree("dir" to "libs", "include" to listOf("*.jar")))

    // 依赖库 kotlin, retrofit, moshi 等
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version")
    implementation("com.squareup.retrofit2:retrofit:2.9.0")
//    implementation(Dependency.moshi)
}

// API 名
val apiName = "github"

// 生成代码位置
val buildApiDir = "$buildDir/openApiGenerator/$apiName"

// 生成代码的package名
val basePackage = "com.my.openapigeneratorsample"

fun String.packageToDir() = replace('.', '/')

task<org.openapitools.generator.gradle.plugin.tasks.GenerateTask>("generate") {
    doFirst {
        delete(file(buildApiDir))
    }

    // OpenAPI Generator 相关设定
    generatorName.set("kotlin") //生成kotlin
    library.set("jvm-retrofit2")
    // templateDir.set("$rootDir/template") // 设置模板用来做修正,后文介绍
    inputSpec.set("$rootDir/api/api.github.com.yaml") //oas地址,取自github/openapi
    outputDir.set(buildApiDir)
    packageName.set(basePackage)
    apiPackage.set("$basePackage.$apiName.api")
    modelPackage.set("$basePackage.$apiName.model")
    configOptions.set(
            mapOf(
                    "dateLibrary" to "java8"
            )
    )
    additionalProperties.set(
            mapOf(
                    // 设置是否使用Rx或者Coroutine
                    "doNotUseRxAndCoroutines" to "true"
            )
    )
    generateApiTests.set(false)
}

task<Copy>("copy") {
    val dirFrom = "$buildApiDir/src/main/kotlin/${basePackage.packageToDir()}/"
    val dirInto = "$projectDir/src/main/java/${basePackage.packageToDir()}/"

    doFirst {
        delete(file(dirInto))
    }

    dependsOn("generate")
    from(dirFrom)
    into(dirInto)
}

task("buildApi") {
    dependsOn("generate", "copy")
}

命令行执行

./gradlew :api:buildApi

在指定目录生成代码

OpenAI ChatGPT API 文档之 Embedding openapi specification_Specification_05


OpenAI ChatGPT API 文档之 Embedding openapi specification_OpenApi_06

tags => XXXApi

前面OSA的配置项中有tag,用来对API进行分组,在paths中也会标记当前API的tag。
生成代码时会根据tag生成XXXApi tags:

- name: actions
  description: Endpoints to manage GitHub Actions using the REST API.
- name: activity
  description: Activity APIs provide access to notifications, subscriptions, and timelines.
- name: apps
  description: Information for integrations and installations.
- name: billing
  description: Monitor charges and usage from Actions and Packages.
...

OpenAI ChatGPT API 文档之 Embedding openapi specification_OpenApi_07

XXXApi中根据paths配置生成API请求方法:

interface ActionsApi {
    /**
     * Get GitHub Actions permissions for an organization
     * Gets the GitHub Actions permissions policy for repositories and allowed actions in an organization.  You must authenticate using an access token with the `admin:org` scope to use this endpoint. GitHub Apps must have the `administration` organization permission to use this API.
     * Responses:
     *  - 200: Response
     * 
     * @param org  
    * @return [Call]<[ActionsMinusOrganizationMinusPermissions]>
     */
    @GET("orgs/{org}/actions/permissions")
    fun actionsActionsPoliciesGetGithubActionsPermissionsOrganization(@Path("org") org: kotlin.String): Call<ActionsMinusOrganizationMinusPermissions>

    /**
     * Add repository access to a self-hosted runner group in an organization
     * The self-hosted runner groups REST API is available with GitHub Enterprise Cloud. For more information, see \"[GitHub's products](https://docs.github.com/github/getting-started-with-github/githubs-products).\"   Adds a repository to the list of selected repositories that can access a self-hosted runner group. The runner group must have `visibility` set to `selected`. For more information, see \"[Create a self-hosted runner group for an organization](#create-a-self-hosted-runner-group-for-an-organization).\"  You must authenticate using an access token with the `admin:org` scope to use this endpoint.
     * Responses:
     *  - 204: Response
     * 
     * @param org  
     * @param runnerGroupId Unique identifier of the self-hosted runner group. 
     * @param repositoryId  
    * @return [Call]<[Unit]>
     */
    @PUT("orgs/{org}/actions/runner-groups/{runner_group_id}/repositories/{repository_id}")
    fun actionsAddRepoAccessToSelfHostedRunnerGroupInOrg(@Path("org") org: kotlin.String, @Path("runner_group_id") runnerGroupId: kotlin.Int, @Path("repository_id") repositoryId: kotlin.Int): Call<Unit>
	
	...
}

components => XXXModel

API返回的Model定义来自OAS中的components.schemas配置。paths配置中会使用这些它们作为parameters,当不匹配时,无法正常生成代码。

components:
	schemas:
		...
	    actor:
	      title: Actor
	      description: Actor
	      type: object
	      properties:
	        id:
	          type: integer
	        login:
	          type: string
	        display_login:
	          type: string
	        gravatar_id:
	          type: string
	          nullable: true
	        url:
	          type: string
	          format: uri
	        avatar_url:
	          type: string
	          format: uri

生成代码:

/**
 * Actor
 * @param id 
 * @param login 
 * @param gravatarId 
 * @param url 
 * @param avatarUrl 
 * @param displayLogin 
 */

data class Actor (
    @Json(name = "id")
    val id: kotlin.Int,
    @Json(name = "login")
    val login: kotlin.String,
    @Json(name = "gravatar_id")
    val gravatarId: kotlin.String?,
    @Json(name = "url")
    val url: java.net.URI,
    @Json(name = "avatar_url")
    val avatarUrl: java.net.URI,
    @Json(name = "display_login")
    val displayLogin: kotlin.String? = null
)



使用Template对生成代码修正


OpenAPI Generator 并非完美,有时生成的代码有问题。

例如v5.0.0-beta2生成的ApiClient.kt中出现会如下错误(已发PR,估计v5.0.0会修复):

OpenAI ChatGPT API 文档之 Embedding openapi specification_Specification_08


此时使用Template可以进行自动修正,避免手工劳动。

我们为ApiClient.kt创建Template文件:libraries/jvm-retrofit2/infrastructure/ApiClient.kt.mustache

...
    fun <S> createService(serviceClass: Class<S>): S {
# REMOVE these lines
#        var usedClient: OkHttpClient? = null
#        this.okHttpClient?.let { usedClient = it } ?: run {usedClient = clientBuilder.build()}
# ADD this line
        val usedClient: OkHttpClient = this.okHttpClient ?: clientBuilder.build()
        return retrofitBuilder.client(usedClient).build().create(serviceClass)
    }
...

关于Template,更多内容参考
https://openapi-generator.tech/docs/templating/

基于修正后的ApiClient.kt,我们就可以直接拿来进行API请求了

val userApi: UserApi = ApiClient(authName = "Bearer", bearerToken = "[MY_TOKEN]")
        .createService(UserApi::class.java)

fun getAllUser(callback: Callback<List<User>>, page: Int? = null, perPage: Int? = null) {
    val call = userApi.getAllUser(page?.toString(), perPage?.toString())
    call.enqueue(callback)
}
...



生产Coroutine代码


上面生成的API中使用的retrofit2.Call返回结果。在Kotlin中我们更希望直接使用suspend函数

build.gradle.kts中稍作修改即可:

additionalProperties.set(mapOf(
// REMOVE this line
//        "doNotUseRxAndCoroutines" to "true"
// ADD this line
        "useCoroutines" to "true"
    ))

生成代码编程下面这样:

interface ActivityApi {
  
    @GET("user/starred/{owner}/{repo}")
    suspend fun activityCheckRepoIsStarredByAuthenticatedUser(@Path("owner") owner: kotlin.String, @Path("repo") repo: kotlin.String): Response<Unit>

  
    @DELETE("repos/{owner}/{repo}/subscription")
    suspend fun activityDeleteRepoSubscription(@Path("owner") owner: kotlin.String, @Path("repo") repo: kotlin.String): Response<Unit>
    ...
}



总结


OpenAPI Generator能够通过多样性的配置生成开箱即用的客户端代码。此外它还可以生成服务端代码,保证了多端一致性。当API发生变更时,只要修改yaml文件就可以自动生成新的代码,大大降低开发负担。借助SwaggerEditor还可以对yaml进行简单测试。

现在越来越多的开放平台都支持了OpenAPI规范,作为客户端开发者,也可以活用OAS的周边工具管理和生成客户端代码,降低我们的开发成本。