说明:功能类似于Zuul,比Zuul1性能更好。
一、Gateway网关使用:
1.导入gateway依赖包:
<dependencies>
<!-- 导入Eureka的Client端依赖包 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 导入Actuator依赖包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 导入Gateway依赖包 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
</dependencies>
2.创建gateway启动类:
@SpringBootApplication //标识为启动类
@EnableDiscoveryClient //注册到注册中心
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class); //加载启动类
}
}
3.application.yml中基础配置:
spring:
application:
name: mall-gateway
cloud:
gateway: #配置gateway
discovery:
locator:
enabled: false #关闭自动路由
lower-case-service-id: true #请求路径识别小写
server:
port: 10004
eureka:
client:
service-url: #导入Eureka Server配置的地址
defaultZone: http://127.0.0.1:10000/eureka/
management: #开启actuator-endpoint
security:
enabled: false #关闭security
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: alwas
4.配置路由规则+过滤器:
(1)方式1,application.yml方式定义:
spring:
...
cloud:
gateway: #配置gateway信息
...
routes: #配置(路由)要拦截的微服务,可以配置多个路由,每个路由以- 开头
- id: mall-usercenter #路由id,可以省略
uri: lb:MALL-USERCENTER #请求转发时URL显示为此路径
predicates:
- Path=/** #配置(路由)需要拦截的路径
filters: #由 http://127.0.0.1:端口/yml/接口路径 -> 变为 http://127.0.0.1:端口/接口路径 -> 最终变成 http://MALL-USERCENTER/接口路径
- StripPrefix=1
(2)方式2,java代码方式定义:
@Configuration
public class GatewayConfig {//java代码方式定义
@Autowired
MyGatewayFilter myGatewayFilter;
@Bean
@Order
public RouteLocator route(RouteLocatorBuilder b) {
return b.routes().route("mall-usercenter", //路由id,可以省略,可以配置多个路由
//添加各种路由规则:
r -> r.path("/usercenter/**") //1.Path方式,配置(路由)需要拦截的路径,可以配置多个path
.and().method(HttpMethod.GET) //2.Method方式,是GET请求时匹配
.and().query("key1") //3.1.RequestParam方式,请求参数包含key1时匹配
.and().query("key1", "value") //3.2.RequestParam方式,请求参数含key1且值为value1时匹配
.and().header("Authorization") //4.1.Header方式,请求头含Authorization属性时匹配
.and().header("Authorization", "xxx") //4.2.Header方式,请求头含Authorization属性且值为xxx时匹配
.and().cookie("token", "xxx") //5.Cookie方式,cookie含token属性且值为xxx时匹配
.and().after(ZonedDateTime.now().plusSeconds(1)) //6.1时间方式,指定时间之后生效(此处为服务启动成功1秒后生效)
.and().before(ZonedDateTime.now().plusMinutes(10)) //6.2时间方式,指定时间之前生效(此处为服务启动后10分钟内有效)
.and().between(null, null) //6.3时间方式,指定时间之间生效
.and().header("头字段") //请求头添加字段
.filters( //添加各种过滤器:
f -> f.stripPrefix(1) //1.StripPrefix过滤器,将请求URL去掉前缀,如/mall-usercenter/insert变成/insert
.prefixPath("mall-usercenter") //2.PrefixPath过滤器,在请求URL上加前缀,如/insert变成/mall-usercenter/insert
.redirect(302, "重定向URL") //3.RedirectTo过滤器,请求路径404时,重定向到指定URL
.saveSession() //4.SaveSession过滤器,调用服务之前保存session,如分布式session
.addRequestHeader("键1", "值1") //5.1.Header过滤器,给请求加上头字段
.addResponseHeader("键1", "值1") //5.2.Header过滤器,给返回加上头字段
.filter(myGatewayFilter) //6.使用自定义过滤器类见(3),如果自定义过滤器implements GlobalFilter(全局)时,此处不用.filter(myGatewayFilter)
)
.uri("lb:MALL-USERCENTER") //请求转发时URL路径
).build();
}
}
(3)自定义过滤器:
@Component //自定义过滤器
public class MyGatewayFilter implements GatewayFilter, Ordered { //如果implements GlobalFilter(全局)时,GatewayConfig中不需要添加.filter(myGatewayFilter)
@Override
public Mono<Void> filter(ServerWebExchange e, GatewayFilterChain c) {
//1.pre型过滤器,在实际接口处理之前执行
... //此处可以完成登录验证等逻辑
return c.filter(e).then(//c.filter将跳到实际接口处理
Mono.fromRunnable(() -> {
//2.post型过滤器,在实际接口处理之后执行
...
})
);
}
@Override
public int getOrder() {
return 0; //设置过滤器优先级,pre型过滤器-数字越大优先级越高。post型过滤器-数字越小优先级越高。
}
}
二、使用Gateway+JWT验证token:
1.pom中添加jwt依赖包:
<!-- 导入jwt依赖包 -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.7.0</version>
</dependency>
2.创建Jwt工具类:
public class JwtUtil {//jwt验证工具类
public String genToken(String userNo) {//生成token
Date curDate = new Date();
return JWT.create().withIssuer("Issuer值").withIssuedAt(curDate).withExpiresAt(new Date(curDate.getTime() + 30 * 24 * 60 * 60 * 1000)) //token过期时间为1个月
.withClaim("userNo", userNo) //使用userNo
.sign(Algorithm.HMAC256("key值"));
}
public boolean verify(String userNo, String token) {//验证token
try {
JWT.require(Algorithm.HMAC256("key值")).withIssuer("Issuer值").withClaim("userNo", userNo).build() //生成JWTVerifier
.verify(token); //验证token
return true; //无报错,验证通过
} catch (Exception e) {
return false; //报错,验证失败
}
}
}
3.在自定义过滤器中验证token:
@Component //自定义过滤器
public class MyGatewayFilter implements GatewayFilter, Ordered {
@Autowired
private JwtUtil jwtUtil;
@Override
public Mono<Void> filter(ServerWebExchange e, GatewayFilterChain c) {
/*
此处验证token等逻辑
*/
ServerHttpRequest request = e.getRequest();
HttpHeaders headers = request.getHeaders();
String token = headers.getFirst("Authorization"); //获取header中的登录token
String userNo = headers.getFirst("userNo"); //获取header中的用户ID
ServerHttpResponse response = e.getResponse();
if (StringUtils.isBlank(token)) { //token为空时,返回错误码
response.setStatusCode(HttpStatus.UNAUTHORIZED); //401
return response.setComplete(); //结束请求
}
if (jwtUtil.verify(token, userNo)) { //token验证不通过时,返回错误码
response.setStatusCode(HttpStatus.FORBIDDEN); //403
return response.setComplete(); //结束请求
}
ServerHttpRequest requestNew = request.mutate().header("userNo", userNo).build();
response.setStatusCode(HttpStatus.OK);
response.getHeaders().add("userNo", userNo);
return c.filter(e.mutate().request(requestNew).response(response).build()); //c.filter将跳到实际接口处理
}
...
}
三、使用Redis限流:
1.添加redis依赖,连接redis:
(1)添加依赖:
<!-- 导入redis限流依赖包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
(2)application.yml配置连接redis:
spring:
application:
name: mall-gateway
redis: #连接redis
host: 127.0.0.1
port: 6379
database: 0
main:
allow-bean-definition-overriding: true #允许bean重载
...
2.创建redis限流类:
@Configuration
public class RedisLimitConfig {//redis限流
@Bean("keyResolver")
@Primary //配置为默认
public KeyResolver keyResolver() {//c.setKeyResolver用到,用来给每次路由请求生成一个Key(限流分组的标识)
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()); //此处基于HostAddress生成key
}
@Bean("usercenterRedisLimit")
@Primary //配置为默认
public RedisRateLimiter usercenterRedisLimit() { //c.setRateLimiter用到,可以定义多个,每个微服务分开限流
return new RedisRateLimiter(10, 50); //池每秒的平均填充速度,池里最大50个
}
}
3.使用redis限流器:
(1)方式1,在gateway配置文件application.yml中使用限流器:
spring:
...
cloud:
gateway: #配置gateway信息
...
routes: #配置(路由)要拦截的微服务,可以配置多个路由,每个路由以- 开头
- id: mall-usercenter #路由id,可以省略
...
filters:
...
- name: RequestRateLimiter #以下配置redis限流
args:
key-resolver: '#{@keyResolver}' #keyResolver为RedisLimitConfig类中变量,用来给每次路由请求生成一个Key(限流分组的标识)
redis-rate-limiter.replenishRate: 10 #池每秒的平均填充速度
redis-rate-limiter.burstCapacity: 50 #池里最大50个
(2)方式2,在gateway配置类中使用限流器:
@Configuration
public class GatewayConfig {//java代码方式配置路由规则
@Autowired
@Qualifier(value = "keyResolver") //指定使用指定bean
KeyResolver keyResolver;
@Autowired
@Qualifier(value = "usercenterRedisLimit") //指定使用指定bean
RateLimiter usercenterRedisLimit;
@Bean
@Order
public RouteLocator route(RouteLocatorBuilder b) {
return b.routes().route(r -> r.path("/usercenter/**").filters(
f -> f.requestRateLimiter( //指定redis限流器
c -> {
c.setKeyResolver(keyResolver); //用来给每次路由请求生成一个Key(限流分组的标识),keyResolver为RedisLimitConfig类中变量
c.setRateLimiter(usercenterRedisLimit); //配置redis限流器,usercenterRedisLimit为RedisLimitConfig类中变量
c.setStatusCode(HttpStatus.BAD_GATEWAY); //触发限流后返回的状态码
}
)
).uri("lb:MALL-USERCENTER"))
.build();
}
}
四、其他配置
1.配置跨域支持,在application.yml中配置:
spring:
...
cloud:
gateway: #配置gateway信息
globalcors: #配置跨域支持
cors-configurations:
'[/**]':
allowedOrigins: #允许将资源共享给请求来源
- "http://127.0.0.1:8080" #配置需要跨域的域名
- "http://www.yyh.com" #配置需要跨域的域名
- "*"
allowCredentials: true #允许跨域携带cookie、token等信息
allowedHeaders: "*" #允许接收所有header
allowedMethods: "*" #允许所有HTTP Method,比如GET、POST等
exposedHeaders: "*" #允许跨域携带所有header
maxAge: 600 #浏览器缓存时间
...
2.全局错误过滤器,返回统一JSON:
(1)全局错误过滤器:
@Component
public class GlobalErrorFilter implements GlobalFilter, Ordered { //全局错误过滤器,对客户端返回统一的JSON串
@Override
public Mono<Void> filter(ServerWebExchange e, GatewayFilterChain c) {
return c.filter(e.mutate().response(
new MyResponse((resp, body) -> Flux.from(body).flatMap(orgBody -> {
byte[] orgContent = new byte[orgBody.readableByteCount()]; //原始响应body的byte[]
orgBody.read(orgContent);
String content = new String(orgContent); //原始响应body的字符串
if (resp.getStatusCode().value() == 500) { //出现500错误时,替换原始响应body,对客户端返回统一JSON串
content = String.format("{\"status\": %d,\"path\":\"%s\"}",
resp.getStatusCode().value(),
e.getRequest().getPath().value());
}
resp.getHeaders().setContentLength(content.length()); //响应内容长度
return resp.writeWith(Flux.just(content).map(bx -> resp.bufferFactory().wrap(bx.getBytes())));
}).then(), e.getResponse())
).build());
}
@Override
public int getOrder() {
return -2; //在WRITE_RESPONSE_FILTER(-1)之前执行
}
}
(2)自定义BiFunction,异常过滤器中,用于写入Body内容的代理类:
public interface MyBiFunction extends BiFunction<ServerHttpResponse, Publisher<? extends DataBuffer>, Mono<Void>> { //自定义BiFunction,异常过滤器中,用于写入Body内容的代理类
}
(3)自定义ServerHttpResponse:
public class MyResponse extends ServerHttpResponseDecorator { //自定义ServerHttpResponse
private MyBiFunction bf; //异常过滤器中,用于写入Body内容的代理类
public MyResponse(MyBiFunction bf, ServerHttpResponse delegate) {
super(delegate);
this.bf = bf;
}
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
return this.bf.apply(getDelegate(), body);
}
}