一、前言
OpenAPI 简介
OpenAPI规范是一个由社区驱动的开放规范,这个规范定义了一个标准的、语言无关的 RESTful API 接口规范,它可以同时允许开发人员和操作系统查看并理解某个服务的功能,而无需访问源代码,文档或网络流量检查。合理地定义OpenAPI规范,能使开发者更好的理解远程服务和与之交互。目前最新的规范是OpenAPI Specification 3.0.3。
Swagger简介
Swagger是一个规范和完整的框架,用于生成 、描述、调用和可视化 RESTful 风格的 Web 服务,简化了开发者开发API。
Springfox简介
SpringFox是 Spring 社区非官方维护的一个项目,主要就是帮助使用者将Swagger集成到Spring 中,在停更了2年多之后,近期终于推出了3.0.0版本,支持了开发者期盼已久的OpenApi 3规范。
二、Springfox 3.0.0 在Spring Boot 项目下的用法
导入依赖(Gradle)
implementation('io.springfox:springfox-boot-starter:3.0.0')
开启注解
@EnableOpenApi
编写配置类
/** * @author Vieruodis * @description Springfox openApi 3.0 的配置类 * @date 2020/7/15 21:43 */ @Configuration @EnableOpenApi public class SpringfoxOpenApiConfig { @Bean public Docket createRestApi() { return new Docket(DocumentationType.OAS_30) .pathMapping("/") // 定义是否开启swagger,false为关闭,可以通过变量控制 .enable(true) // 将api的元信息设置为包含在json ResourceListing响应中。 .apiInfo(apiInfo()) // 选择哪些接口作为swagger的doc发布 .select() .apis(RequestHandlerSelectors.basePackage("com.vieruodis.vmall.manage.product.controller")) .paths(PathSelectors.any()) .build() .groupName("defaultBB") // 支持的通讯协议集合 .protocols(Set.of("https", "http")) ; } /** * API 页面上半部分展示信息 */ private ApiInfo apiInfo() { return new ApiInfoBuilder() .version("1.0.0") .title("Vieruodis Api Doc") .description("Vmall shop application") .contact(new Contact("Vieruodis", null, "huaiyuan0660@outlook.com")) .termsOfServiceUrl("http://localhost:88/") .license("Apache 2.0") .licenseUrl("https://www.apache.org/licenses/LICENSE-2.0.html") .build(); } }
编写Controller的注解
@Api(value = "AlbumController") public interface AlbumControllerApi { /** * Album 多条件分页查询 * * @param page 页码 * @param size 每页数 * @param album 查询条件 * @return 封装了 【Album 分页数据】 的响应结果 */ @ApiOperation(value = "Album条件分页查询",notes = "分页条件查询Album方法详情",tags = {"AlbumController"}) @ApiImplicitParams({ @ApiImplicitParam(paramType = "path", name = "page", value = "当前页", required = true, dataType = "Integer"), @ApiImplicitParam(paramType = "path", name = "size", value = "每页显示条数", required = true, dataType = "Integer") }) PageResponseResult<Album> findPage(int page, int size, @ApiParam(name = "Album对象",value = "传入JSON数据") Album album); /** * Album 分页查询 * * @param page 页码 * @param size 每页数 * @return 封装了 【Album 分页数据】 的响应结果 */ @ApiOperation(value = "Album分页查询",notes = "分页查询Album方法详情",tags = {"AlbumController"}) @ApiImplicitParams({ @ApiImplicitParam(paramType = "path", name = "page", value = "当前页", required = true, dataType = "Integer"), @ApiImplicitParam(paramType = "path", name = "size", value = "每页显示条数", required = true, dataType = "Integer") }) PageResponseResult<Album> findPage(int page, int size); /** * Album 多条件查询 * * @param album 查询条件 * @return 封装了 【Album 数据集】 的响应结果 */ @ApiOperation(value = "Album条件查询",notes = "条件查询Album方法详情",tags = {"AlbumController"}) CommonResponseResult<List<Album>> findList(@ApiParam(name = "Album对象",value = "传入JSON数据") Album album); /** * 查询所有 Album * * @return 封装了 【Album 数据集】 的响应结果 */ @ApiOperation(value = "查询所有Album",notes = "查询所Album有方法详情",tags = {"AlbumController"}) CommonResponseResult<List<Album>> findAll(); /** * 根据ID查询 Album * * @param id 实体ID * @return 封装了 【Album 数据】 的响应结果 */ @ApiOperation(value = "Album根据ID查询",notes = "根据ID查询Album方法详情",tags = {"AlbumController"}) @ApiImplicitParam(paramType = "path", name = "id", value = "主键ID", required = true, dataType = "Long") CommonResponseResult<Album> findById(Long id); /** * 新增 Album * * @param album 新增实体 * @return 通用响应结果 */ @ApiOperation(value = "Album添加",notes = "添加Album方法详情",tags = {"AlbumController"}) ResponseResult add(@ApiParam(name = "Album对象",value = "传入JSON数据",required = true) Album album); /** * 修改 Album * * @param id 实体ID * @param album Album 数据 * @return 通用响应结果 */ @ApiOperation(value = "Album根据ID修改",notes = "根据ID修改Album方法详情",tags = {"AlbumController"}) @ApiImplicitParam(paramType = "path", name = "id", value = "主键ID", required = true, dataType = "Long") ResponseResult update(Long id, @ApiParam(name = "Album对象",value = "传入JSON数据") Album album); /** * 删除 Album * * @param id 实体ID * @return 通用响应结果 */ @ApiOperation(value = "Album根据ID删除",notes = "根据ID删除Album方法详情",tags = {"AlbumController"}) @ApiImplicitParam(paramType = "path", name = "id", value = "主键ID", required = true, dataType = "Long") ResponseResult delete(Long id); }
编写实体的注解
@Data @ApiModel(description = "Album",value = "Album") @Entity @Table(name="tb_album") public class Album implements Serializable { /** * 编号 */ @ApiModelProperty(value = "编号") @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Long id; /** * 相册名称 */ @ApiModelProperty(value = "相册名称") @Column(name = "title") private String title; /** * 相册封面 */ @ApiModelProperty(value = "相册封面") @Column(name = "image") private String image; /** * 图片列表 */ @ApiModelProperty(value = "图片列表") @Column(name = "image_items") private String imageItems; }
测试
打开: http://localhost:8000/swagger-ui/index.html
三、与Spring Cloud Gateway集成
整合思路
1,定义一个SwaggerProvider类用于填充右上角下拉框的数据,格式是(name,oas3Url)
2,定义过滤器,对oas3Url进行修正,让其能够获取文档
实践
导入依赖(Gradle):
implementation('io.springfox:springfox-boot-starter:3.0.0')
定义SwaggerProvider类填充右上角下拉框数据
@ConfigurationProperties(prefix = "open-api.swagger") @EnableConfigurationProperties @Component @Primary public class SwaggerProvider implements SwaggerResourcesProvider { @Setter @Getter private List<String> resources; public static final String API_URI = "/v3/api-docs"; @Override public List<SwaggerResource> get() { List<SwaggerResource> swaggerResources = new ArrayList<>(); resources.forEach(s -> { //name,oas3Url,group String[] split = s.split(","); swaggerResources.add(resource(split[0], split[1] + API_URI, split[2])); }); return swaggerResources; } /** * @param name 显示的名字 * @param oas3Url 文档格式:/manage/product/v3/api-docs(?group=...) */ private SwaggerResource resource( String name, String oas3Url, String swaggerGroup) { SwaggerResource swaggerResource = new SwaggerResource(); swaggerResource.setName(name); //url: 要看其他微服务是否设置了group 设置了的话 if (Docket.DEFAULT_GROUP_NAME.equals(swaggerGroup)) { swaggerResource.setUrl(oas3Url); } else { //其他微服务是否设置了group 需要加上"?group=" + 组名 swaggerResource.setUrl(oas3Url + "?group=" + swaggerGroup); } System.out.println("name: " + name + " url: " + oas3Url); swaggerResource.setSwaggerVersion("3.0.3"); return swaggerResource; } }
填充的数据来源于配置文件:
open-api:
swagger:
# 仅供 springfox 使用
resources: # name,oas3Url,group
- manage-product-service-1,/manage/product,defaultBB
- manage-product-service-2,/manage/product,manage-product
- file-service,/file,default
上面我给它填充的url是:localhost:端口号/【manage/product】/v3/api-docs(?group= ) 形式的,目的是为了让它找到微服务,因为我的路由设置是:
spring:
cloud:
gateway:
routes:
- id: vmall-manage-product-service
uri: lb://vmall-manage-product
predicates:
- Path=/manage/product/**
但是Springfox获取文档的地址格式是:localhost:端口号/v3/api-docs(?group= ),所以我们需要定义一个过滤器,让它能够获取到文档:
@Component public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory { @Override public GatewayFilter apply(Object config) { return (exchange, chain) -> { ServerHttpRequest request = exchange.getRequest(); String path = request.getURI().getPath(); System.out.println("path:"+path); if (!StringUtils.endsWithIgnoreCase(path, SwaggerProvider.API_URI)) { return chain.filter(exchange); } ServerHttpRequest newRequest = request.mutate().path(SwaggerProvider.API_URI).build(); ServerWebExchange newWebExchange = exchange.mutate().request(newRequest).build(); return chain.filter(newWebExchange); }; } }
给路由配上过滤器:
spring:
cloud:
gateway:
routes:
- id: vmall-manage-product-service
uri: lb://vmall-manage-product
predicates:
- Path=/manage/product/**
filters:
- SwaggerHeaderFilter
测试:
打开:http://localhost:88/swagger-ui/index.html
测试调用文档的方法,请求的是 localhost:88/manage/product/album , 这样通过网关就能找到对应的微服务,并且正确返回json数据了。
四、总结
Springfox3.0.0中支持了webflux,所以就不需要再写swaggerHandler解决请求映射了,相比SpringDoc openApi整合Spring Cloud Gateway 比较简单,个人比较推荐用来整合Spring Cloud Gateway。