Spring Cloud Gateway整合Swagger聚合微服務系統API文件(非Zuul)
最近在學習SpringBoot2和Spring Cloud.Finchley版,網上資料也是少的可憐,大部分還是通過一些github或者碼雲上的一些開源框架來學習,途中出現的一些bug也只能自己看看原始碼嘗試解決。最近使用Spring Cloud Gateway替換Zuul的時候發現Swagger並不支援以WebFlux為底層的Gateway,無法整合,執行報錯。下面分享我愚鈍的解決思路,和關鍵程式碼,若有改進之處,望大佬指點,詳細程式碼可以下載原始碼檢視。
首先是子專案Spring Boot專案正常整合Swagger。在業務專案Admin中新增Swagger依賴包(使用Eureka為註冊中心,文章未展示多餘部分)。
<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency>
新增測試Controller。
@RestController @RequestMapping("/test") @Api("測試") public class TestController { @ApiOperation(value = "計算+", notes = "加法") @ApiImplicitParams({ @ApiImplicitParam(name = "a", paramType = "path", value = "數字a", required = true, dataType = "Long"), @ApiImplicitParam(name = "b", paramType = "path", value = "數字b", required = true, dataType = "Long") }) @GetMapping("/{a}/{b}") public Integer get(@PathVariable Integer a, @PathVariable Integer b) { return a + b; } }
配置Swagger使API註解生效。
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Swagger API")
.description("test")
.termsOfServiceUrl("")
.contact(new Contact("wd", "", ""))
.version("2.0")
.build();
}
}
此時啟動Admin專案後應該能正常訪問admin-ip:admin:port/swagger-ui.html。下面是閘道器gateway部分。
建立閘道器專案gateway,新增核心依賴包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
新增gateway路由配置
server:
port: 3333
spring:
application:
name: wd-gateway
cloud:
gateway:
locator:
enabled: true
routes:
- id: wd-admin
uri: lb://wd-admin
predicates:
- Path=/admin/**
filters:
- SwaggerHeaderFilter
- StripPrefix=1
eureka:
instance:
prefer-ip-address: true
client:
service-url:
defaultZone: http://localhost:8060/eureka/
因為Swagger暫不支援webflux專案,所以Gateway裡不能配置SwaggerConfig,也就是說Gateway無法提供自身API。但我想一般也不會在閘道器專案程式碼裡寫業務API程式碼吧。。所以這裡的整合只是基於基於WebMvc的微服務專案。
配置SwaggerProvider,獲取Api-doc,即SwaggerResources。
@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<>();
//取出gateway的route
routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));
//結合配置的route-路徑(Path),和route過濾,只獲取有效的route節點
gatewayProperties.getRoutes().stream().filter(routeDefinition -> routes.contains(routeDefinition.getId()))
.forEach(routeDefinition -> routeDefinition.getPredicates().stream()
.filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))
.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;
}
}
因為Gateway裡沒有配置SwaggerConfig,而執行Swagger-ui又需要依賴一些介面,所以我的想法是自己建立相應的swagger-resource端點。
@RestController
@RequestMapping("/swagger-resources")
public class SwaggerHandler {
@Autowired(required = false)
private SecurityConfiguration securityConfiguration;
@Autowired(required = false)
private UiConfiguration uiConfiguration;
private final SwaggerResourcesProvider swaggerResources;
@Autowired
public SwaggerHandler(SwaggerResourcesProvider swaggerResources) {
this.swaggerResources = swaggerResources;
}
@GetMapping("/configuration/security")
public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
}
@GetMapping("/configuration/ui")
public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
}
@GetMapping("")
public Mono<ResponseEntity> swaggerResources() {
return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
}
}
另外,我發現在路由為admin/test/{a}/{b},在swagger會顯示為test/{a}/{b},缺少了admin這個路由節點。斷點原始碼時發現在Swagger中會根據X-Forwarded-Prefix這個Header來獲取BasePath,將它新增至介面路徑與host中間,這樣才能正常做介面測試,而Gateway在做轉發的時候並沒有這個Header新增進Request,所以發生介面除錯的404錯誤。解決思路是在Gateway里加一個過濾器來新增這個header。
@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);
};
}
}
在配置檔案中為admin節點新增過濾器生效
routes:
- id: wd-admin
uri: lb://wd-admin
predicates:
- Path=/admin/**
filters:
- SwaggerHeaderFilter
- StripPrefix=1
這時啟動Gateway,訪問gateway-ip:gateway-port/swagger-ui.html時,即可正常使用swagger。大家可以多加幾個API服務試試效果
最後附上效果圖