整合 Spring Doc 介面檔案和 knife4j-SpringBoot 2.7.2 實戰基礎

2022-08-08 18:00:57

優雅哥 SpringBoot 2.7.2 實戰基礎 - 04 -整合 Spring Doc 介面檔案和 knife4j

前面已經整合 MyBatis Plus、Druid 資料來源,開發了 5 個介面。在測試這 5 個介面時使用了 HTTP Client 或 PostMan,無論是啥都比較麻煩:得自己寫請求地址 URL、請求引數等,於是多年前就出現了 Swagger 這個玩意。Swagger 可以自動生成介面檔案,還能很方便的測試各個介面。但不幸的是,MVN Repository 上面 Springfox Swagger2 的版本停止於 2020 年 7月,而寫下這篇文章是 2022 年 8 月,已經兩年過去沒有動靜了,與此同時,springdoc-openapi 悄然出現。

spring doc open api 支援 Open API 3、Swagger-ui等,可以很方便與 Spring Boot 整合,設定和使用與 Springfox Swagger2 類似。

1 整合 Spring Doc

1.1 新增依賴

springdoc-openapi 不是 Spring Framework 官方團隊開發的,而是社群專案,沒有包含在 spring-boot-dependencies 中。故需要先定義版本號:

<properties>
		....
    <springdoc-openapi-ui.version>1.6.9</springdoc-openapi-ui.version>
</properties>

新增依賴:

<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-ui</artifactId>
    <version>${springdoc-openapi-ui.version}</version>
</dependency>

該依賴裡面使用了 swagger-ui,以HTML形式展示檔案。

1.2 編寫設定類

其實設定類寫不寫都可以,如果不寫設定類,就通過註解定義檔案資訊就可以。建立類:com.yygnb.demo.config.SpringDocConfig

package com.yygnb.demo.config;

import io.swagger.v3.oas.models.ExternalDocumentation;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringDocConfig {

    private String title = "Hero SpringBoot Demo";
    private String description = "Hero Demo for usage of Spring Boot";
    private String version = "v0.0.1";
    private String websiteName = "Hero Website";
    private String websiteUrl = "http://www.yygnb.com";

    @Bean
    public OpenAPI heroOpenAPI() {
        return new OpenAPI()
                .info(new Info().title(title)
                        .description(description)
                        .version(version))
                .externalDocs(new ExternalDocumentation().description(websiteName)
                        .url(websiteUrl));
    }
}

上面的設定定義了展示的檔案的資訊,與介面上對應關係如下:

在組態檔中除了可以設定檔案的資訊,還可以設定檔案分組、Authorization 等,在後面的企業級實戰文章中會具體描寫,將會在閘道器層 spring cloud gateway 中整合所有微服務的介面。

1.3 設定yml

在 application.yml 設定 springdoc:

# 介面檔案
springdoc:
  packages-to-scan: com.yygnb.demo.controller
  swagger-ui:
    enabled: true

這兩項不設定也可以,packages-to-scan 預設為啟動類所在的路徑;springdoc.swagger-ui.enabled 預設為true,設定後可以在不同的環境中開啟或關閉。

1.4 新增註解

springdoc-openapi 與 springfox-swagger2 提供的註解有很大差別:

swagger 2 spring doc 描述
@Api @Tag 修飾 controller 類,類的說明
@ApiOperation @Operation 修飾 controller 中的介面方法,介面的說明
@ApiModel @Schema 修飾實體類,該實體的說明
@ApiModelProperty @Schema 修飾實體類的屬性,實體類中屬性的說明
@ApiImplicitParams @Parameters 介面引數集合
@ApiImplicitParam @Parameter 介面引數
@ApiParam @Parameter 介面引數

修改實體類 Computer,新增 springdoc-openapi 註解:

@Schema(title = "電腦")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Computer implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    @Schema(title = "尺寸")
    private BigDecimal size;

    @Schema(title = "作業系統")
    private String operation;

    @Schema(title = "年份")
    private String year;
}

修改控制器 ComputerController,新增註解:

@Tag(name = "電腦相關介面")
@RequiredArgsConstructor
@RestController
@RequestMapping("/computer")
public class ComputerController {

    private final IComputerService computerService;

    @Operation(summary = "根據id查詢電腦")
    @GetMapping("/{id}")
    public Computer findById(
            @Parameter(name = "id", required = true, description = "電腦id") @PathVariable Long id) {
        return this.computerService.getById(id);
    }

    @Operation(summary = "分頁查詢電腦列表")
    @Parameters(value = {
            @Parameter(name = "page", description = "頁面,從1開始", example = "2"),
            @Parameter(name = "size", description = "每頁大小", example = "10")
    })
    @GetMapping("/find-page/{page}/{size}")
    public Page<Computer> findPage(@PathVariable Integer page, @PathVariable Integer size) {
        return this.computerService.page(new Page<>(page, size));
    }

    @Operation(summary = "新增電腦")
    @PostMapping()
    public Computer save(@RequestBody Computer computer) {
        computer.setId(null);
        this.computerService.save(computer);
        return computer;
    }

    @Operation(summary = "根據id修改電腦")
    @PutMapping("/{id}")
    public Computer update(
            @Parameter(name = "id", required = true, description = "電腦id") @PathVariable Long id,
            @RequestBody Computer computer) {
        computer.setId(id);
        this.computerService.updateById(computer);
        return computer;
    }

    @Operation(summary = "根據id刪除電腦")
    @DeleteMapping("/{id}")
    @Parameter(name = "id", required = true, description = "電腦id")
    public void delete(@PathVariable Long id) {
        this.computerService.removeById(id);
    }
}

1.5 執行測試

啟動服務,在瀏覽器中存取:

http://localhost:9099/swagger-ui/index.html

2 api-docs

在檔案標題下面有一個小連結:/v3/api-docs,點選該連結,會在新頁面中顯示一大坨 JSON 資料。

看似很無聊的資料,卻有著重大意義,swagger-ui 就是通過這些資料渲染出頁面的。此外,這些JSON資料在某種程度上可以簡化前端的開發及前後端網路請求的工作量。

hero-admin-ui

優雅哥正在開發的基於 Vue 3 + TypeScript 的開源專案 hero-admin-ui,其特色就是基於 JSON Schema 的表單和列表。通過 JSON Schema 可以快速渲染出一個列表、表單,甚至是搜尋頁和詳情表單頁。如果獨立使用 hero-admin-ui,需要手動編寫 JSON Schema,但如果後端介面整合了 Swagger 或 Spring Doc,上面 api-docs 返回的 JSON,就包含了 JSON Schema,二者結合可以快速實現搜尋頁、表單頁等。目前 hero-admin-ui 已釋出在 npmjs 上,也已經提交到 github上,大家可以搜尋鍵碼 hero-admin-ui 檢視。在後面的實戰篇中,前端部分將會使用這個元件庫實現前端頁面。

3 自定義設定

在 」1.2 編寫設定類「 一節,檔案資訊都是寫死在程式碼中的,如果多個微服務都要整合 spring doc,可以把前面寫的 SpringDocConfig 提取到公共模組中,通過maven 依賴參照,在 application.yml 中設定不同的變數。

3.1 新增依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

設定該依賴的目的是在編寫 application.yml 時,自定義的屬性有程式碼提示。它會生成設定後設資料,無需自己手動編寫。

3.2 定義設定的實體類

建立 com.yygnb.demo.config.DocInfo,將 SpringDocConfig 中寫死的屬性都移到這個設定實體類中:

@Data
@Component
@ConfigurationProperties(prefix = "doc-info")
public class DocInfo {

    private String title = "Demo Title";
    private String description = "Demo Description";
    private String version = "v0.0.1";
    private String websiteName = "Demo Website";
    private String websiteUrl = "http://www.yygnb.com";
}

註解 @ConfigurationProperties(prefix = "doc-info") 宣告設定屬性,在 application.yml 設定時就可以使用 doc-info。

3.3 重構 SpringDocConfig

在 SpringDocConfig 中引入 DocInfo,並通過建構函式進行注入:

@RequiredArgsConstructor
@Configuration
public class SpringDocConfig {

    private final DocInfo docInfo;

    @Bean
    public OpenAPI springShopOpenAPI() {
        return new OpenAPI()
                .info(new Info().title(docInfo.getTitle())
                        .description(docInfo.getDescription())
                        .version(docInfo.getVersion()))
                .externalDocs(new ExternalDocumentation().description(docInfo.getWebsiteName())
                        .url(docInfo.getWebsiteUrl()));
    }
}

補充一個小點:註解 @RequiredArgsConstructor 是 lombok 中提供的,它等價於在類中編寫了方法:

public SpringDocConfig(DocInfo docInfo) {
    this.docInfo = docInfo;
}

構造注入時,在建構函式中寫大量的屬性,毫無意義。既然已經使用了 @Data 註解,為啥不用 @RequiredArgsConstructor 呢?

3.4 使用自定義設定

自定義設定已經完成,可以在 application.yml 中使用 DocInfo 對應的設定了:

doc-info:
  title: SpringBoot Demo演示
  description: 學習 Spring Boot 2.7.2

DocInfo 所有屬性都定義了預設值,在 application.yml 可以覆蓋預設值,如上面的 titledescription 屬性。重啟服務檢視執行效果:

4 整合 knife4j

在之前 springfox-swagger 的時代,很多同學不喜歡 swagger-ui 的介面風格,會整合 knife4j 的 ui。Spring Doc 也可以整合 knife4j。

如果要使用 knife4j ,Spring Doc 的設定中需要新增分組設定,我們這裡新增一個最簡單的分組設定。

com.yygnb.demo.config.SpringDocConfig

@RequiredArgsConstructor
@Configuration
public class SpringDocConfig {

    private final DocInfo docInfo;

    @Bean
    public OpenAPI heroOpenAPI() {
        return new OpenAPI()
                .info(new Info().title(docInfo.getTitle())
                        .description(docInfo.getDescription())
                        .version(docInfo.getVersion()))
                .externalDocs(new ExternalDocumentation().description(docInfo.getWebsiteName())
                        .url(docInfo.getWebsiteUrl()));
    }

    @Bean
    public GroupedOpenApi publicApi() {
        return GroupedOpenApi.builder()
                .group(docInfo.getTitle())
                .pathsToMatch("/**")
                .build();
    }
}

如果不新增這個 GroupedOpenApi 範例,knife4j ui就顯示不出來。

在 pom.xml 中引入 knife4j

<properties>
...
    <knife4j-springdoc-ui.version>3.0.3</knife4j-springdoc-ui.version>
</properties>

<dependencies>
...
    <dependency>
        <groupId>com.github.xiaoymin</groupId>
        <artifactId>knife4j-springdoc-ui</artifactId>
        <version>${knife4j-springdoc-ui.version}</version>
    </dependency>
</dependencies>

啟動服務,存取:

http://localhost:9099/doc.html

顯示 knife4j 的ui:


今日優雅哥(工\/youyacoder)學習結束,期待關注留言分享~~