大家好,我是飄渺。

SpringBoot老鳥系列的文章已經寫了兩篇,每篇的閱讀反響都還不錯,果然大家還是對SpringBoot比較感興趣。那今天我們就帶來老鳥系列的第三篇:整合Swagger介面文件以及Swagger的高階功能。 文章涉及到的程式碼已經上傳到了github,希望最終能應用在你們實際專案上,當然如果有其他需要我新增到內容也可以直接留言告訴我,我會視情況給你們加上去的。

好了,閒話少敘,讓我們先來看看為什麼要用Swagger?

為什麼要用Swagger ?

作為一名程式設計師,我們最討厭兩件事:1. 別人不寫註釋。2. 自己寫註釋。

而作為一名介面開發者,我們同樣討厭兩件事:1. 別人不寫介面文件,文件不及時更新。2. 需要自己寫介面文件,還需要及時更新。

相信無論是前端還是後端開發,都或多或少地被介面文件折磨過。前端經常抱怨後端給的介面文件與實際情況不一致。後端又覺得編寫及維護介面文件會耗費不少精力,經常來不及更新。

而隨著Springboot、Springcloud等微服務的流行,每個專案都有成百上千個介面呼叫,這時候再要求人工編寫介面文件並且保證文件的實時更新幾乎是一件不可能完成的事,所以這時候我們迫切需要一個工具,一個能幫我們自動化生成介面文件以及自動更新文件的工具。它就是Swagger。

Swagger 提供了一個全新的維護 API 文件的方式,有4大優點:

  1. 自動生成文件:只需要少量的註解,Swagger 就可以根據程式碼自動生成 API 文件,很好的保證了文件的時效性。
  2. 跨語言性,支援 40 多種語言。
  3. Swagger UI 呈現出來的是一份可互動式的 API 文件,我們可以直接在文件頁面嘗試 API 的呼叫,省去了準備複雜的呼叫引數的過程。
  4. 還可以將文件規範匯入相關的工具(例如 SoapUI), 這些工具將會為我們自動地建立自動化測試。

現在我們知道了Swagger的作用,接下來將其整合到我們專案中。

Swagger整合

整合Swagger很簡單,只需要簡單三步。

第一步: 引入依賴包

<!--swagger-->
<dependency>
<groupid>io.springfox</groupid>
<artifactid>springfox-swagger2</artifactid>
<version>2.9.2</version>
</dependency> <!--swagger-ui-->
<dependency>
<groupid>io.springfox</groupid>
<artifactid>springfox-swagger-ui</artifactid>
<version>2.9.2</version>
</dependency>

第二步:修改配置檔案

  1. application.properties 加入配置
# 用於控制是否開啟Swagger,生產環境記得關閉Swagger,將值設定為 false
springfox.swagger2.enabled = true

2.增加一個swagger配置類

@Configuration
@EnableSwagger2
@ConditionalOnClass(Docket.class)
public class SwaggerConfig { private static final String VERSION = "1.0"; @Value("${springfox.swagger2.enabled}")
private Boolean swaggerEnabled; @Bean
public Docket createRestApi(){
return new Docket(DocumentationType.SWAGGER_2)
.enable(swaggerEnabled)
.groupName("SwaggerDemo")
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
.paths(PathSelectors.any())
.build();
} /**
* 新增摘要資訊
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("介面文件")
.contact(new Contact("JAVA日知錄","http://javadaily.cn","[email protected]"))
.description("Swagger介面文件")
.version(VERSION)
.build();
} }

這裡通過 .apis(RequestHandlerSelectors.withClassAnnotation(Api.class))表面給加上 @Api註解的類自動生成介面文件。

第三步,配置API介面

@RestController
@Api(tags = "引數校驗")
@Slf4j
@Validated
public class ValidController { @PostMapping("/valid/test1")
@ApiOperation("RequestBody校驗")
public String test1(@Validated @RequestBody ValidVO validVO){
log.info("validEntity is {}", validVO);
return "test1 valid success";
} @ApiOperation("Form校驗")
@PostMapping(value = "/valid/test2")
public String test2(@Validated ValidVO validVO){
log.info("validEntity is {}", validVO);
return "test2 valid success";
} @ApiOperation("單引數校驗")
@PostMapping(value = "/valid/test3")
public String test3(@Email String email){
log.info("email is {}", email);
return "email valid success";
}
}

通過 @Api註解標註需要生成介面文件,通過 @ApiOperation註解標註介面名。

同時我們給 ValidVO也加上對應的註解

@Data
@ApiModel(value = "引數校驗類")
public class ValidVO { @ApiModelProperty("ID")
private String id; @ApiModelProperty(value = "應用ID",example = "cloud")
private String appId; @NotEmpty(message = "級別不能為空")
@ApiModelProperty(value = "級別")
private String level; @ApiModelProperty(value = "年齡")
private int age; ... }

通過 @ApiModel標註這是一個引數實體,通過 @ApiModelProperty標註欄位說明。

Unable to infer base url

簡單三步,我們專案就集成了Swagger介面文件,趕緊啟動服務,訪問 http://localhost:8080/swagger-ui.html體驗一下。

好吧,出了點小問題,不過不用慌。

出現這個問題的原因是因為我們加上了 ResponseBodyAdvice統一處理返回值/響應體,導致給Swagger的返回值也包裝了一層,UI頁面無法解析。可以通過 http://localhost:8080/v2/api-docs?group=SwaggerDemo觀察Swagger返回的json資料。

既然知道了問題原因那就很好解決了,我們只需要在ResponseBodyAdvice處理類中只轉換我們自己專案的介面即可。

@RestControllerAdvice(basePackages = "com.jianzh5.blog")
@Slf4j
public class ResponseAdvice implements ResponseBodyAdvice<object> {
...
}

通過新增basePackage屬性限定統一返回值的範圍,這樣就不影響Swagger了。

重啟伺服器再次訪問swagger介面地址,就可以看到介面文件頁面了。

For input string: ""

Swagger2.9.2有個bug,就是當我們引數實體有int型別的引數時,開啟Swagger介面頁面時後端會一直提示異常:

java.lang.NumberFormatException: For input string: ""
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.base/java.lang.Long.parseLong(Long.java:702)
at java.base/java.lang.Long.valueOf(Long.java:1144)

有兩種解決方案:

  1. 給int型別的欄位使用@ApiModelPorperty註解時新增example屬性
@ApiModelProperty(value = "年齡",example = "10")
private int age;
  1. 去除原swagger中的swagger-modelsswagger-annotations,自行引入高版本的annotations和models
<dependency>
<groupid>io.springfox</groupid>
<artifactid>springfox-swagger2</artifactid>
<version>2.9.2</version>
<exclusions>
<exclusion>
<groupid>io.swagger</groupid>
<artifactid>swagger-annotations</artifactid>
</exclusion>
<exclusion>
<groupid>io.swagger</groupid>
<artifactid>swagger-models</artifactid>
</exclusion>
</exclusions>
</dependency> <dependency>
<groupid>io.swagger</groupid>
<artifactid>swagger-annotations</artifactid>
<version>1.5.22</version>
</dependency>
<dependency>
<groupid>io.swagger</groupid>
<artifactid>swagger-models</artifactid>
<version>1.5.22</version>
</dependency>

整合Swagger過程中雖然會出現兩個小問題,解決後我們就可以愉快享受Swagger給我們帶來的便利了。

Swagger美化

Swagger原生UI有點醜,我們可以藉助Swagger的增強工具 knife4j優化一下。

第一步: 引入依賴包

 <!--整合Knife4j-->
<dependency>
<groupid>com.github.xiaoymin</groupid>
<artifactid>knife4j-spring-boot-starter</artifactid>
<version>2.0.4</version>
</dependency>

由於knife4j中已經帶了 swagger-annotationsswagger-models的依賴,所以我們可以把上文中手動新增的兩個依賴刪除。

第二步:啟用knife4j增強

@Configuration
@EnableSwagger2
@ConditionalOnClass(Docket.class)
@EnableKnife4j
public class SwaggerConfig {
...
}

通過上面兩步我們就完成了Swagger的美化,通過瀏覽器訪問 http://localhost:8080/doc.html即可看到效果。

Swagger引數分組

看到這裡的同學心理肯定會想,就這?這就是老鳥的做法?跟我們新手也沒啥區別呀

別急,我們先來看一個效果。

首先我們定義了兩個介面,一個新增,一個編輯

@ApiOperation("新增")
@PostMapping(value = "/valid/add")
public String add(@Validated(value = {ValidGroup.Crud.Create.class}) ValidVO validVO){
log.info("validEntity is {}", validVO);
return "test3 valid success";
} @ApiOperation("更新")
@PostMapping(value = "/valid/update")
public String update(@Validated(value = ValidGroup.Crud.Update.class) ValidVO validVO){
log.info("validEntity is {}", validVO);
return "test4 valid success";
}

注意看,這裡用的是同一個實體 ValidVO來接收前端引數,只不過使用了引數校驗中的分組,然後我們開啟kife4j頁面觀察兩者的介面文件有何不同。

新增:

編輯:

通過上面可以看到,雖然用於接受引數的實體一樣,但是當分組不一樣時展示給前端的引數也不一樣,這就是Swagger的分組功能

當然原生的Swagger是不支援分組功能的,我們需要對Swagger進行擴充套件。我已經將程式碼上傳到了github上,由於程式碼量比較多這裡就不展示了,大家可以自行查閱。

引入擴充套件類後還需要在Swagger配置類 SwaggerConfig中注入對應的Bean。

@Configuration
@EnableSwagger2
@ConditionalOnClass(Docket.class)
@EnableKnife4j
public class SwaggerConfig {
... @Bean
@Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 1000)
public GroupOperationModelsProviderPlugin groupOperationModelsProviderPlugin() {
return new GroupOperationModelsProviderPlugin();
} @Bean
@Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 1000)
public GroupModelBuilderPlugin groupModelBuilderPlugin() {
return new GroupModelBuilderPlugin();
} @Bean
@Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 1000)
public GroupModelPropertyBuilderPlugin groupModelPropertyBuilderPlugin() {
return new GroupModelPropertyBuilderPlugin();
} @Bean
@Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 1000)
public GroupExpandedParameterBuilderPlugin groupExpandedParameterBuilderPlugin() {
return new GroupExpandedParameterBuilderPlugin();
} @Bean
@Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 1000)
public GroupOperationBuilderPlugin groupOperationBuilderPlugin() {
return new GroupOperationBuilderPlugin();
} @Bean
@Primary
@Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 1000)
public GroupModelAttributeParameterExpander groupModelAttributeParameterExpander(FieldProvider fields, AccessorsProvider accessors, EnumTypeDeterminer enumTypeDeterminer) {
return new GroupModelAttributeParameterExpander(fields, accessors, enumTypeDeterminer);
} }

分組使用說明

1.在bean物件的屬性裡配置如下注釋

@Null(groups = ValidGroup.Crud.Create.class)
@NotNull(groups = ValidGroup.Crud.Update.class,message = "應用ID不能為空")
@ApiModelProperty(value = "應用ID",example = "cloud")
private String appId;

當新增場景的時候,appId為空,不需要傳值; 當修改場景的時候,appId不能為空,需要傳值 ;其他沒有配置組的皆為預設組(Default)

2.在介面引數的時候加入組規則校驗

 @ApiOperation("新增")
@PostMapping(value = "/valid/add")
public String add(@Validated(value = {ValidGroup.Crud.Create.class}) ValidVO validVO){
log.info("validEntity is {}", validVO);
return "test3 valid success";
}

當前介面會針對預設組的bean屬性進行校驗,同時針對儲存常見的屬性進行校驗。

小結

Swagger整合相對來說還是很簡單的,雖然在整合過程中也出現了幾個小問題,不過也很容易就解決了。今天文章的重點內容是Swagger分組功能,跟之前的引數校驗文章一樣,很多同學遇到這種分組場景時往往會選擇建立多個實體類,雖然也能解決問題,只不過總是有點彆扭。

不過遺憾的是,本文中Swagger的分組擴充套件只支援Swagger2,至於新版本Swagger3就不怎麼支援了。如果有同學已經擴充套件好了,歡迎給我提pr呀。

最後,我是飄渺Jam,一名寫程式碼的架構師,做架構的程式設計師,期待您的轉發與關注,當然也可以新增我的個人微信 jianzh5,咱們一起聊技術!

老鳥系列文章github地址:https://github.com/jianzh5/cloud-blog/