Springfox与SpringDoc——swagger如何选择(SpringDoc入门)
Posted 天翼云开发者社区
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Springfox与SpringDoc——swagger如何选择(SpringDoc入门)相关的知识,希望对你有一定的参考价值。
本文分享自天翼云开发者社区@《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 = "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配置中增加对应内容,如下:
@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,毕竟可以获得更加清晰明了的显示界面与参数解释。
花了大半天,写了个springdoc/springfox/swagger文档转word的工具
背景
领导让我们把项目中的上百个接口用word写一个接口文档,每个项目都用了swagger,只是有的是swagger2.0的有的是swagger3.0的。
说给我1天的时间,给我分了几十个,让手动写一天。我很好奇说这个不能直接把swagger地址丢给对方吗,或者用公司统一的接口管理平台EOLINKER,领导表示不行,说调用方是外部用户直接给swagger行不通,eolinker的话也没有时间录入了,现在只能手写。
没办法后就只能手写了。我写了几个后表示这波操作确实难受。
难受的原因主要是:首先没有啥技术含量,且浪费大量的时间,让找个小学初中生都可以搞定这事。操作过程就是ctrl+c/v,再调调样式,写完后再一个一个地核对,几百个接口时还要好几个同事一起来干这事着实感觉没必要。
开源项目swagger2word
想到既然项目中都用到了swagger,何不直接解析swagger的api的json文件就可以了。想到这里就在网上搜了下,果真找到了一个开源项目:swagger2word。
clone下来这个项目后觉得原作者确实牛逼,想法很不错也进行了对应的实现。不过拿过来用时感觉还是有2个小主要问题:
- 不支持3.0版本的api参数解析
- 缺少仅筛选某个项目中部分接口文档的功能
网上也有一个支持3.0版本的,不过没有开源,仅有在线版。地址为:swagger转word文档。
正因为没有源码,如果需要对word界面进行调整就不方便了。大致看了下原项目的源码也还算不复杂,所以我决定自己做一版并进行开源,支持3.0版本api格式的同时还支持excel导入的方式仅导出指定的api。
扩展版开源项目swagger2word
用了一下午的时间完成了上面2个功能开发,并支持了docker独立部署功能。在这里给大家分享一下:
源码地址为:
https://github.com/puhaiyang/swagger2word
主要用了策略模式将3.0的解析代码进行了一个处理,如果后续有其他版本的swagger接口协议又需要解析时,就可以直接在上面扩展了。
excel导入方式预览:
项目对应的swagger界面预览:
excel方式的导出结果预览:
导出word文档预览
详细使用说明见源码README文件
源码地址为:
https://github.com/puhaiyang/swagger2word
以上是关于Springfox与SpringDoc——swagger如何选择(SpringDoc入门)的主要内容,如果未能解决你的问题,请参考以下文章
从 Springfox Swagger 2 迁移到 Springdoc Open API
花了大半天,写了个springdoc/springfox/swagger文档转word的工具
花了大半天,写了个springdoc/springfox/swagger文档转word的工具