spring-cloud-alibaba-demo
八、网关springcloudgateway
路由(route) 路由是网关中最基础的部分, 路由信息包括一个ID、一个目的URI、一组断言工厂、一组Filter组成。如果断言为真, 则说明请求的URL和配置的路由匹配
断言(predicates) Java 8中的断言函数, Spring Cloud Gateway中的断言函数类型是Spring 5.0框架中的Server Web Exchange断言函数允许开发者去定义匹配Httprequest中的任何信息, 比如请求头和参数等
过滤器(Filter) Spring Cloud Gateway中的iter分为Gateway Fil r和Global Filter。Filter可以对请求和响应进行处理
- API Gateway,是系统的唯一对外的入口,介于客户端和服务器端之间的中间层,处理非业务功能 提供路由请求、鉴权、监控、缓存、限流等功能
- 统一接入
- 智能路由
- AB测试、灰度测试
- 负载均衡、容灾处理
- 日志埋点(类似Nignx日志)
- 流量监控
- 限流处理
- 服务降级
- 安全防护
- 鉴权处理
- 监控
- 机器网络隔离
这里选用 springcloud gateway 网关
springcloud gateway: Spring公司专门开发的网关
新增maven依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
源码中已经配置了【gateway-adapter】的配置,无需再次配置
添加配置:
spring:
# nacos 地址
cloud:
nacos:
discovery:
server-addr: 81.69.43.78:8848
gateway:
routes: #数组形式
- id: system-service #路由唯一标识
#uri: http://127.0.0.1:8000 #想要转发到的地址
uri: lb://cloud-system-service # 从nacos获取名称转发,lb是负载均衡轮训策略
predicates: #断言 配置哪个路径才转发,不配置Path则不用添加前缀
- Path=/system-service/**
filters: #过滤器,请求在传递过程中通过过滤器修改
- StripPrefix=1 #去掉第一层前缀
# 请求 http://127.0.0.1:8002/system-service/v1/user/list 会转发到 http://cloud-system-service/v1/user/list
discovery:
locator:
#开启网关拉取nacos的服务 若不配置 routes 规则,则默认通过 服务名 开头去调用
# 注意这里配置后在请求开头添加服务名后,路由和断路器会失效
enabled: true
请求 http://127.0.0.1:8002/system-service/v1/user/list 会转发到 http://cloud-system-service/v1/user/list #开启网关拉取nacos的服务,则可以通过 服务名 开头去调用
内置断言工厂:
spring:
# nacos 地址
cloud:
nacos:
discovery:
server-addr: 81.69.43.78:8848
gateway:
routes: #数组形式
- id: system-service #路由唯一标识
#uri: http://127.0.0.1:8000 #想要转发到的地址
uri: lb://cloud-system-service # 从nacos获取名称转发,lb是负载均衡轮训策略
predicates: #断言 配置哪个路径才转发
- Query=name,test|test2|test3 #http://127.0.0.1:8002/v1/user/list?name=test
- After=2021-03-18T17:32:58.129+08:00[Asia/Shanghai]
# 运行网关拉取nacos服务
discovery:
locator:
#开启网关拉取nacos的服务,则可以通过 服务名 开头去调用
# 注意这里配置后在【请求路径添加服务名后】,路由和断路器会失效
enabled: true
自定义断言工厂
自定义路由断言工厂需要继承AbstractRoutePredicateFactory类, 重写apply方法的逻辑。在apply方法中可以通过exchange.getRequest() 拿到ServerHttpRequest对象, 从而可以获取到请求的参数、请求方式、请求头 等信息。 1、必须spring组件bean 2.类必须加上RoutePredicateFactory作为结尾 3.必须继承AbstractRoutePredicateFactory 4.必须声明静态内部类声明属性来接收配置文件中对应的断言的信息 5.需要结合shortcutFieldOrder进行绑定 6.通过apply进行逻辑判断true就是匹配成功false匹配失败
package cn.mesmile.gateway.predicate;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
import javax.validation.constraints.NotEmpty;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
/**
* @author zb
* @Description
* 配置
* predicates: #断言 配置哪个路径才转发
* - Query=name,test|test2|test3 #http://127.0.0.1:8002/v1/user/list?name=test
* - CheckParam=test
*/
@Component
public class CheckParamRoutePredicateFactory extends
AbstractRoutePredicateFactory<CheckParamRoutePredicateFactory.Config> {
/**
* Param key.
*/
public static final String PARAM_KEY = "param";
// /**
// * Regexp key.
// */
// public static final String REGEXP_KEY = "regexp";
public CheckParamRoutePredicateFactory() {
super(CheckParamRoutePredicateFactory.Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(PARAM_KEY);
}
@Override
public Predicate<ServerWebExchange> apply(CheckParamRoutePredicateFactory.Config config) {
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange exchange) {
String param = config.getParam();
if (Objects.equals("test", param)){
return true;
}
// if (!StringUtils.hasText(config.regexp)) {
// // check existence of header
// return exchange.getRequest().getQueryParams()
// .containsKey(config.param);
// }
//
// List<String> values = exchange.getRequest().getQueryParams()
// .get(config.param);
// if (values == null) {
// return false;
// }
// for (String value : values) {
// if (value != null && value.matches(config.regexp)) {
// return true;
// }
// }
return false;
}
};
}
@Validated
public static class Config {
@NotEmpty
private String param;
// private String regexp;
public String getParam() {
return param;
}
public CheckParamRoutePredicateFactory.Config setParam(String param) {
this.param = param;
return this;
}
// public String getRegexp() {
// return regexp;
// }
//
// public CheckParamRoutePredicateFactory.Config setRegexp(String regexp) {
// this.regexp = regexp;
// return this;
// }
}
}
内置过滤器
自定义过滤器
package cn.mesmile.gateway.filter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.List;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.*;
/**
* @author zb
* @Description
* 配置
* filters: #过滤器,请求在传递过程中通过过滤器修改
* - CheckParam=1
*/
@Component
public class CheckParamGatewayFilterFactory extends AbstractGatewayFilterFactory<CheckParamGatewayFilterFactory.Config> {
/**
* Prefix key.
*/
public static final String PREFIX_KEY = "prefix";
private static final Log log = LogFactory
.getLog(CheckParamGatewayFilterFactory.class);
public CheckParamGatewayFilterFactory() {
super(CheckParamGatewayFilterFactory.Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(PREFIX_KEY);
}
@Override
public GatewayFilter apply(CheckParamGatewayFilterFactory.Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange,
GatewayFilterChain chain) {
String prefix = config.getPrefix();
return chain.filter(exchange);
// boolean alreadyPrefixed = exchange
// .getAttributeOrDefault(GATEWAY_ALREADY_PREFIXED_ATTR, false);
// if (alreadyPrefixed) {
// return chain.filter(exchange);
// }
// exchange.getAttributes().put(GATEWAY_ALREADY_PREFIXED_ATTR, true);
//
// ServerHttpRequest req = exchange.getRequest();
// addOriginalRequestUrl(exchange, req.getURI());
// String newPath = config.prefix + req.getURI().getRawPath();
//
// ServerHttpRequest request = req.mutate().path(newPath).build();
//
// exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, request.getURI());
//
// if (log.isTraceEnabled()) {
// log.trace("Prefixed URI with: " + config.prefix + " -> "
// + request.getURI());
// }
//
// return chain.filter(exchange.mutate().request(request).build());
}
// @Override
// public String toString() {
// return filterToStringCreator(CheckParamGatewayFilterFactory.this)
// .append("prefix", config.getPrefix()).toString();
// }
};
}
public static class Config {
private String prefix;
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
}
}
cloud-gateway模块自定义【全局】的过滤器
@Component
public class GlobalApiFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 写业务逻辑
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
// 根据业务鉴权
// if (StringUtils.isEmpty(token)) {
// exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
// Mono<Void> voidMono = exchange.getResponse().setComplete();
// return voidMono;
// }
// 继续往下执行
Mono<Void> filter = chain.filter(exchange);
return filter;
}
@Override
public int getOrder() {
// 数字越小,优先级越高
return 0;
}
}
框架自带的过滤器
记录网关请求日志
gateway跨越配置(cors gateway)
方式一:配置文件的方式配置
*号表示所有
方式二:java代码的方式配置
gateway整合sentinel
导入依赖
<!--sentinel流量卫兵相当于 Hystrix-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--gateway整合sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
添加配置:
spring:
cloud:
# sentinel 配置
sentinel:
transport:
dashboard: 127.0.0.1:8080
port: 9904
#dashboard: 8080 控制台端口
#port: 9904 本地启的端口,随机选个不能被占用的,与dashboard进行数据交互,会在应用对应的机器上启动一个 Http Server,该 Server 会与 Sentinel 控制台做交互, 若被占用,则开始+1一次扫描
gateway全局sentinel限流异常返回
@Component
public class InitBlockHandle {
@PostConstruct
public void init(){
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable e) {
// 降级业务
Map<String,Object> backMap=new HashMap<>();
if (e instanceof FlowException){
backMap.put("code",-1);
backMap.put("msg","限流-异常啦");
}else if (e instanceof DegradeException){
backMap.put("code",-2);
backMap.put("msg","降级-异常啦");
}else if (e instanceof ParamFlowException){
backMap.put("code",-3);
backMap.put("msg","热点-异常啦");
}else if (e instanceof SystemBlockException){
backMap.put("code",-4);
backMap.put("msg","系统规则-异常啦");
}else if (e instanceof AuthorityException){
backMap.put("code",-5);
backMap.put("msg","认证-异常啦");
}
backMap.put("success",false);
return ServerResponse.status(HttpStatus.OK)
.header("content-Type","application/json;charset=UTF-8")
.body(BodyInserters.fromValue(backMap));
// 设置返回json数据
// response.setStatus(200);
// response.setHeader("content-Type","application/json;charset=UTF-8");
// response.getWriter().write(JSONObject.toJSONString(backMap));
}
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
}