Spring-Cloud-Gateway-07

news2024/11/20 7:06:25

前言

1、什么是网关

网关是微服务最边缘的服务,直接暴露给用户,用来做用户和微服务的桥梁

在这里插入图片描述

  • 没有网关:客户端直接访问我们的微服务,会需要在客户端配置很多的ip:port,如果user-service并发比较大,则无法完成负载均衡
  • 有网关:客户端访问网关,网关来访问微服务,(网关可以和注册中心整合,通过服务名称找到目标的ip:prot)这样只需要使用服务名称即可访问微服务,可以实现负载均衡,可以实现token拦截、权限验证、限流等操作

2、Geteway简介

网关共有两个:Zuul和Geteway

  • Zuul:由newflix公司创建,分为1.0、2.0版本。Zuul的本质,一组过滤器,根据自定义的过滤器顺序来执行,本质就是web组件
    • Zuul1.0:使用的是BIO(Blocking IO) tomcat7.0以前都是BIO 性能一般
    • Zuul2.0:使用的NIO,性能好
  • SpringCloud:基于spring5.x,springboot2.x和ProjectReactor等技术。它的目的是让路由更加简单,灵活,还提供了一些强大的过滤器功能,如:熔断、限流、重试、自定义过滤器等token校验ip黑名单等。
  • SpringCloud Geteway作为Spring Cloud生态的网关,目标是替代Zuul,在SpringCloud2.0以上的版本中,没有对新版本的zuul2.0以上的最新高性能版本进行集成,仍然还是使用的zuul1.x。
  • SpringCloud Geteway是基于webFlux框架实现的,而webFlus框架底层则使用了高性能的Reactor模式通信框架的Netty
  • SpringCloud Geteway功能:
    • 建立在Spring Framework 5,Project Reactor和Spring Boot2.0之上
    • 能够匹配任何请求属性上的路由
    • Hystrix断路器集成
    • Spring Cloud DiscoveryClient集成
    • 易于编写的谓词和过滤器
    • 请求速率限制
    • 路径改写

3、Geteway工作流程

网关的核心:就是一组过滤器,按照先后执行顺序来执行过滤操作order 0 1 2

客户端向springCloud Gateway发出请求,然后在Gateway Handler Mapping中找到与请求相匹配的路由,将其发送到Gateway Web Handler

Handler 再通过指定的过滤器来将请求发送到我们实际的服务的业务逻辑,然后返回。过滤器之间用虚线分开是因为过滤器可能会在发送请求之间[pre]或之后[post]执行业务逻辑,对其进行加强或处理。

Filter在[pre]类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等

在[post]类型的过滤器中可以做响应的内容、响应头的修改、日志的输出,流量监控等有着非常重要的作用

总结:Gateway的核心逻辑也就是 路由转发 + 执行过滤器链

在这里插入图片描述

4、Gateway三大概念

4.1、Route(路由)(重点 和 eureka结合做动态路由)

路由信息的组成:

由一个ID、一个目的URL、一组断言工厂、一组Filter组成。

如果路由断言为真,说明请求URL和配置路由匹配

4.2、Predicate(断言)(就是一个返回bool的表达式)

Java 8 中的断言函数,lambda四大接口 供给型,消费性,函数型,断言型

Spring Cloud Gateway 中的断言函数输入类型是Spring5.0框架中的ServerWebExchange。String Cloud Gateway的断言函数允许开发者去定义匹配来自于Http Request中的任何信息。比如请求头和参数

4.3、Filter(过滤)(重点)

一个标准的Spring WebFilter。 Web三大组件(servlet listener filter) mvc interceptor

Spirng Cloud Gateway中的Filter分为两种类型的Filter,分别是Gateway Filter 和Global Filter。过滤器Filter将会对请求和相应进行修改处理。

一个针对某一个路由的filter 对某一个接口做限流

一个针对全局的filter (token ip黑名单)

5、Nginx和Gateway的区别

Nginx在做路由,负载均衡,限流之前,都有修改nginx.conf的配置文件,把需要负载均衡,路由,限流的规则加在里面。

在这里插入图片描述

快速使用

1、配置文件写法

  1. 新建一个项目,然后依赖只需要导入Gateway,不需要导入web依赖,因为Gateway是用netty写的,Spring Web集成的是Tomcat

    在这里插入图片描述

  2. 项目创建好后,修改SpringCloud依赖版本HoxtenSr12、SpringBoot依赖版本2.3.12.RELEASE

    在这里插入图片描述

  3. 依赖修改好后,编写路由的配置文件,本次测试使用Spring-Cloud-Feign-03创建的order-server测试服务,转发调用buyCar接口,先把测试项目启动起来

  4. 然后编写GateWay的配置文件

    server:
      port: 80 # 网关一般是80
    
    spring:
      application:
        name: gateway-server
      cloud:
        gateway:
          enabled: true # 只要加了依赖,就默认开启
          routes:
            - id: order-server # 路由的id,保持唯一即可
              uri: http://localhost:8080 # uri统一资源标识符 url统一资源定位符
              predicates:
                - Path=/buyCar # 匹配规则 只要你Path匹配上了/buyCar 就往uri转发 并且将路径带上
    
  5. 配置好后,然后启动项目,先访问localhost:8080/buyCar(先测试order-a接口是否可以正常调用)

    在这里插入图片描述

  6. 可以调用后,我们再访问locahost/buyCar(端口为80的可以省略:80),调用Gateway项目的buyCar接口,查看是否可以转发到localhost:8080/buyCar接口上,可以发现,调用成功

    在这里插入图片描述

  7. 到此为止,配置文件配置路由已经完毕。

2、代码写法

配置路由可以使用配置文件进行配置,也可以使用Bean对象进行配置,官网给我们的案例也是使用Bean对象进行配置的Gateway

在这里插入图片描述

  1. 我们把配置文件配置的给注解掉,然后copy上方案例,进行修改配置接口

    package com.tcc.config;
    
    import org.springframework.cloud.gateway.route.RouteLocator;
    import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * @author :tcc
     * @date :Created on 2022/12/18 15:50
     * @description:
     * @modified By:
     * @version:
     */
    // 先注入到Bean中
    @Configuration
    public class RouteConfit {
        // 然后copy官网的这段配置,进行修改
        // 代码的路由,和yml不冲突,都可以使用
        @Bean
        public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
            return builder.routes()
                    .route("order-service", r -> r.path("/buyCar")
                            .uri("http://localhost:8080"))
                    .build();
        }
    }
    
  2. 然后再访问localhost/buyCar可以查看到依旧访问成功

    在这里插入图片描述

  3. 到此为止,代码配置路由完毕

配置动态路由(重要)

我们像上面一样配置,就需要一个api配置一个路由,肯定很不合理,解决办法共有两种:

  • 一种是修改配置文件的匹配规则为Path=/car/**,这样就能转发car路由下所有子路由(/car/buyCar),但是这样也需要配置很多路由
  • 另外一种就是配置动态路由(常用)

我们这里一一演示

1、配置为主路由

  1. 为了方便测试,我们在order-service服务增加/car主路由,并配置多个子路由

    package com.tcc.controller;
    
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @author 宇辰
     * @date 2022/9/27-8:59
     **/
    @RestController
    @RequestMapping("car")
    public class TestController {
    
        // 买车
        @RequestMapping("buyCar")
        public String buyCar() {
            return "您购买了:劳斯莱斯-幻觉";
        }
    
        // 洗车
        @RequestMapping("washCar")
        public String washCar() {
            return "您洗了洗您的:劳斯莱斯-幻觉";
        }
    
        // 卖车
        @RequestMapping("sellCar")
        public String sellCar(){
            return "您卖出了:劳斯莱斯-幻觉";
        }
    }
    
  2. 配置好后,启动项目,进行测试

    在这里插入图片描述

  3. 然后在Gateway项目里修改配置文件,本次演示使用配置文件,配置/car下所有子路由的转发,记得把上面代码写法的配置文件解注,代码配置注掉

    server:
      port: 80 # 网关一般是80
    
    spring:
      application:
        name: gateway-server
      cloud:
        gateway:
          enabled: true # 只要加了依赖,就默认开启
          routes:
            - id: order-server # 路由的id,保持唯一即可
              uri: http://localhost:8080 # uri统一资源标识符 url统一资源定位符
              predicates:
                - Path=/car/** # 匹配规则 只要你Path匹配上了/car下的路由 就往uri转发 并且将路径带上
    
  4. 配置好后,重启项目,然后依次访问localhost/car/buyCar、localhost/car/washCar、localhost/car/sellCar

    在这里插入图片描述

  5. 到此为止,这种做法配置完毕

2、配置动态路由

配置动态路由需要在Gateway项目里添加Eureka依赖,根据拉取的服务列表,来根据地址上的服务名称,获取列表里的该服务的地址及端口号

  1. 添加Eureka的依赖以及配置文件

    server:
      port: 80 # 网关一般是80
    
    spring:
      application:
        name: gateway-server
      cloud:
        gateway:
          enabled: true # 只要加了依赖,就默认开启
          routes:
            - id: order-server # 路由的id,保持唯一即可
              uri: http://localhost:8080 # uri统一资源标识符 url统一资源定位符
              predicates:
                - Path=/car/buyCar # 匹配规则 只要你Path匹配上了/buyCar 就往uri转发 并且将路径带上
    
    eureka: # 配置Eureka
      client:
        service-url:
          defaultZone: http://82.157.246.210:8761/eureka
        registry-fetch-interval-seconds: 3 # 网关拉取服务列表的时间缩短
    
  2. 添加好后,开启Gateway的动态路由

    server:
      port: 80 # 网关一般是80
    
    spring:
      application:
        name: gateway-server
      cloud:
        gateway:
          enabled: true # 只要加了依赖,就默认开启
          routes:
            - id: order-server # 路由的id,保持唯一即可
              uri: http://localhost:8080 # uri统一资源标识符 url统一资源定位符
              predicates:
                - Path=/car/buyCar # 匹配规则 只要你Path匹配上了/buyCar 就往uri转发 并且将路径带上
          discovery: # 配置动态路由
            locator:
              enabled: true # 开启动态路由 开启通过应用名称 找到服务的功能
              lower-case-service-id: true # 开启服务名称小写
    
    eureka: # 配置Eureka
      client:
        service-url:
          defaultZone: http://82.157.246.210:8761/eureka
        registry-fetch-interval-seconds: 3 # 网关拉取服务列表的时间缩短
    
  3. 开启好后,访问localhost/服务名称/api地址即可

    1. 配置的静态路由依旧可以访问

      在这里插入图片描述

    2. 动态路由

      在这里插入图片描述

  4. 到此为止,动态路由配置完毕!

负载均衡

从之前的配置里面我们可以看到我们的URL都是写死的,这不符合我们微服务的要求,我们微服务是只要知道服务的名字,根据名字去找,而直接写死就没有负载均衡的效果了。

默认情况下Gateway会根据注册中心的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能

需要注意的是uri的协议为lb(load Balance),表示启用Gateway的负载均衡功能。

lb://serviceName是spring cloud gateway在微服务中自动为我们创建的负载均衡uri

协议:就是双方约定的一个街头暗号

Predicate断言

在gateway启动时会去加载一些路由断言工厂,启动的时候在控制台也能看到。使用断言可以在限时抢购的时候使用

在这里插入图片描述

使用方法

可以参考官网:Spring Cloud Gateway,我下面演示也会使用如下方法

在这里插入图片描述

Gateway接受一个由ZonedDateTime类生成的时间

在这里插入图片描述

断言可以匹配很多东西,比如指定主机(hose)、Cookie、方法参数等,下面只演示After一种,有其他需求的可以查看上面地址的官网演示,我这里也从bilibili上观看的学习视频上截取了几张图片

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

1、快速使用

  1. 修改配置文件,改为/washCar必须在2022年12月19日之后才能进行访问

  2. 先获取指定的时间格式

    @Test
    void contextLoads() {
        ZonedDateTime now = ZonedDateTime.now();
        System.out.println(now); // 2022-12-19T20:24:25.659+08:00[Asia/Shanghai]
    }
    
  3. 然后修改配置文件,在指定的时间之后才能进行访问

    server:
      port: 80 # 网关一般是80
    
    spring:
      application:
        name: gateway-server
      cloud:
        gateway:
          enabled: true # 只要加了依赖,就默认开启
          routes:
            - id: order-server # 路由的id,保持唯一即可
              uri: http://localhost:8080 # uri统一资源标识符 url统一资源定位符
    #          uri: lb://localhost:8080 # 使用lb协议 微服务名称做负载均衡
              predicates:
                - Path=/car/washCar # 匹配规则 只要你Path匹配上了/car/washCar 就往uri转发 并且将路径带上
                - After=2022-12-19T20:28:25.659+08:00[Asia/Shanghai] # 在20:28分钟后才能进行访问
          discovery: # 配置动态路由
            locator:
              enabled: true # 开启动态路由 开启通过应用名称 找到服务的功能
              lower-case-service-id: true # 开启服务名称小写
    
    eureka:
      client:
        service-url:
          defaultZone: http://82.157.246.210:8761/eureka
        registry-fetch-interval-seconds: 3 # 网关拉取服务列表的时间缩短
    
  4. 配置完毕后,然后启动服务,查看在指定时间之前是否可访问

    1. 28之前

      在这里插入图片描述

    2. 28的时候

      在这里插入图片描述

  5. 到此为止,断言演示完毕

2、查看源码

有兴趣了解源码的,可以查看RoutePredicateFactory接口类,查看它的实现类,里面写了所有断言规则,意味着你也可以自定义一个

在这里插入图片描述

在这里插入图片描述

Filter过滤器(重点)

1、概述

gateway里面的过滤器和Servlet里面的过滤器,功能差不多,路由过滤器可以用于修改进入Http请求和返回Http响应

2、过滤器分类

  • 按生命周期分两种

    • pre 在业务逻辑之前**(本编主要讲的是在逻辑之前的一个过滤)**

    • post 在业务逻辑之后

  • 按种类分也是两种

    • GatewayFilter 需要配置某个路由,才能过滤。如果需要使用全局路由,需要配置Default Filters

    • GlobalFilter 全局过滤器,不需要配置路由,系统初始化作用到所有的路由上

全局过滤器用处:统计请求次数、限流、token的校验、ip黑名单拦截、跨域、144开头的电话和一些ip的访问

过滤器实例也可以查看官网:Spring Cloud Gateway示例,官网提供了很多种过滤器,不过我们一般自己根据自己项目的需求写属于自己的过滤器,下面演示自定义过滤器的使用

3、局部过滤器

局部过滤器就不演示了,可以针对某个/些路由进行过滤操作,需要自写过滤器的话,可以实现GatewayFilter接口,参考官方提供的org.springframework.cloud.gateway.filter.factory.AddRequestHeaderGatewayFilterFactory过滤器

4、全局过滤器

主要演示的就是全局过滤器,对所有路由都适用的过滤器的实现操作

自定义过滤器需要实现一个GlobalFilter的接口,然后在实现的方法里面编写自己的需求

4.1、全局过滤器的放行

package com.tcc.filter;

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * @author :tcc
 * @date :Created on 2022/12/21 19:38
 * @description:
 * @modified By:
 * @version:
 */
public class MyGlobalFilter implements GatewayFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        
        // 放行
        return chain.filter(exchange);
    }
}

4.2、过滤器内能获取的信息

通过前端发出请求,我们能获取到请求内的所有信息,以及ip、方法名称、请求路径等信息,用作过滤逻辑使用

注意:编写完过滤器后,我们要对过滤器进行排序操作,以保证代码以正确的过滤顺序依次执行

package com.tcc.filter;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * @author :tcc
 * @date :Created on 2022/12/21 19:38
 * @description:Ordered是对过滤器的排序
 * @modified By:
 * @version:
 */
@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {

    /**
    	实现过滤方法
    **/
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        // 访问地址
        String path = request.getURI().getPath();
        System.out.println(path);

        // 请求头
        HttpHeaders headers = request.getHeaders();
        System.out.println(headers);

        // 请求方式
        String methodName = request.getMethod().name();
        System.out.println(methodName);

        // 主机名称
        String hostName = headers.getHost().getHostString();
        System.out.println(hostName);

        // 放行
        return chain.filter(exchange);
    }

    /**
    	实现排序方法
    **/
    @Override
    public int getOrder() {
        // 过滤的顺序,数值越小,越先执行过滤
        return 0;
    }
}

访问接口后,结果

在这里插入图片描述

4.3、不放行返回内容

如果该请求满足条件,被过滤掉了,我们通常会返回一个状态码和信息,告知前端因为什么原因请求没有被放行,通常数据的格式为json格式。下面就模拟过滤失败返回的内容

package com.tcc.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.HashMap;

/**
 * @author :tcc
 * @date :Created on 2022/12/21 19:38
 * @description:Ordered是对过滤器的排序
 * @modified By:
 * @version:
 */
@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 放行
//        return chain.filter(exchange);

        ServerHttpResponse response = exchange.getResponse();
        // 防止乱码
        response.getHeaders().set("content-type","application/json;charset=utf-8");
        // {"code":"200","msg","ok"}

        // 组装返回值
        HashMap<String, Object> map = new HashMap<>();
        map.put("code", HttpStatus.UNAUTHORIZED.value());
        map.put("msg","你未授权");

        ObjectMapper objectMapper = new ObjectMapper();
        // 把map转换成一个字节
        byte[] bytes = null;
        try {
            bytes = objectMapper.writeValueAsBytes(map);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        // 通过buffer工厂将字节数组包装成一个数据包
        DataBuffer wrap = response.bufferFactory().wrap(bytes);
        return response.writeWith(Mono.just(wrap));
    }

    @Override
    public int getOrder() {
        // 过滤的顺序,数值越小,越先执行过滤
        return 0;
    }
}

再次访问接口,结果

在这里插入图片描述

4.4、校验ip

通过过滤器,我们可以做一些黑名单/白名单的操作。

  • 黑名单:不让指定的人进入,比如打电话,有一些骚扰电话会进行自动拦截
  • 白名单:只允许指定的人进入,比如数据库权限

有时候,我们需要过滤一些国外的ip,或恶意攻击的ip,相当于是一个黑名单的操作

后面我们会进行多种情节使用举例,我们可以先把过滤失败返回的操作给封装城一个工具类使用

工具类

package com.tcc.util;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.HashMap;

/**
 * @author :tcc
 * @date :Created on 2022/12/21 20:18
 * @description:
 * @modified By:
 * @version:
 */
public class MyUtil {
    public static Mono<Void> returnData(ServerWebExchange exchange, Integer code, String msg){
        ServerHttpResponse response = exchange.getResponse();
        // 防止乱码
        response.getHeaders().set("content-type","application/json;charset=utf-8");
        // {"code":"200","msg","ok"}

        // 组装返回值
        HashMap<String, Object> map = new HashMap<>();
        map.put("code", code);
        map.put("msg",msg);
        ObjectMapper objectMapper = new ObjectMapper();
        // 把map转换成一个字节
        byte[] bytes = null;
        try {
            bytes = objectMapper.writeValueAsBytes(map);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        // 通过buffer工厂将字节数组包装成一个数据包
        DataBuffer wrap = response.bufferFactory().wrap(bytes);
        return response.writeWith(Mono.just(wrap));
    }
}

代码

package com.tcc.filter;

import com.tcc.util.MyUtil;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
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;

/**
 * @author :tcc
 * @date :Created on 2022/12/21 19:38
 * @description:Ordered是对过滤器的排序
 * @modified By:
 * @version:
 */
@Component
public class IPGlobalFilter implements GlobalFilter, Ordered {

    // 每次查数据库会很消耗性能,所以一般会写在内存里,或者使用正则进行匹配过滤
    private List<String> blackList = Arrays.asList("127.0.0.1");

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        HttpHeaders headers = request.getHeaders();
        String hostString = headers.getHost().getHostString();
        // 判断是否满足条件
        boolean b = blackList.stream().anyMatch(element -> element.equals(hostString));
        // 如果为黑名单内容,则返回失败
        if (b){
            return MyUtil.returnData(exchange, 520,"你是黑名单");
        }
        // 放行
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        // 过滤的顺序,数值越小,越先执行过滤
        return 0;
    }
}

测试

会拦截127.0.0.1的ip

  • 访问http://127.0.0.1/order-a/car/washCar

    在这里插入图片描述

  • 访问http://localhost/order-a/car/washCar

    在这里插入图片描述

4.5、校验token

我们token一般会存在redis中,当用户登录成功的时候,就会返回前端一个tokne,然后后面每次请求带着token即可访问接口。

token一般是放在redis中,我们下面演示会用到redis,进行存取token的操作。

前端在请求头中带token的时候,一般会有个前缀bearer (尾部有个空格),不了解的可以查看:https://www.jianshu.com/p/61d592ae33ee。下面进行实操

Redis Windows版本下载和使用可以参考:http://c.biancheng.net/redis/windows-installer.html

开发

  1. 先在order-a项目里新建一个login接口,用来开车锁。需要导入redis的依赖

    1. redis依赖

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-data-redis</artifactId>
      </dependency>
      
    2. testController

      package com.tcc.controller;
      
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.data.redis.core.StringRedisTemplate;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;
      
      import java.util.UUID;
      
      /**
       * @author 宇辰
       * @date 2022/9/27-8:59
       **/
      @RestController
      @RequestMapping("car")
      public class TestController {
      
          @Autowired
          private StringRedisTemplate redisTemplate;
      
      
          // 买车
          @RequestMapping("buyCar")
          public String buyCar() {
              return "您购买了:劳斯莱斯-幻觉";
          }
      
          // 开锁,上车
          @RequestMapping("login")
          public String login(String username,String password){
              // 假定登录操作
              User user = new User(1L, username, password);
              String token = UUID.randomUUID().toString();
      
              // 把token存到redis中
              redisTemplate.opsForValue().set(token,user.toString(),7200L);
              return "您已开锁,请上车,您的token是:" + token;
          }
      
          // 洗车
          @RequestMapping("washCar")
          public String washCar() {
              return "您洗了洗您的:劳斯莱斯-幻觉";
          }
      
          // 卖车
          @RequestMapping("sellCar")
          public String sellCar(){
              return "您卖出了:劳斯莱斯-幻觉";
          }
      }
      
      class User{
          private Long id;
          private String username;
          private String password;
      
          public User() {
          }
      
          public User(Long id, String username, String password) {
              this.id = id;
              this.username = username;
              this.password = password;
          }
      
          public Long getId() {
              return id;
          }
      
          public void setId(Long id) {
              this.id = id;
          }
      
          public String getUsername() {
              return username;
          }
      
          @Override
          public String toString() {
              return "User{" +
                      "id=" + id +
                      ", username='" + username + '\'' +
                      ", password='" + password + '\'' +
                      '}';
          }
      
          public void setUsername(String username) {
              this.username = username;
          }
      
          public String getPassword() {
              return password;
          }
      
          public void setPassword(String password) {
              this.password = password;
          }
      }
      
  2. 编写完毕后,启动order-a项目。然后在gateway项目里也导入redis依赖,用来判断token是否正确

  3. 然后编写拦截器

    package com.tcc.filter;
    
    import com.tcc.util.MyUtil;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.core.Ordered;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.server.reactive.ServerHttpRequest;
    import org.springframework.stereotype.Component;
    import org.springframework.util.CollectionUtils;
    import org.springframework.util.StringUtils;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;
    
    import java.util.List;
    
    /**
     * @author :tcc
     * @date :Created on 2022/12/21 19:38
     * @description:Ordered是对过滤器的排序
     * @modified By:
     * @version:
     */
    @Component
    public class IPGlobalFilter implements GlobalFilter, Ordered {
    
        @Autowired
        private StringRedisTemplate redisTemplate;
    
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            // 1.先获取请求头
            ServerHttpRequest request = exchange.getRequest();
            HttpHeaders headers = request.getHeaders();
    
            // 2.判断是否为登录操作,如果是,则直接放行
            String path = request.getURI().getPath();
            if("/order-a/car/login".equals(path)){
                return chain.filter(exchange);
            }
    
            // 3.不是登录操作,先从请求头中获取token,可能以为会有多个相同属性,所以是个list集合
            List<String> authorization = headers.get("authorization");
            // 4、判断前端是否带token了
            if(!CollectionUtils.isEmpty(authorization)){
                String token = authorization.get(0);
                if (StringUtils.hasText(token)){
                    // 5、带了的话,就获取token内容
                    String realToken = token.replaceFirst("bearer ", "");
                    // 6、判断是否能获取token,并且在redis中可以查到,就放行
                    if(StringUtils.hasText(realToken) && redisTemplate.hasKey(realToken)){
                        return chain.filter(exchange);
                    }
                }
            }
            // 7、没带token/没获取到token 拦截
            return MyUtil.returnData(exchange, HttpStatus.UNAUTHORIZED.value(),"未授权");
        }
    
        @Override
        public int getOrder() {
            // 过滤的顺序,数值越小,越先执行过滤
            return 0;
        }
    }
    
  4. 编写完毕后,启动gateway项目,记得在本地把Redis的服务端运行起来,否则会找不到localhost:6379的服务,存不到redis里,报错

测试

  1. 先访问http://localhost/order-a/car/login?username=张三&password=123456,进行一个登录的操作,账号密码内容随意。

    在这里插入图片描述

  2. 然后复制后台返回来的token,使用apiPost或postman访问http://localhost/order-a/car/washCar并且带上token(记得在token前面添加**bearer **),查看是否可以成功访问

    在这里插入图片描述

  3. 也可以把redis中的token删除,然后再次访问,这时候带的就是一个错误的token,就会返回未授权

    在这里插入图片描述

    在这里插入图片描述

限流

什么是限流

  • 限流就是限制一段时间内,用户访问资源的次数,减轻服务器压力,限流大致分为两种
    • IP限流(5s内同一个ip访问超过3次,则限制不让访问,过一段时间才可以继续访问)
    • 请求量限流(只要在一段时间内(窗口期),请求次数达到阈值,就直接决绝后面来的访问了,过一段时间才可以继续访问)(粒度可以细化到一个api(url),一个服务)

限流模型:漏斗算法、令牌桶算法、窗口滑动算法、计数器算法

本次演示的为令牌桶算法

1、令牌桶算法

在这里插入图片描述

用一个次来概括就是:入不敷出

  1. 所有的请求在处理之前都需要拿到一个可用的令牌才会被放行处理
  2. 根据限流大小,设置按照一定的速率往桶里添加令牌
  3. 桶设置最大的防止令牌限制,当桶满时,新添加的令牌就被丢弃或者拒绝
  4. 请求到达后,先获取令牌桶中的令牌,拿着令牌才可以进行其他的业务逻辑,处理完业务逻辑后,将令牌直接删除
  5. 令牌桶有最低限额,当令牌桶中的令牌达到最低限额的时候,请求处理完之后将不会删除令牌,以保证足够的限流

2、Gateway结合Redis实现请求量限流

Spring Cloud Gateway 已经内置了一个RequestRateLimiterGatewayFilterFactory,我们可以直接使用

目前RequestRateLimiterGatewayFilterFactory的实现依赖于Redis,所以我们还要导入spring-boot-starter-data-redis-reactive

导入依赖

<!-- 里面包含了redis,所以直接在原来的依赖上添加 -reactive 即可 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

3、针对某一个接口ip来限流

3.1、编写配置代码

package com.tcc.config;

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;

/**
 * @author :tcc
 * @date :Created on 2022/12/24 13:59
 * @description:
 * @modified By:
 * @version:
 */
@Configuration
public class RequestLimitConfig {
    
    // 针对某一个接口 ip来限流 /doLogin 每一个ip 根据配置文件的生成令牌规则,每秒生成1个令牌,最多装3个令牌
    @Bean
    public KeyResolver ipKeyResolver(){
        return exchange -> Mono.just(exchange.getRequest().getHeaders().getHost().getHostString());
    }
    
}

3.2、修改配置文件

server:
  port: 80 # 网关一般是80

spring:
  application:
    name: gateway-server
  cloud:
    gateway:
      enabled: true # 只要加了依赖,就默认开启
      routes:
        - id: order-server # 路由的id,保持唯一即可
          uri: http://localhost:8080 # uri统一资源标识符 url统一资源定位符
#          uri: lb://localhost:8080 # 使用lb协议 微服务名称做负载均衡
          predicates:
            - Path=/car/washCar # 匹配规则 只要你Path匹配上了/car/washCar 就往uri转发 并且将路径带上
            - After=2022-12-19T20:28:25.659+08:00[Asia/Shanghai] # 在20:28分钟后才能进行访问
          filters:
            - name: RequestRateLimiter # 过滤器的名称
              args: # 过滤器的参数
                   key-resolver: '#{@ipKeyResolver}' # 通过spel表达式取 ioc容器中的bean的值
                   redis-rate-limiter.replenishRate: 1 # 每秒生成1个令牌,方便测试
                   redis-rate-limiter.burstCapacity: 3 # 桶容量为三个令牌

      discovery: # 配置动态路由
        locator:
          enabled: true # 开启动态路由 开启通过应用名称 找到服务的功能
          lower-case-service-id: true # 开启服务名称小写

eureka:
  client:
    service-url:
      defaultZone: http://82.157.246.210:8761/eureka
    registry-fetch-interval-seconds: 3 # 网关拉取服务列表的时间缩短

3.3、编写好代码后,把上面做的所有过滤先注释掉,方便本次测试

在这里插入图片描述

3.4、启动项目,访问http://localhost/car/washCar,然后多次刷新浏览器,查看

在这里插入图片描述

3.5、打开Redis服务端,查看新增的key(有过期时间,尽快查看)

在这里插入图片描述

4、针对某个路径限流

3.1、编写配置代码

package com.tcc.config;

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import reactor.core.publisher.Mono;

/**
 * @author :tcc
 * @date :Created on 2022/12/24 13:59
 * @description:
 * @modified By:
 * @version:
 */
@Configuration
public class RequestLimitConfig {

    // 针对某一个接口 ip来限流 /doLogin 每一个ip 10秒内只能访问3次
    @Bean
    public KeyResolver ipKeyResolver(){
        return exchange -> Mono.just(exchange.getRequest().getHeaders().getHost().getHostString());
    }

    // 针对某一个接口 ip来限流 /doLogin 每一个ip 10秒内只能访问3次
    @Bean
    @Primary // 主候选的,不然会匹配到两个相同的Bean报错
    public KeyResolver apiKeyResolver(){
        return exchange -> Mono.just(exchange.getRequest().getPath().value());
    }
}

3.2、修改配置文件

filters:
            - name: RequestRateLimiter # 过滤器的名称
              args: # 过滤器的参数
                   key-resolver: '#{@apiKeyResolver}' # 通过spel表达式取 ioc容器中的bean的值 只需要修改匹配的Bean对象即可
                   redis-rate-limiter.replenishRate: 1
                   redis-rate-limiter.burstCapacity: 3

3.3、和上面测试方法一样,这里就不再演示了

访问其他接口无限制,访问/order-a/car/washCar会被限制

跨域配置

因为网关是微服务的边缘,所有的请求都要走网关,跨域的配置只需要写在网关即可

可以使用配置文件配置、代码配置两种方式

1、yarm配置

spring:
  cloud:
    gateway:
      globalcors:
        corsConfigurations:
          '[/**]':
            allowCredentials: true
            allowedHeaders: '*'
            allowedMethods: '*'
            allowedOrigins: '*'

2、代码配置

package net.youqu.micro.service.config;

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;

/**
 * description
 * from www.fhadmin.org
 */
@Configuration
public class CorsConfig {
    @Bean
    public CorsWebFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedMethod("*");
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
        source.registerCorsConfiguration("/**", config);

        return new CorsWebFilter(source);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/112214.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深度学习YoloV3案例

目录1 数据获取2 TFrecord文件2.1 什么是TFrecord文件2.2 将数据转换为TFRecord文件2.3 读取TFRecord文件2.4 数据处理3 模型构建4 模型训练4.1 损失函数的计算4.2 正负样本的设定4.3 模型训练4.3.1 获取数据集4.3.2 加载模型4.3.3 模型训练5 模型预测6 总结1 数据获取 根据要…

计算机工作原理简单介绍

文章目录一、冯诺依曼体系结构二、CPU基本工作流程CPU工作流程三、操作系统操作系统的基本功能四、进程&#xff08;process&#xff09;/任务&#xff08;task&#xff09;操作系统如何管理进程描述一个进程&#xff08;进程的相关属性&#xff09;组织若干进程CPU的分配内存的…

推荐系统,计算广告模型论文,代码与数据集汇总

Rec-Models 更多细节参考项目&#xff1a;https://github.com/JackHCC/Rec-Models https://github.com/JackHCC/Rec-Models &#x1f4dd; Summary of recommendation, advertising and search models. Recall Papers PaperResourceOthers[2019阿里SDM模型] SDM: Sequen…

Zebec Chain缘何能成为新晋应用链,熊市下又为何值得我们关注?

流支付生态 Zebec 正处于发展的火热阶段&#xff0c;Zebec此前于12月20日举办的为期3天的Web3.0 TechHive Summit 2022 大会&#xff0c;目前已经落幕&#xff0c;此次大会参会项目多达34个囊括了公链、钱包、DID、GameFi等多个主流行业赛道&#xff0c;并围绕行业安全、发展趋…

ConvLSTM时空预测实战代码详解

写在前面 时空预测是很多领域都存在的问题&#xff0c;不同于时间序列&#xff0c;时空预测不仅需要探究时间的变化&#xff0c;也需要关注空间的变化。许多预测问题都只片面的关注时间问题&#xff0c;如预测某人未来3年患某种病的概率&#xff0c;食堂就餐人数等&#xff0c…

CSS--圆角边框

单独对四个角进行设置&#xff1a; boder-top-left-radius&#xff1a;30px&#xff1b; //左上角 boder-top-right-radius&#xff1a;30px&#xff1b; //右上角 boder-bottom-left-radius&#xff1a;30px&#xff1b; //右下角 boder-bottom-right-radius&#xff1a;30px&…

群晖 Sonology NAS DS920+ 拆机装机方法

文章内容&#xff1a;群晖 Sonology NAS DS920 拆机方法 关键词组&#xff1a;群晖&#xff0c;Sonology, nas, ds920, 拆机, 外壳 使用软件&#xff1a;无 虚拟环境&#xff1a;无 操作系统&#xff1a;无 目录一、事件起因三、拆装机方法一、事件起因 起初&#xff0c;由于机…

OpenCV环境下实现图像任意角度旋转的原理及代码

OpenCV环境下实现图像任意角度旋转的原理及代码 实现图像任意角度旋转的原理如下&#xff1a; Step01-把图像原点从左上角转换到旋转中心点。 Step02-利用极坐标系计算出旋转后各点的坐标。 Step03-确定旋转后图像的左边界、右边界、上边界、下边界&#xff0c;进而得出旋转后…

计数排序 [数据结构与算法][Java]

计数排序 计数排序和基数排序都是桶排序的一种应用 适用场景: 量大但是范围小 比如对10000个数进行排序, 但是这10000个数中只有10种数字(0 - 9)典型题目: 某大型企业数万名员工年龄排序如何快速得知高考名次(腾讯面试) 这里我们以某大型企业数万名员工年龄排序来进行一个…

RV1126笔记十四:吸烟行为检测及部署<二>

若该文为原创文章,转载请注明原文出处。 PC下yolov5环境搭建 我使用的训练环境是Windows10+MiniConda 接下来记录搭建全过程 备注:条件允许就使用ubuntu物理机,最好要有显卡,训练有显卡速度会快很多,没有显卡,训练300轮,亲测大概40小时,不值得。 一、miniconda 安装…

从零了解进程(操作系统定位,进程的概念,特征,虚拟地址)

目录 操作系统的定位 进程的概念 如何描述进程? 如何组织进程? 进程的特征 1.pid 2.内存指针 3.文件描述符 4.进程调度的相关属性 (1)进程的状态 (2)优先级 (3)上下文 (4)记账信息 进程是如何利用cpu资源的? 进程的虚拟地址 物理地址 内存随机访问的特性 为…

【ML实验7】人脸识别综合项目(PCA、多分类SVM)

实验代码获取 github repo 山东大学机器学习课程资源索引 实验目的 实验环境 实验内容 PCA 两种方法EVD-PCA和SVD-PCA的实现、效率对比见我之前的博客一个PCA加速技巧&#xff0c;这里补充SVD方法的数学推导&#xff1a; 首先&#xff0c;设方阵AAA的特征值分解为AUΣUTAU\S…

ZKP应用:石头剪刀布游戏

1. 引言 开源代码见&#xff1a; https://github.com/spalladino/zkp-tests 对比了分别使用&#xff1a; Iden3团队的circom语言&#xff08;易于学习ZKP&#xff09;ZCash团队的Halo2框架Aztec团队的Noir语言&#xff08;最友好&#xff09; 编写石头剪刀布游戏的ZKP证明…

IPv6(计算机网络-网络层)

目录 IPv6 的特点 IPv6 数据报的格式 IPv6 分组的格式 IPv6 的扩展首部 从计算机本身发展以及从互联网规模和网络传输速率来看&#xff0c;现在 IPv4已很不适用。 要解决 IP 地址耗尽的问题的措施&#xff1a; 采用无类别编址 CIDR&#xff0c;使 IP 地址的分配更加合理…

MySQL 锁机制

文章目录MySQL 锁机制表锁读锁场景一场景二场景三总结写锁场景一场景二场景三总结行锁场景一场景二总结间隙锁缺点如何锁定一行MySQL 锁机制 表锁 读锁 查看哪些表被加锁了 语法&#xff1a;show open tables 添加读锁 read 读锁关键字 | write 写锁关键字 语法&#xff1a;l…

qt下采用libcurl实现ftp与tftp功能,提供源代码程序

一、FTP简介 FTP&#xff08;文件传输协议&#xff09;&#xff0c;工作在应用层&#xff0c;是用于在网络上进行文件传输的一套标准协议。它使用 TCP 传输&#xff0c;客户在和服务器建立连接前要经过一个“三次握手”的过程&#xff0c; 保证客户与服务器之间的连接是可靠的&…

基于xml的自动装配之byName

基于xml的自动装配之byName 自动装配方式&#xff1a;byName byName&#xff1a;将自动装配的属性的属性名&#xff0c;作为bean的id在IOC容器中匹配相对应的bean进行赋值总结&#xff1a;当类型匹配的 bean 有多个时&#xff0c;此时可以使用 byName 实现自动装配 配置bean &…

02:损失函数总结

目录 nn.L1Loss: nn.NLLLoss: nn.MSELoss: nn.CrossEntropyLoss: 损失函数是用来估量模型的预测值与真实值的不一致程度&#xff0c;它是一个非负实值函数。我们训练模型的过程&#xff0c;就是通过不断的迭代计算&#xff0c;使用梯度下降的优化算法&#xff0c;使得损失函…

多叉树 [数据结构与算法][Java]

多叉树 在二叉树中每个结点只能有一个数据项, 并且最多有两个子节点, 如果允许每个结点可以有更多的数据项和更多的子节点, 那么就是多叉树 多叉树: multiway tree 那么我们为什么要提出多叉树? 因为二叉树有一定的问题: 即使二叉树的操作效率高, 但是也存在问题: 二叉树需…

django之前后端不分离的操作(增删改查)

背景&#xff1a; demo采用的是前后端不分离的操作&#xff0c;用Bootstrap作为前段页面框架使用 1.先说模板之间的继承&#xff08;针对HTML来讲&#xff09; 父模板中编写好公共代码块&#xff0c;例如一个网站的导航和footer部分 在内容部分注明 {%block content%} {%e…