SpringCloudGateway是一个微服务的组件,所以如果要想去使用它就必须自己手工创建微服务的项目,同时肯定要有专属的微服务的依赖库,那么下面就直接开整。
1、【microcloud项目】创建一个新的子模块,模块的名称定义为“gateway-9501"
2、【microcloud项目】修改build.gradle配置文件,为gateway-9501模块添加所需要的依赖库
网关最终实现的是一个微服务的访问代理操作,那么既然要进行访问代理就需要知道明确的已经存在的微服务的信息,所有微服务的信息都在Nacos 中进行了保存,这样一来就需要引入发现服务;
project(":gateway-9501") { // 网关模块
dependencies {
implementation('com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery') {
exclude group: 'com.alibaba.nacos', module: 'nacos-client' // 移除旧版本的Nacos依赖
}
implementation(libraries.'nacos-client') // 引入与当前的Nacos匹配的依赖库
implementation('org.springframework.cloud:spring-cloud-starter-gateway') // 网关依赖
}
}
3、【gateway-9501子模块】在“src/main/resources”源代码目录之中创建 application.yml配置文件,配置网关应用的端口以及Nacos相关的配置项。
server:
port: 9501 # 网关服务的端口
spring:
application:
name: microcloud.gateway # 网关名称
cloud: # Cloud配置
nacos: # Nacos注册中心配置
discovery: # 发现服务
server-addr: nacos-server:8848 # Nacos服务地址
namespace: 96c23d77-8d08-4648-b750-1217845607ee # 命名空间ID
group: MICROCLOUD_GROUP # 一般建议大写
cluster-name: MuyanGateway # 配置集群名称
gateway: # 网关配置
discovery: # 服务发现
locator: # 资源定位
enabled: true # 通过服务发现查找其他的微服务
4、【gateway-9501子模块】既然已经成功的开发了网关,那么随后就可以在项目之中进行程序启动类的定义。
package com.yootk.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class StartGatewayApplication9501 { // 网关应用启动类
public static void main(String[] args) {
SpringApplication.run(StartGatewayApplication9501.class, args);
}
}
此时所使用的是网关代码的最基础的开发模型,同时最终还是要落实到具体的网关服务代理上。
5、【Nacos 控制台】当前的网关应用启动之后会向Nacos注册中心里面进行微服务的注册,所以这个时候打开Nacos控制台观察是否已经正确注册(PS:请顺道启动一下部门微服务);
如果可以在微服务的列表里面看见已经启动了两个微服务的信息,则表示当前的网关配置成功。
6、【本地系统】为了便于网关微服务的调用,建议添加一个新的主机名称,修改hosts配置文件:
127.0.0.1 gateway-9501
7、【Postman工具】既然已经启动了网关,同时也设置了网关的主机名称,那么下面就可以通过网关代理的形式来访问部门微服务的应用。
当前的SpringCloudGateway网关一旦引入了项目之中,就可以直接利用网关来实现微服务的调用处理,从整个的实现机制来说是没有任何难度的,但是对于微服务已经起到了一层最基本的管理模式。
当前的项目开发过程之中,已经引入了SpringCloudGateway网关,但是这个网关一旦引入之后,不仅仅是针对于部门微服务调用的改进,实际上也包含有消费端的改进,这个改进仅仅是修改远程业务接口。
1、【common-api子模块】修改IDeptService接口对象。
package com.yootk.service;
import com.yootk.common.dto.DeptDTO;
import com.yootk.service.config.FeignConfig;
import com.yootk.service.fallback.DeptServiceFallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
import java.util.Map;
@FeignClient(value = "microcloud.gateway", // 使用网关的名称进行访问
configuration = FeignConfig.class,
fallbackFactory = DeptServiceFallbackFactory.class) // 部门降级配置
public interface IDeptService { // 业务接口
/**
* 根据部门的编号获取部门的完整信息
* @param id 要查询的部门编号
* @return 编号存在则以DTO对象的形式返回部门数据,如果不存在返回null
*/
@GetMapping("/dept.provider/provider/dept/get/{deptno}") // 远程REST接口
public DeptDTO get(@PathVariable("deptno") long id);
/**
* 增加部门对象
* @param dto 保存要增加部门的详细数据
* @return 增加成功返回true,否则返回false
*/
@PostMapping("/dept.provider/provider/dept/add")
public boolean add(DeptDTO dto);
/**
* 列出所有的部门数据信息
* @return 全部数据的集合, 如果没有任何的部门数据则集合为空(size() == 0)
*/
@GetMapping("/dept.provider/provider/dept/list")
public List<DeptDTO> list();
/**
* 进行部门的分页数据加载操作
* @param currentPage 当前所在页
* @param lineSize 每页加载的数据行数
* @param column 模糊查询的数据列
* @param keyword 模糊查询关键字
* @return 部门集合数据以及统计数据,返回的数据项包括:
* 1、key = allDepts、value = List集合(部门的全部数据对象)
* 2、key = allRecorders、value = 总记录数;
* 3、key = allPages、value = 页数。
*/
@GetMapping("/dept.provider/provider/dept/split")
public Map<String, Object> split(
@RequestParam("cp") int currentPage,
@RequestParam("ls") int lineSize,
@RequestParam("col") String column,
@RequestParam("kw") String keyword);
}
所有的消费端将原始编写的部门微服务的具体地址更改为网关的服务名称,随后再修改每一个具体业务方法对应的路径的地址(把具体微服务的名称添加上来),就可以实现消费端的调用操作了。
现在所开发的“gateway-9501”子模块是一种基于默认方式实现的网关调用,整个的操作里面会自动的Nacos进行配置服务的抓取,而后只要匹配成功了路径就可以进行调用,这种形式没有任何的理解难度,而在实际的开发之中,很多网关之中的路由是需要由开发者配置的。
网关的主要目的有两个: 第一个是保护服务资源、第二个进行资源隔离(某些资源是不希望被开放的),可是当前的网关开发等于开放了全部的访问端口,中间仅仅是来了一个转发的功能。
网关的静态路由的维护性是很差。
此时的配置表示当前的网关只能够访问部门微服务之中的两个具体的路径地址,而未配置的地址将无法进行访问。
下面主要是针对于路由谓词工厂实现类来进行使用分析,这个部分主要是一个基本的应用,所有的谓词工厂只要会用即可,而这些配置都在application.yml 中完成的。
1、【gateway-9501子模块】定义网关在指定日期之后允许访问
可以考虑某一个网关只能够在2050年之后允许你进行访问,而在这之前是不允许你进行访问的,如果要想实现这样的限制那么首先要获取一个日期时间数据内容,这个内容是有特定的组成要求的,这个格式可以通过Java之中提供的工具类来生成。
package com.yootk.test;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
public class CreateGatewayDatetime { // 创建一个日期时间数据
public static void main(String[] args) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
ZoneId zoneId = ZoneId.systemDefault(); // 获取默认的区域ID
LocalDateTime localDateTime = LocalDateTime.parse("2050-02-17 21:15:32", formatter);
ZonedDateTime now = ZonedDateTime.of(localDateTime, zoneId);
System.out.println(now);
}
}
server:
port: 9501 # 网关服务的端口
spring:
application:
name: microcloud.gateway # 网关名称
cloud: # Cloud配置
nacos: # Nacos注册中心配置
discovery: # 发现服务
server-addr: nacos-server:8848 # Nacos服务地址
namespace: 96c23d77-8d08-4648-b750-1217845607ee # 命名空间ID
group: MICROCLOUD_GROUP # 一般建议大写
cluster-name: MuyanGateway # 配置集群名称
gateway: # 网关配置
discovery: # 服务发现
locator: # 资源定位
enabled: false # 取消默认路由配置,默认值就是false
routes: # 定义静态路由
- id: dept # 路由标记
uri: lb://dept.provider # 负载均衡调用
predicates: # 路由谓词工厂
- Path=/** # 匹配全部的路径
- After=2050-02-17T21:15:32+08:00[Asia/Shanghai] # 指定日期时间之后访问
像一般进行产品预售的时候,往往会使用xx时间段之后进行接口的开放,就可以通过网关来进行配置的。
2、【gateway-9501子模块】在进行请求的时候是可以向服务端发送Cookie访问数据的,现在要求在访问网关的时候也必须传入指定的Cookie数据而后才允许访问。
server:
port: 9501 # 网关服务的端口
spring:
application:
name: microcloud.gateway # 网关名称
cloud: # Cloud配置
nacos: # Nacos注册中心配置
discovery: # 发现服务
server-addr: nacos-server:8848 # Nacos服务地址
namespace: 96c23d77-8d08-4648-b750-1217845607ee # 命名空间ID
group: MICROCLOUD_GROUP # 一般建议大写
cluster-name: MuyanGateway # 配置集群名称
gateway: # 网关配置
discovery: # 服务发现
locator: # 资源定位
enabled: false # 取消默认路由配置,默认值就是false
routes: # 定义静态路由
- id: dept # 路由标记
uri: lb://dept.provider # 负载均衡调用
predicates: # 路由谓词工厂
- Path=/** # 匹配全部的路径
- Cookie=muyan-yootk-key, muyan\-\w+
通过这样的谓词工厂可以保证网关的微服务调用更加的安全,具体的安全在SpringCloud之中也有具体的应用的。
3、【gateway-9501子模块】既然已经可以实现Cookie的处理配置了,那么下面就可以考虑通过头信息的形式来进行安全的防护了,例如:要求必须传入指定的头信息之后才允许进行网关的调用。
server:
port: 9501 # 网关服务的端口
spring:
application:
name: microcloud.gateway # 网关名称
cloud: # Cloud配置
nacos: # Nacos注册中心配置
discovery: # 发现服务
server-addr: nacos-server:8848 # Nacos服务地址
namespace: 96c23d77-8d08-4648-b750-1217845607ee # 命名空间ID
group: MICROCLOUD_GROUP # 一般建议大写
cluster-name: MuyanGateway # 配置集群名称
gateway: # 网关配置
discovery: # 服务发现
locator: # 资源定位
enabled: false # 取消默认路由配置,默认值就是false
routes: # 定义静态路由
- id: dept # 路由标记
uri: lb://dept.provider # 负载均衡调用
predicates: # 路由谓词工厂
- Path=/** # 匹配全部的路径
- Header=X-Muyan-Request-Id, \d+ # 具有指定的头信息允许访问
4、【gateway-9501子模块】只允许特定的主机进行访问。
server:
port: 9501 # 网关服务的端口
spring:
application:
name: microcloud.gateway # 网关名称
cloud: # Cloud配置
nacos: # Nacos注册中心配置
discovery: # 发现服务
server-addr: nacos-server:8848 # Nacos服务地址
namespace: 96c23d77-8d08-4648-b750-1217845607ee # 命名空间ID
group: MICROCLOUD_GROUP # 一般建议大写
cluster-name: MuyanGateway # 配置集群名称
gateway: # 网关配置
discovery: # 服务发现
locator: # 资源定位
enabled: false # 取消默认路由配置,默认值就是false
routes: # 定义静态路由
- id: dept # 路由标记
uri: lb://dept.provider # 负载均衡调用
predicates: # 路由谓词工厂
- Path=/** # 匹配全部的路径
- Host=gateway-**,**.yootk.com # 特定主机访问
- Method=GET # 只允许GET模式访问
5、 【gateway-9501子模块】在每次进行服务调用的时候实际上可以通过URL重写的形式发送一些请求数据过去,这种数据一般被称为Query数据,所以可以进行Query的数据验证。
server:
port: 9501 # 网关服务的端口
spring:
application:
name: microcloud.gateway # 网关名称
cloud: # Cloud配置
nacos: # Nacos注册中心配置
discovery: # 发现服务
server-addr: nacos-server:8848 # Nacos服务地址
namespace: 96c23d77-8d08-4648-b750-1217845607ee # 命名空间ID
group: MICROCLOUD_GROUP # 一般建议大写
cluster-name: MuyanGateway # 配置集群名称
gateway: # 网关配置
discovery: # 服务发现
locator: # 资源定位
enabled: false # 取消默认路由配置,默认值就是false
routes: # 定义静态路由
- id: dept # 路由标记
uri: lb://dept.provider # 负载均衡调用
predicates: # 路由谓词工厂
- Path=/** # 匹配全部的路径
- Query=msg, yootk-\w+ # 参数名称以及匹配的数据内容
程序访问路径:gateway-9501:9501/provider/dept/list?msg=yootk-lixinghua
所有的路由谓词工厂可以通过断言的形式对用户的请求进行各种的判断处理,有了这些判断处理,对于网关的服务管理是非常有帮助的,所以网关在整个的项目开发之中不仅仅是一个所谓的服务治理功能,还可以对一些请求进行拦截以及徼服务的保护。
通过之前的分析已经可以清楚的掌握SpringCloudGateway之中所提供的路由谓词工厂的作用了,但是除了这些内置的路由谓词工厂的实现子类之外,实际上还需要进行一些功能上的扩充需要,例如:有些用户只允许在每天的“08:0O"、“12:00”、“16:00”.“20:00”、“24:0O”这五个时间点上进行微服务的调用,这种谓词路由工厂就不存在了,就必须由开发者自行创建。
内置路由谓词工厂的基本的继承结构:
【接口】RoutePredicateFactory .
l【抽象类】AbstractRoutePredicateFactory .
推荐做法就是在项目之中通过继承AbstractRoutePredicateFactory 抽象类来实现自定义路由谓词工厂的开发,是比较合适的,毕竟相比较接口来讲,抽象类会有部分的公共实现。
1、 【gateway-9501子模块】创建一个用于保存时间分段的配置类,这个配置类可以实现application.yml 中的配置写入
package com.yootk.gateway.predicate.config;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
public class TimeSubsectionConfig {
private Set<String> section = new LinkedHashSet<>(); // 按照顺序保存不重复的数据
public void setSection(List<String> section) {
this.section.addAll(section);
}
public Set<String> getSection() {
return section;
}
}
2、 【gateway-9501子模块】创建自定义路由谓词工厂类
package com.yootk.gateway.predicate.factory;
import com.yootk.gateway.predicate.config.TimeSubsectionConfig;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
@Component
public class DefaultTimeSubsectionRoutePredicateFactory
extends AbstractRoutePredicateFactory<TimeSubsectionConfig> {
// 既然需要根据当前的时间进行判断,所以这个时候就需要有一个时间的转换处理类
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("HH:mm");
public DefaultTimeSubsectionRoutePredicateFactory() {
super(TimeSubsectionConfig.class); // 传递配置项
}
@Override
public Predicate<ServerWebExchange> apply(TimeSubsectionConfig config) {
return serverWebExchange -> {
String now = LocalTime.now().format(FORMATTER); // 获取当前的时和分
return config.getSection().contains(now); // 存在判断
};
}
@Override
public List<String> shortcutFieldOrder() { // 配置项
// 按照“,”定义多个不同的配置项,而后此时会自动进行数据的拆分
return Collections.singletonList("section"); // 配置项的名称定义
}
@Override
public ShortcutType shortcutType() { // 明确的定义分割项
return ShortcutType.GATHER_LIST;
}
}
3、【gateway-9501子模块】修改application.yml配置文件,追加新的自定义路由谓词工厂名称
server:
port: 9501 # 网关服务的端口
spring:
application:
name: microcloud.gateway # 网关名称
cloud: # Cloud配置
nacos: # Nacos注册中心配置
discovery: # 发现服务
server-addr: nacos-server:8848 # Nacos服务地址
namespace: 96c23d77-8d08-4648-b750-1217845607ee # 命名空间ID
group: MICROCLOUD_GROUP # 一般建议大写
cluster-name: MuyanGateway # 配置集群名称
gateway: # 网关配置
discovery: # 服务发现
locator: # 资源定位
enabled: false # 取消默认路由配置,默认值就是false
routes: # 定义静态路由
- id: dept # 路由标记
uri: lb://dept.provider # 负载均衡调用
predicates: # 路由谓词工厂
- Path=/** # 匹配全部的路径
- DefaultTimeSubsection=08:00,16:00,20:00,24:00
过滤的处理指的是可以自动的完成某些的功能配置,例如:可以对用户的权限进行检测,或者是在请求上添加额外的数据信息,在SpringCloudGateway组件里面提供有过滤的处理支持能力。
所有的请求客户端需要通过网关来进行微服务的调用,在调用之前肯定要进行断言处理,判断能否访问指定的接口资源,或者是否有指定的头信息存在,而在最终进行服务调用的时候,还可以进行过滤处理,可以自动的在用户请求上添加一些头信息,或者是传递额外的数据内容。这样在过滤操作结构之中就存在有两个阶段: PRE阶段(请求转发之前)、POST阶段(请求响应之前)。
1、【gateway-9501子模块】如果要想观察过滤的作用,下面可以使用一个内置的过滤器,来为请求自动的添加一个头信息 以及 别的过滤操作
server:
port: 9501 # 网关服务的端口
spring:
application:
name: microcloud.gateway # 网关名称
cloud: # Cloud配置
nacos: # Nacos注册中心配置
discovery: # 发现服务
server-addr: nacos-server:8848 # Nacos服务地址
namespace: 96c23d77-8d08-4648-b750-1217845607ee # 命名空间ID
group: MICROCLOUD_GROUP # 一般建议大写
cluster-name: MuyanGateway # 配置集群名称
gateway: # 网关配置
discovery: # 服务发现
locator: # 资源定位
enabled: false # 取消默认路由配置,默认值就是false
routes: # 定义静态路由
- id: dept # 路由标记
uri: lb://dept.provider # 负载均衡调用
predicates: # 路由谓词工厂
- Path=/** # 匹配全部的路径
filters: # 配置过滤器
- AddRequestHeader=Request-Token-Muyan, www.yootk.com # 追加头信息
- AddRequestParameter=message, edu.yootk.com # 添加参数
- MapRequestHeader=Request-Token-Yootk, Muyan-Yootk-Key # 头信息转换
- RemoveRequestHeader=Request-Token-1234 # 删除指定的头信息
- RedirectTo=302, https://www.yootk.com # 必须设置3xx的代码,同时进行重定向
这样每当请求传递到网关之后,那么就可以直接添加一个头信息,并且将头信息随着请求发送到目标微服务上。由于此时微服务端可以实现所有请求头信息的输出,通过后台的日志可以清楚的发现内容已经存在了,所以过滤可以帮助用户完善一些业务的处理,同时具有自动的处理支持能力。
在SpringCloudGateway之中只要按照既定的结构也是可以轻松的实现自定义过滤工厂的配置的,那么下面首先通过基本的操作形式观察过滤工厂的具体定义结构。
1、【gateway-9501子模块】创建一个日志过滤工厂
package com.yootk.gateway.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractNameValueGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
@Slf4j
@Component
public class LogGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {// 日志过滤工厂类
@Override
public GatewayFilter apply(NameValueConfig config) { // 过滤处理
return (exchange, chain) -> { // 编写具体的过滤实现
ServerHttpRequest request = exchange.getRequest().mutate().build();
ServerWebExchange webExchange = exchange.mutate().request(request).build();
log.info("配置参数:{},{}", config.getName(), config.getValue()); // application.yml
log.info("请求路径:{}、请求模式:{}", request.getPath(), request.getMethod());
return chain.filter(webExchange); // 向下执行
};
}
}
2、 【gateway-9501子模块】所有的过滤器如果要想生效则必须在application.yml之中进行配置。
server:
port: 9501 # 网关服务的端口
spring:
application:
name: microcloud.gateway # 网关名称
cloud: # Cloud配置
nacos: # Nacos注册中心配置
discovery: # 发现服务
server-addr: nacos-server:8848 # Nacos服务地址
namespace: 96c23d77-8d08-4648-b750-1217845607ee # 命名空间ID
group: MICROCLOUD_GROUP # 一般建议大写
cluster-name: MuyanGateway # 配置集群名称
gateway: # 网关配置
discovery: # 服务发现
locator: # 资源定位
enabled: false # 取消默认路由配置,默认值就是false
routes: # 定义静态路由
- id: dept # 路由标记
uri: lb://dept.provider # 负载均衡调用
predicates: # 路由谓词工厂
- Path=/** # 匹配全部的路径
filters: # 配置过滤器
- RemoveRequestHeader=Request-Token-Muyan # 删除指定的头信息
- Log=muyan, yootk # 过滤器=NameValueConfig(name属性, value属性)
那么此时就根据已有的过滤工厂结构实现了自定义的过滤器配置,从而可以实现更加方便的开发,过滤器是一个公共且可以自动完成的处理机制。
在SpringCloudGateway里面所有的请求和响应的处理操作全部都需要经过一系列的过滤器来进行处理,通过断言来确定该路由是否执行,而后具体的执行过程之中就要使用到过滤器。
网关的主要作用是进行代理资源的请求转发,而在SpringCloudGateway中为了实现这样的转发处理机制,提供了一个GlobalFilter(全局过滤)接口,该接口的核心功能实现了一个请求转发的处理操作,在进行请求转发处理之前需要执行一些过滤的处理操作(认证与授权操作);
网关的请求最终都是由GlobalFilter实现转发处理的,所以开发者可以通过全局过滤器来实现统一的业务处理需求,例如:认证与授权检测、服务限流等。
SpringCloudGateway 之中比较强悍的一点在于里面有大量的内置过滤器的存在,这些过滤器不管用户是否使用已经为用户安排好了,但是如果要想获取到这些过滤器的信息,那么就需要通过如下的步骤来完成(通过Actuator来获取)。
1、【microcloud项目】修改build.gradle配置文件,为gateway-9501子模块添加所需要的依赖库:
project(":gateway-9501") { // 网关模块
dependencies {
implementation('com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery') {
exclude group: 'com.alibaba.nacos', module: 'nacos-client' // 移除旧版本的Nacos依赖
}
implementation(libraries.'nacos-client') // 引入与当前的Nacos匹配的依赖库
implementation('org.springframework.cloud:spring-cloud-starter-gateway') // 网关依赖
implementation('org.springframework.boot:spring-boot-starter-actuator') // Actuator依赖库
}
}
2、 【gateway-9501子模块】如果要想启用Actuator监控,还需要修改application.yml配置文件,在这个配置文件之中启用全部的Actuator 监控终端
management:
server:
port: 9090 # Actuator端口
endpoints:
web:
exposure:
include: "*" # 开启全部的监控终端
base-path: /actuator # 访问子路径
3、【Postman测试】启动当前的网关应用模块,随后通过 Postman进行接口调用测试得到全局过滤
gateway-9501:9090/actuator/gateway/globalfilters
如果要想去研究这些过滤器,那么首先需要解决的问题就是过滤器的实现类的继承结构,所有的过滤器都会继承一个公共的父接口—— org.springframework.cloud.gateway.filter.GlobalFilter,在这个接口里面提供有一个过滤的处理方法:
既然此时已经找到了父接口,那么就可以依据这个父接口查看其所有可能存在的实现子类。
在网关之中的过滤器的开发本身是非常容易的,因为只要有GlobalFilter 接口,而后定义一个具体的实现子类,并且将其配置到Spring容器之中,那么就可以直接使用了,本次创建一个复合全局过滤器。
1、【gateway-9501子模块】创建全局过滤器,同时在该过滤器中配置有两个过滤的操作处理。
package com.yootk.gateway.filter.global;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import reactor.core.publisher.Mono;
@Configuration // 向容器之中进行注册
@Slf4j // 启用日志注解
public class CombinedGlobalFilter { // 自定义过滤器
@Bean // 进行Bean注册
@Order(-20) // 数值越小,执行越靠前
public GlobalFilter getFirstFilter() { // 创建一个过滤器
return (exchange, chain) -> {
log.info("【FirstFilter - PRE阶段】请求ID:{}、请求路径:{}",
exchange.getRequest().getId(), exchange.getRequest().getPath());
return chain.filter(exchange).then(Mono.fromRunnable(()->{
log.info("【FirstFilter - POST阶段】请求ID:{}、请求路径:{}",
exchange.getRequest().getId(), exchange.getRequest().getPath());
}));
};// 创建过滤器的实例
}
@Bean // 进行Bean注册
@Order(-10) // 数值越小,执行越靠前
public GlobalFilter getSecondFilter() { // 创建二个过滤器
return (exchange, chain) -> {
log.info("【SecondFilter - PRE阶段】请求ID:{}、请求路径:{}",
exchange.getRequest().getId(), exchange.getRequest().getPath());
return chain.filter(exchange).then(Mono.fromRunnable(()->{
log.info("【SecondFilter - POST阶段】请求ID:{}、请求路径:{}",
exchange.getRequest().getId(), exchange.getRequest().getPath());
}));
};// 创建过滤器的实例
}
}
2、【Postman 工具】既然已经定义好了全局过滤器,那么就需要通过Actuator监控来查看该过滤器是否存在。gateway-9501:9090/actuator/gateway/globalfilters
3、【Postman 工具】既然网关已经启用了过滤器,那么随后就需要通过测试来观察此过滤器是否正常执行。
【FirstFilter - PRE阶段】请求ID:065eedeb-2、请求路径:/provider/dept/list
【SecondFilter - PRE阶段】请求ID:065eedeb-2、请求路径:/provider/dept/listFlippingproperty:dept.providerribbon.ActiveConnectionsLimittouseNEXTpropertyniws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
【SecondFilter - POST阶段】请求ID:065eedeb-2、请求路径:/provider/dept/lis
【FirstFilter - POST阶段】请求ID:065eedeb-2、请求路径:/provider/dept/list
此时的过滤器使用了Order配置了执行的顺序,所以在执行的时候就会按照:
Forward就是一个转发的操作含义,SpringCloudGateway网关是基于WebFlux 技术开发出来的,这样除了可以实现其下游资源的转发之外,也可以在网关的内部定义所需的服务接口,此时就可以在网关中配置采用“forward://路径”的形式实现
如果在 application.yml里面配置了以“forward:/”开头的路径,这个时候就会使用ForwardRoutingFilter进行处理,由于此概念比较抽象,下面还是基于以上的模型,动手来实现一个该操作。
1、【gateway-9501子模块】在网关内部定义一个访问资源,用于实现内部转换操作。
package com.yootk.gateway.action;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ServerWebExchange;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/gateway/action/*") // 定义访问父路径
public class GatewayAction { // 网关Action
@RequestMapping("globalforward") // 定义子路径
public Map<String, String> forward(ServerWebExchange exchange) {
Map<String, String> result = new HashMap<>(); // 保存所有的处理结果
result.put("message", "forward"); // 信息保存
result.put("requestId", exchange.getRequest().getId());
result.put("requestPath", exchange.getRequest().getPath().toString());
return result;
}
}
再次提醒的是,此时定义的Action是在网关内部定义的一个路径,该路径没有进行所谓的转发处理,仅仅是在网关的内部实现了信息的显示
2、【gateway-9501子模块】修改application.yml配置文件,配置一个本地转发路由
spring:
application:
name: microcloud.gateway # 网关名称
cloud: # Cloud配置
nacos: # Nacos注册中心配置
discovery: # 发现服务
server-addr: nacos-server:8848 # Nacos服务地址
namespace: 96c23d77-8d08-4648-b750-1217845607ee # 命名空间ID
group: MICROCLOUD_GROUP # 一般建议大写
cluster-name: MuyanGateway # 配置集群名称
gateway: # 网关配置
discovery: # 服务发现
locator: # 资源定位
enabled: false # 取消默认路由配置,默认值就是false
routes: # 定义静态路由
- id: forward_example # 配置路由ID
uri: forward://globalforward # 配置本地转发
predicates:
- Path=/globalforward # 配置访问路径
filters:
- PrefixPath=/gateway/action # 路径前缀
- id: dept # 路由标记
uri: lb://dept.provider # 负载均衡调用
predicates: # 路由谓词工厂
- Path=/** # 匹配全部的路径
filters: # 配置过滤器
- RemoveRequestHeader=Request-Token-Muyan # 删除指定的头信息
- Log=muyan, yootk # 过滤器=NameValueConfig(name属性, value属性)
3、【Postman工具】此时已经配置完成了全部的结构,那么下面就可以通过Postman访问forward 配置的路径:
gateway-9501:9501/globalfoward
此时的程序已经实现了内部的路径匹配,这样直接访问forward配置的路径之后就可以跳转到内部实现,但是这种操作是由专属的全局过滤器来完成的
SpringCloudGateway网关开发之中采用的是Reactive 开发模型实现的,采用的是响应式的编程方式实现代码
SpringCloudGateway 网关技术,除了可以实现微服务的代理之外,实际上还可以访问各种外网的资源,此时只需要设置到正确的服务地址即可。
1、【gateway-9501子模块】下面配置一个外网访问,定义一个“/muyan-yootk”的断言地址,而后让其跳转到沐言优拓的官方首页: ““https://www.yootk.com/resources”; 此时所给出的资源采用的是“https”开头,修改application.yml配置文件
spring:
application:
name: microcloud.gateway # 网关名称
cloud: # Cloud配置
nacos: # Nacos注册中心配置
discovery: # 发现服务
server-addr: nacos-server:8848 # Nacos服务地址
namespace: 96c23d77-8d08-4648-b750-1217845607ee # 命名空间ID
group: MICROCLOUD_GROUP # 一般建议大写
cluster-name: MuyanGateway # 配置集群名称
gateway: # 网关配置
discovery: # 服务发现
locator: # 资源定位
enabled: false # 取消默认路由配置,默认值就是false
routes: # 定义静态路由
- id: yootk_example # 配置路由ID
uri: https://www.yootk.com/resources # 设置访问路径的匹配
predicates:
- Path=/muyan-yootk # 配置访问路径
2、【Postman工具】重新启动路由应用,随后通过Postman工具来进行访问,路径:
gateway-9501.9501/muyan-yootk
额外说明:
对于现在很多的服务对接来讲,使用RESTful设计架构的交互模式已经非常的多了,那么在以后的开发过程之中会通过REST 实现各类服务的整合,此时最佳的做法就是通过网关集成。
现在的问题就来了,具体的整个处理是由那个过滤器完成的呢?于是可以告诉大家,是由Netty过滤器完成的。
可以发现在进行最终资源响应的时候还会通过NettyWriteResponseFilter来处理,在NettyRoutingFilter之中所进行的响应,仅仅是服务端数据的响应接收,而不是最终响应给微服务客户端的数据,而这个响应的数据需要NettyWriteResponseFilter过滤器来实现。
在使用SpringCloudGateway进行HTTP或HTTPS资源访问的时候,所采用的是何种模式?
采用的是Netty开发模型完成的服务资源的处理。
在微服务设计的时候,集群是肯定要优先考虑到的问题,包括在之前进行网关基本整合的时候也整合过多个微服务的节点,在整合的过程里面将地址使用了“lb://服务ID”的形式进行注册,随后就可以启动负载均衡处理了。
网关可以采用“lb://服务名称”的形式实现某一个微服务集群的转发代理操作,同时在进行调用时还提供有负载均衡的支持(LoadBalancer),而在SpringCloudGateway中针对于负载均衡代理有两个GlobalFilter实现类:
1、默认的负载均衡代理类(LoadBalancerClientFilter)
2、响应式负载均衡代理类(ReactiveLoadBalancerClientFilter)
通过源代码可以发现,LoadBalancerClientFilter 这个网关过滤器已经不再推荐使用了,那么随后可以考虑通过Actuator给出的访问路径来观察当前的网关里面所支持的过滤器是否包含有当前的这个过滤器。
gateway-9501:9090/actuator/gateway/globalfilters
在当前的情况下,默认所使用的负载均衡过滤器是一个不推荐继续使用的过滤器了,因为这个过滤器实现过程之中采用的是一种同步的处理形式完成的,这一点对于网关的性能是非常致命的,所以应该考虑更换为ReactiveLoadBalancerClientFilter过滤器了
如何更换?
可以发现此时的程序类之中所使用的org.springframework.cloud.loadbalancer开发包是不存在的,所以当前这种基于响应式的负载均衡的过滤器肯定无法使用。解决的方案只有一个:引入新的依赖库。
1.【microcloud项目】如果要想使用响应式的负载均衡过滤器则需要引入一个新的依赖库,修改build.gradle配置文件:
project(":gateway-9501") { // 网关模块
dependencies {
implementation('com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery') {
exclude group: 'com.alibaba.nacos', module: 'nacos-client' // 移除旧版本的Nacos依赖
}
implementation(libraries.'nacos-client') // 引入与当前的Nacos匹配的依赖库
implementation('org.springframework.cloud:spring-cloud-starter-gateway') // 网关依赖
implementation('org.springframework.boot:spring-boot-starter-actuator') // Actuator依赖库
implementation('org.springframework.cloud:spring-cloud-starter-loadbalancer')
}
}
2、【gateway-9501子模块】遗憾的是,此时再次查询出来发现并没有进行过滤器的更换,因为还需要修改application.yml配置文件,关闭默认的负载均衡配置。
spring:
application:
name: microcloud.gateway # 网关名称
cloud: # Cloud配置
loadbalancer:
ribbon:
enabled: false # 关闭默认配置
3、【Postman 工具】配置完成之后,再次查看当前全部网关过滤器配置项,可以发现已经启用了响应式的负载均衡处理。
4、【gateway-9501子模块】虽然此时已经成功的更换了负载均衡加载器,但是随后又会出现有一个新的警告信息,这个警告信息是在每次启动网关的时候出现的日志信息。
中文含义: SpringCloud负载均衡在当前所使用默认缓存是一个Caffeine缓存组件(当前默认的缓存组件,性能贼高,命中率非常的高,同时基于了LRU算法),是Guava后续版本延续
5、【microcloud项目】修改 build.gradle配置文件,引入 Caffeine缓存组件依赖库:
dependencies.gradle
ext.versions = [
caffeine : '3.0.4', // Caffeine缓存组件版本号
]
ext.libraries = [
// 以下的配置为LoadBalancer所需要的Caffeine组件有关依赖
'caffeine' : "com.github.ben-manes.caffeine:caffeine:${versions.caffeine}"
]
build.gradle
project(":gateway-9501") { // 网关模块
dependencies {
implementation('com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery') {
exclude group: 'com.alibaba.nacos', module: 'nacos-client' // 移除旧版本的Nacos依赖
}
implementation(libraries.'nacos-client') // 引入与当前的Nacos匹配的依赖库
implementation('org.springframework.cloud:spring-cloud-starter-gateway') // 网关依赖
implementation('org.springframework.boot:spring-boot-starter-actuator') // Actuator依赖库
implementation('org.springframework.cloud:spring-cloud-starter-loadbalancer')
implementation(libraries.'caffeine')
}
}
一切都配置完成之后,当前的网关在启动时候就没有了之前的警告信息,同时对于网关的处理性能也有了很大的提高,所以这个时候才是一个在实际的应用开发中所应该具有的网关的样子。
SpringCloudGateway是一个独立的应用, 而且这种应用全部都是基于SpringBoot基础之上开发出来的, 在学习SpringBoot之中讲解过了关于Actuator + Prometheus + Grafana 实现服务监控的操作案例,那么本次可以直接利用已经搭建完成的监控服务器(Prometheus + Grafana)实现网关监控数据的获取。
1、【microcloud项目】如果要想进行服务的监控数据采集(所有的数据被采集到Prometheus之中),那么就需要修改项目所对应的依赖库,修改build.gradle配置文件:
dependencies.gradle
ext.versions = [
micrometer : '1.7.0', // Prometheus相关监控依赖,与服务部署的版本号相同
]
ext.libraries = [
// 以下的配置为Prometheus服务整合
'micrometer-registry-prometheus': "io.micrometer:micrometer-registry-prometheus:${versions.micrometer}",
'micrometer-core': "io.micrometer:micrometer-core:${versions.micrometer}",
]
build.gradle
project(":gateway-9501") { // 网关模块
dependencies {
implementation('com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery') {
exclude group: 'com.alibaba.nacos', module: 'nacos-client' // 移除旧版本的Nacos依赖
}
implementation(libraries.'nacos-client') // 引入与当前的Nacos匹配的依赖库
implementation('org.springframework.cloud:spring-cloud-starter-gateway') // 网关依赖
implementation('org.springframework.boot:spring-boot-starter-actuator') // Actuator依赖库
implementation('org.springframework.cloud:spring-cloud-starter-loadbalancer')
implementation(libraries.'caffeine')
implementation(libraries.'micrometer-registry-prometheus')
implementation(libraries.'micrometer-core')
}
}
2、【gateway-9501子模块】修改application.yml配置文件,启用metrics监控
spring:
application:
name: microcloud.gateway # 网关名称
cloud: # Cloud配置
loadbalancer:
ribbon:
enabled: false # 关闭默认配置
nacos: # Nacos注册中心配置
discovery: # 发现服务
server-addr: nacos-server:8848 # Nacos服务地址
namespace: 96c23d77-8d08-4648-b750-1217845607ee # 命名空间ID
group: MICROCLOUD_GROUP # 一般建议大写
cluster-name: MuyanGateway # 配置集群名称
username: muyan # 用户名
password: yootk # 密码
gateway: # 网关配置
metrics:
enabled: true # 启用服务监控
3、【Postman 工具】此时已经启用了网关的Actuator监控,那么下面通过Postman查看一下监控数据:
4、【promethues-server】打开Prometheus配置文件:vi /usr/local/prometheus/prometheus.yml
5、【promethues-server】修改Prometheus配置文件,添加网关的监控路径。
global:
scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
scrape_configs:
- job_name: 'microcloud'
scrape_interval: 10s
scrape_timeout: 5s
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['gateway-server:9090']
6、【prometheus-server】检查当前的Prometheus配置是否正确:
/usr/local/prometheus/promtool check config /usr/local/prometheus/prometheus.yml
7、【prometheus-server】启动当前的Prometheus服务进程: systemctl start prometheus
8、【prometheus-server】查看一下当前端口的占用情况,配置的Prometheus占用的是999端口: netstat -nptl
9、【Prometheus控制台】由于在当前的系统之中已经配置了主机名称,直接输入路径:
http://prometheus-server:9999/
10、【grafana-server】为了更加丰富图形展示的效果,那么可以启用Grafana服务: systemctl start grafana;
11、【grafana控制台】本地系统已经配置了hosts主机名称,所以直接打开控制台观察:
http://grafana-server:3000
在之前所配置的账户信息为: admin / admin
只要是服务开发,那么伴随的一定有两个隐含的内容: 认证与授权、服务监控与警报。
虽然已经可以通过REST接口实现动态路由的配置了,但是这些配置一旦在网关重新启动之后,所有的配置项依然会自动消失,除非你个人可以在每次重新启动之后不厌其烦的进行路由的配置,否则最佳的做法就是找到一个存储空间。
1、【Nacos控制台】在dev的命名空间下定义一个新的配置项,配置项的名称为:Gateway.config
[
{
"id": "dept",
"uri": "lb://dept.provider",
"order": 1,
"predicates": [
{
"name": "Path",
"args": {
"pattern": "/**"
}
}
],
"filters": [
{
"name": "AddRequestHeader",
"args": {
"_genkey_0": "Request-Token-Muyan",
"_genkey_1": "www.yootk.com"
}
}
]
}
]
2、【microcloud项目】为“gateway-9501”添加相关的依赖库,这个地方主要是配置Nacos 有关的依赖库。
project(":gateway-9501") { // 网关模块
dependencies {
implementation('com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery') {
exclude group: 'com.alibaba.nacos', module: 'nacos-client' // 移除旧版本的Nacos依赖
}
implementation(libraries.'nacos-client') // 引入与当前的Nacos匹配的依赖库
implementation('org.springframework.cloud:spring-cloud-starter-gateway') // 网关依赖
implementation('org.springframework.boot:spring-boot-starter-actuator') // Actuator依赖库
implementation('org.springframework.cloud:spring-cloud-starter-loadbalancer')
implementation(libraries.'caffeine')
implementation(libraries.'micrometer-registry-prometheus')
implementation(libraries.'micrometer-core')
}
}
3、【gateway-9501子模块】此时需要通过Gateway对Nacos做监听,而这个监听跟已有的Nacos配置是两回事,所以应该创建一个网关的Nacos配置存储类。
package com.yootk.gateway.config;
import com.alibaba.nacos.api.PropertyKeyConst;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Properties;
@Component
@Data
@ConfigurationProperties(prefix = "spring.cloud.nacos.discovery")
public class GatewayNacosConfig { // 自定义配置存储类
private String serverAddr;
private String namespace;
private String group;
private String dataId = "gateway.config";
private long timeout = 2000;
public Properties getNacosProperties() {
Properties properties = new Properties();
properties.put(PropertyKeyConst.SERVER_ADDR, this.serverAddr);
properties.put(PropertyKeyConst.NAMESPACE, this.namespace);
return properties;
}
}
4、【gateway-9501子模块】创建路由数据的操作业务类。
package com.yootk.gateway.service;
import javassist.NotFoundException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
@Service
@Slf4j
public class DynamicRouteService implements ApplicationEventPublisherAware {
@Autowired
private RouteDefinitionWriter routeDefinitionWriter; // 路由数据的写入
private ApplicationEventPublisher publisher; // 事件发布器
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher; // 保存事件发布器
}
public boolean add(RouteDefinition definition) { // 追加新的路由配置
log.info("增加路由配置项,新的路由ID为:{}", definition.getId()); // 日志输出
try {
this.routeDefinitionWriter.save(Mono.just(definition)).subscribe(); // 配置写入
this.publisher.publishEvent(new RefreshRoutesEvent(this)); // 发布路由事件
} catch (Exception e) {
e.printStackTrace();
log.error("路由增加失败,增加的路由ID为:{}", definition.getId());
return false;
}
return true;
}
public Mono<ResponseEntity<Object>> delete(String id) { // 根据id删除数据
log.info("删除路由配置项,删除的路由ID为:{}", id); // 日志输出
return this.routeDefinitionWriter.delete(Mono.just(id)).then(Mono.defer(() -> {
return Mono.just(ResponseEntity.ok().build());
})).onErrorResume((t) -> {
return t instanceof NotFoundException;
}, (r) -> {
return Mono.just(ResponseEntity.notFound().build());
});
}
public boolean update(RouteDefinition definition) { // 修改已有的路由配置
log.info("更新路由配置项,新的路由ID为:{}", definition.getId()); // 日志输出
try {
this.delete(definition.getId()); // 根据ID删除已有路由
this.routeDefinitionWriter.save(Mono.just(definition)).subscribe(); // 配置写入
this.publisher.publishEvent(new RefreshRoutesEvent(this)); // 发布路由事件
} catch (Exception e) {
log.error("路由更新失败,增加的路由ID为:{}", definition.getId());
return false;
}
return true;
}
}
5、【gateway-9501子模块】定义监听处理类,此时的操作最好可以在容器启动之后运行。
package com.yootk.gateway.listener;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yootk.gateway.config.GatewayNacosConfig;
import com.yootk.gateway.service.DynamicRouteService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.stereotype.Component;
import java.util.concurrent.Executor;
@Component
@Slf4j
public class GatewayNacosRouteListener implements CommandLineRunner {
@Autowired
private DynamicRouteService dynamicRouteService; // 设置业务层处理
@Autowired
private GatewayNacosConfig nacosConfig; // Nacos服务配置
// 因为Nacos里面保存的数据类型是JSON数据,所以需要对JSON进行解析,直接使用Jackson组件了
@Autowired
private ObjectMapper mapper; // 获取Jackson组件
@Override
public void run(String... args) throws Exception {
this.nacosDynmaicRouteListener();// 启动时加载配置
}
public void nacosDynmaicRouteListener() { // 动态路由监听
try {
ConfigService configService = NacosFactory.createConfigService(this.nacosConfig.getNacosProperties());
String content = configService.getConfig(this.nacosConfig.getDataId(), this.nacosConfig.getGroup(), this.nacosConfig.getTimeout()); // 获取指定的配置项
log.info("【网关启动】读取Nacos网关配置项:{}", content); // 日志输出
GatewayNacosRouteListener.this.setRoute(content); // 路由配置
configService.addListener(this.nacosConfig.getDataId(), this.nacosConfig.getGroup(), new Listener() {
@Override
public Executor getExecutor() {
return null;
}
@Override
public void receiveConfigInfo(String configInfo) {
log.info("【网关更新】读取Nacos网关配置项:{}", configInfo); // 日志输出
GatewayNacosRouteListener.this.setRoute(configInfo);
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
private void setRoute(String configInfo) { // 定义路由处理
try { // 将读取到的数据内容转为路由的配置定义,本操作是由Jackson组件完成的
RouteDefinition[] routes = this.mapper.readValue(configInfo, RouteDefinition[].class);
for (RouteDefinition route : routes) {
this.dynamicRouteService.update(route); // 业务更新
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
6、【Nacos控制台】此时通过控制台修改路由的配置项,会自动的进行路由更新。
此时可以实现 SpringCloudGateway网关的动态维护处理,整个的代码就变为了生产环境下可以使用的操作模型,而这样的过程实际上也是一个完善的SpringCloud开发技术的最简化版本了。