热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

springcloudgateway聚合swagger

springcloudgateway聚合swagger,Go语言社区,Golang程序员人脉社

在spring cloud 的使用的时候,我发现测试起来很不方便,需要使用Postman等类似的工具来调用我们的接口,这显然是很麻烦的,那么有没有一种方式可以让我们在gateway里使用swagger来测试呢。本文基于Finchley.RELEASE和最新版的Finchley.SR2,这两个版本有所改动,后面介绍。

答案是肯定的,我查阅资料发现了之前有人实现了zuul网关的聚合swagger,通过他的思路我自己写了一些类,首先需要,在gateway网关中创建三个类,下面贴出来

SwaggerHandler

package com.e6yun.ms.config;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.http.HttpStatus;

import org.springframework.http.ResponseEntity;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

import reactor.core.publisher.Mono;

import springfox.documentation.swagger.web.*;

import java.util.Optional;

/**

 * @Description

 * @Author changyandong@e6yun.com

 * @Created Date: 2018/8/16 11:52

 * @ClassName SwaggerHandler

 * @Version: 1.0

 */

@RestController

@RequestMapping("/swagger-resources")

public class SwaggerHandler {

    @Autowired(required = false)

    private SecurityConfiguration securityConfiguration;

    @Autowired(required = false)

    private UiConfiguration uiConfiguration;

    private final SwaggerResourcesProvider swaggerResources;

    @Autowired

    public SwaggerHandler(SwaggerResourcesProvider swaggerResources) {

        this.swaggerResources = 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)));

    }

}

SwaggerProvider


package com.e6yun.ms.config;

import org.springframework.cloud.gateway.config.GatewayProperties;

import org.springframework.cloud.gateway.route.RouteLocator;

import org.springframework.context.annotation.Primary;

import org.springframework.stereotype.Component;

import springfox.documentation.swagger.web.SwaggerResource;

import springfox.documentation.swagger.web.SwaggerResourcesProvider;

import java.util.ArrayList;

import java.util.List;

/**

 * @Description

 * @Author changyandong@e6yun.com

 * @Created Date: 2018/8/15 16:04

 * @ClassName SwaggerProvider

 * @Version: 1.0

 */

@Component

@Primary

public class SwaggerProvider implements SwaggerResourcesProvider {

    public static final String API_URI = "/v2/api-docs";

    private final RouteLocator routeLocator;

    private final GatewayProperties gatewayProperties;

    public SwaggerProvider(RouteLocator routeLocator, GatewayProperties gatewayProperties) {

        this.routeLocator = routeLocator;

        this.gatewayProperties = gatewayProperties;

    }

    @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 -> resources.add(swaggerResource(routeDefinition.getId(),

                                predicateDefinition.getArgs().get(""pattern"")

                                        .replace("/**", API_URI)))));

        return resources;

    }

    private SwaggerResource swaggerResource(String name, String location) {

        SwaggerResource swaggerResource = new SwaggerResource();

        swaggerResource.setName(name);

        swaggerResource.setLocation(location);

        swaggerResource.setSwaggerVersion("2.0");

        return swaggerResource;

    }

}

SwaggerHeaderFilter 这个类只是在Finchley.RELEASE版本需要实现,SR2版本无需实现。


package com.e6yun.ms.config;

import org.apache.commons.lang3.StringUtils;

import org.springframework.cloud.gateway.filter.GatewayFilter;

import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;

import org.springframework.http.server.reactive.ServerHttpRequest;

import org.springframework.stereotype.Component;

import org.springframework.web.server.ServerWebExchange;

/**

 * @Description

 * @Author changyandong@e6yun.com

 * @Created Date: 2018/8/16 12:29

 * @ClassName SwaggerHeaderFilter

 * @Version: 1.0

 */

@Component

public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory {

    private static final String HEADER_NAME = "X-Forwarded-Prefix";

    private static final String HOST_NAME = "X-Forwarded-Host";

    @Override

    public GatewayFilter apply(Object config) {

        return (exchange, chain) -> {

            ServerHttpRequest request = exchange.getRequest();

            String path = request.getURI().getPath();

            if (!StringUtils.endsWithIgnoreCase(path, SwaggerProvider.API_URI)) {

                return chain.filter(exchange);

            }

            String basePath = path.substring(0, path.lastIndexOf(SwaggerProvider.API_URI));

            ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build();

            ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();

            return chain.filter(newExchange);

        };

    }

}

之后你只需要在你的路由上添加一个配置,这里贴出我的路由配置,这是properties的配置,当然yml的配置也是可以的,我这里就不贴了,直接去官方api里查,官方api中没有properties的配置方式,我在这里贴出


spring.cloud.gateway.routes[0].id = terminal-rpc-impl

spring.cloud.gateway.routes[0].uri = lb://RPC-IMPL-TERMINAL/

spring.cloud.gateway.routes[0].predicates[0].name = Path

spring.cloud.gateway.routes[0].predicates[0].args["pattern"] = /terminal-rpc-impl/**

#SR2版本删除这个

spring.cloud.gateway.routes[0].filters[0] = SwaggerHeaderFilter

spring.cloud.gateway.routes[0].filters[1] = StripPrefix=1

spring.cloud.gateway.routes[1].id = terminal-api-web

spring.cloud.gateway.routes[1].uri = lb://TERMINAL-API-WEB/

spring.cloud.gateway.routes[1].predicates[0].name = Path

spring.cloud.gateway.routes[1].predicates[0].args["pattern"] = /terminal-api-web/**

spring.cloud.gateway.routes[1].filters[0] = SwaggerHeaderFilter

spring.cloud.gateway.routes[1].filters[1] = StripPrefix=1

当然这比较麻烦,每次新写一个接口还要去新增一组配置,我又实现了一种基于eureka服务注册发现机制的实现,只需要重写上面的SwaggerProvider这个类,就可以通过DiscoveryClientRouteDefinitionLocator这个服务发现的路由处理器,来为我们服务。下面贴上代码。


package com.e6yun.ms.config;

import org.springframework.cloud.gateway.config.GatewayProperties;

import org.springframework.cloud.gateway.discovery.DiscoveryClientRouteDefinitionLocator;

import org.springframework.cloud.gateway.route.RouteLocator;

import org.springframework.context.annotation.Primary;

import org.springframework.stereotype.Component;

import springfox.documentation.swagger.web.SwaggerResource;

import springfox.documentation.swagger.web.SwaggerResourcesProvider;

import java.util.ArrayList;

import java.util.List;

/**

 * @Description

 * @Author changyandong@e6yun.com

 * @Created Date: 2018/8/15 16:04

 * @ClassName SwaggerProvider

 * @Version: 1.0

 */

@Component

@Primary

public class SwaggerProvider implements SwaggerResourcesProvider {

    public static final String API_URI = "/v2/api-docs";

    public static final String EUREKA_SUB_PRIX = "CompositeDiscoveryClient_";

    private final DiscoveryClientRouteDefinitionLocator routeLocator;

    public SwaggerProvider(DiscoveryClientRouteDefinitionLocator routeLocator) {

        this.routeLocator = routeLocator;

    }

    @Override

    public List<SwaggerResource> get() {

        List<SwaggerResource> resources = new ArrayList<>();

        List<String> routes = new ArrayList<>();

        //从DiscoveryClientRouteDefinitionLocator 中取出routes,构造成swaggerResource

        routeLocator.getRouteDefinitions().subscribe(routeDefinition -> {

          resources.add(swaggerResource(routeDefinition.getId().substring(EUREKA_SUB_PRIX.length()),routeDefinition.getPredicates().get(0).getArgs().get("pattern").replace("/**", API_URI)));

        });

        return resources;

    }

    private SwaggerResource swaggerResource(String name, String location) {

        SwaggerResource swaggerResource = new SwaggerResource();

        swaggerResource.setName(name);

        swaggerResource.setLocation(location);

        swaggerResource.setSwaggerVersion("2.0");

        return swaggerResource;

    }

}

这样写完后,页面就可以发现注册到eureka的服务了。

这时,由于我们使用的是服务发现的routes,我们写的SwaggerHeaderFilter 不再生效了,所以这里访问会丢失服务名,这时我们需要在配置文件中添加一条语句,这里追加一个default-filters即可。SR2版本无需写


spring.cloud.gateway.default-filters[0]=SwaggerHeaderFilter

我这里还重写了spring cloud gateway的ForwardedHeadersFilter这是由于我们使用的swagger版本是2.6.1,新版的代码中它修复了这个bug 它里面的源码有一处是这么写的


package springfox.documentation.swagger2.web;

import javax.servlet.http.HttpServletRequest;

import org.springframework.util.StringUtils;

import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import org.springframework.web.util.UriComponents;

public class HostNameProvider {

    public HostNameProvider() {

        throw new UnsupportedOperationException();

    }

    static UriComponents componentsFrom(HttpServletRequest request) {

        ServletUriComponentsBuilder builder = ServletUriComponentsBuilder.fromServletMapping(request);

        ForwardedHeader forwarded = ForwardedHeader.of(request.getHeader(ForwardedHeader.NAME));

        String proto = StringUtils.hasText(forwarded.getProto()) ? forwarded.getProto() : request.getHeader("X-Forwarded-Proto");

        String forwardedSsl = request.getHeader("X-Forwarded-Ssl");

        if (StringUtils.hasText(proto)) {

            builder.scheme(proto);

        } else if (StringUtils.hasText(forwardedSsl) && forwardedSsl.equalsIgnoreCase("on")) {

            builder.scheme("https");

        }

        String host = forwarded.getHost();

        host = StringUtils.hasText(host) ? host : request.getHeader("X-Forwarded-Host");

        if (!StringUtils.hasText(host)) {

            return builder.build();

        } else {

            String[] hosts = StringUtils.commaDelimitedListToStringArray(host);

            String hostToUse = hosts[0];

            if (hostToUse.contains(":")) {

                String[] hostAndPort = StringUtils.split(hostToUse, ":");

                builder.host(hostAndPort[0]);

                builder.port(Integer.parseInt(hostAndPort[1]));

            } else {

                builder.host(hostToUse);

                builder.port(-1);

            }

            String port = request.getHeader("X-Forwarded-Port");

            if (StringUtils.hasText(port)) {

                // 这里他写了对post转int的操作,但是gateway传入的port是一个String类型的,导致转换异常

                builder.port(Integer.parseInt(port));

            }

            return builder.build();

        }

    }

}

为此我们有两种方案:

1.重写这个类

2.重写gateway中传过来port的类

我最终选择了2号方案,原因是我们做的这个聚合swagger gateway只是用来做开发测试使用,所以这个gateway和我们正式的gateway不是一个东西,但是你去重写了swagger的源码,将会导致所有的服务的swagger源码都被修改,没有必要。所以我重写了ForwardedHeadersFilter


package org.springframework.cloud.gateway.filter.headers;

import org.springframework.core.Ordered;

import org.springframework.http.HttpHeaders;

import org.springframework.http.server.reactive.ServerHttpRequest;

import org.springframework.stereotype.Component;

import org.springframework.util.CollectionUtils;

import org.springframework.util.LinkedCaseInsensitiveMap;

import org.springframework.util.ObjectUtils;

import org.springframework.util.StringUtils;

import org.springframework.web.server.ServerWebExchange;

import java.net.InetSocketAddress;

import java.net.URI;

import java.util.*;

/**

 * @Description

 * @Author changyandong@e6yun.com

 * @Created Date: 2018/8/16 17:14

 * @ClassName ForwardedHeadersFilter

 * @Version: 1.0

 */

@Component

public class ForwardedHeadersFilter implements HttpHeadersFilter, Ordered {

    public static final String FORWARDED_HEADER = "Forwarded";

    public ForwardedHeadersFilter() {

    }

    @Override

    public int getOrder() {

        return 0;

    }

    @Override

    public HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange) {

        ServerHttpRequest request = exchange.getRequest();

        HttpHeaders updated = new HttpHeaders();

        input.entrySet().stream().filter((entry) -> {

            return !((String)entry.getKey()).toLowerCase().equalsIgnoreCase("Forwarded");

        }).forEach((entry) -> {

            updated.addAll((String)entry.getKey(), (List)entry.getValue());

        
        
        
    
推荐阅读
author-avatar
清响11
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有