0、前提
1、为什么使用网关SpringCloud-Gateway?
目前主流的是微服务,一个项目会有很多服务。这些服务有各自的IP和端口,在没有网关之前,客户端维护IP、端口,管理效率太低。而且在集群的情况下,客户端无法实现负载均衡。
虽然后来有Nginx可以解决这个问题,但还是存在很多的硬编码。
而SpringCloud-Gateway,是基于Spring5、SpringBoot2.0和Project Reactor等技术开发的网关,目的是为微服务架构系统提供高性能且简单易用的API路由管理方式。它性能强劲,是第一代网关Zuul的1.6倍;它功能强大,内置很多实用的功能,如:路由、过滤、限流就、监控等...而且易于扩展。
同时,SpringCloud-Gateway本身也是一个微服务,通过服务名称远程调用其他服务,客户端只需记住网关的IP和端口,需把请求发给Gateway,Gateway做一个路由转发,转发给对应的服务请求即可。1、SpringCloud-Gateway的位置
处于客户端和服务群之间,API网关
客户端---Nginx---API网关---服务群
2、可以做的事:
1、路由转发
2、过滤,比如统一鉴权
3、限流
4、监控
5、....
2、核心概念(组件)
1、Route(路由)
路由是构建网关的基本模块,它由ID、目标URI、一系列的断言和过滤器组件,如果断言为true则匹配该路由
2、Predicate(断言、谓词)
开发人员可匹配Http请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
3、Filter(过滤)
指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或之后队请求进行修改,比如token验证
3、工作流程
1、客户端向SpringCloud Gateway发出请求
2、Gateway Handler Mapping中找到与请求相匹配的路由
3、将其发送到Gateway Web Handler
4、Gateway Web Handler再通过指定的过滤器链来将请求发送到实际的服务执行业务逻辑,然后返回(过滤器可能会在发送代理请求之前("pre")或之后("post")执行业务逻辑)。
4、搭建网关
0、新建Module--cloud-gateway
1、加入依赖
spring-cloud-starter-gateway 网关,底层基于Netty,不要依赖spring-cloud-starter-web。因为spring-cloud-starter-web,是基于SpringMVC的,是servlet编程模型,运行服务器是tomcat,而spring-cloud-starter-gateway,基于Spring WebFlux,是reactor编程模型,运行服务器是netty容器。
<!--gateway网关场景依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <!--端点监控场景依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--Nacos服务注册与发现场景依赖--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!--Nacos配置中心场景依赖--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <!--@ConfigurationProperties注解飘红去除--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
2、编写启动类
import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; /** * ===================================================== * 网关 * @author 汐小旅Shiory * @date 2021/12/14 21:24 * ===================================================== */ @Slf4j @SpringBootApplication @EnableDiscoveryClient // 服务注册与发现 public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } }
3、编写配置文件
1、新建application.yml,配置服务名称和端口
server: port: 9999 spring: application: name: cloud-gateway
2、在Nacos上新建一个配置文件,编写Nacos服务注册与发现配置
spring: cloud: nacos: discovery: server-addr: 192.168.2.102:8848 # nacos-server的地址 username: nacos # nacos-server用户名 password: nacos # nacos-server密码 namespace: shiory # 命名空间ID group: DEFAULT_GROUP # 组
3、新建一个bootstrap.yml,编写Naocs配置中心配置
spring: cloud: nacos: config: server-addr: 192.168.2.102:8848 # nacos-server的地址 username: nacos # nacos-server用户 password: nacos # nacos-server密码 namespace: shiory # 命名空间ID group: DEFAULT_GROUP # 组 # 文件名是通过公式来拼接的: ${prefix}-${spring.profiles.active}.${file-extension} prefix: shiory-gateway # 配置文件名前缀${prefix} file-extension: yml # 配置文件名扩展${file-extension} profiles: active: dev # 配置文件名扩展${spring.profiles.active}
4、路由配置(最终是使用动态路由)
查看源码可知,路由是一个数组
注意事项:路由配置多个以后,会从上往下,一旦匹配到上面,就不会再去匹配下面。所以如果有"/**",一定要放在最后
情况1、路由到指定的URL
在Nacos上新建的配置文件中加入下面的内容
spring: cloud: # 网关配置 gateway: discovery: locator: # 开启注册中心的路由定位器 enabled: true # 路由配置 routes: - id: baidu # ID保证唯一 uri: http://www.baidu.com # 服务地址 predicates: - Path=/** # 路径匹配规则
启动cloud-gateway服务
访问:localhost:9999,就会跳转到百度页面
访问:localhost:9999/a,就会出现404
原因:localhost:9999 ===> http://www.baidu.com
localhost:9999/a ===> http://www.baidu.com/a
情况2:路由到微服务---1、静态路由(有硬编码,IP和端口)
此处的"/**"放到了最后,不然无法匹配到"/demo3/**"
spring: cloud: # 网关配置 gateway: discovery: locator: # 开启注册中心的路由定位器 enabled: true # 路由配置 routes: - id:cloud-demo3 # ID保证唯一 uri: http://192.168.2.102:7003/ # 服务地址 predicates: - Path=/demo3/** # 路径断言,多个用逗号隔开 - id: baidu # ID保证唯一 uri: http://www.baidu.com # 服务地址 predicates: - Path=/** # 路径匹配规则
情况2:路由到微服务---2、动态路由
spring: application: name: cloud-gateway cloud: # 网关配置 gateway: discovery: locator: # 开启注册中心的路由定位器 enabled: true # 路由配置 routes: - id: cloud-demo3 # ID保证唯一 uri: lb://cloud-demo3 # 服务名称 lb,LoadBalance,负载均衡,通过ribbon实现的 predicates: - Path=/demo3/**,/testNacosRefresh/** # 路径断言,多个用逗号隔开 - id: baidu # ID保证唯一 uri: http://www.baidu.com # 服务地址 predicates: - Path=/** # 路径匹配规则
访问:localhost:9999/demo3/xxx,就会访问到服务demo3的接口
5、谓词工厂
多个谓词工厂可以并存
1、内置谓词工厂
SpringCloudGateway提供了十来种路由谓词工厂(见官网),为网关实现灵活的转发提供了基石。官网上有使用例子
2、自定义谓词工厂
1、自定义谓词工厂的命名规范:后缀必须是RoutePredicateFactory
2、继承AbstractRoutePredicateFactory<配置的数据类型>
6、过滤器
1、内置过滤器
见官网,局部路由器,只针对某个路由生效。官网上有使用例子
2、自定义局部过滤器
与自定义谓词工厂差不多。局部路由器,只针对某个路由生效
1、命名规范:后缀为GatewayFilterFactory
2、继承AbstractGatewayFilterFactory<配置的数据类型>
// 前处理 chain.filter(exchange)放行 chain.filter(exchange).then()放行,then()中可以处理放行之后要处理的事 then( Mono.fromRunnable(() -> { // filter后处理 }) )
3、全局过滤器
1、内置全局过滤器:官网查看:Spring Cloud Gateway
2、自定义全局过滤器
步骤:
1、实现GlobalFilter,Ordered 2、重写filter方法和getOrder方法 其中,filter方法负责前处理、后处理;getOrder方法,返回数字越小越先执行 自定义全局过滤器,比如登录认证过滤器
import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; /** * ===================================================== * 全局Token 过滤器 * @author 汐小旅Shiory * @date 2021/12/14 22:02 * ===================================================== */ @Slf4j @Component public class GlobalAccessTokenFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { log.info("全局过滤器============前执行"); //Mono<Void> mono = chain.filter(exchange); // 直接放行 Mono<Void> mono = chain.filter(exchange).then(// 放行之后再后执行 Mono.fromRunnable(() -> { log.info("全局过滤器============后执行"); }) ); return mono; } // 返回数字越小越先执行 @Override public int getOrder() { return -2; } }
另一种全局过滤器写法,在启动类中编写
import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.context.annotation.Bean; import org.springframework.core.annotation.Order; import reactor.core.publisher.Mono; /** * ===================================================== * 网关 * @author 汐小旅Shiory * @date 2021/12/14 21:24 * ===================================================== */ @Slf4j @SpringBootApplication @EnableDiscoveryClient // 服务注册与发现 public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } @Bean @Order(-1) public GlobalFilter gf1(){ return (exchange, chain) -> { log.info("前执行1================="); return chain.filter(exchange).then( Mono.fromRunnable(() -> { log.info("后执行1================="); }) ); }; } }
7、Gateway跨域问题
浏览器到网关:存在跨域
网关到其他服务:不存在跨域
springboot项目跨域解决方式:加注解@CrossOrigin
由于Gateway使用的是Webflux,而不是SpringMVC,所以需要先关闭SpringMVC的cors,再从Gateway的Filter里面设置cors
Gateway解决跨域
1、配置方式
spring: cloud: # 网关配置 gateway: # 跨域配置 globalcors: cors-configurations: '[/**]': allowCredentials: true # 开启支持用户凭据 allowedOrigins: "*" # 远程服务器 allowedMethods: "*" # 请求方法 allowedHeaders: "*" # 请求头
2、编码方式
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.reactive.CorsWebFilter; import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource; /** * ===================================================== * Gateway跨域配置 * @author 汐小旅Shiory * @date 2021/12/15 23:22 * ===================================================== */ @Configuration public class CorsConfig { /** * CorsFilter 是SpringMVC的cors * CorsWebFilter 是Webflux的cors * @return */ @Bean public CorsWebFilter corsWebFilter(){ // 跨域配置 CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true);// 开启支持用户凭据 config.addAllowedOrigin("*");// 远程服务器 config.addAllowedMethod("*");// 请求方法 config.addAllowedHeader("*");// 请求头 UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); // 为指定路径注册跨域配置 source.registerCorsConfiguration("/**",config); CorsWebFilter corsWebFilter = new CorsWebFilter(source); return corsWebFilter; } }
3、跨域测试前端代码
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>跨域测试</title> <script src="https://unpkg.com/vue@next"></script> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> </head> <body> <div id="app"> {{ info }} </div> <script> const app = { data() { return { info: '请求失败!!' } }, mounted () { axios .get('http://192.168.2.102:9999/demo3/gatewayTest') .then(response => (this.info = response.data)) .catch(function (error) { // 请求失败处理 console.log(error); }); } } Vue.createApp(app).mount('#app') </script> </body> </html>