微服务架构➖SpringCloud➖Gateway
关于作者
- 作者介绍
🍓 博客主页:作者主页
🍓 简介:JAVA领域优质创作者🥇、一名在校大三学生🎓、在校期间参加各种省赛、国赛,斩获一系列荣誉 🏆、阿里云专家博主 、51CTO专家博主
🍓 关注我:关注我学习资料、文档下载统统都有,每日定时更新文章,励志做一名JAVA资深程序猿👨💻
9. Filter 过滤器工厂(重点)
gateway存在两种相似功能的过滤器,分别是Gateway中的过滤器和Servlet中的过滤器。这些过滤器可以用于修改进入的HTTP请求和返回的HTTP响应。
首先,按照生命周期的不同,这些过滤器可以分为两种类型:
- "pre"类型:在业务逻辑之前执行。
- "post"类型:在业务逻辑之后执行。
其次,按照种类的不同,过滤器可以进一步划分为两种类型:
- GatewayFilter:需要配置特定的路由才能生效。如果需要应用到全局路由,需要配置DefaultFilters。
- GlobalFilter:全局过滤器,无需配置特定路由,会在系统初始化时应用到所有路由上。全局过滤器可以用于统计请求次数、限流、验证令牌、拦截IP黑名单、处理跨域问题等。实际上,它们本质上都是过滤器。
通过使用这些过滤器,我们可以对进入的HTTP请求和返回的HTTP响应进行必要的修改和处理,例如限制以"135" 等其他开头的电话号码或限制特定IP的访问。
自定义过滤器
9.1 全局过滤器 myGlobalFilter
java
package com.zmz.filter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.HashMap;
/**
* @ProjectName: 04-hystrix
* @Package: com.zmz.filter
* @ClassName: GlobalFilter
* @Author: 张晟睿
* @Date: 2022/10/9 16:32
* @Version: 1.0
*/
/**
* 对过滤器进行排序
*/
@Component
public class myGlobalFilter implements GlobalFilter, Ordered {
/**
* 这个就是过滤方法
* 过滤器链模式
* 责任链模式
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//针对请求的过滤 拿到请求头 header url 参数
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
System.out.println(path);
HttpHeaders headers = request.getHeaders();
System.out.println(headers);
String name = request.getMethod().name();
System.out.println(name);
String hostName = request.getRemoteAddress().getHostName();
//request.getHeaders().getHost().getHostString() 拿到ipv4 上图为0:0:0:0:0:0:0:1 为ipv6地址
System.out.println(hostName);
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().set("content-type","application/json;charset=utf-8");
//组装业务返回值
HashMap<String, Object> map = new HashMap<>();
map.put("code", HttpStatus.UNAUTHORIZED.value());
map.put("msg", "未授权token");
ObjectMapper objectMapper = new ObjectMapper();
byte[] bytes = new byte[0];
try {
bytes = objectMapper.writeValueAsBytes(map);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
//获取字节工厂-> 包装成databuffer
DataBuffer wrap = response.bufferFactory().wrap(bytes);
// 放行 chain.filter(exchange);
return response.writeWith(Mono.just(wrap));
// return chain.filter(exchange);
}
/**
* 指定顺序的方法,越小越先执行
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
9.2 IP黑名单过滤器IPCheckFilter
JAVA
package com.zmz.filter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
/**
* 网关里面 过滤器
* ip拦截
* 请求都有一个源头
* 电话 144 027 010
* 请求------->gateway------->service
* 黑名单 black_list
* 白名单 white_list
* 根据数量
* 像具体的业务服务 一般黑名单
* 一般像数据库 用白名单
*/
/**
* @ProjectName: 04-hystrix
* @Package: com.zmz.config
* @ClassName: IPCheckFilter
* @Author: 张晟睿
* @Date: 2022/10/9 16:57
* @Version: 1.0
*/
@Component
public class IPCheckFilter implements GlobalFilter, Ordered {
public static final List<String> BLACK_LIST = Arrays.asList("127.0.0.1","8.120.3.58");
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String hostString = request.getHeaders().getHost().getHostString();
if (!BLACK_LIST.contains(hostString)) {
return chain.filter(exchange);//放行
}
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().set("content-type", "application/json;charset=utf-8");
HashMap<String, Object> result = new HashMap<>(4);
result.put("code",601);
result.put("msg","你是黑名单");
ObjectMapper objectMapper = new ObjectMapper();
byte[] bytes = new byte[0];
try {
bytes = objectMapper.writeValueAsBytes(result);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
DataBuffer wrap = response.bufferFactory().wrap(bytes);
return response.writeWith(Mono.just(wrap));
// return chain.filter(exchange);
}
@Override
public int getOrder() {
return 2;
}
}
9.3 Token校验过滤器 TokenCheckFilter
JAVA
package com.zmz.filter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
/**
* token校验
* @ProjectName: 04-hystrix
* @Package: com.zmz.filter
* @ClassName: TokenCheckFilter
* @Author: 张晟睿
* @Date: 2022/10/9 19:04
* @Version: 1.0
*/
@Component
public class TokenCheckFilter implements GlobalFilter, Ordered {
public static final List<String> ALLOW_URL = Arrays.asList("/login-service/doLogin","/myUrl","/doLogin");
@Autowired
public StringRedisTemplate redisTemplate;
/**
* 前提是? 和前端约定好 一般放在请求头里面 一般key Authorization value bearer token
* 1.拿到请求url
* 2.判断放行
* 3.拿到请求头
* 4.拿到token
* 5.校验
* 6.放行/拦截
*
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
if (ALLOW_URL.contains(path)) {
return chain.filter(exchange);
}
HttpHeaders headers = request.getHeaders();
List<String> authorization = headers.get("Authorization");
if (!CollectionUtils.isEmpty(authorization)) {
String token = authorization.get(0);
if (StringUtils.hasText(token)) {
//约定好的token 去掉头部的bearer
/*
在截取真正的token时,一定注意bearer后面有一个空格
*/
String realtoken = token.replaceFirst("bearer ", "");
if (StringUtils.hasText(realtoken) && redisTemplate.hasKey(realtoken)) {
return chain.filter(exchange);
}
}
}
//校验失败,进行拦截处理
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().set("content-type","application/json;charset=utf-8");
HashMap<String, Object> result = new HashMap<>(4);
result.put("code", HttpStatus.UNAUTHORIZED.value());
result.put("msg","未授权token");
ObjectMapper objectMapper = new ObjectMapper();
byte[] bytes = new byte[0];
try {
bytes = objectMapper.writeValueAsBytes(result);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
DataBuffer wrap = response.bufferFactory().wrap(bytes);
return response.writeWith(Mono.just(wrap));
}
@Override
public int getOrder() {
return 0;
}
}
10. 限流
通俗地说,限流是一种控制用户在一段时间内访问资源次数的方法,以减轻服务器的压力。限流大致可以分为两种类型:
- IP限流:如果在10秒内同一个IP地址的访问次数超过3次,将限制其继续访问,需要在一段时间后才能再次访问。
- 请求量限流:在一段时间内(窗口期),如果请求次数达到设定的阈值,将直接拒绝后续的访问,需要在一段时间后才能继续访问。这种限流粒度可以细化到每个API(URL)或每个服务。
常用的限流算法包括漏斗算法、令牌桶算法和窗口滑动算法。此外,还有计数器算法等其他限流模型可供选择。
入不敷出
- 在处理之前,所有的请求需要获取一个可用的令牌才能被处理。
- 根据限流大小,按照一定的速率向令牌桶中添加令牌。
- 令牌桶设置了最大的令牌容量限制,当令牌桶已满时,新添加的令牌将被丢弃或拒绝。
- 当请求到达时,首先需要从令牌桶中获取令牌,只有拿到令牌后才能进行其他的业务逻辑处理。在处理完业务逻辑之后,将令牌直接删除。
- 令牌桶还设有最低限额,当令牌桶中的令牌数量达到最低限额时,请求处理完后不会删除令牌,从而确保有足够的限流。
10.1 Gateway 结合 redis 实现请求量限流
Spring Cloud Gateway 已经内置了一个 RequestRateLimiterGatewayFilterFactory
,我们 可以直接使用。 目前 RequestRateLimiterGatewayFilterFactory
的实现依赖于 Redis,所以我们还要引入 spring-boot-starter-data-redis-reactive
。
添加依赖
xml
<!--限流要引入 Redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
修改配置文件
yml
spring:
application:
name: gateway-server
cloud:
gateway:
enabled: true #只要加了依赖,默认开启
routes:
- id: login-service-route #路由id,保持唯一
uri: http://localhost:8088 # 或者直接使用uri: lb://login-service 来进行动态路由 #uri 统一资源定位符 url统一资源标识符
predicates: #断言是给某个路由来做的 断言不能给动态路由来配置,只能在写好的predicates断言内才能生效
- Path=/doLogin #断言匹配规则,只要匹配上/doLogin 就往uri转发 并且将路径带上
- After=2022-10-09T17:18:39.831+08:00[Asia/Shanghai]
- Method=GET,POST
# - Query=name,admin. #正则表达式的值
# - Path=/mySerivice/** #多个路径进行匹配
filters:
- name: RequestRateLimiter #这个是过滤器的名称
args: #过滤器的参数
key-resolver: '#{@ipKeyResolver}' #通过spel表达式取IOC容器中的值
redis-rate-limiter.replenishRate: 1 #生成令牌的速度
redis-rate-limiter.burstCapacity: 3 #桶容量
discovery:
locator:
enabled: true #开启动态路由 开启通过业务名称找到对应服务的功能
lower-case-service-id: true #服务名称小写开启
# globalcors:
# corsConfigurations:
# '[/**]':
# allowCredentials: true # 可以携带cookie
# allowedHeaders: '*'
# allowedMethods: '*'
# allowedOrigins: '*'
配置文件说明
在上述配置文件中,包含了对Redis信息的配置,并配置了RequestRateLimiter的限流过滤器。该过滤器需要配置三个参数:
- burstCapacity:令牌桶的总容量。
- replenishRate:令牌桶每秒的平均填充速率。
- key-resolver:用于限流的键解析器的Bean对象的名称。它使用SpEL表达式,通过
@beanName
从Spring容器中获取相应的Bean。
创建配置类 RequestRateLimiterConfig
java
package com.zmz.config;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import reactor.core.publisher.Mono;
/**
* @ProjectName: 04-hystrix
* @Package: com.zmz.config
* @ClassName: RequestLimitConfig
* @Author: 张晟睿
* @Date: 2022/10/10 8:09
* @Version: 1.0
*/
/**
* 自定义限制的一个类
*/
@Configuration
public class RequestLimitConfig {
/**
* Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
* 报错是IOC中存在两个bean实例,需要选择一个进行注入
*/
//针对某个ip来进行限流 每个ip只能访问3次
//ioc注入默认以方法名称进行注入的
@Bean
@Primary
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getHeaders().getHost().getHostString());
}
//针对某个路径来进行限流 每个路径在规定时间内只能访问3次
//api 接口限制 新一代网关
@Bean
public KeyResolver apiKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getPath().value());
}
}
10.2 跨域配置
跨域? ajax 同源策略 8080、8081,因为网关是微服务的边缘 所有的请求都要走网关 跨域的配置只需要写在网关。
java
package com.zmz.config;
import org.springframework.context.annotation.Bean;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;
/**
* @ProjectName: 04-hystrix
* @Package: com.zmz.filter
* @ClassName: CorsConfig
* @Author: 张晟睿
* @Date: 2022/10/10 8:43
* @Version: 1.0
*/
//跨域配置 通过bean注入 或者 通过yml配置文件进行配置
public class CorsConfig {
@Bean
public CorsWebFilter corsWebFilter() {
CorsConfiguration cors = new CorsConfiguration();
cors.addAllowedHeader("*");
cors.addAllowedMethod("*");
cors.addAllowedOrigin("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**",cors);
return new CorsWebFilter(source);
}
}
yml
spring:
cloud:
gateway:
globalcors:
corsConfigurations:
'[/**]':
allowCredentials: true # 可以携带cookie
allowedHeaders: '*'
allowedMethods: '*'
allowedOrigins: '*'
11. 健康状态检查
导入依赖
xml
<!-- 健康检查的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
添加配置文件
yml
management:
endpoints:
web:
exposure:
include: '*' #暴露检查的端点