Spring Cloud Gateway 聚合swagger文档

关于pigX:全网最新的微服务脚手架,Spring Cloud Finchley、oAuth2的最佳实践

在微服务架构下,通常每个微服务都会使用Swagger来管理我们的接口文档,当微服务越来越多,接口查找管理无形中要浪费我们不少时间,毕竟懒是程序员的美德。
由于swagger2暂时不支持webflux 走了很多坑,完成这个效果感谢 @dreamlu @世言。

文档聚合效果

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

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

Pig的Zuul 核心实现

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


@Component

@Primary

public class RegistrySwaggerResourcesProvider implements SwaggerResourcesProvider {

    private final RouteLocator routeLocator;

    public RegistrySwaggerResourcesProvider(RouteLocator routeLocator) {

        this.routeLocator = routeLocator;

    }

    @Override

    public List<SwaggerResource> get() {

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

        List<Route> routes = routeLocator.getRoutes();

        routes.forEach(route -> {

            //授权不维护到swagge

            if (!StringUtils.contains(route.getId(), ServiceNameConstant.AUTH\_SERVICE)){

                resources.add(swaggerResource(route.getId(), route.getFullPath().replace("\*\*", "v2/api-docs")));

            }

        });

        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;

    }

}

PigX的Spring Cloud Gateway 实现

注入路由到**SwaggerResource**

@Component

@Primary

@AllArgsConstructo

public class SwaggerProvider implements SwaggerResourcesProvider {

    public static final String API\_URI = "/v2/api-docs";

    private final RouteLocator routeLocator;

    private final GatewayProperties gatewayProperties;


    @Override

    public List<SwaggerResource> get() {

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

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

        routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));

        gatewayProperties.getRoutes().stream().filter(routeDefinition -> routes.contains(routeDefinition.getId()))

            .forEach(routeDefinition -> routeDefinition.getPredicates().stream()

                .filter(predicateDefinition -> "Path".equalsIgnoreCase(predicateDefinition.getName()))

                .filter(predicateDefinition -> !"pigx-auth".equalsIgnoreCase(routeDefinition.getId()))

                .forEach(predicateDefinition -> resources.add(swaggerResource(routeDefinition.getId(),

                    predicateDefinition.getArgs().get(NameUtils.GENERATED\_NAME\_PREFIX + "0")

                        .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;

    }

}

提供swagger 对外接口配置



@Slf4j

@Configuration

@AllArgsConstructo

public class RouterFunctionConfiguration {

    private final SwaggerResourceHandler swaggerResourceHandler;

    private final SwaggerSecurityHandler swaggerSecurityHandler;

    private final SwaggerUiHandler swaggerUiHandler;



    @Bean

    public RouterFunction routerFunction() {

        return RouterFunctions.route(

            .andRoute(RequestPredicates.GET("/swagger-resources")

                .and(RequestPredicates.accept(MediaType.ALL)), swaggerResourceHandler)

            .andRoute(RequestPredicates.GET("/swagger-resources/configuration/ui")

                .and(RequestPredicates.accept(MediaType.ALL)), swaggerUiHandler)

            .andRoute(RequestPredicates.GET("/swagger-resources/configuration/security")

                .and(RequestPredicates.accept(MediaType.ALL)), swaggerSecurityHandler);



    }

}

业务handler 的实现

    @Override

    public Mono<ServerResponse> handle(ServerRequest request) {

        return ServerResponse.status(HttpStatus.OK)

            .contentType(MediaType.APPLICATION\_JSON\_UTF8)

            .body(BodyInserters.fromObject(swaggerResources.get()));

    }

    

    @Override

    public Mono<ServerResponse> handle(ServerRequest request) {

        return ServerResponse.status(HttpStatus.OK)

            .contentType(MediaType.APPLICATION\_JSON\_UTF8)

            .body(BodyInserters.fromObject(

                Optional.ofNullable(securityConfiguration)

                    .orElse(SecurityConfigurationBuilder.builder().build())));

    }

    

    @Override

    public Mono<ServerResponse> handle(ServerRequest request) {

        return ServerResponse.status(HttpStatus.OK)

            .contentType(MediaType.APPLICATION\_JSON\_UTF8)

            .body(BodyInserters.fromObject(

                Optional.ofNullable(uiConfiguration)

                    .orElse(UiConfigurationBuilder.builder().build())));

    }

swagger路径转换

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

swagger 文档中的路径为:

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

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

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

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

@Component

public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory {

    private static final String HEADER\_NAME = "X-Forwarded-Prefix";



    @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);

        };

    }

}

总结

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Ryan Miao

在dropwizard中使用feign,使用hystrix

前言 用惯了spring全家桶之后,试试dropwizard的Hello World也别有一帆风味。为了增强对外访问API的能力,需要引入open feign...

423120
来自专栏xingoo, 一个梦想做发明家的程序员

漫谈Java IO之 Netty与NIO服务器

前面介绍了基本的网络模型以及IO与NIO,那么有了NIO来开发非阻塞服务器,大家就满足了吗?有了技术支持,就回去追求效率,因此就产生了很多NIO的框架对NIO...

44180
来自专栏Ryan Miao

使用dropwizard(4)-加入测试-jacoco代码覆盖率

前言 dropwizard提供了一个简单的测试框架。这里简单集成并加入jacoco测试。 Demo source https://github.com/Rya...

42580
来自专栏CodeSheep的技术分享

ElasticSearch搜索引擎在SpringBoot中的实践

首先当然需要安装好elastic search环境,最好再安装上可视化插件 elasticsearch-head来便于我们直观地查看数据。

384110
来自专栏菩提树下的杨过

linq学习笔记(二)

试用了几天linq,感觉确实方便,而且生成的sql也还不错,下面是几点体会 1.几种常见的等效select写法 var s = from c in ctx.T...

204100
来自专栏微信公众号:Java团长

Java高效读取大文件

读取文件行的标准方式是在内存中读取,Guava 和Apache Commons IO都提供了如下所示快速读取文件行的方法:

57520
来自专栏互联网开发者交流社区

SpringBoot与Web开发

15040
来自专栏猿天地

ElasticSearch搜索引擎在SpringBoot中的实践

实验环境 ES版本:5.3.0 spring bt版本:1.5.9 首先当然需要安装好elastic search环境,最好再安装上可视化插件 elastics...

49050
来自专栏hbbliyong

Spring Boot搭建Web项目常用功能

     首先要弄清楚为什么要包装统一结构结果数据,这是因为当任意的ajax请求超时或者越权操作时,系统能返回统一的错误信息给到前端,前端通过封装统一的ajax...

35220
来自专栏一个会写诗的程序员的博客

第13章 Kotlin 集成 SpringBoot 服务端开发(1)第13章 Kotlin 集成 SpringBoot 服务端开发

本章介绍Kotlin服务端开发的相关内容。首先,我们简单介绍一下Spring Boot服务端开发框架,快速给出一个 Restful Hello World的示例...

32630

扫码关注云+社区

领取腾讯云代金券