本文分享自天翼云開發者社區@《Springfox與SpringDoc——swagger如何選擇(SpringDoc入門)》,作者: 才開始學技術的小白
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.
*/@Configurationpublic 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("https://github.com/")))
.externalDocs(new ExternalDocumentation()
.description("SpringBoot專案")
.url("http://www.ctyun.com"));
}
@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.啟動
默認配置之后直接進入:http://localhost:8080/swagger-ui/index.html 即可
注意這里與springfox略有不同(http://localhost:8080/swagger-ui.html)
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 = "https://www.cnblogs.com/developer-tianyiyun/archive/2023/04/07/listAll", method = RequestMethod.GET)
@ResponseBody
public CommonResult<List<PmsBrand>> getBrandList() {
return CommonResult.success(brandService.listAllBrand());
}
@Operation(summary = "添加品牌")
@RequestMapping(value = "https://www.cnblogs.com/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 = "https://www.cnblogs.com/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 = "https://www.cnblogs.com/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 = "https://www.cnblogs.com/list", method = RequestMethod.GET)
@ResponseBody
@PreAuthorize("hasRole('ADMIN')")
public CommonResult<CommonPage<PmsBrand>> listBrand(@RequestParam(value = "https://www.cnblogs.com/developer-tianyiyun/archive/2023/04/07/pageNum", defaultValue = "https://www.cnblogs.com/developer-tianyiyun/archive/2023/04/07/1")
@Parameter(description = "頁碼") Integer pageNum,
@RequestParam(value = "https://www.cnblogs.com/developer-tianyiyun/archive/2023/04/07/pageSize", defaultValue = "https://www.cnblogs.com/developer-tianyiyun/archive/2023/04/07/3")
@Parameter(description = "每頁數量") Integer pageSize) {
List<PmsBrand> brandList = brandService.listBrand(pageNum, pageSize);
return CommonResult.success(CommonPage.restPage(brandList));
}
@Operation(summary = "獲取指定id的品牌詳情")
@RequestMapping(value = "https://www.cnblogs.com/{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配置中增加對應內容,如下:
@Configurationpublic 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("https://gitlab.ctyun.cn/hpc/galaxy-parent/-/tree/v1.2.0")))
.externalDocs(new ExternalDocumentation()
.description("彈性高性能計算(CTHPC)")
.url("http://www.ctyun.cn"))
//以下是針對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,畢竟可以獲得更加清晰明了的顯示界面與引數解釋,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/549393.html
標籤:其他
