上回書說到,knife4j基于注冊中心eureka集成舌缤,由于有些小伙伴可能使用了nacos箕戳、Consul某残、zk等注冊中心,均有對應的集成方法陵吸,但是一旦切換了注冊中心(比如從eureka切換成nacos)玻墅,則需要重新做集成。因此本文介紹一下基于gateway的集成方式壮虫。
一澳厢、工程結構
- eboot-center:eureka注冊中心(服務端)
eboot-knife4j:文檔服務,本文不需要
eboot-common:包含了一些基礎的認證囚似、全局異常等處理剩拢,本文暫不需要 - eboot-gateway:spring-cloud-gateway,大多是情況下微服務均走網關
- eboot-modulars:即各個業(yè)務子系統(tǒng)饶唤,這里分了三個:認證管理徐伐、文件管理、系統(tǒng)管理
eboo-ui:前端項目募狂,本文不需要
二办素、center、modulars配置
均參考上篇文章即可祸穷,可回頭看《【Knife4j】小試牛刀性穿,基于eureka的集成》
三、gateway聚合配置
此處建議先看官網配置
pom配置
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
原springboot基于tomcat雷滚,而gateway基于webflux需曾,因此需要以下操作:
springfox-swagger提供的分組接口是swagger-resource,返回的是分組接口名稱、地址等信息揭措。在Spring Cloud微服務架構下,我們需要重寫該接口,主要是通過網關的注冊中心動態(tài)發(fā)現所有的微服務文檔,代碼如下:
package com.mos.eboot.gateway.config.properties;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import java.util.ArrayList;
import java.util.List;
/**
* swagger資源配置
*
* @author 小塵哥
* @date 2022/05/30
*/
@Slf4j
@AllArgsConstructor
@Component
@Primary
public class SwaggerResourceConfig implements SwaggerResourcesProvider {
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(route -> {
route.getPredicates().stream()
.filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))
.forEach(predicateDefinition -> resources.add(swaggerResource(route.getId(),
predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
.replace("**", "v2/api-docs"))));
});
return resources;
}
private SwaggerResource swaggerResource(String name, String location) {
log.info("name:{},location:{}",name,location);
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(name);
swaggerResource.setLocation(location);
swaggerResource.setSwaggerVersion("2.0");
return swaggerResource;
}
}
接口如下
package com.mos.eboot.gateway.handler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import springfox.documentation.swagger.web.*;
import java.util.Optional;
@RestController
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("/swagger-resources/configuration/security")
public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
}
@GetMapping("/swagger-resources/configuration/ui")
public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
}
@GetMapping("/swagger-resources")
public Mono<ResponseEntity> swaggerResources() {
return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
}
}
yaml配置(基于yaml中的routes進行分組)
server:
port: 9005
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
appname: eboot-gateway
leaseRenewalIntervalInSeconds: 1
lease-expiration-duration-in-seconds: 2
prefer-ip-address: true
instance-id: 127.0.0.1:9005
spring:
main:
allow-bean-definition-overriding: true
allow-circular-references: true
web-application-type: REACTIVE
cloud:
gateway:
discovery:
locator:
# gateway開啟服務注冊和發(fā)現的功能,
enabled: true
# 將請求路徑上的服務名配置為小寫(因為服務注冊的時候刻蚯,向注冊中心注冊時將服務名轉成大寫的了)
lowerCaseServiceId: true
routes:
- id: boot-system
uri: http://localhost:9093
predicates:
- Path=/system/**
# filters:
# - SwaggerHeaderFilter
# - StripPrefix=1
- id: eboot-auth
uri: http://localhost:9091
predicates:
- Path=/auth/**
四绊含、展示
五、注意點
在集成Spring Cloud Gateway網關的時候,會出現沒有basePath的情況(即定義的例如/user炊汹、/order等微服務的前綴),這個情況在使用zuul網關的時候不會出現此問題,因此,在Gateway網關需要添加一個Filter實體Bean
由于我使用的springcloud版本為3.1.2躬充,未出現該問題,若出現請參考官網的解決方案讨便,添加以下過濾器,同時放開yaml中的routes.filters配置項
@Component
public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory {
private static final String HEADER_NAME = "X-Forwarded-Prefix";
private static final String URI = "/v2/api-docs";
@Override
public GatewayFilter apply(Object config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
if (!StringUtils.endsWithIgnoreCase(path,URI )) {
return chain.filter(exchange);
}
String basePath = path.substring(0, path.lastIndexOf(URI));
ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build();
ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
return chain.filter(newExchange);
};
}
}