Skip to content

OpenAPI 規格產生

必要的相依性io.ktor:ktor-server-routing-openapi

程式碼範例 openapi-spec-gen , openapi-spec-gen-maven

Ktor 支援在執行時從一個或多個文件來源建置 OpenAPI 規格。

此功能可透過以下方式使用:

  • OpenAPI 編譯器擴充功能(包含在 Ktor Gradle 外掛程式中),它會在編譯時分析路由程式碼,並產生在執行時註冊 OpenAPI 元資料的 Kotlin 程式碼。
  • 路由註解執行時 API,它會直接將 OpenAPI 元資料附加到執行中應用程式的路由。

您可以單獨使用其中一種或結合兩者,並將其與 OpenAPISwaggerUI 外掛程式結合使用,以提供互動式 API 文件。

OpenAPI Gradle 擴充功能需要 Kotlin 2.2.20。使用其他版本可能會導致編譯錯誤。

新增相依性

  • 要啟用 OpenAPI 元資料產生,請將 Ktor 編譯器外掛程式套用到您的專案。

    kotlin
    plugins {
        id("io.ktor.plugin") version "3.3.3"
    }
    groovy
    plugins {
        id 'io.ktor.plugin' version "3.3.3"
    }

    與 Gradle 不同,Maven 未提供 Ktor 編譯器外掛程式的內建整合。要啟用 OpenAPI 規格產生,您需要手動配置編譯器外掛程式。

    1. 套用 Ktor Maven 外掛程式(執行和封裝應用程式時必要):

      xml
      <build>
          <plugins>
              <plugin>
                  <groupId>io.ktor</groupId>
                  <artifactId>ktor-maven-plugin</artifactId>
                  <version>3.3.3</version>
              </plugin>
          </plugins>
      </build>
    2. 編譯器外掛程式必須以 JAR 檔案形式提供。新增以下配置以自動下載並將其複製到穩定位置:

      xml
      <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-dependency-plugin</artifactId>
          <version>3.9.0</version>
          <executions>
              <execution>
                  <id>copy-ktor-compiler-plugin</id>
                  <phase>generate-sources</phase>
                  <goals>
                      <goal>copy</goal>
                  </goals>
                  <configuration>
                      <artifactItems>
                          <artifactItem>
                              <groupId>io.ktor</groupId>
                              <artifactId>ktor-compiler-plugin</artifactId>
                              <version>3.3.3</version>
                              <outputDirectory>${project.build.directory}/kotlin-plugins</outputDirectory>
                              <destFileName>ktor-compiler-plugin.jar</destFileName>
                          </artifactItem>
                      </artifactItems>
                  </configuration>
              </execution>
          </executions>
      </plugin>
    3. 配置 Kotlin 編譯器:

      xml
      <plugin>
          <groupId>org.jetbrains.kotlin</groupId>
          <artifactId>kotlin-maven-plugin</artifactId>
          <version>2.2.20</version>
      
          <configuration>
              <jvmTarget>21</jvmTarget>
      
              <compilerPlugins>
                  <plugin>kotlinx-serialization</plugin>
              </compilerPlugins>
      
              <args>
                  <arg>-Xplugin=${project.build.directory}/kotlin-plugins/ktor-compiler-plugin.jar</arg>
      
                  <arg>-P</arg>
                  <arg>plugin:io.ktor.ktor-compiler-plugin:openApiEnabled=true</arg>
      
                  <arg>-P</arg>
                  <arg>plugin:io.ktor.ktor-compiler-plugin:openApiCodeInference=true</arg>
      
                  <arg>-P</arg>
                  <arg>plugin:io.ktor.ktor-compiler-plugin:openApiOnlyCommented=false</arg>
              </args>
          </configuration>
      
          <dependencies>
              <dependency>
                  <groupId>io.ktor</groupId>
                  <artifactId>ktor-compiler-plugin</artifactId>
                  <version>3.3.3</version>
              </dependency>
              <dependency>
                  <groupId>org.jetbrains.kotlin</groupId>
                  <artifactId>kotlin-maven-serialization</artifactId>
                  <version>${kotlin_version}</version>
              </dependency>
          </dependencies>
          <executions>
              <execution>
                  <id>compile</id>
                  <phase>compile</phase>
                  <goals>
                      <goal>compile</goal>
                  </goals>
              </execution>
              <execution>
                  <id>test-compile</id>
                  <phase>test-compile</phase>
                  <goals>
                      <goal>test-compile</goal>
                  </goals>
              </execution>
          </executions>
      </plugin>
  • 要使用執行時路由註解,請將 ktor-server-routing-openapi 構件新增到您的組建指令碼:

    Kotlin
    Groovy
    XML

配置 OpenAPI 編譯器擴充功能

OpenAPI 編譯器擴充功能控制如何在編譯時收集路由元資料。它本身並不定義最終的 OpenAPI 文件。

在編譯期間,該外掛程式會產生 Kotlin 程式碼,該程式碼使用 OpenAPI 執行時 API 來註冊從路由宣告、程式碼模式和註解中衍生的元資料。

一般的 OpenAPI 資訊 —— 例如 API 標題、版本、伺服器、安全性配置和詳細架構 —— 會在產生規格時的執行時提供。

要配置編譯器外掛程式擴充功能,請在 build.gradle.kts 檔案的 ktor 擴充功能內使用 openApi {} 區塊:

kotlin
ktor {
    openApi {
        enabled = true
        codeInferenceEnabled = true
        onlyCommented = false
    }
}

配置選項

enabled
<code>enabled</code> 啟用或停用 OpenAPI 路由註解程式碼產生。預設為 false
codeInferenceEnabled
<code>codeInferenceEnabled</code> 控制編譯器是否嘗試從路由程式碼推論 OpenAPI 元資料。預設為 true。如果推論產生不正確的結果,或者您偏好使用註解明確定義元資料,請停用此選項。如需更多詳細資訊,請參閱程式碼推論規則
onlyCommented
<code>onlyCommented</code> 將元資料產生限制在包含註解註解的路由。預設為 false,這表示除了明確標記為 @ignore 的路由外,所有的路由呼叫都會被處理。

路由結構分析

Ktor 編譯器外掛程式會分析您的伺服器路由 DSL,以確定 API 的結構形狀。此分析僅基於路由宣告,不會檢查路由處理常式的內容。

以下內容會自動從路由 API 樹中的選擇器中推論:

  • 合併後的路徑(例如 /api/v1/users/{id})。
  • HTTP 方法(例如 GETPOST)。
  • 路徑參數。
kotlin
routing {
    route("/api/v1") {
        get("/users") { }
        get("/users/{id}") { }
        post("/users") { }
    }
}

由於請求參數、主體和回應是在路由 Lambda 內部處理的,編譯器無法僅從路由結構推論出完整的 OpenAPI 描述。為了豐富產生的元資料,Ktor 支援基於常見請求處理模式的註解自動推論

程式碼推論

當啟用程式碼推論時,編譯器外掛程式會識別常見的 Ktor 使用模式,並自動產生等效的執行時註解。

下表總結了支援的推論規則:

規則描述輸入輸出 (來自 annotate 作用域)
請求主體 (Request Body)ContentNegotiation 讀取提供請求主體架構call.receive<T>()requestBody { schema = jsonSchema<T>() }
回應主體 (Response Body)ContentNegotiation 寫入提供回應主體架構call.respond<T>()responses { HttpStatusCode.OK { schema = jsonSchema<T>() } }
回應標頭 (Response Headers)包含回應的自訂標頭call.response.header("X-Foo", "Bar")responses { HttpStatusCode.OK { headers { header("X-Foo", "Bar") } } }
路徑參數 (Path Parameters)尋找路徑參數參照call.parameters["id"]parameters { path("id") }
查詢參數 (Query Parameters)尋找查詢參數參照call.queryParameters["name"]parameters { query("name") }
請求標頭 (Request Headers)尋找請求標頭參照call.request.headers["X-Foo"]parameters { header("X-Foo") }
Resource API 路由為 Resources 路由 API 推論呼叫結構call.get<List> { /**/ }; @Resource("/list") class List(val name: String)parameters { query("name") }

推論會儘可能遵循提取的函式,並嘗試為典型的請求和回應流程產生一致的文件。

停用特定端點的推論

如果推論為特定端點產生了不正確的元資料,您可以透過新增 ignore 標記來排除它:

kotlin
// ignore!
get("/comments") {
    // ...
}

註解路由

為了豐富規格內容,Ktor 支援兩種註解路由的方式:

您可以採用其中一種方法,或將兩者結合使用。

基於註解的路由註解

基於註解的註解提供了無法從程式碼推論的元資料,並能與現有路由無縫整合。

元資料的定義方式是在行首放置關鍵字,後跟冒號 (:) 及其值。

您可以直接將註解附加到路由宣告:

kotlin
/**
 * Get a single user by ID.
 *
 * Path: id [ULong] the ID of the user
 *
 * Responses:
 *   – 400 The ID parameter is malformatted or missing.
 *   – 404 The user for the given ID does not exist.
 *   – 200 [User] The user found with the given ID.
 */
get("/{id}") {
    val id = call.parameters["id"]?.toULongOrNull()
        ?: return@get call.respond(HttpStatusCode.BadRequest)
    val user = list.find { it.id == id }
        ?: return@get call.respond(HttpStatusCode.NotFound)
    call.respond(user)
}

格式化規則

  • 關鍵字必須出現在行首。
  • 冒號 (:) 用於分隔關鍵字及其值。
  • 複數形式(例如 TagsResponses)允許分組定義。
  • 單數形式(例如 TagResponse)也受支援。
  • 頂層項目符號 (-) 是可選的,僅影響格式。

以下變體是等效的:

kotlin
/**
 * Tag: widgets
 * 
 * Tags:
 *   - widgets
 * 
 * - Tags:
 *  - widgets
 */

支援的註解欄位

標籤格式描述
TagTag: name將端點歸類在一個標籤下
PathPath: [Type] name description路徑參數
QueryQuery: [Type] name description查詢參數
HeaderHeader: [Type] name description標頭參數
CookieCookie: [Type] name descriptionCookie 參數
BodyBody: contentType [Type] description請求主體
ResponseResponse: code contentType [Type] description回應定義
DeprecatedDeprecated: reason將端點標記為已棄用
DescriptionDescription: text詳細描述
SecuritySecurity: scheme安全性需求
ExternalDocsExternalDocs: href外部文件連結
Experimental

執行時路由註解

在編譯時分析不足的情況下,例如使用動態路由、攔截器或條件邏輯時,您可以在執行時使用 .describe {} 擴充函式將 OpenAPI 操作元資料直接附加到路由。

每個加上註解的路由都定義了一個 OpenAPI Operation 物件,它在產生的 OpenAPI 規格中代表單個 HTTP 操作(例如 GET /users)。元資料在執行時附加到路由樹,並由 OpenAPI 和 Swagger UI 外掛程式取用。

.describe {} DSL 直接對應到 OpenAPI 規格。屬性名稱和結構與為 Operation 物件定義的欄位一致,包括參數、請求主體、回應、安全性需求、伺服器、回呼和規格擴充 (x-*)。

執行時路由註解 API 是實驗性的,需要使用 @OptIn(ExperimentalKtorApi::class) 選擇加入:

kotlin
                @OptIn(ExperimentalKtorApi::class)
                get("/users") {
                    val query = call.parameters["q"]
                    val result = if (query != null) {
                        list.filter {it.name.contains(query, ignoreCase = true)  }
                    } else {
                        list
                    }

                    call.respond(result)
                }.describe {
                    summary = "Get users"
                    description = "Retrieves a list of users."
                    parameters {
                        query("q") {
                            description = "An encoded query"
                            required = false
                        }
                    }
                    responses {
                        HttpStatusCode.OK {
                            description = "A list of users"
                            schema = jsonSchema<List<User>>()
                        }
                        HttpStatusCode.BadRequest {
                            description = "Invalid query"
                            ContentType.Text.Plain()
                        }
                    }
                }

如需可用欄位的完整列表,請參閱 OpenAPI 規格

執行時註解會與編譯器產生的元資料以及基於註解的元資料合併。當多個來源定義相同的 OpenAPI 欄位時,執行時註解提供的值具有優先權

在 OpenAPI 規格中隱藏路由

要將某個路由及其子路由從產生的 OpenAPI 文件中排除,請使用 Route.hide() 函式:

kotlin
@OptIn(ExperimentalKtorApi::class)
get("/routes") {
    // ....
}.hide()

這對於不應發佈的內部、管理或診斷端點非常有用,包括用於產生 OpenAPI 規格本身的路由。

OpenAPI 和 Swagger UI 外掛程式會自動呼叫 .hide(),因此它們的路由會從產生的文件中排除。

架構推論

Ktor 在建置 OpenAPI 規格時會自動為請求和回應型別產生 JSON 架構。預設情況下,架構是透過使用資料類別上的 kotlinx-serialization 描述符從型別參考中推論出來的。這使得大多數常見的資料模型無需額外努力即可被記錄。

使用註解自訂架構

您可以透過在資料類別中加入 @JsonSchema 註解來覆寫自動產生的 JSON 架構欄位。這允許您新增描述、將欄位標記為必填等:

kotlin
@JsonSchema.Description("Represents a news article")
data class Article(
    val title: String,
    val content: String
)

使用基於反射的架構推論

對於使用 Jackson 或 Gson 而非 kotlinx-serialization 的專案,您可以使用基於反射的架構推論。要執行此操作,請在 OpenAPI 或 SwaggerUI 外掛程式的 Routing 來源中設定 schemaInference 欄位:

kotlin
openAPI("docs") {
    outputPath = "docs/routes"
    info = OpenApiInfo("Books API from routes", "1.0.0")
    source = OpenApiDocSource.Routing(
        contentType = ContentType.Application.Json,
        schemaInference = ReflectionJsonSchemaInference.Default,
    )
}

自訂反射行為

您可以提供自訂的 SchemaReflectionAdapter 來處理不直接支援的註解或命名慣例。

SchemaReflectionAdapterReflectionJsonSchemaInference 的一個欄位,它允許您覆寫預設行為,例如屬性名稱、被忽略的欄位或可 null 性規則。

例如,您可以自訂行為以支援 Gson 的 @SerializedName 註解:

kotlin
ReflectionJsonSchemaInference(object : SchemaReflectionAdapter {
    override fun getName(type: KType): String? {
        return (type.classifier as? KClass<*>)?.let {
            findAnnotations(SerializedName::class)?.value ?: it.simpleName
        }
    }
})

提供自訂架構

如果自動架構推論或註解不足,您可以使用 JsonSchema 類別手動建構 JSON 架構。

這允許您明確定義所有支援的架構屬性,例如型別、格式、巢狀結構或進階結構(例如用於類元組陣列定義的 prefixItems)。

如需可用屬性的完整清單,請參閱 JsonSchema API 文件

產生並提供規格

OpenAPI 規格是在執行時根據執行時路由註解和編譯器外掛程式產生的元資料組合而成的。

您可以透過以下方式公開規格:

組合並提供規格

要在執行時組合完整的 OpenAPI 文件,請建立一個 OpenApiDoc 執行個體並提供應包含在規格中的路由。

該文件是根據路由樹中編譯器產生的元資料和執行時路由註解組合而成的。產生的 OpenApiDoc 執行個體始終反映應用程式的當前狀態。

您通常從路由處理常式建構文件並直接對其進行回應:

kotlin

        get("/docs.json") {
            val doc = OpenApiDoc(info = OpenApiInfo("My API", "1.0")) + call.application.routingRoot.descendants()
            call.respond(doc)

在此範例中,OpenAPI 文件使用 ContentNegotiation 外掛程式進行序列化。這假設已安裝了 JSON 序列化器(例如 kotlinx.serialization)。

不需要額外的組建或產生步驟。下次請求規格時,對路由或註解的更改將自動反映出來。

如果您想使序列化更明確,或避免依賴 ContentNegotiation,您可以手動對文件進行編碼並回應 JSON:

kotlin
call.respondText(
  Json.encodeToString(docs),
  ContentType.Application.Json
)

提供互動式文件

要透過互動式 UI 公開 OpenAPI 規格,請使用 OpenAPISwagger UI 外掛程式。

這兩個外掛程式都會在執行時組合規格,並可以直接從路由樹讀取元資料。它們在文件呈現方式上有所不同:

  • OpenAPI 外掛程式在伺服器上呈現文件並提供預先產生的 HTML。
  • Swagger UI 外掛程式以 JSON 或 YAML 形式提供 OpenAPI 規格,並使用 Swagger UI 在瀏覽器中呈現 UI。
kotlin
// 提供 OpenAPI UI
openAPI("/openApi")

// 提供 Swagger UI
swaggerUI("/swaggerUI") {
    info = OpenApiInfo("My API", "1.0")
    source = OpenApiDocSource.Routing(
        contentType = ContentType.Application.Json,
    )
}

元資料優先順序

最終的 OpenAPI 規格是在執行時透過合併多個來源貢獻的元資料來組合的。

以下來源依序套用:

  1. 編譯器產生的元資料,包括:
  2. 基於註解的路由註解
  3. 執行時路由註解

當同一個 OpenAPI 欄位由多個來源定義時,執行時註解提供的值優先於基於註解的註解和編譯器產生的元資料。

未被明確覆寫的元資料將被保留並合併到最終文件中。