前面已經整合 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 類似。
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形式展示檔案。
其實設定類寫不寫都可以,如果不寫設定類,就通過註解定義檔案資訊就可以。建立類: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 中整合所有微服務的介面。
在 application.yml 設定 springdoc:
# 介面檔案
springdoc:
packages-to-scan: com.yygnb.demo.controller
swagger-ui:
enabled: true
這兩項不設定也可以,packages-to-scan 預設為啟動類所在的路徑;springdoc.swagger-ui.enabled 預設為true,設定後可以在不同的環境中開啟或關閉。
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);
}
}
啟動服務,在瀏覽器中存取:
http://localhost:9099/swagger-ui/index.html
在檔案標題下面有一個小連結:/v3/api-docs
,點選該連結,會在新頁面中顯示一大坨 JSON 資料。
看似很無聊的資料,卻有著重大意義,swagger-ui 就是通過這些資料渲染出頁面的。此外,這些JSON資料在某種程度上可以簡化前端的開發及前後端網路請求的工作量。
優雅哥正在開發的基於 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
檢視。在後面的實戰篇中,前端部分將會使用這個元件庫實現前端頁面。
在 」1.2 編寫設定類「 一節,檔案資訊都是寫死在程式碼中的,如果多個微服務都要整合 spring doc,可以把前面寫的 SpringDocConfig 提取到公共模組中,通過maven 依賴參照,在 application.yml 中設定不同的變數。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
設定該依賴的目的是在編寫 application.yml 時,自定義的屬性有程式碼提示。它會生成設定後設資料,無需自己手動編寫。
建立 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。
在 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
呢?
自定義設定已經完成,可以在 application.yml 中使用 DocInfo 對應的設定了:
doc-info:
title: SpringBoot Demo演示
description: 學習 Spring Boot 2.7.2
DocInfo 所有屬性都定義了預設值,在 application.yml 可以覆蓋預設值,如上面的 title
和 description
屬性。重啟服務檢視執行效果:
在之前 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)學習結束,期待關注留言分享~~