在微服务架构下,通常每个微服务都会使用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}
总结
-
相对zuul的实现,核心逻辑都是一样,获取到配置路由信息,重写swaggerresource
-
gateway的配置稍微麻烦,资源的提供handler,swagger url 重写的细节
-
源码获取:最新Spring Cloud 技术栈,基于Spring Cloud Finchley.RELEASE、oAuth2 实现的权限系统