Gateway整合Swagger


原文链接:https://blog.csdn.net/ttzommed/article/details/81103609

【源码中的Spring Boot版本为2.1.3,更新了一点小细节,主要是看思路吧】最近在学习SpringBoot2和Spring Cloud.Finchley版,网上资料也是少的可怜,大部分还是通过一些github或者码云上的一些开源框架来学习,途中出现的一些bug也只能自己看看源码尝试解决。最近使用Spring Cloud Gateway替换Zuul的时候发现Swagger并不支持以WebFlux为底层的Gateway,无法集成,运行报错。下面分享我愚钝的解决思路,和关键代码,若有改进之处,望大佬指点,详细代码可以下载源码查看。

贴上源码https://gitee.com/wxdfun/sw

首先是子项目Spring Boot项目正常集成Swagger。在业务项目Admin中添加Swagger依赖包(使用Eureka为注册中心,文章未展示多余部分)。

  1.  
  2.   io.springfox
  3.   springfox-swagger2
  4.   2.9.2
  5.  

添加测试Controller。

  1.   @RestController
  2.   @RequestMapping("/test")
  3.   @Api("测试")
  4.   public class TestController {
  5.    
  6.   @ApiOperation(value = "计算+", notes = "加法")
  7.   @ApiImplicitParams({
  8.   @ApiImplicitParam(name = "a", paramType = "path", value = "数字a", required = true, dataType = "Long"),
  9.   @ApiImplicitParam(name = "b", paramType = "path", value = "数字b", required = true, dataType = "Long")
  10.   })
  11.   @GetMapping("/{a}/{b}")
  12.   public Integer get(@PathVariable Integer a, @PathVariable Integer b) {
  13.   return a + b;
  14.   }
  15.   }

配置Swagger使API注解生效。

  1.   @Configuration
  2.   @EnableSwagger2
  3.   public class SwaggerConfig {
  4.   @Bean
  5.   public Docket createRestApi() {
  6.   return new Docket(DocumentationType.SWAGGER_2)
  7.   .apiInfo(apiInfo())
  8.   .select()
  9.   .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
  10.   .paths(PathSelectors.any())
  11.   .build();
  12.   }
  13.   private ApiInfo apiInfo() {
  14.   return new ApiInfoBuilder()
  15.   .title("Swagger API")
  16.   .description("test")
  17.   .termsOfServiceUrl("")
  18.   .contact(new Contact("wd", "", ""))
  19.   .version("2.0")
  20.   .build();
  21.   }
  22.   }

此时启动Admin项目后应该能正常访问admin-ip:admin:port/swagger-ui.html。下面是网关gateway部分。

建立网关项目gateway,添加核心依赖包

  1.  
  2.   org.springframework.cloud
  3.   spring-cloud-starter-gateway
  4.  
  5.  
  6.   org.springframework.boot
  7.   spring-boot-starter-data-redis-reactive
  8.  
  9.  
  10.   io.springfox
  11.   springfox-swagger-ui
  12.   2.9.2
  13.  
  14.  
  15.   io.springfox
  16.   springfox-swagger2
  17.   2.9.2
  18.  

添加gateway路由配置

  1.   server:
  2.   port: 3333
  3.    
  4.   spring:
  5.   application:
  6.   name: wd-gateway
  7.   cloud:
  8.   gateway:
  9.   locator:
  10.   enabled: true
  11.   routes:
  12.   - id: wd-admin
  13.   uri: lb://wd-admin
  14.   predicates:
  15.   - Path=/admin/**
  16.   filters:
  17.   - SwaggerHeaderFilter
  18.   - StripPrefix=1
  19.    
  20.   eureka:
  21.   instance:
  22.   prefer-ip-address: true
  23.   client:
  24.   service-url:
  25.   defaultZone: http://localhost:8060/eureka/
  26.    

filter配置StripPrefix=1的意思:把path的前1个路径截掉

因为Swagger暂不支持webflux项目,所以Gateway里不能配置SwaggerConfig,也就是说Gateway无法提供自身API。但我想一般也不会在网关项目代码里写业务API代码吧。。所以这里的集成只是基于基于WebMvc的微服务项目。

配置SwaggerProvider,获取Api-doc,即SwaggerResources。

  1.   @Component
  2.   @Primary
  3.   @AllArgsConstructor
  4.   public 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 get() {
  12.   List resources = new ArrayList<>();
  13.   List routes = new ArrayList<>();
  14.   //取出gateway的route
  15.   routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));
  16.   //结合配置的route-路径(Path),和route过滤,只获取有效的route节点
  17.   gatewayProperties.getRoutes().stream().filter(routeDefinition -> routes.contains(routeDefinition.getId()))
  18.   .forEach(routeDefinition -> routeDefinition.getPredicates().stream()
  19.   .filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))
  20.   .forEach(predicateDefinition -> resources.add(swaggerResource(routeDefinition.getId(),
  21.   predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
  22.   .replace("/**", API_URI)))));
  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.   }

因为Gateway里没有配置SwaggerConfig,而运行Swagger-ui又需要依赖一些接口,所以我的想法是自己建立相应的swagger-resource端点。

  1.   @RestController
  2.   @RequestMapping("/swagger-resources")
  3.   public class SwaggerHandler {
  4.   @Autowired(required = false)
  5.   private SecurityConfiguration securityConfiguration;
  6.   @Autowired(required = false)
  7.   private UiConfiguration uiConfiguration;
  8.   private final SwaggerResourcesProvider swaggerResources;
  9.    
  10.   @Autowired
  11.   public SwaggerHandler(SwaggerResourcesProvider swaggerResources) {
  12.   this.swaggerResources = swaggerResources;
  13.   }
  14.    
  15.    
  16.   @GetMapping("/configuration/security")
  17.   public Mono> securityConfiguration() {
  18.   return Mono.just(new ResponseEntity<>(
  19.   Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
  20.   }
  21.    
  22.   @GetMapping("/configuration/ui")
  23.   public Mono> uiConfiguration() {
  24.   return Mono.just(new ResponseEntity<>(
  25.   Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
  26.   }
  27.    
  28.   @GetMapping("")
  29.   public Mono swaggerResources() {
  30.   return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
  31.   }
  32.   }

【Spring Boot版本超过2.0.6的应该可以跳过这一步,最新源码也更新了。Spring修复了bug给我们添加上了这个Header】另外,我发现在路由为admin/test/{a}/{b},在swagger会显示为test/{a}/{b},缺少了admin这个路由节点。断点源码时发现在Swagger中会根据X-Forwarded-Prefix这个Header来获取BasePath,将它添加至接口路径与host中间,这样才能正常做接口测试,而Gateway在做转发的时候并没有这个Header添加进Request,所以发生接口调试的404错误。解决思路是在Gateway里加一个过滤器来添加这个header。

  1.   //@Component
  2.   public 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.   String basePath = path.substring(0, path.lastIndexOf(SwaggerProvider.API_URI));
  14.   ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build();
  15.   ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
  16.   return chain.filter(newExchange);
  17.   };
  18.   }
  19.   }

在配置文件中为admin节点添加过滤器生效

  1.   routes:
  2.   - id: wd-admin
  3.   uri: lb://wd-admin
  4.   predicates:
  5.   - Path=/admin/**
  6.   filters:
  7.   - SwaggerHeaderFilter
  8.   - StripPrefix=1

这时启动Gateway,访问gateway-ip:gateway-port/swagger-ui.html时,即可正常使用swagger。大家可以多加几个API服务试试效果

最后附上效果图