Skip to content
🔴🟠🟡🟢🔵🟣🟤⚫⚪

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依赖

xml
<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

源码中已经配置了【gateway-adapter】的配置,无需再次配置

添加配置:

yaml
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的服务,则可以通过 服务名 开头去调用

内置断言工厂:

yaml
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匹配失败

java
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;
//        }

    }

}

内置过滤器

自定义过滤器

java
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模块自定义【全局】的过滤器

java
@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

导入依赖

xml
<!--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>

添加配置:

yaml
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限流异常返回

java
@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);
    }
}

gateway api 分组流控规则