GateWay—网关介绍

所谓的API网关,就是指系统的统一入口,它封装了应用程序的内部结构,为客户端提供统一服务,一些与业务本身功能天关的公共逻辑可以在这里实现,诸如认证、签权、监控、路由转发等等。添加上API网关之后,系统的架构图变成了如下所示:

Java 网关接口字段映射实现 java gateway网关_Java 网关接口字段映射实现

优点:

Java 网关接口字段映射实现 java gateway网关_spring_02

整体微服务结构图,网关Gateway的位置

Java 网关接口字段映射实现 java gateway网关_spring cloud_03

GateWay介绍

网关作为流量的入口,常用的功能包括路由转发,权限校验,限流等。

Spring Cloud Gateway 是Spring Cloud官方推出的第二代网关框架,定位于取代NetflexZuul 1.0。相比Zuul来说,Spring Clout Gateway)提供更优秀的性能,更强大的有功能

Spring Cloud Gateway是由WebFlux + Netty + Reactor实现的响应式的APl网关。它不能在传统的 servlet容器中工作,也不能构建成war包.

Spring Clout Gateway旨在为微服务架构提供一种简单且有效的API路由的管理方式,并基于Filter的方式提供网关的基本功能,例如说安全认证、监控、限流等等。

功能特征

  • 基于Spring Framework 5,Project Reactor和 Spring Boot 2.0进行构建;
  • 动态路由:能够匹配任何请求属性;
  • 支持路径重写;
  • 集成 Spring Cloud服务发现功能(Nacos、Eruka) ;
  • 可集成流控降级功能(Sentinel、Hystrix) ;
  • 可以对路由指定易于编写的 Predicate (断言)和Filter(过滤器);

核心概念:

  • 路由

路由是网关中最基础的部分,路由信息包括一个ID、一个目的URI、一组断言工厂、一组Fiter组成。如果断言为真,则说明请求的URL和配置的路由匹配.

  • 断言(predicates)

Java8中的断言函数,SprigCloud Gateway中的断言函数类型是Sping5 o框架中的SererWebExchange。断言函数允许开发者去定义匹配Http request中的任何信息,比如请求头和参数等。

  • 过滤器(Fllter)

SpringCloud Gateway中的filter分为Gateway Filler和Global Filter。Filter可以对请求和响应进行处理。

GateWay—初体验

1.创建新的子项目gateway 引入依赖

<!--   gateway的依赖 springcloud开发 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

2.编写配置文件

server:
  port: 8088
spring:
  application:
    name: api-gateway
  cloud:
    #gateway的配置
    gateway:
      #路由配置
      routes:
        - id: order_route  #路由的唯一标识,路由到order
          uri: http://localhost:8020  #需要转发的地址
          #断言规则  用于路由规则的匹配
          predicates:
            - Path=/order-serv/**
            # 请求 http://localhost:8088/order-serv/order/add路由到
            # http://localhost:8020/order-serv/order/add
          filters:
            - StripPrefix=1  #转发之前去掉第一层路径
            # http://localhost:8020/order/add

3.主启动类启动

@SpringBootApplication
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class,args);
    }
}

访问之前启动 order-nacos 和 stock-nacos模块

order-nacos模块 controller代码

@RestController
@RequestMapping("/order")
public class OrderController {
    @Autowired
    RestTemplate restTemplate;

    @RequestMapping("/add")
    public String add(){
        System.out.println("下单成功!");
        String msg = restTemplate.getForObject("http://stock-service/stock/reduct", String.class);
        return "hello world"+msg;
    }
}

stock-nacos模块 controller代码

@RestController
@RequestMapping("/stock")
public class StockController {

    @Value("${server.port}")
    String port;

    @RequestMapping("/reduct")
    public String reduct(){
        System.out.println("扣减库存");
        return "扣减库存"+port;
    }
}

三个模块全部启动

测试访问 http://localhost:8088/order-serv/order/add

页面显示 hello world扣减库存8021

GateWay整合nacos

现在在配置文件中写死了转发路径的地址,前面我们已经分析过地址写死带来的问题,接下来我们从注册中心获取此地址。

集成Nacos

1.再引入依赖

<!--    nacos服务注册发现 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

2.编写yml配置文件

server:
  port: 8088
spring:
  application:
    name: api-gateway
  cloud:
    #gateway的配置
    gateway:
      #路由配置
      routes:
        - id: order_route  #路由的唯一标识,路由到order
          uri: lb://order-service  #需要转发的地址 lb 使用nacos本地负载均衡策略 order-service 服务名  
          predicates:
            - Path=/order-serv/**
          filters:
            - StripPrefix=1  
    #配置Nacos
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        username: nacos
        password: nacos

测试访问 http://localhost:8088/order-serv/order/add

页面显示 hello world扣减库存8021

简写形式

  • 比较少用
  • 自己去配置断言和过滤器比较好
server:
  port: 8088
spring:
  application:
    name: api-gateway
  cloud:
    #gateway的配置
    gateway:
      discovery:
        locator:
          enabled: true #是否启动自动识别nacos服务
    #路由配置

    #配置Nacos
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        username: nacos
        password: nacos

测试访问 http://localhost:8088/order-service/order/add 用的是服务名

页面显示 hello world扣减库存8021

GateWay—内置路由断言工厂

作用 : 当请求gateway的时候,使用断言对请求进行匹配,如果匹配成功就路由转发,如果匹配失败就返回404

类型 内置 、自定义

  • 基于Datetime类型的断言工厂

此类型的断言根据时间做判断,主要有三个:

AfterRoutePredicateFactory:接收一个日期参数,判断请求日期是否晚于指定日期

BeforeRoutePredicateFactory:接收一个日期参数,判断请求日期是否早于指定日期

BetweenRoutePredicateFactory:接收两个日期参数,判断请求日期是否在指定时间段内

predicates:
	- Path=/order-serv/**
	- After=2019-12-31T23:59:59.789+08:00[Asia/Shanghai]
  • 基于远程地址的断言工厂

RemoteAddrRoutePredicateFactory:接收一个IP地址段,判断请求主机地址是否在地址段中

- RemoteAddr=192.168.1.1/24
  • 基于Cookie的断言工厂

CookieRoutePredicateFactory: 接收两个参数,cookie名字和一个正则表达式。判断请求cookie是否具有给定名称且值与正则表达式匹配。

- Cookie=chocolate,ch.
  • 基于Header的断言工厂

HeaderRoutePredicateFactory:接收两个参数,标题名称和正则表达式。判断请求Header是否员有给定名称且值与正则表达式匹配.

- Header=X-Request-Id,\d+
  • 基于Host的断言工厂

HostRoutePredicateFactory:接收一个参数,主机名模式。判断请求的Host是否满足匹配规则

- Host=**.testhost.org
  • 基于Method请求方法的断言工厂

MethodRoutePredicateFactory:接收一个参数,判断请求类型是否跟指定的类型匹配。

- Method=GET
  • 基于Path请求路径的断言工厂

PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则,

- Path=/foo/{segment}
  • 基于Query请求参数的断言工厂
- Query=name,xushu|zhuge  
#要有name参数 而且要等于xushu或者zhuge

GateWay—自定义路由断言工厂

自定义路由断言工厂需要继承AbstractRoutePredicateFactory 类,重写 apply 方法的逻辑。在apply方法中可以通过 exchange.getRequest()拿到ServerhttptRequest对象,从而可以获取到请求的参数、请求方式、请求头等信息.

1.必须spring组件 bean

2.类必须加上RoutePredicateFactory作为结尾

3.必须继承AbstractRoutePredicateFactory

4.必须声明静态内部类 声明属性来接受 配置文件中的对应的断言的信息

5.需要结合shortcutFieldOrder进行绑定

6.通过apply进行逻辑判断 true就是匹配成功 false匹配失败

写个自定义参数叫CheckAuth 值必须是xushu才可以访问

package com.tian.predicates;

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.Iterator;
import java.util.List;
import java.util.function.Predicate;
@Component
public class CheckAuthRoutePredicateFactory extends AbstractRoutePredicateFactory<CheckAuthRoutePredicateFactory.Config> {


    public CheckAuthRoutePredicateFactory() {
        super(CheckAuthRoutePredicateFactory.Config.class);
    }

    public List<String> shortcutFieldOrder() {
        return Arrays.asList("name");
    }

    public Predicate<ServerWebExchange> apply(CheckAuthRoutePredicateFactory.Config config) {
        return new GatewayPredicate() {
            public boolean test(ServerWebExchange exchange) {
                if(config.getName().equals("xushu")){
                    return true;
                }
                return false;
            }

        };
    }
//用来接收配置文件中 断言的信息
@Validated
public static class Config {
private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
}

配置文件

predicates:
#- Path=/order-serv/**
	- Path=/order/**
	- CheckAuth=xushu
	#- CheckAuth=xushu2

访问 http://localhost:8088/order/add xushu就可以正确访问,xushu2页面就会报404

GateWay—局部(内置、自定义)过滤器

添加请求头

#gateway模块配置文件
spring:
  cloud:
    gateway:
      routes:
      - id: order_route
      uri: http://localhost:8020
      filters:
       - AddRequestHeader=X-Request-color,red  #添加请求头

order-nacos模块controller

@RequestMapping("/header")
public String header(@RequestHeader("X-Request-color") String color){
    return color;

访问 http://localhost:8088/order/header 页面显示 red

Java 网关接口字段映射实现 java gateway网关_Java 网关接口字段映射实现_04

Java 网关接口字段映射实现 java gateway网关_gateway_05

Java 网关接口字段映射实现 java gateway网关_Java 网关接口字段映射实现_06

自定义过滤器工厂

  • 和断言工厂差不多

Java 网关接口字段映射实现 java gateway网关_gateway_07

配置文件

filters:
 - CheckAuth=fox,男

访问输出CheckAuthGatewayFilterFactory===fox: 男

GateWay—全局过滤器

局部过滤器和全同过滤器区别:

局部:局部针对某个路由,需要在路由中进行配置

全局:针对所有路由请求,你定义就会投入使用

自定义全局过滤器

@Component
public class LogFilter implements GlobalFilter {
    Logger log= LoggerFactory.getLogger(this.getClass());

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info(exchange.getRequest().getPath().value());
        return chain.filter(exchange);
    }
}

GateWay—请求日记记录&跨域处理

要启用Reactor Netty 访问日志,请设置

-Dreactor.netty.http.server.accessLogEnabled=true.

Java 网关接口字段映射实现 java gateway网关_gateway_08

重启 控制台就会打印输出日志

它必须是Java系统属性,而不是 Spring Boot属性。

跨域问题

配置文件application.yml

#跨域配置
spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':  #允许跨域访问的资源
            allowedOrigins: "https://docs.spring.io" #跨域允许来源
            allowedMethods:
            - GET
            - POST

配置类配置

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;

@Configuration
public class CorsConfig {

    @Bean
    public CorsWebFilter corsWebFilter(){
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedMethod("*"); //允许的method
        config.addAllowedOrigin("*"); //允许的来源
        config.addAllowedHeader("*"); //允许的请求头参数
        // 运行访问的资源
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
        source.registerCorsConfiguration("/**",config);
        return new CorsWebFilter(source);
    }

}

GateWay—整合Sentinel流控降级

api-gateway模块

1.添加依赖

<!--    sentinel整合gateway    -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
<!--    sentinel的依赖     -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

2.配置文件

spring:
  cloud:
  	  #配置sentinel
    sentinel:
      transport:
        dashboard: 127.0.0.1:8888

3.启动sentinel 控制台配置规则

Java 网关接口字段映射实现 java gateway网关_Java 网关接口字段映射实现_09

4.访问

http://localhost:8088/order/add

现象 前两次正常页面显示 第三次页面显示

Blocked by Sentinel: ParamFlowException

GateWay—整合Sentinel流控降级详细配置

Java 网关接口字段映射实现 java gateway网关_java_10

API管理

Java 网关接口字段映射实现 java gateway网关_spring cloud_11

Java 网关接口字段映射实现 java gateway网关_Java 网关接口字段映射实现_12

降级

Java 网关接口字段映射实现 java gateway网关_java_13

处理方法(在降级规则那配置)

Java 网关接口字段映射实现 java gateway网关_spring_14

自定义异常

  • 让页面显示自定义异常信息
package com.tian.config;

import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.util.HashMap;

@Configuration
public class GatewayConfig {
    @PostConstruct
    public void init(){
        BlockRequestHandler blockRequestHandler=new BlockRequestHandler() {
            @Override
            public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {

                HashMap<String, String> map = new HashMap<>();
                map.put("code",HttpStatus.TOO_MANY_REQUESTS.toString());
                map.put("message","限流了");
                //自定义异常处理
                return ServerResponse.status(HttpStatus.OK)
                        .contentType(MediaType.APPLICATION_JSON)
                        .body(BodyInserters.fromValue(map));
            }
        };
        GatewayCallbackManager.setBlockHandler(blockRequestHandler);
    }
}

或 yml配置

spring:
  cloud:
  	sentinel:
  	  scg:
        fallback:
          mode: response
          response-body: "{code:'',message:''}"