网关是流量的入口 用来转发、权限校验、限流等
网关的作用 一个微服务项目 如果不使用网关 那么就意味着 权限校验、拦截黑名单请求等这种功能都需要在每个微服务中一一实现 10个微服务需要权限校验就需要写10次 如果把权限校验功能单独放到公共服务中 每个微服务也都要写同样的权限校验代码 后续升级也是问题 所以就有了网关 首先请求来了先去网关 在网关中权限校验等功能 满足了条件网关去调用微服务
gateway给我们实现了网关的功能
核心概念
路由(route)
路由就是我们想让gateway去调用哪个微服务
断言(predicates)
用户给服务端发送了一个请求 会来到网关 然后网关会根据这样请求地址进行判断要去调用那个微服务 这个断言就是用来写条件的
过滤器(filter)
过滤器 可以做权限校验的功能跟Spring mvc过滤器差不多
gateway使用
引入依赖
<!-- gateway网关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- nacos服务注册与发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
编写yml配置文件
server:
port: 8888
spring:
application:
name: mall-gateway
#配置nacos注册中心地址
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
gateway:
discovery:
locator:
# 默认为false,设为true开启通过微服务创建路由的功能,即可以通过微服务名访问服务
# http://localhost:8888/mall-order/order/findOrderByUserId/1
enabled: true
# 是否开启网关
enabled: true
路由断言工厂(Route Predicate Factories)配置
自定义断言工厂
@Component
@Slf4j
public class CheckAuthRoutePredicateFactory extends AbstractRoutePredicateFactory<CheckAuthRoutePredicateFactory.Config> {
public CheckAuthRoutePredicateFactory() {
super(Config.class);
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange serverWebExchange) {
log.info("调用CheckAuthRoutePredicateFactory" + config.getName());
if(config.getName().equals("11")){
return true;
}
return false;
}
};
}
/**
* 快捷配置
* @return
*/
@Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList("name");
}
public static class Config {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
当yml文件配置了这个断言会进入这个apply方法 config可以得到请求中的信息 test方法返回false就不会去调用微服务
断言实现类名字 CheckAuthRoutePredicateFactory 前面是CheckAuth 后面是RoutePredicateFactory 前面的CheckAuth是自定义 后面RoutePredicateFactory必须这样写 这是一个规定
使用自定义断言工厂
yml中配置
spring:
cloud:
gateway:
#设置路由:路由id、路由到微服务的uri、断言
routes:
- id: order_route #路由ID,全局唯一
uri: http://localhost:8020 #目标微服务的请求地址和端口
predicates:
# 测试:http://localhost:8888/order/findOrderByUserId/1
- Path=/order/** #Path路径匹配
#自定义CheckAuth断言工厂
# - name: CheckAuth
# args:
# name: 11
- CheckAuth=11
过滤器工厂( GatewayFilter Factories)配置
SpringCloudGateway 内置了很多的过滤器工厂,我们通过一些过滤器工厂可以进行一些业务逻辑处理器,比如添加剔除响应头,添加去除参数等
官网文档 很详细
https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories
过滤器添加请求参数
添加请求参数
spring:
cloud:
gateway:
#设置路由:路由id、路由到微服务的uri、断言
routes:
- id: order_route #路由ID,全局唯一
uri: http://localhost:8020 #目标微服务的请求地址和端口
#配置过滤器工厂
filters:
- AddRequestParameter=color, blue # 添加请求参数 color是键 blue是值
可以这样获取
@GetMapping("/testgateway3")
public String testGateway3(@RequestParam("color") String color) throws Exception {
log.info("gateWay获取请求参数color:"+color);
return "success";
}
自定义过滤器工厂
继承AbstractNameValueGatewayFilterFactory且我们的自定义名称必须要以GatewayFilterFactory结尾并交给spring管理。
@Component
@Slf4j
public class CheckAuthGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
@Override
public GatewayFilter apply(NameValueConfig config) {
return (exchange, chain) -> {
log.info("调用CheckAuthGatewayFilterFactory==="
+ config.getName() + ":" + config.getValue());
return chain.filter(exchange);
};
}
全局过滤器(Global Filters)配置
@Component
@Order(-1) // 辅助表示我们的自定义过滤器会排在 内置过滤器的前面
@Slf4j
public class CheckAuthFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//校验请求头中的token
List<String> token = exchange.getRequest().getHeaders().get("token");
log.info("token:"+ token);
if (token.isEmpty()){
return null;
}
return chain.filter(exchange);
}
}
@Component
public class CheckIPFilter implements GlobalFilter, Ordered {
@Override
public int getOrder() { // 如果执行这个过滤器之前还需要执行一个过滤器实现这个方法给他排序 @Order(-1)注解跟它一个意思
return 0;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
HttpHeaders headers = exchange.getRequest().getHeaders();
//模拟对 IP 的访问限制,即不在 IP 白名单中就不能调用的需求
if (getIp(headers).equals("127.0.0.1")) {
return null;
}
return chain.filter(exchange);
}
private String getIp(HttpHeaders headers) {
return headers.getHost().getHostName();
}
}
通过yml配置的方式
https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#cors-configuration
spring:
cloud:
gateway:
globalcors:
cors-configurations: // 拦截的请求
'[/**]':
allowedOrigins: "*" // 解决跨域问题
allowedMethods:
- GET
- POST
- DELETE
- PUT
- OPTION
gateway整合sentinel限流
官网地址 访问说的依赖包有问题
https://github.com/alibaba/Sentinel/wiki/%E7%BD%91%E5%85%B3%E9%99%90%E6%B5%81
不要导入官网说的依赖 导入下面这个
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
<version>x.y.z</version>
</dependency>
接入sentinel dashboard,添加yml配置
spring:
application:
name: mall-gateway-sentinel-demo
#配置nacos注册中心地址
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
sentinel:
transport:
# 添加sentinel的控制台地址
dashboard: 127.0.0.1:8080
配置类 使用时只需注入对应的 SentinelGatewayFilter 实例以及 SentinelGatewayBlockExceptionHandler 实例即可
@Configuration
public class GatewayConfiguration {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
/**
* 限流异常处理器
* @return
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
// Register the block exception handler for Spring Cloud Gateway.
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
/**
* 限流过滤器
* @return
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
}
手动设置限流规则 如果这个类被ioc管理了 在sentinel管理界面 会跟官网地址中显示的界面不一样 导入官网给的依赖也会不一样
用户可以通过 GatewayRuleManager.loadRules(rules) 手动加载网关规则
GatewayConfiguration中添加
@PostConstruct
public void doInit() {
//初始化自定义的API
initCustomizedApis();
//初始化网关限流规则
initGatewayRules();
//自定义限流异常处理器
initBlockRequestHandler();
}
private void initCustomizedApis() {
Set<ApiDefinition> definitions = new HashSet<>();
ApiDefinition api = new ApiDefinition("user_service_api")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem().setPattern("/user/**")
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}});
definitions.add(api);
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
private void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
//resource:资源名称,可以是网关中的 route 名称或者用户自定义的 API 分组名称。
//count:限流阈值
//intervalSec:统计时间窗口,单位是秒,默认是 1 秒。
rules.add(new GatewayFlowRule("order_route")
.setCount(2)
.setIntervalSec(1)
);
rules.add(new GatewayFlowRule("user_service_api")
.setCount(2)
.setIntervalSec(1)
);
// 加载网关规则
GatewayRuleManager.loadRules(rules);
}
private void initBlockRequestHandler() {
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange exchange, Throwable t) {
HashMap<String, String> result = new HashMap<>();
result.put("code",String.valueOf(HttpStatus.TOO_MANY_REQUESTS.value()));
result.put("msg", HttpStatus.TOO_MANY_REQUESTS.getReasonPhrase());
return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(result));
}
};
//设置自定义异常处理器
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
gateway高可用 启动多个gateway实例 使用Nginx这种负载均衡框架进行负载实现