關於pigX:全網最新的微服務腳手架,Spring Cloud Finchley、oAuth2的最佳實踐
在微服務架構下,通常每個微服務 都會使用Swagger來管理我們的介面文件,當微服務越來越多,介面查詢管理無形中要浪費我們不少時間,畢竟懶是程式設計師的美德。
由於swagger2暫時不支援webflux 走了很多坑,完成這個效果感謝 @dreamlu @世言。
文件聚合效果
通過訪問閘道器的 host:port/swagger-ui.html,即可實現: pig聚合文件效果預覽傳送門
通過右上角的Select a spec 選擇服務模組來檢視swagger文件
Pig的Zuul 核心實現
獲取到zuul配置的路由資訊,主要到SwaggerResource
/**
* 參考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 功能發現路徑是路由切割後的路徑比如:
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);
};
}
}
複製程式碼
總結
-
相對zuul的實現,核心邏輯都是一樣,獲取到配置路由資訊,重寫swaggerresource
-
gateway的配置稍微麻煩,資源的提供handler,swagger url 重寫的細節
-
我的知識星球:《微服務最前沿》 免費的微服務資訊分享