Spring Cloud Gateway 接口文档聚合实现

​ 在微服务架构下,通常每个微服务都会使用Swagger来管理我们的接口文档,当微服务越来越多,接口查找管理无形中要浪费我们不少时间,毕竟懒是程序员的美德。

​ 由于swagger2暂时不支持webflux 走了很多坑,完成这个效果感谢 @dreamlu @世言。

文档聚合效果

通过访问网关的 host:port/swagger-ui.html,即可实现: pig聚合文档效果预览传送门

通过右上角的Select a spec 选择服务模块来查看swagger文档

Pig的Zuul 核心实现

获取到zuul配置的路由信息,主要到SwaggerResource

1/** 2* 参考jhipster 3* GatewaySwaggerResourcesProvider 4*/ 5@Component 6@Primary 7public class RegistrySwaggerResourcesProvider implements SwaggerResourcesProvider { 8 private final RouteLocator routeLocator; 9 public RegistrySwaggerResourcesProvider(RouteLocator routeLocator) { 10 this.routeLocator = routeLocator; 11 } 12 13 @Override 14 public List<SwaggerResource> get() { 15 List<SwaggerResource> resources = new ArrayList<>(); 16 List<Route> routes = routeLocator.getRoutes(); 17 routes.forEach(route -> { 18 //授权不维护到swagger 19 if (!StringUtils.contains(route.getId(), ServiceNameConstant.AUTH_SERVICE)){ 20 resources.add(swaggerResource(route.getId(), route.getFullPath().replace("**", "v2/api-docs"))); 21 } 22 }); 23 return resources; 24 } 25 26 private SwaggerResource swaggerResource(String name, String location) { 27 SwaggerResource swaggerResource = new SwaggerResource(); 28 swaggerResource.setName(name); 29 swaggerResource.setLocation(location); 30 swaggerResource.setSwaggerVersion("2.0"); 31 return swaggerResource; 32 } 33}

PigX的Spring Cloud Gateway 实现

注入路由到SwaggerResource

1@Component 2@Primary 3@AllArgsConstructor 4public class SwaggerProvider implements SwaggerResourcesProvider { 5 public static final String API_URI = "/v2/api-docs"; 6 private final RouteLocator routeLocator; 7 private final GatewayProperties gatewayProperties; 8 9 10 @Override 11 public List<SwaggerResource> get() { 12 List<SwaggerResource> resources = new ArrayList<>(); 13 List<String> routes = new ArrayList<>(); 14 routeLocator.getRoutes().subscribe(route -> routes.add(route.getId())); 15 gatewayProperties.getRoutes().stream().filter(routeDefinition -> routes.contains(routeDefinition.getId())) 16 .forEach(routeDefinition -> routeDefinition.getPredicates().stream() 17 .filter(predicateDefinition -> "Path".equalsIgnoreCase(predicateDefinition.getName())) 18 .filter(predicateDefinition -> !"pigx-auth".equalsIgnoreCase(routeDefinition.getId())) 19 .forEach(predicateDefinition -> resources.add(swaggerResource(routeDefinition.getId(), 20 predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0") 21 .replace("/**", API_URI))))); 22 return resources; 23 } 24 25 private SwaggerResource swaggerResource(String name, String location) { 26 SwaggerResource swaggerResource = new SwaggerResource(); 27 swaggerResource.setName(name); 28 swaggerResource.setLocation(location); 29 swaggerResource.setSwaggerVersion("2.0"); 30 return swaggerResource; 31 } 32} 33

提供swagger 对外接口配置

1@Slf4j 2@Configuration 3@AllArgsConstructor 4public class RouterFunctionConfiguration { 5 private final SwaggerResourceHandler swaggerResourceHandler; 6 private final SwaggerSecurityHandler swaggerSecurityHandler; 7 private final SwaggerUiHandler swaggerUiHandler; 8 9 @Bean 10 public RouterFunction routerFunction() { 11 return RouterFunctions.route( 12 .andRoute(RequestPredicates.GET("/swagger-resources") 13 .and(RequestPredicates.accept(MediaType.ALL)), swaggerResourceHandler) 14 .andRoute(RequestPredicates.GET("/swagger-resources/configuration/ui") 15 .and(RequestPredicates.accept(MediaType.ALL)), swaggerUiHandler) 16 .andRoute(RequestPredicates.GET("/swagger-resources/configuration/security") 17 .and(RequestPredicates.accept(MediaType.ALL)), swaggerSecurityHandler); 18 19 } 20}

业务handler 的实现

1 @Override 2 public Mono<ServerResponse> handle(ServerRequest request) { 3 return ServerResponse.status(HttpStatus.OK) 4 .contentType(MediaType.APPLICATION_JSON_UTF8) 5 .body(BodyInserters.fromObject(swaggerResources.get())); 6 } 7 8 @Override 9 public Mono<ServerResponse> handle(ServerRequest request) { 10 return ServerResponse.status(HttpStatus.OK) 11 .contentType(MediaType.APPLICATION_JSON_UTF8) 12 .body(BodyInserters.fromObject( 13 Optional.ofNullable(securityConfiguration) 14 .orElse(SecurityConfigurationBuilder.builder().build()))); 15 } 16 17 @Override 18 public Mono<ServerResponse> handle(ServerRequest request) { 19 return ServerResponse.status(HttpStatus.OK) 20 .contentType(MediaType.APPLICATION_JSON_UTF8) 21 .body(BodyInserters.fromObject( 22 Optional.ofNullable(uiConfiguration) 23 .orElse(UiConfigurationBuilder.builder().build()))); 24 }

swagger路径转换

通过以上配置,可以实现文档的参考和展示了,但是使用swagger 的 try it out 功能发现路径是路由切割后的路径比如:

swagger 文档中的路径为: 主机名:端口:映射路径 少了一个 服务路由前缀,是因为展示handler 经过了 StripPrefixGatewayFilterFactory 这个过滤器的处理,原有的 路由前缀被过滤掉了!

方案1,通过swagger 的host 配置手动维护一个前缀

1return new Docket(DocumentationType.SWAGGER_2) 2 .apiInfo(apiInfo()) 3 .host("主机名:端口:服务前缀") //注意这里的主机名:端口是网关的地址和端口 4 .select() 5 .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) 6 .paths(PathSelectors.any()) 7 .build() 8 .globalOperationParameters(parameterList);

方案2,增加X-Forwarded-Prefix

swagger 在拼装URL 数据时候,会增加X-Forwarder-Prefix 请求头里面的信息为前缀


通过如上分析,知道应该在哪里下手了吧,在 网关上追加一个请求头即可

1@Component 2public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory { 3 private static final String HEADER_NAME = "X-Forwarded-Prefix"; 4 5 @Override 6 public GatewayFilter apply(Object config) { 7 return (exchange, chain) -> { 8 ServerHttpRequest request = exchange.getRequest(); 9 String path = request.getURI().getPath(); 10 if (!StringUtils.endsWithIgnoreCase(path, SwaggerProvider.API_URI)) { 11 return chain.filter(exchange); 12 } 13 14 String basePath = path.substring(0, path.lastIndexOf(SwaggerProvider.API_URI)); 15 16 17 ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build(); 18 ServerWebExchange newExchange = exchange.mutate().request(newRequest).build(); 19 return chain.filter(newExchange); 20 }; 21 } 22}

总结

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )