0%

Spring Cloud Gateway 集成 Swagger

  1. 微服务中配置多组文档接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    @EnableSwagger2
    @Configuration
    public class RESTFulWebAPI {

    @Bean
    public Docket createControllerApi() {
    return new Docket(DocumentationType.SWAGGER_2)
    .groupName(" 接口分组名 1")
    .apiInfo(apiInfo())
    .select()
    .apis(RequestHandlerSelectors.basePackage("com.**.**.controller"))
    .paths(PathSelectors.any())
    .build()
    .securitySchemes(securitySchemes())
    .securityContexts(securityContexts());
    }

    @Bean
    public Docket createRpcApi() {
    return new Docket(DocumentationType.SWAGGER_2)
    .groupName(" 接口分组名 2")
    .apiInfo(apiInfo())
    .select()
    .apis(RequestHandlerSelectors.basePackage("com.**.**.rpc"))
    .paths(PathSelectors.any())
    .build()
    // 设置全局 token, 解决接口需要 token 验证的问题
    .securitySchemes(securitySchemes())
    .securityContexts(securityContexts());
    }

    private List<ApiKey> securitySchemes() {
    List<ApiKey> apiKeyList = new ArrayList();
    apiKeyList.add(new ApiKey("Authorization", "Authorization", "header"));
    return apiKeyList;
    }

    private List<SecurityContext> securityContexts() {
    List<SecurityContext> securityContexts = new ArrayList<>();
    securityContexts.add(
    SecurityContext.builder()
    .securityReferences(defaultAuth())
    .forPaths(PathSelectors.regex("^(?!auth).*$"))
    .build());
    return securityContexts;
    }

    private List<SecurityReference> defaultAuth() {
    AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
    AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
    authorizationScopes[0] = authorizationScope;
    List<SecurityReference> securityReferences = new ArrayList<>();
    securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
    return securityReferences;
    }

    private ApiInfo apiInfo() {
    return new ApiInfoBuilder()
    .title(" 文档标题 ")
    .version("0.0.0.1")
    .description("API 描述 ")
    .build();
    }
    }
  2. Gateway 配置网关

    1. pom 文件添加依赖
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      <dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox-swagger2</artifactId>
      <version>2.9.2</version>
      <scope>compile</scope>
      </dependency>
      <dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox-swagger-ui</artifactId>
      <version>2.9.2</version>
      </dependency>
    2. 注入路由到 SwaggerResource
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      @Component
      @Primary
      @AllArgsConstructor
      public class SwaggerProvider implements SwaggerResourcesProvider {

      private final RouteLocator routeLocator;
      private final GatewayProperties gatewayProperties;
      private final RestTemplate restTemplate;

      @Override
      public List<SwaggerResource> get() {
      List<SwaggerResource> resources = new ArrayList<>();
      List<String> routes = new ArrayList<>();
      // 取出 gateway 的 route
      routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));
      // 结合配置的 route- 路径(Path),和 route 过滤,只获取有效的 route 节点
      gatewayProperties.getRoutes().stream().filter(routeDefinition -> routes.contains(routeDefinition.getId()))
      .forEach(routeDefinition -> routeDefinition.getPredicates().stream()
      .filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))
      .forEach(predicateDefinition -> {
      SwaggerResource[] swaggerResources;
      try {
      swaggerResources = restTemplate
      .getForObject("http://" + routeDefinition.getId() + "/swagger-resources", SwaggerResource[].class);
      } catch (HttpClientErrorException | IllegalStateException e) {
      return;
      }
      if (Objects.isNull(swaggerResources)) {
      return;
      }
      for (SwaggerResource resource : swaggerResources) {
      resource.setUrl(predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
      .replace("/**", resource.getUrl()));
      resources.add(resource);
      }
      }));
      return resources;
      }
      }
    3. 提供 Swagger 对外接口
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      @RestController
      @RequestMapping("/swagger-resources")
      public class SwaggerController {

      @Autowired(required = false)
      private SecurityConfiguration securityConfiguration;
      @Autowired(required = false)
      private UiConfiguration uiConfiguration;
      private final SwaggerResourcesProvider swaggerResources;


      @GetMapping("/configuration/security")
      public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
      return Mono.just(new ResponseEntity<>(
      Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
      }

      @GetMapping("/configuration/ui")
      public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
      return Mono.just(new ResponseEntity<>(
      Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
      }

      @GetMapping
      public Mono<ResponseEntity> swaggerResources() {
      return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
      }

      @Autowired
      public SwaggerController(SwaggerResourcesProvider swaggerResources) {
      this.swaggerResources = swaggerResources;
      }

      }
    4. Swagger 的路径转换

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

      • 方案 1:通过 Swagger 的 host 配置手动维护一个前缀
        1
        2
        3
        4
        5
        6
        7
        8
        return new Docket(DocumentationType.SWAGGER_2)
        .apiInfo(apiInfo())
        .host(" 主机名:端口:服务前缀 ") // 注意这里的主机名:端口是网关的地址和端口
        .select()
        .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
        .paths(PathSelectors.any())
        .build()
        .globalOperationParameters(parameterList);
      • 方案 2:增加 X-Forwarded-Prefix
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        @Component
        public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory {

        private static final String HEADER_NAME = "X-Forwarded-Prefix";
        private static final String API_URI = "/v2/api-docs";

        @Override
        public GatewayFilter apply(Object config) {
        return (exchange, chain) -> {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        if (!StringUtils.endsWithIgnoreCase(path, API_URI)) {
        return chain.filter(exchange);
        }
        String basePath = path.substring(0, path.lastIndexOf(API_URI));
        ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build();
        ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
        return chain.filter(newExchange);
        };
        }
        }
        配置 appction.yml
        1
        2
        3
        4
        5
        6
        7
        - id: test
        uri: lb://test
        predicates:
        - Path=/test/**
        filters:
        - SwaggerHeaderFilter
        - StripPrefix=1

      采用方案 2,并且将 spring-cloud-dependencies 依赖从 Finchley.RELEASE< 升级到 Finchley.SR4,spring-boot-starter-parent 依赖从 2.0.1.RELEASE 升级到2.0.9.RELEASE。文档中的路径多了一个服务路由前缀,将方案二代码去除,文档中的路径恢复正常。

  3. Nginx 配置

    1. Nginx 配置文件
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      # 配置使用用户名和密码登录
      location /swagger-ui.html {
      auth_basic "test";
      # 相对路径:htpasswd 在机器上的位置:/usr/local/nginx/conf/htpasswd
      auth_basic_user_file htpasswd;
      # 绝对路径:htpasswd 在机器上的位置:/tmp/htpasswd
      # auth_basic_user_file /tmp/htpasswd;
      proxy_pass http://***/swagger-ui.html;
      }

      location /swagger-resources {
      proxy_pass http://**/swagger-resources;
      }

      location /webjars {
      proxy_pass http://**/webjars;
      }
    2. 创建 htpasswd
      1
      2
      # 格式:用户名: 密码,注意密码是使用 crypt 加密过的
      admin:PbSRr7orsxaso
  4. 参考