Gateway/Zuul + OpenApi 集中管理 API 資源

Gateway + OpenApi

爲了啓用 OpenApi,提供 API 的服務中需要添加以下依賴:

<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-webmvc-core</artifactId>
    <version>1.4.1</version>
</dependency>

我們的 gateway,Spring Cloud Gateway 使用 Netty 作爲內嵌服務器,Netty 基於 Spring WebFlux。同時,爲了顯示 UI 界面,也要提供相應的庫:

<dependency>
   	<groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-webflux-core</artifactId>
    <version>1.4.1</version>
 </dependency>
 <dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-webflux-ui</artifactId>
    <version>1.4.1</version>
 </dependency>

每個微服務都會暴露 /v3/api-docs 接口,用來獲取 API 信息。可以通過 springdoc.api-docs.path 配置來修改這個路由,建議不修改。不同於 Swagger,OpenAPI 並不提供類似 SwaggerResource 可以用來收集這些 API 信息的類

幸運的是,我們可以使用 SwaggerUiConfig 來實現我們的目的

@Component
public class OpenApiConfig {

    public OpenApiConfig(RouteLocator locator, SwaggerUiConfig swaggerUiConfig) {

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

		// 去重
		Collection<Route> distinctRoutes = routes.stream()
				.collect(Collectors.toMap(Route::getLocation, p -> p, (p, q) -> p)).values();

		// 將服務名添加到下拉框
	    distinctRoutes.stream().filter(route -> route.getLocation().matches(".+-service")).forEach(route -> {
	    	String serviceName = route.getLocation();
	    	swaggerUiConfig.addGroup(serviceName);
    }
}

SwaggeUiConfig.addGroup(serviceName ) 會在 UI 界面上生成一個下拉框,並加入一個名爲 serviceName 的選項。點擊這個選項將會訪問 /v3/api-docs/serviceName,而我們期望的是 /serviceName/v3/api-docs。所以,需要對路由進行重寫

在此之前,需要配置我們的路由

spring:
  application:
    name: gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/user/**
          filters:
            - RewritePath=/user/(?<path>.*), /$\{path}
        - id: task-service
          uri: lb://task-service
          predicates:
            - Path=/task/**
          filters:
            - RewritePath=/task/(?<path>.*), /$\{path}

uri: lb//user-service 將自動實現所有 user-service 實例間的負載均衡。 RewritePath=/service/(?<path>.*), /$\{path} 將在訪問請求時去掉 /service 前綴

然後,將 /v3/api-docs/serviceName 的路由重寫爲 /serviceName/v3/api-docs

- id: openapi
  uri: http://localhost:${server.port}
  predicates:
    - Path=/v3/api-docs/**
  filters:
    - RewritePath=/v3/api-docs/(?<path>.*), /$\{path}/v3/api-docs

最後效果呈現如下:
在這裏插入圖片描述

Zuul + OpenApi

Zuul 內嵌的是 Tomcat,不需要 WebFlux 加持,所以使用以下依賴

<dependency>
 	<groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-ui</artifactId>
    <version>1.4.1</version>
</dependency>

OpenApiConfig 和前半節中的相同

Zuul 中 路由配置:

spring:
  application:
    name: gateway

zuul:
  ignoredPatterns:
    - /v3/api-docs
    - /v3/api-docs/swagger-config
  routes:
    user:
      service-id: user-service
      path: /user/**
    task:
      service-id: task-service
      path: /task/**
    apis:
      service-id: gateway
      path: /v3/api-docs/**

/v3/api-docs/swagger-config 並不需要路由,所以加到 ignoredPatterns

如我們看到的,Zuul 不提供 Gateway 中的 RewritePath。我們可以使用過濾器來完成重寫路由的工作:

@Component
public class UrlPathFilter extends ZuulFilter {

    @Override
    public String filterType() {
        return "route";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        String requestURI = RequestContext.getCurrentContext().getRequest().getRequestURI();
        return requestURI.matches("/v3/api-docs/.+");
    }

    @Override
    public Object run() {
        String requestURI = RequestContext.getCurrentContext().getRequest().getRequestURI();
		
		// 提取出路由最後一節,即服務名
        String servicePattern = "/v3/api-docs/(?<group>.+)";
        Pattern compile = Pattern.compile(servicePattern);
        Matcher matcher = compile.matcher(requestURI);

        String group = "";
        while (matcher.find()) {
            group = matcher.group("group");
        }

		// 重寫路由
        String path = "/" + group + "/v3/api-docs";

        RequestContext context = RequestContext.getCurrentContext();
        context.put(FilterConstants.REQUEST_URI_KEY, path);
        System.out.println(path);
        return null;
    }
}

最後的效果和上面相同

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章