前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >在网关实现合并多个微服务Swagger接口文档的详细步骤

在网关实现合并多个微服务Swagger接口文档的详细步骤

作者头像
吴就业
发布2021-05-11 14:16:52
3.5K0
发布2021-05-11 14:16:52
举报
文章被收录于专栏:Java艺术Java艺术

由于微服务的划分,使用Swagger生成的接口文档也随之拆散,前端同事不得不把每个微服务的接口文档保存为浏览器标签,方便快速切换。在引入网关之后我们想改善这个问题,统一多个微服务接口文档的入口,最好不需要将每个微服务暴露到外网,能够统一配置是否开启接口文档功能,也不需要为接口文档配置路由规则。

WebFlux整合Swagger

基于Spring Cloud Gateway开发微服务网关的前提是我们已经了解了响应式编程,并且会使用Project Reactor、WebFlux提供的API。而在网关项目中整合Swagger实际就是在WebFlux项目中整合Swagger。

首先是在项目中添加Swagger相关依赖,注意选择版本号。

由于我们项目使用的Spring Boot版本号是2.3.0.RELEASE,因此我们选择的Swagger版本号为2.10.x。

代码语言:javascript
复制
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-spring-webflux</artifactId>
    <version>2.10.5</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.10.5</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.10.5</version>
</dependency>

接着需要为WebFlux添加静态资源文件访问路径映射,即添加ResourceWebHandler。

代码语言:javascript
复制
@EnableSwagger2WebFlux
@Configuration
public class WebfluxConfiguration implements WebFluxConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/swagger-ui.html**")
                .addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

}

到此为止,我们只是为一个WebFlux项目配置了Swagger功能,现在打开浏览器访问/swagger-ui.html#/看到的只是Swagger为网关项目生成的接口文档。

合并多个微服务Swagger接口文档

方案一(笔者从一些博客看到的)

在网关项目中自定义SwaggerResourcesProvider替换Swagger提供的。

自定义SwaggerResourcesProvider实现SwaggerResourcesProvider接口的get方法,方法可返回多个SwaggerResource,每个SwaggerResource对应每个微服务,我们可以过滤掉网关自身的,代码如下。

代码语言:javascript
复制
@Primary
@Profile({"dev", "test"}) // 仅本地测试、测试环境开启
@Component
public class GatewaySwaggerResourcesProvider implements SwaggerResourcesProvider{
    @Override
    public List<SwaggerResource> get() {
        List<SwaggerResource> swaggerResources = new ArrayList<>();
        routeProperties.getRoutes().forEach(routeDefinition -> {
            String routeId = routeDefinition.getId();
            String baseUrl;
            if (isLocalDebug) {
                 baseUrl = "http://127.0.0.1:" + routeDefinition.getUri().getPort();
            } else {
                 baseUrl = ${网关域名} + "/" + ${路由前缀};
            }
            swaggerResources.add(getSwaggerResources(routeId, baseUrl));
        });
        return swaggerResources;
    }

    private SwaggerResource getSwaggerResources(String name, String baseUrl) {
            SwaggerResource resource = new SwaggerResource();
            resource.setName(name);
            resource.setLocation(baseUrl + "/v2/api-docs");
            resource.setSwaggerVersion("2.0");
            return resource;
    }
}

这些SwaggerResource就是我们在Swagger ui看到的"select a definition"的一个个选项,如下图所示。

关于SwaggerResource:

  • name:swagger资源名称,微服务名称,也是我们在Swagger ui看到的选项的名称;
  • location:对应微服务接口文档的/v2/api-docs API的url(即https://{网关host}/{路由到该应用的路由规则}/v2/api-docs)。

GatewaySwaggerResourcesProvider中的下面这段代码只是拼接接口文档的baseUrl,满足“baseUrl+/v2/api-docs”能够直接在浏览器访问。

代码语言:javascript
复制
String baseUrl;
if (isLocalDebug) {
   baseUrl = "http://127.0.0.1:" + routeDefinition.getUri().getPort();
} else {
   baseUrl = ${网关域名} + "/" + ${路由到该应用的路由规则};
}

由于SwaggerResource配置的location是由前端直接发起请求的,而不是由网关发起请求获取再响应给前端,因此需要在网关为每个微服务配置“/v2/api-docs”接口的路由规则。

最后还需要为其它微服务配置支持跨域请求,否则Swagger前端无法调用SwaggerResource配置的location向后端服务发起“/v2/api-docs”接口请求。

在其它微服务中添加跨域请求配置如下(注意:不是在网关添加!)。

代码语言:javascript
复制
@Profile({"dev", "test"}) // 仅测试环境
@ConditionalOnClass(WebMvcConfigurer.class)
@Configuration
public class CorsAutoConfiguration {

    private CorsConfiguration buildConfig() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        return corsConfiguration;
    }

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", buildConfig());
        return new CorsFilter(source);
    }

}

方案二

虽然方案一可行,但弊端是要为每个微服务添加Swagger的“/v2/api-docs”接口路由配置,笔者并未采用这种方式,而是使用自己想的一种方案:通过在网关代理后端每个微服务的“/v2/api-docs”接口请求实现。

简单说,就是每个SwaggerResource的location配置的都是网关代理的“/v2/api-docs”接口,并且给location拼接一个参数,如指向用户中心的SwaggerResource配置的location为“https://网关host/proxySwagger/v2/api-docs?routeId=usercenter”。然后由网关提供/proxySwagger/v2/api-docs接口,实现根据参数routeId向后端微服务发起“/v2/api-docs”接口请求,并将响应结果直接响应给前端。

GatewaySwaggerResourcesProvider代码实现如下:

代码语言:javascript
复制
@Primary
@Profile({"dev", "test"}) // 仅本地测试、测试环境开启
@Component
public class GatewaySwaggerResourcesProvider implements SwaggerResourcesProvider {

    @Resource
    private RouteProperties routeProperties;
    @Value("${server.domain:http://127.0.0.1:8600}")
    private String gatewayDomain;

    @Override
    public List<SwaggerResource> get() {
        List<SwaggerResource> swaggerResources = new ArrayList<>();
        // 遍历路由定义
        routeProperties.getRoutes().forEach(routeDefinition -> {
            String routeId = routeDefinition.getId();
            // 参数1:路由的id
            // 参数2:网关的域名
            swaggerResources.add(getSwaggerResources(routeId, gatewayDomain));
        });
        return swaggerResources;
    }

    private SwaggerResource getSwaggerResources(String routeId, String baseUrl) {
        SwaggerResource resource = new SwaggerResource();
        resource.setName(routeId);
        resource.setLocation(baseUrl + "/proxySwagger/v2/api-docs?routeId=" + routeId);
        resource.setSwaggerVersion("2.0");
        return resource;
    }

}

由网关提供/v2/api-docs的代理接口/proxySwagger/v2/api-docs?routeId=${routeId},代码如下:

代码语言:javascript
复制
@Profile({"dev", "test"})
@RestController
@RequestMapping("/proxySwagger")
public class GatewayApiDocsController {

    @Resource
    private RouteProperties routeProperties;
    private WebClient webClient;
    private boolean isLocalDebug;

    @PostConstruct
    public void init() {
        webClient = WebClient.create();
        isLocalDebug = ....;// dev: true, test: false
    }

    @GetMapping("/v2/api-docs")
    public Mono<String> proxyApiDocs(@RequestParam("routeId") String routeId) {
        // 根据路由id获取路由配置
        RouteDefinition routeDefinition = routeProperties.getRoutes().stream()
                .filter(rd -> rd.getId().equals(routeId))
                .findFirst().get();
        String baseUrl;
        URI routeUri = routeDefinition.getUri();
        if (isLocalDebug) {
            // 本地debug
            baseUrl = "http://127.0.0.1";
        } else {
            baseUrl = "http://" + routeUri.getHost();
        }
        if (routeUri.getPort() > 0) {
            baseUrl += (":" + routeUri.getPort());
        }
        // 转发给后端微服务
        return webClient.get()
                .uri(baseUrl + "/v2/api-docs")
                .retrieve()
                .bodyToMono(String.class);
    }
    
}
  • 注意:由于我们项目是部署在k8s上的,我们去掉了服务注册/发现,所有路由规则配置的uri就已经是k8s集群内部pod可以直接访问的。

这种方案相比第一种方案优点在于:

  • 1、严格统一流量入口,不让Swagger前端绕过网关直接访问后端微服务的接口;
  • 2、不必要求每个微服务都配置支持跨域请求;
  • 3、不必为Swagger的api配置路由规则。
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-05-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java艺术 微信公众号,前往查看

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

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • WebFlux整合Swagger
  • 合并多个微服务Swagger接口文档
    • 方案一(笔者从一些博客看到的)
      • 方案二
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档