Springfox與SpringDoc——swagger如何選擇(SpringDoc入門)

天翼雲開發者社群發表於2023-04-07

本文分享自天翼雲開發者社群 @《Springfox與SpringDoc——swagger如何選擇(SpringDoc入門)》,作者: 才開始學技術的小白

連結:

https://www.ctyun.cn/developer/article/381687816880197?track=|cp:cz_bk|tgdy:wenzhang|ttjh:bokeshequ|key:bw308|pf:PC

0.引言

之前寫過一篇關於 swagger(實際上是springfox)的使用指南(https://www.ctyun.cn/developer/article/371704742199365),涵蓋了本人在開發與學習的時候碰到的各種大坑。但由於springfox已經不更新了,很多專案都在往springdoc遷移

筆者也是花了一些時間試了一下這個號稱 “把springfox按在地下摩擦”的springdoc究竟好不好使,本文就來簡單介紹下springdoc的使用以及優劣勢

1.引入maven依賴

這裡有個大坑一定要注意!!!

如果你跟我一樣,現在使用的是 springfox,但是想往springdoc遷移,結果試了一下發現還是springfox好用/懶得改那麼多註解,還是想換回springfox, 一定要把 springdoc的maven依賴刪掉!!!不然springboot會預設你用的是springdoc,導致swagger介面出不來

< dependency >

             < groupId > org . springdoc </ groupId >

             < artifactId > springdoc - openapi - ui </ artifactId >

             < version > 1.6.11 </ version >

  </ dependency >

2.springdoc配置類

實際上 springdoc的配置非常簡單,使用的是OpenAPI類與GroupedOpenApi來配置

/**

  * SpringDoc API文件相關配置

  * Created by macro on 2023/02/02.

  */ @Configuration public   class   SpringDocConfig   {

     @Bean

     public   OpenAPI   mallTinyOpenAPI ()   {

         return   new   OpenAPI ()

                 . info ( new   Info (). title ( "CTYUN API" )

                         . description ( "SpringDoc API 演示" )

                         . version ( "v1.0.0" )

                         . license ( new   License (). name ( "Apache 2.0" ). url ( " )))

                 . externalDocs ( new   ExternalDocumentation ()

                         . description ( "SpringBoot專案" )

                         . url ( " ));

     }

 

     @Bean

     public   GroupedOpenApi   adminApi ()   {

         return   GroupedOpenApi . builder ()

                 . group ( "admin" )

                 . pathsToMatch ( "/**" )

                 . build ();

     }

     //可以建立不同的GroupedOpenApi來判斷不同的controller

     @Bean

     public   GroupedOpenApi   userApi ()   {

         return   GroupedOpenApi . builder ()

                 . group ( "user" )

                 . pathsToMatch ( "/user/**" )

                 . build ();

     }}

3.啟動

預設配置之後直接進入: 即可

注意這裡與 springfox略有不同()

 

4.與SpringFox的註解對照

 

5.SpringDoc基本註解用法

這裡轉載一個寫的非常全面的示例介面,原文可以去看: https://blog.csdn.net/zhenghongcs/article/details/123812583

/**

  * 品牌管理Controller

  * Created by macro on 2019/4/19.

  */ @Tag( name =   "PmsBrandController" ,   description =   "商品品牌管理" )@Controller@RequestMapping( "/brand" ) public   class   PmsBrandController   {

     @Autowired

     private   PmsBrandService   brandService ;

 

     private   static   final   Logger   LOGGER   =   LoggerFactory . getLogger ( PmsBrandController . class );

 

     @Operation( summary =   "獲取所有品牌列表" , description =   "需要登入後訪問" )

     @RequestMapping( value =   "listAll" ,   method =   RequestMethod . GET )

     @ResponseBody

     public   CommonResult < List < PmsBrand >>   getBrandList ()   {

         return   CommonResult . success ( brandService . listAllBrand ());

     }

 

     @Operation( summary =   "新增品牌" )

     @RequestMapping( value =   "/create" ,   method =   RequestMethod . POST )

     @ResponseBody

     @PreAuthorize( "hasRole('ADMIN')" )

     public   CommonResult   createBrand (@RequestBody   PmsBrand   pmsBrand )   {

         CommonResult   commonResult ;

         int   count =   brandService . createBrand ( pmsBrand );

         if   ( count ==   1 )   {

             commonResult =   CommonResult . success ( pmsBrand );

             LOGGER . debug ( "createBrand success:{}" ,   pmsBrand );

         }   else   {

             commonResult =   CommonResult . failed ( "操作失敗" );

             LOGGER . debug ( "createBrand failed:{}" ,   pmsBrand );

         }

         return   commonResult ;

     }

 

     @Operation( summary =   "更新指定id品牌資訊" )

     @RequestMapping( value =   "/update/{id}" ,   method =   RequestMethod . POST )

     @ResponseBody

     @PreAuthorize( "hasRole('ADMIN')" )

     public   CommonResult   updateBrand (@PathVariable( "id" )   Long   id ,   @RequestBody   PmsBrand   pmsBrandDto ,   BindingResult   result )   {

         CommonResult   commonResult ;

         int   count =   brandService . updateBrand ( id ,   pmsBrandDto );

         if   ( count ==   1 )   {

             commonResult =   CommonResult . success ( pmsBrandDto );

             LOGGER . debug ( "updateBrand success:{}" ,   pmsBrandDto );

         }   else   {

             commonResult =   CommonResult . failed ( "操作失敗" );

             LOGGER . debug ( "updateBrand failed:{}" ,   pmsBrandDto );

         }

         return   commonResult ;

     }

 

     @Operation( summary =   "刪除指定id的品牌" )

     @RequestMapping( value =   "/delete/{id}" ,   method =   RequestMethod . GET )

     @ResponseBody

     @PreAuthorize( "hasRole('ADMIN')" )

     public   CommonResult   deleteBrand (@PathVariable( "id" )   Long   id )   {

         int   count =   brandService . deleteBrand ( id );

         if   ( count ==   1 )   {

             LOGGER . debug ( "deleteBrand success :id={}" ,   id );

             return   CommonResult . success ( null );

         }   else   {

             LOGGER . debug ( "deleteBrand failed :id={}" ,   id );

             return   CommonResult . failed ( "操作失敗" );

         }

     }

 

     @Operation( summary =   "分頁查詢品牌列表" )

     @RequestMapping( value =   "/list" ,   method =   RequestMethod . GET )

     @ResponseBody

     @PreAuthorize( "hasRole('ADMIN')" )

     public   CommonResult < CommonPage < PmsBrand >>   listBrand (@RequestParam( value =   "pageNum" ,   defaultValue =   "1" )

                                                         @Parameter( description =   "頁碼" )   Integer   pageNum ,

                                                         @RequestParam( value =   "pageSize" ,   defaultValue =   "3" )

                                                         @Parameter( description =   "每頁數量" )   Integer   pageSize )   {

         List < PmsBrand >   brandList =   brandService . listBrand ( pageNum ,   pageSize );

         return   CommonResult . success ( CommonPage . restPage ( brandList ));

     }

 

     @Operation( summary =   "獲取指定id的品牌詳情" )

     @RequestMapping( value =   "/{id}" ,   method =   RequestMethod . GET )

     @ResponseBody

     @PreAuthorize( "hasRole('ADMIN')" )

     public   CommonResult < PmsBrand >   brand (@PathVariable( "id" )   Long   id )   {

         return   CommonResult . success ( brandService . getBrand ( id ));

     }}

6.與SpringSecurity的結合

如果專案中使用了 SpringSecurity,需要做兩個配置來讓springdoc正常使用:

1.在SpringSecurity配置類中放行白名單:"/v3/api-docs/**", "/swagger-ui/**"

2.在SpringDoc配置中增加對應內容,如下:

@Configuration public   class   SpringDocConfig   {

 

     private   static   final   String   SECURITY_SCHEME_NAME   =   "BearerAuth" ;

 

     @Bean

     public   OpenAPI   managerOpenAPI ()   {

 

         return   new   OpenAPI ()

                 . info ( new   Info (). title ( "Galaxy-Cluster-Manager後端介面文件" )

                         . description ( "提供給前端介面(portal)的介面文件" )

                         . version ( "v1.0.0" )

                         . license ( new   License (). name ( "galaxy 1.2.0" ). url ( " )))

                 . externalDocs ( new   ExternalDocumentation ()

                         . description ( "彈性高效能運算(CTHPC)" )

                         . url ( " ))

                 //以下是針對SpringSecurity的設定,同時還有設定白名單

                 . addSecurityItem ( new   SecurityRequirement (). addList ( SECURITY_SCHEME_NAME ))

                 . components ( new   Components ()

                         . addSecuritySchemes ( SECURITY_SCHEME_NAME ,

                                 new   SecurityScheme ()

                                         . name ( SECURITY_SCHEME_NAME )

                                         . type ( SecurityScheme . Type . HTTP )

                                         . scheme ( "bearer" )

                                         . bearerFormat ( "JWT" )));

     }

     @Bean

     public   GroupedOpenApi   publicApi ()   {

         return   GroupedOpenApi . builder ()

                 . group ( "portal" )

                 . pathsToMatch ( "/api/**" )

                 . build ();

     }

}

7.SpringDoc使用物件作為Query引數的問題

實際上 springfox也會有這個問題,使用物件作為query傳參的時候,頁面通常是這樣的:

 

就沒有辦法逐個描述引數,也不能逐個除錯(只能用 json除錯),非常的麻煩;springdoc有一個解決這個問題非常方便的註解:@ParameterObject

比如某一個控制器的入參是 User user,我們只需要在這前面加上註解變為:@ParameterObject User user 即可,結果如下;

 

這裡也有一個大坑:引數的型別可能會不正確,比如這裡我們的 id引數實際上是int,但顯示出來是string,這個時候就需要我們在User類的屬性中加上對應的註解 ,比如:

@Parameter(description = "id傳參",example = "6")

再重啟 UI就會發現引數型別正確了

8.SpringDoc配置掃包範圍

有的時候僅僅使用 @Hidden並不能滿足我們的需要,因為可能需要配置不同group的controller類,這個時候就需要在配置類中取設定掃包範圍程式碼如下:

 

9.SpringDoc的優劣勢

優勢: SpringDoc有著非常好看的UI,以及比Springfox更加完善的引數註解體系,看起來非常舒服,並且還在不斷更新與維護中

劣勢:一些冷門功能還不完善,比如:

a.有十個介面,他們的url是一樣的但是可以透過query引數來分別(如:@PostMapping(params = "action=QueryUsers"))這個時候springdoc只能透過掃包範圍配置,來寫多個GroupOpenApi來解決,非常的麻煩;springfox可以在docket建立的時候使用:docket.enableUrlTemplating(true); 這個方法即可解決

b.springdoc的網路配置可能會與springfox衝突,如果遷移,需要逐個嘗試網路配置是否合適(主要是GsonHttpMessageConverter的配置)

c.相容性問題仍需要觀望,相對於springfox,springdoc的相容性並沒有那麼好,在許多時候可能會出現序列化的亂碼問題

 

總結:如果當前專案 /工程已經整合了完備的springfox,建議不要輕易嘗試遷移到springdoc,尤其是介面型別比較複雜、springfox配置docket比較多的專案;但如果是從頭開始的專案,由於介面相對比較簡單,可以採用springdoc,畢竟可以獲得更加清晰明瞭的顯示介面與引數解釋。

 


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70014251/viewspace-2944055/,如需轉載,請註明出處,否則將追究法律責任。

相關文章