spring cloud alibaba-Gateway详解
Gateway介绍
在 Spring Cloud Alibaba 生态系统中,Gateway 是一个非常重要的组件,用于构建微服务架构中的网关服务。它基于 Spring Cloud Gateway 进行扩展和优化,提供了更强大的功能和更好的性能。
Gateway功能
- 统一入口
• 微服务架构中,多个服务通常部署在不同的地址和端口上。网关作为统一的入口,将所有服务的请求集中管理,客户端只需要与网关交互,而不需要直接与各个微服务通信。
• 这样可以简化客户端的逻辑,同时提高系统的安全性。 - 请求路由
• 根据请求的特征(如路径、方法、参数等)将请求转发到对应的后端服务。
• 路由规则可以动态配置,方便调整服务的访问路径。 - 负载均衡
• 当后端服务有多个实例时,网关可以根据一定的策略(如轮询、随机、权重等)将请求分发到不同的实例,提高系统的可用性和性能。 - 流量控制
• 限制请求的流量,防止后端服务被过多的请求压垮。
• 常见的流量控制算法包括令牌桶算法、滑动窗口算法等。 - 身份认证
• 对请求进行身份验证,确保只有合法的用户才能访问后端服务。
• 常见的身份验证方式包括基于令牌(如 JWT)、基于用户名和密码等。 - 协议转换
• 将一种协议的请求转换为另一种协议的请求,例如将 HTTP 请求转换为 gRPC 请求。
• 这样可以实现不同协议之间的通信,提高系统的灵活性。 - 系统监控
• 监控网关的运行状态,包括请求量、响应时间、错误率等指标。
• 通过监控可以及时发现系统的问题,提高系统的可靠性。 - 安全防护
• 防止恶意攻击,如 SQL 注入、XSS 攻击、CSRF 攻击等。
• 通过安全策略(如防火墙规则、WAF 等)保护系统免受攻击。
创建网关
创建一个新项目
- 导入依赖
java
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
</dependencies>
- 编写启动类
java
package com.nie.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class GatewayApplicationMain {
public static void main(String[] args) {
SpringApplication.run(GatewayApplicationMain.class,args);
}
}
- 配置application.yml文件
xml
spring:
application:
name: gateway
cloud:
nacos:
server-addr: 127.0.0.1:8848
profiles:
include: route
server:
port: 80
- 配置路由规则 application-route.yml
java
spring:
cloud:
gateway:
routes:
- id: order-route
uri: lb://service-order
predicates:
- Path=/api/order/**
- id: product-route
uri: lb://service-product
predicates:
- Path=/api/product/**
java
@RequestMapping("/api/order/")
@RestController
public class OrderController {
@GetMapping("/readDb")
public String readDb(){
return "readDb";
}
}
测试:说明是能访问到的
- 进入Nacos
断言
断言工厂
名称 | 参数(个数/类型) | 作用 |
---|---|---|
After | 1/datetime | 在指定时间之后 |
Before | 1/datetime | 在指定时间之前 |
Between | 2/datetime | 在指定时间区间内 |
Cookie | 2/string, regexp | 包含cookie名且必须匹配指定值 |
Header | 2/string, regexp | 包含请求头且必须匹配指定值 |
Host | N/string | 请求host必须是指定枚举值 |
Method | N/string | 请求方式必须是指定枚举值 |
Path | 2/List, bool | 请求路径满足规则,是否匹配最后的/ |
Query | 2/string, regexp | 包含指定请求参数 |
RemoteAddr | 1/List | 请求来源于指定网络域(CIDR写法) |
Weight | 2/string, int | 按指定权重负载均衡 |
XForwarded | 1/List | 从X-Forwarded-For请求头中解析请求来源,并判断是否来源于指定网络域 |
例如:
predicates的意思就是断言
Cookie:路由断言工厂(名)
mycookie,mycookievalue:参数
java
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- Cookie=mycookie,mycookievalue
假如我们要转发到百度 我们在加上一些断言规则
java
spring:
cloud:
gateway:
routes:
- id: bing-route
uri: https://cn.bing.com/
predicates:
- name: Path
args:
pattern: /search
- name: Query
args:
param: q
regexp: haha
这时候我们访问时必须为/serrch 必须带上参数 参数名为q 值必须为haha我们才能成功转发
如果值不等于haha 或者参数名不为q 那么会出现404找不到页面错误
自定义断言工厂
java
package com.nie.gateway.predicate;
import jakarta.validation.constraints.NotEmpty;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
/**
* 自定义的VIP路由断言工厂类,用于根据请求参数判断是否匹配VIP路由。
* 继承自AbstractRoutePredicateFactory,实现了自定义路由断言逻辑。
*/
@Component
public class VipRoutePredicateFactory extends AbstractRoutePredicateFactory<VipRoutePredicateFactory.Config> {
/**
* 构造函数,指定配置类为Config。
*/
public VipRoutePredicateFactory() {
super(Config.class);
}
/**
* 实现apply方法,根据Config中的配置生成路由断言逻辑。
*
* @param config 配置对象,包含请求参数名和期望值
* @return 返回一个Predicate<ServerWebExchange>,用于判断请求是否匹配VIP路由
*/
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return new GatewayPredicate() {
/**
* 判断请求是否匹配VIP路由的逻辑。
*
* @param serverWebExchange 当前的请求上下文
* @return 如果请求参数中存在指定参数且值匹配,则返回true,否则返回false
*/
@Override
public boolean test(ServerWebExchange serverWebExchange) {
ServerHttpRequest request = serverWebExchange.getRequest();
// 获取请求参数中指定参数的第一个值
String first = request.getQueryParams().getFirst(config.param);
// 判断参数值是否存在且是否等于配置的期望值
if (StringUtils.hasText(first) && first.equals(config.value)) {
return true;
}
return false;
}
};
}
/**
* 定义配置类的字段顺序,用于在配置文件中指定参数时的顺序。
*
* @return 返回字段顺序列表
*/
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("param", "value");
}
/**
* 配置类,用于存储路由断言的配置信息。
*/
@Validated
public static class Config {
/**
* 请求参数名,用于从请求中获取参数值。
*/
@NotEmpty
private String param;
/**
* 请求参数期望值,用于判断请求参数值是否匹配。
*/
@NotEmpty
private String value;
/**
* 获取请求参数名。
*
* @return 返回请求参数名
*/
public String getParam() {
return param;
}
/**
* 设置请求参数名。
*
* @param param 请求参数名
*/
public void setParam(String param) {
this.param = param;
}
/**
* 获取请求参数期望值。
*
* @return 返回请求参数期望值
*/
public String getValue() {
return value;
}
/**
* 设置请求参数期望值。
*
* @param value 请求参数期望值
*/
public void setValue(String value) {
this.value = value;
}
}
}
java
spring:
cloud:
gateway:
routes:
- id: bing-route
uri: https://cn.bing.com/
predicates:
- name: Path
args:
pattern: /search
- name: Query
args:
param: q
regexp: haha
- name: Vip
args:
param: user
value: xiaonie

我出现的错误
我在这里犯了一个大错误
java
java.lang.IllegalArgumentException: Unable to find RoutePredicateFactory with name VipRoutePredicateFactory
我检查了好几遍代码都没问题 最后我发现我在这里把这个包放在了外面去了 所以他没有扫描到这个包 希望打架也注意一下
过滤器
路径重写
如果加上了路径重写那么微服务前面就不需要加上/api/模块名
xml
spring:
cloud:
gateway:
routes:
- id: bing-route
uri: https://cn.bing.com/
predicates:
- name: Path
args:
pattern: /search
- name: Query
args:
param: q
regexp: haha
- name: Vip
args:
param: user
value: xiaonie
- id: order-route
uri: lb://service-order
predicates:
- Path=/api/order/**
filters:
- RewritePath=/api/order/(?<segment>.*), /$\{segment}
order: 1
- id: product-route
uri: lb://service-product
predicates:
- Path=/api/product/**
filters:
- RewritePath=/api/product/(?<segment>.*), /$\{segment}
order: 2
java
package com.nie.order.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.nie.order.bean.Order;
import com.nie.order.properties.OrderProperties;
import com.nie.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@Autowired
private OrderProperties orderProperties;
@GetMapping("/readDb")
public String readDb(){
return "readDb";
}
@GetMapping("/seckill")
@SentinelResource(value = "seckill-order", fallback = "seckillFallback")
public Order seckill(@RequestParam("userId") Long userId,
@RequestParam("productId") Long productId){
Order order = orderService.createOrder(productId, userId);
order.setId(Long.MAX_VALUE);
return order;
}
public Order seckillFallback(Long userId, Long productId, Throwable exception){
System.out.println("已经开始限制了");
Order order = new Order();
order.setId(productId);
order.setUserId(userId);
order.setAddress("异常信息:" + exception.getClass());
return order;
}
@GetMapping("/config")
public String config(){
return "order.timeout = " + orderProperties.getTimeout()
+ ", order.auto-confirm = " + orderProperties.getAutoConfirm()
+"order.db="+orderProperties.getDb();
}
@GetMapping("/create")
public Order createOrder(@RequestParam("userId") Long userId,
@RequestParam("productId") Long productId){
Order order = orderService.createOrder(productId,userId);
return order;
}
}
这样也就能访问 避免后期因为规划不周到 需要大量改前缀
java
filters:
- RewritePath=/api/product/(?<segment>.*), /$\{segment}
- AddResponseHeader=X-Response-Abcd, 123
如果我们加上AddResponseHeader=X-Response-Abcd, 123
那么在我们每次请求中他都会带上响应头 值为123
默认过滤器
java
default-filters:
- AddResponseHeader=X-Response-Abcd, 123
用上面的例子的话我们就在每次请求中都带入了请求头 值为123
就不用在每个微服务上面都加了

全局Filters
java
package com.nie.gateway.filter;
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.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;
@Slf4j
@Component
public class RtGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
String uri = request.getURI().toString();
long start = System.currentTimeMillis();
log.info("请求【{}】开始: 时间: {}", uri, start);
//=============以上是前置逻辑================
Mono<Void> filter = chain.filter(exchange)
.doFinally((result)->{
//=============以下是后置逻辑================
long end = System.currentTimeMillis();
log.info("请求【{}】结束: 时间: {}, 耗时: {}ms",uri,end,end-start);
}); //放行 10s
return filter;
}
@Override
public int getOrder() {
return 0;
}
}

自定义过滤器
xml
filters:
- RewritePath=/api/order/(?<segment>.*), /$\{segment}
- OnceTooken=X-Response-Token, uuid
java
package com.nie.gateway.filter;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractNameValueGatewayFilterFactory;
import org.springframework.http.HttpHeaders;
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.UUID;
@Component
public class OnceTokenGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
@Override
public GatewayFilter apply(NameValueConfig config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//每次响应之前,添加一个一次性令牌,支持 uuid, jwt等各种格式
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
ServerHttpResponse response = exchange.getResponse();
HttpHeaders headers = response.getHeaders();
String value = config.getValue();
if ("uuid".equalsIgnoreCase(value)) {
value = UUID.randomUUID().toString();
}
if ("jwt".equalsIgnoreCase(value)) {
value = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiIjoiMjQ4OTY3Nzg5IiwibmFtZSI6IkpvaG4gRG9lIiLCJleHAiOjE2NTM5MDk3OTksImlhdCI6MTY1MzkwOTc5OCwiZXhwIjoxNjUzOTA5NzkwLCJpc3MiOiJhdXRoIjoiLCJuYW1lIjoiYXV0aG9ycyIsImV4cCI6MTY1MzkwOTc5OCwianRpIjoiMjAyMyIsInR5cCI6IkpXVCJ9LCJpYXQiOjE2NTM5MDk3OTksImV4cCI6MTY1MzkwOTc5OCwiaXNzIjoiaHR0cHM6Ly9hdXRoLmF1dGhvcnMubmV0L3VzZXIiIsImF1dGhvcml0eSI6eyJpZGVudGl0eSI6eyJjbGllbnRzIjp7InVzZXJAZXhhbXBsZS5jb20iOnsiYWV0Ijp7InVzZXJAZXhhbXBsZS5jb20iOnsiZXhhbXBsZSI6eyJ1c2VyQGV4YW1wbGUiOnsiZXhhbXBsZSI6eyJ1c2VyQGV4YW1wbGUiOnsiZXhhbXBsZSI6eyJ1c2VyQGV4YW1wbGUiOnsiZXhhbXBsZSI6eyJ1c2VyQGV4YW1wbGUiOnsiZXhhbXBsZSI6eyJ1c2VyQGV4YW1wbGUiOnsiZXhhbXBsZSI6eyJ1c2VyGVyIjp7fSwicmV4cCI6eyJyIjoiLCJLCJsYW1vdW50aWVub3JhbCIsImZSI6eyJ1c2VyIjp7fX19LCJ30=\n";
}
headers.add(config.getName(), value);
}));
}
};
}
}

全局跨域
这样配置application.yml之后
允许所有请求跨域 allowed-origin-patterns: ''
允许全部请求来源 允许所有的头 allowed-headers: ' '
允许所有的请求方式 allowed-methods: '*'
xml
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowed-origin-patterns: '*'
allowed-headers: '*'
allowed-methods: '*'
