Spring Cloud Gateway 接口文檔聚合實現

​ 在微服務架構下,一般每一個微服務都會使用Swagger來管理咱們的接口文檔,當微服務愈來愈多,接口查找管理無形中要浪費咱們很多時間,畢竟懶是程序員的美德。html

​ 因爲swagger2暫時不支持webflux 走了不少坑,完成這個效果感謝 @dreamlu @世言。java

文檔聚合效果

經過訪問網關的 host:port/swagger-ui.html,便可實現: pig聚合文檔效果預覽傳送門git

經過右上角的Select a spec 選擇服務模塊來查看swagger文檔程序員

Pig的Zuul 核心實現

獲取到zuul配置的路由信息,主要到SwaggerResourceweb

/**
* 參考jhipster
* GatewaySwaggerResourcesProvider
*/
@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 -> {
            //受權不維護到swagger
            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
@AllArgsConstructor
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
@AllArgsConstructor
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 功能發現路徑是路由切割後的路徑好比:api

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 請求頭裏面的信息爲前綴app


經過如上分析,知道應該在哪裏下手了吧,在 網關上追加一個請求頭便可ide

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

總結

相關文章
相關標籤/搜索