JavaEE:SpringCloud-使用Gateway网关

说明:功能类似于Zuul,比Zuul1性能更好。

一、Gateway网关使用:

1.导入gateway依赖包:

<dependencies>
    <!-- 导入Eureka的Client端依赖包 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <!-- 导入Actuator依赖包 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!-- 导入Gateway依赖包 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
</dependencies>

2.创建gateway启动类:

@SpringBootApplication  //标识为启动类
@EnableDiscoveryClient  //注册到注册中心
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class); //加载启动类
    }
}

3.application.yml中基础配置:

spring:
  application:
    name: mall-gateway
  cloud:
    gateway:  #配置gateway
      discovery:
        locator:
          enabled: false   #关闭自动路由
          lower-case-service-id: true   #请求路径识别小写
server:
  port: 10004
eureka:
  client:
    service-url: #导入Eureka Server配置的地址
      defaultZone: http://127.0.0.1:10000/eureka/
management: #开启actuator-endpoint
  security:
    enabled: false  #关闭security
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    health:
      show-details: alwas

4.配置路由规则+过滤器:

(1)方式1,application.yml方式定义:

spring:
  ...
  cloud:
    gateway:  #配置gateway信息
      ...
      routes: #配置(路由)要拦截的微服务,可以配置多个路由,每个路由以- 开头
        - id: mall-usercenter    #路由id,可以省略
          uri: lb:MALL-USERCENTER  #请求转发时URL显示为此路径
          predicates:
            - Path=/**   #配置(路由)需要拦截的路径
          filters:  #由 http://127.0.0.1:端口/yml/接口路径 -> 变为 http://127.0.0.1:端口/接口路径 -> 最终变成 http://MALL-USERCENTER/接口路径
            - StripPrefix=1

(2)方式2,java代码方式定义:

@Configuration
public class GatewayConfig {//java代码方式定义
    @Autowired
    MyGatewayFilter myGatewayFilter;

    @Bean
    @Order
    public RouteLocator route(RouteLocatorBuilder b) {
        return b.routes().route("mall-usercenter",   //路由id,可以省略,可以配置多个路由
                                                                             //添加各种路由规则:
                r -> r.path("/usercenter/**")                                //1.Path方式,配置(路由)需要拦截的路径,可以配置多个path
                        .and().method(HttpMethod.GET)                        //2.Method方式,是GET请求时匹配
                        .and().query("key1")                                 //3.1.RequestParam方式,请求参数包含key1时匹配
                        .and().query("key1", "value")                        //3.2.RequestParam方式,请求参数含key1且值为value1时匹配
                        .and().header("Authorization")                       //4.1.Header方式,请求头含Authorization属性时匹配
                        .and().header("Authorization", "xxx")                //4.2.Header方式,请求头含Authorization属性且值为xxx时匹配
                        .and().cookie("token", "xxx")                        //5.Cookie方式,cookie含token属性且值为xxx时匹配
                        .and().after(ZonedDateTime.now().plusSeconds(1))     //6.1时间方式,指定时间之后生效(此处为服务启动成功1秒后生效)
                        .and().before(ZonedDateTime.now().plusMinutes(10))   //6.2时间方式,指定时间之前生效(此处为服务启动后10分钟内有效)
                        .and().between(null, null)                           //6.3时间方式,指定时间之间生效
                        .and().header("头字段")  //请求头添加字段
                        .filters(                                            //添加各种过滤器:
                                f -> f.stripPrefix(1)                        //1.StripPrefix过滤器,将请求URL去掉前缀,如/mall-usercenter/insert变成/insert
                                        .prefixPath("mall-usercenter")       //2.PrefixPath过滤器,在请求URL上加前缀,如/insert变成/mall-usercenter/insert
                                        .redirect(302, "重定向URL")          //3.RedirectTo过滤器,请求路径404时,重定向到指定URL
                                        .saveSession()                       //4.SaveSession过滤器,调用服务之前保存session,如分布式session
                                        .addRequestHeader("键1", "值1")      //5.1.Header过滤器,给请求加上头字段
                                        .addResponseHeader("键1", "值1")     //5.2.Header过滤器,给返回加上头字段
                                        .filter(myGatewayFilter)             //6.使用自定义过滤器类见(3),如果自定义过滤器implements GlobalFilter(全局)时,此处不用.filter(myGatewayFilter)
                        )
                        .uri("lb:MALL-USERCENTER")      //请求转发时URL路径

        ).build();
    }
}

(3)自定义过滤器:

@Component  //自定义过滤器
public class MyGatewayFilter implements GatewayFilter, Ordered { //如果implements GlobalFilter(全局)时,GatewayConfig中不需要添加.filter(myGatewayFilter)
    @Override
    public Mono<Void> filter(ServerWebExchange e, GatewayFilterChain c) {
        //1.pre型过滤器,在实际接口处理之前执行
        ...  //此处可以完成登录验证等逻辑
        return c.filter(e).then(//c.filter将跳到实际接口处理
                Mono.fromRunnable(() -> {
                    //2.post型过滤器,在实际接口处理之后执行
                    ...
                })
        );
    }
    @Override
    public int getOrder() {
        return 0;   //设置过滤器优先级,pre型过滤器-数字越大优先级越高。post型过滤器-数字越小优先级越高。
    }
}

二、使用Gateway+JWT验证token:

1.pom中添加jwt依赖包:

<!-- 导入jwt依赖包 -->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.7.0</version>
</dependency>

2.创建Jwt工具类:

public class JwtUtil {//jwt验证工具类
    public String genToken(String userNo) {//生成token
        Date curDate = new Date();
        return JWT.create().withIssuer("Issuer值").withIssuedAt(curDate).withExpiresAt(new Date(curDate.getTime() + 30 * 24 * 60 * 60 * 1000)) //token过期时间为1个月
                .withClaim("userNo", userNo)  //使用userNo
                .sign(Algorithm.HMAC256("key值"));
    }
    public boolean verify(String userNo, String token) {//验证token
        try {
            JWT.require(Algorithm.HMAC256("key值")).withIssuer("Issuer值").withClaim("userNo", userNo).build()  //生成JWTVerifier
                    .verify(token);  //验证token
            return true;  //无报错,验证通过
        } catch (Exception e) {
            return false;  //报错,验证失败
        }
    }
}

3.在自定义过滤器中验证token:

@Component  //自定义过滤器
public class MyGatewayFilter implements GatewayFilter, Ordered {
    @Autowired
    private JwtUtil jwtUtil;
    @Override
    public Mono<Void> filter(ServerWebExchange e, GatewayFilterChain c) {
        /*
        此处验证token等逻辑
         */
        ServerHttpRequest request = e.getRequest();
        HttpHeaders headers = request.getHeaders();
        String token = headers.getFirst("Authorization");  //获取header中的登录token
        String userNo = headers.getFirst("userNo");    //获取header中的用户ID
        ServerHttpResponse response = e.getResponse();
        if (StringUtils.isBlank(token)) {  //token为空时,返回错误码
            response.setStatusCode(HttpStatus.UNAUTHORIZED);  //401
            return response.setComplete();  //结束请求
        }
        if (jwtUtil.verify(token, userNo)) { //token验证不通过时,返回错误码
            response.setStatusCode(HttpStatus.FORBIDDEN);  //403
            return response.setComplete(); //结束请求
        }
        ServerHttpRequest requestNew = request.mutate().header("userNo", userNo).build();
        response.setStatusCode(HttpStatus.OK);
        response.getHeaders().add("userNo", userNo);
        return c.filter(e.mutate().request(requestNew).response(response).build()); //c.filter将跳到实际接口处理
    }
    ...
}

三、使用Redis限流:

1.添加redis依赖,连接redis:

(1)添加依赖:

<!-- 导入redis限流依赖包 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

(2)application.yml配置连接redis:

spring:
  application:
    name: mall-gateway
  redis:  #连接redis
    host: 127.0.0.1
    port: 6379
    database: 0
  main:
    allow-bean-definition-overriding: true  #允许bean重载
  ...

2.创建redis限流类:

@Configuration
public class RedisLimitConfig {//redis限流
    @Bean("keyResolver")
    @Primary  //配置为默认
    public KeyResolver keyResolver() {//c.setKeyResolver用到,用来给每次路由请求生成一个Key(限流分组的标识)
        return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()); //此处基于HostAddress生成key
    }
    @Bean("usercenterRedisLimit")
    @Primary  //配置为默认
    public RedisRateLimiter usercenterRedisLimit() { //c.setRateLimiter用到,可以定义多个,每个微服务分开限流
        return new RedisRateLimiter(10, 50); //池每秒的平均填充速度,池里最大50个
    }
}

3.使用redis限流器:

(1)方式1,在gateway配置文件application.yml中使用限流器:

spring:
  ...
  cloud:
    gateway:  #配置gateway信息
      ...
      routes: #配置(路由)要拦截的微服务,可以配置多个路由,每个路由以- 开头
        - id: mall-usercenter    #路由id,可以省略
          ...
          filters:
            ...
            - name: RequestRateLimiter  #以下配置redis限流
              args:
                key-resolver: '#{@keyResolver}'  #keyResolver为RedisLimitConfig类中变量,用来给每次路由请求生成一个Key(限流分组的标识)
                redis-rate-limiter.replenishRate: 10 #池每秒的平均填充速度
                redis-rate-limiter.burstCapacity: 50 #池里最大50个

(2)方式2,在gateway配置类中使用限流器:

@Configuration
public class GatewayConfig {//java代码方式配置路由规则
    @Autowired
    @Qualifier(value = "keyResolver")  //指定使用指定bean
    KeyResolver keyResolver;
    @Autowired
    @Qualifier(value = "usercenterRedisLimit")  //指定使用指定bean
    RateLimiter usercenterRedisLimit;
    @Bean
    @Order
    public RouteLocator route(RouteLocatorBuilder b) {
        return b.routes().route(r -> r.path("/usercenter/**").filters(
                f -> f.requestRateLimiter( //指定redis限流器
                    c -> {
                        c.setKeyResolver(keyResolver); //用来给每次路由请求生成一个Key(限流分组的标识),keyResolver为RedisLimitConfig类中变量
                        c.setRateLimiter(usercenterRedisLimit);  //配置redis限流器,usercenterRedisLimit为RedisLimitConfig类中变量
                        c.setStatusCode(HttpStatus.BAD_GATEWAY); //触发限流后返回的状态码
                    }
                )
        ).uri("lb:MALL-USERCENTER"))
        .build();
    }
}

四、其他配置

1.配置跨域支持,在application.yml中配置:

spring:
  ...
  cloud:
    gateway:  #配置gateway信息
      globalcors:  #配置跨域支持
        cors-configurations:
          '[/**]':
            allowedOrigins:  #允许将资源共享给请求来源
              - "http://127.0.0.1:8080"   #配置需要跨域的域名
              - "http://www.yyh.com"      #配置需要跨域的域名
              - "*"
            allowCredentials: true  #允许跨域携带cookie、token等信息
            allowedHeaders: "*"  #允许接收所有header
            allowedMethods: "*" #允许所有HTTP Method,比如GET、POST等
            exposedHeaders: "*" #允许跨域携带所有header
            maxAge: 600   #浏览器缓存时间
      ...

2.全局错误过滤器,返回统一JSON:

(1)全局错误过滤器:

@Component
public class GlobalErrorFilter implements GlobalFilter, Ordered {  //全局错误过滤器,对客户端返回统一的JSON串
    @Override
    public Mono<Void> filter(ServerWebExchange e, GatewayFilterChain c) {
        return c.filter(e.mutate().response(
                    new MyResponse((resp, body) -> Flux.from(body).flatMap(orgBody -> {
                        byte[] orgContent = new byte[orgBody.readableByteCount()];  //原始响应body的byte[]
                        orgBody.read(orgContent);
                        String content = new String(orgContent);  //原始响应body的字符串
                        if (resp.getStatusCode().value() == 500) {  //出现500错误时,替换原始响应body,对客户端返回统一JSON串
                            content = String.format("{\"status\": %d,\"path\":\"%s\"}",
                                    resp.getStatusCode().value(),
                                    e.getRequest().getPath().value());
                        }
                        resp.getHeaders().setContentLength(content.length());  //响应内容长度
                        return resp.writeWith(Flux.just(content).map(bx -> resp.bufferFactory().wrap(bx.getBytes())));
                    }).then(), e.getResponse())
               ).build());
    }
    @Override
    public int getOrder() {
        return -2;  //在WRITE_RESPONSE_FILTER(-1)之前执行
    }
}

(2)自定义BiFunction,异常过滤器中,用于写入Body内容的代理类:

public interface MyBiFunction extends BiFunction<ServerHttpResponse, Publisher<? extends DataBuffer>, Mono<Void>> { //自定义BiFunction,异常过滤器中,用于写入Body内容的代理类
}

(3)自定义ServerHttpResponse:

public class MyResponse extends ServerHttpResponseDecorator {  //自定义ServerHttpResponse
    private MyBiFunction bf;  //异常过滤器中,用于写入Body内容的代理类
    public MyResponse(MyBiFunction bf, ServerHttpResponse delegate) {
        super(delegate);
        this.bf = bf;
    }
    @Override
    public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
        return this.bf.apply(getDelegate(), body);
    }
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值