官网:Spring Cloud Gateway
中文文档:Spring Cloud Gateway 2.1.0 中文官网文档 - 腾讯云开发者社区-腾讯云
一、网关介绍:
编辑
网关就是当前微服务的统一入口
通常在微服务项目中,只有网关项目是暴露在网络里的,其他服务一般都是在内网里,
用户访问网关,网关根据访问的路径,来进行路由
Gateway 网关也是微服务的一部分,需要将项目注册到Nacos
因为某一个服务可能存在多台服务器,所以也需要负载均衡依赖
二、案例:
2.1.依赖项
<!--Gateway网关依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--网关负载均衡依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!--Nacos依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2.2.yml文件配置的方式实现路由
纯yml文件配置的方式实现路由(包含Nacos注册与Gateway配置)
spring:
application:
name: gateway
cloud:
nacos:
discovery:
# 设置nacos服务器的地址
server-addr: localhost:8848
# 默认为true是临时实例, 改为 false 就是永久实例
ephemeral: true
gateway:
#下面编写Gateway路由配置
routes:
#当前路由名称
- id: gateway-beijing
#当匹配路由的路径时,设置访问的服务器(Nacos里注册的服务器,在那个项目的spring.application.name设置里)
#lb是loadbalancer负载均衡的缩写
uri: lb://beijing
#断言 既满足条件时做某些事情
predicates:
#当请求路径以/bj/开头时,就会路由到上面设置的 lb://beijing 服务器
- Path=/bj/**
- id: gateway-shanghai
uri: lb://shanghai
predicates:
- Path=/sh/**
编写配置类的方式实现配置(此种方式,配置文件里只配置Nacos注册中心即可)
2.3.用配置类进行配置
用配置类进行配置,则配置文件只需要写下边部分就够了
spring:
application:
name: gateway
cloud:
nacos:
discovery:
# 设置nacos服务器的地址
server-addr: localhost:8848
配置类如下:
package cn.tedu.gateway.config;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class GatewayConfiguration {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
routes
//名称为gateway-beijing的路由,匹配地址 /bj/** 使用 Nacos 里的 beijing 去处理请求 lb为负载均衡
.route("gateway-beijing", r -> r.path("/bj/**").uri("lb://beijing"))
.route("gateway-shanghai", r -> r.path("/sh/**").uri("lb://shanghai"))
.route("gateway-after", r ->
//匹配路径为 /show
r.path("/show")
//多个断言之间,使用and方法连接
.and()
//断言时间,只能在此时间后访问
.after(ZonedDateTime.parse("2022-08-25T10:00:00+08:00[Asia/Shanghai]"))
.and()
//断言查询参数,必须包含age,如 /show?age=1
.query("age")
//设置过滤器,在过滤器内添加请求参数,那么实际控制器收到的请求为: /show?age=1&name=tom
.filters(f -> f.addRequestParameter("name", "tom"))
//使用shanghai去处理请求
.uri("lb://shanghai")
)
//将路径为 /personal 的请求,转到石墨文档,石墨文档收到请求后,请求地址为: https://shimo.im/personal
.route("gateway-shimo", r -> r.path("/personal").uri("https://shimo.im"))
.build();
return routes.build();
}
}
2.4.配置文件中添加动态路由:
动态路由(默认关闭)
开启方式:在配置文件中设置spring.cloud.gateway.discovery.locator.enabled=true
spring:
application:
name: gateway
cloud:
nacos:
discovery:
# 设置nacos服务器的地址
server-addr: localhost:8848
# 默认为true是临时实例, 改为 false 就是永久实例
ephemeral: true
gateway:
discovery:
locator:
#开启动态路由
enabled: true
开启后,无需编写配置类或yaml内的配置
访问路由时,需要带上Nacos里注册的名称
如,假设
Gateway网关的端口为9000
北京服务器端口为9001(Nacos注册名为beijing)
上海服务器端口为9002(Nacos注册名为shanghai)
2个服务器同时都暴露了一个 /xx/show 的接口
原访问地址:
http://localhost:9001/bj/show http://localhost:9002/sh/show
通过网关的访问地址:
http://localhost:9000/beijing/bj/show >>等价于>> http://localhost:9001/bj/show
http://localhost:9000/shanghai/sh/show >>等价于>> http://localhost:9002/sh/show
其中,地址中的 **shanghai/beijing **即为Nacos里,注册服务器的名称,请求会被路由到相应的服务器
注:动态路由与手动设置配置文件或编写配置类可以同时存在,并且同时生效
2.5.网关模块用knife4j测试:
在网关中使用swagger/knife4j来测试其他服务的接口
在pom文件中添加swagger/knife4j依赖
添加依赖
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
添加以下3个类…添加后,
访问 http://网关地址:网关端口/服务名称/doc.html 来访问
如下,其中 nacos-stock 是注册到Nacos的一个服务名称
控制器类:
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import springfox.documentation.swagger.web.*;
import java.util.Optional;
@RestController
@RequestMapping("/swagger-resources")
public class SwaggerController {
@Autowired(required = false)
private SecurityConfiguration securityConfiguration;
@Autowired(required = false)
private UiConfiguration uiConfiguration;
private final SwaggerResourcesProvider swaggerResources;
@Autowired
public SwaggerController(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)));
}
}
配置类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Component
public class SwaggerProvider implements SwaggerResourcesProvider {
/**
* 接口地址
*/
public static final String API_URI = "/v2/api-docs";
/**
* 路由加载器
*/
@Autowired
private RouteLocator routeLocator;
/**
* 网关应用名称
*/
@Value("${spring.application.name}")
private String applicationName;
@Override
public List<SwaggerResource> get() {
//接口资源列表
List<SwaggerResource> resources = new ArrayList<>();
//服务名称列表
List<String> routeHosts = new ArrayList<>();
// 获取所有可用的应用名称
routeLocator.getRoutes().filter(route -> route.getUri().getHost() != null)
.filter(route -> !applicationName.equals(route.getUri().getHost()))
.subscribe(route -> routeHosts.add(route.getUri().getHost()));
// 去重,多负载服务只添加一次
Set<String> existsServer = new HashSet<>();
routeHosts.forEach(host -> {
// 拼接url
String url = "/" + host + API_URI;
//不存在则添加
if (!existsServer.contains(url)) {
existsServer.add(url);
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setUrl(url);
swaggerResource.setName(host);
resources.add(swaggerResource);
}
});
return resources;
}
}
过滤器类
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
@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);
};
}
}
注意: 由于Spring Boot,Spring Cloud Gateway都带有spring-boot-starter-web依赖,但是前者使用Tomcat,后者使用Netty,会导致冲突,项目无法启动
解决办法有3个
1. 在配置文件中,指定spring.main.web-application-type=reactive
spring:
main:
web-application-type: reactive
2. 添加spring-boot-starter-web依赖时,排除Tomcat
<!-- web实例 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
3.不添加spring-boot-starter-web依赖,只添加Spring Cloud Gateway依赖
<!-- web实例 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>