Gateway新一代网关

Gateway新一代网关

1、概述

​ Cloud全家桶中有个很重要的组件就是网关,在1.x版本中都是采用的Zuul网关;

​ 但在2.x版本中,zuul的升级一直跳票,SpringCloud最后自己研发了一个网关SpringCloud Gateway替代Zuul。

​ 官网:Spring Cloud Gateway 中文文档 (springdoc.cn)

image-20240316155756665

微服务架构中网关在哪里?

image-20240316161655332

Spring Cloud Gateway能干什么?

  • 反向代理;
  • 鉴权;
  • 流量控制;
  • 熔断;
  • 日志监控;

总结:

​ **Spring Cloud Gateway组件的核心是一系列的过滤器,通过这些过滤器可以将客户端发送的请求转发(路由)到对应的微服务。 Spring Cloud Gateway是加在整个微服务最前沿的防火墙和代理器,隐藏微服务结点IP端口信息,从而加强安全保护。**Spring Cloud Gateway本身也是一个微服务,需要注册进服务注册中心。

image-20240316162148933

2、Gateway三大核心

  • Route(路由): 网关的基本构件。它由一个ID、一个目的地URI、一个谓词(Predicate)集合和一个过滤器(Filter)集合定义。如果集合谓词为真,则路由被匹配。
  • Predicate(谓词): 这是一个 Java 8 Function Predicate。输入类型是 Spring Framework ServerWebExchange。这让你可以在HTTP请求中的任何内容上进行匹配,比如header或查询参数。
  • Filter(过滤器): 这些是 GatewayFilter 的实例,已经用特定工厂构建。在这里,你可以在发送下游请求之前或之后修改请求和响应。
  1. web前端请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。
  2. predicate就是我们的匹配条件;
  3. filter,就可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标uri,就可以实现一个具体的路由了

image-20240316162611930

3、Spring Cloud Gateway 工作流程

image-20240316162812264

​ 客户端向 Spring Cloud Gateway 发出请求。如果Gateway处理程序映射确定一个请求与路由相匹配,它将被发送到Gateway Web处理程序。这个处理程序通过一个特定于该请求的过滤器链来运行该请求。过滤器被虚线分割的原因是,过滤器可以在代理请求发送之前和之后运行逻辑。

​ 所有的 "pre" (前)过滤器逻辑都被执行。然后发出代理请求。在代理请求发出后,"post" (后)过滤器逻辑被运行。

注意:在路由中定义的没有端口的URI,其HTTP和HTTPS URI的默认端口值分别为80和443。

总结:路由转发+断言判断+执行过滤器链

4、入门配置

新建module,cloud-gateway9527

image-20240316164839950

添加POM,添加springcloudgateway依赖。

 <dependencies>
        <!--gateway-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!--服务注册发现consul discovery,网关也要注册进服务注册中心统一管控-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>
        <!-- 指标监控健康检查的actuator,网关是响应式编程删除掉spring-boot-starter-web dependency-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

​ 写配置文件application.yml

server:
  port: 9527

spring:
  application:
    name: cloud-gateway #以微服务注册进consul或nacos服务列表内
  cloud:
    consul: #配置consul地址
      host: localhost
      port: 8500
      discovery:
        prefer-ip-address: true
        service-name: ${spring.application.name}

​ 主启动类:

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

​ 业务类是不需要写的,网关与业务无关。

​ 先启动Consul,然后启动网关入住进服务注册中心。

image-20240316165637332

5、9527网关如何做路由映射

​ 我们目前不希望暴露8001端口,希望在8001这个真正的支付微服务外面再套一个9527网关。

8001新建GateWayController

@RestController
public class GateWayController {
    @Resource
    PayService payService;

    @GetMapping(value = "/pay/gateway/get/{id}")
    public ResultData<Pay> getById(@PathVariable("id") Integer id)
    {
        Pay pay = payService.getById(id);
        return ResultData.success(pay);
    }

    @GetMapping(value = "/pay/gateway/info")
    public ResultData<String> getGatewayInfo()
    {
        return ResultData.success("gateway info test:"+ IdUtil.simpleUUID());
    }
}

​ 测试一下:

image-20240316174047314

​ 没有问题,下面在网关的配置文件中新增配置,新增路由和断言配置。

server:
  port: 9527

spring:
  application:
    name: cloud-gateway #以微服务注册进consul或nacos服务列表内
  cloud:
    consul: #配置consul地址
      host: localhost
      port: 8500
      discovery:
        prefer-ip-address: true
        service-name: ${spring.application.name}
    gateway:
      routes:
        - id: pay_routh1 #pay_routh1                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:8001                #匹配后提供服务的路由地址
          predicates:
            - Path=/pay/gateway/get/**              # 断言,路径相匹配的进行路由
        - id: pay_routh2 #pay_routh2                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:8001                #匹配后提供服务的路由地址
          predicates:
            - Path=/pay/gateway/info/**              # 断言,路径相匹配的进行路由


​ 添加网关之后的访问就不是8001了,应该是localhost:9527/pay/gateway/get/1

image-20240316180242818

​ 目前通过网关访问是成功的,如果通过80访问网关然后再访问8001呢?

​ 我们启动80订单微服务,它从Consul注册中心通过微服务名称找到8001支付微服务进行调用,

​ 80 → 9527 → 8001

​ 要求访问9527网关后才能访问8001,如果我们此时启动80订单,可以做到吗?

​ 修改cloud-api-commons的api接口添加网关测试方法

//网关测试
@GetMapping(value = "/pay/gateway/get/{id}")
public ResultData getById(@PathVariable("id") Integer id);
@GetMapping(value = "/pay/gateway/info")
public ResultData<String> getGatewayInfo();

​ cloud-consumer-feign-order80添加网关测试的controller

@RestController
public class OrderGateWayController
{
    @Resource
    private PayFeignApi payFeignApi;

    @GetMapping(value = "/feign/pay/gateway/get/{id}")
    public ResultData getById(@PathVariable("id") Integer id)
    {
        return payFeignApi.getById(id);
    }

    @GetMapping(value = "/feign/pay/gateway/info")
    public ResultData<String> getGatewayInfo()
    {
        return payFeignApi.getGatewayInfo();
    }
}

image-20240316203012095

​ 我们发现是可以访问的,真的走网关了吗?我们的API接口上@FeignClient(value = "cloud-payment-service")打的可是cloud-payment-service微服务,根本没有网关的事呀,所以,这个时候的网关压根就没有使用,我们把网关的微服务关掉再用80访问一定是可以访问的。

image-20240316203243097

​ 网关的9527关掉了,现在访问

image-20240316203312417

​ 依旧正常访问。我们想一下,API上@FeignClient注解上原来就是cloud-payment-service,我们换成网关的服务名称cloud-gateway不就成了。

//@FeignClient(value = "cloud-payment-service")
@FeignClient(value = "cloud-gateway")  //换成网关微服务
public interface PayFeignApi {
    /**
     * 新增一条支付相关流水记录
     * @param payDTO
     * @return
     */
    @PostMapping("/pay/add")
    public ResultData addPay(@RequestBody PayDTO payDTO);
    //通过id查询
    @GetMapping(value = "/pay/get/{id}")
    public ResultData getPayById(@PathVariable("id") Integer id);
    //删除
    @DeleteMapping("/pay/del/{id}")
    public ResultData delById(@PathVariable("id") Integer integer);
    //修改
    @PutMapping("/pay/update")
    public ResultData update(@RequestBody PayDTO payDTO);
    //查全部
    @GetMapping("/pay/getall")
    public ResultData getAll();

    //openfeign天然支持负载均衡演示
    @GetMapping("/pay/getInfo")
    public String myLB();

    //=========Resilience4j CircuitBreaker 的例子
    @GetMapping("/pay/circuit/{id}")
    public String myCircuit(@PathVariable("id") Integer id);

    //=========Resilience4j bulkhead 的例子
    @GetMapping(value = "/pay/bulkhead/{id}")
    public String myBulkhead(@PathVariable("id") Integer id);
    //=========Resilience4j ratelimit 的例子
    @GetMapping(value = "/pay/ratelimit/{id}")
    public String myRatelimit(@PathVariable("id") Integer id);

    @GetMapping(value = "/pay/micrometer/{id}")
    public String myMicrometer(@PathVariable("id") Integer id);

    //网关测试
    @GetMapping(value = "/pay/gateway/get/{id}")
    public ResultData getById(@PathVariable("id") Integer id);
    @GetMapping(value = "/pay/gateway/info")
    public ResultData<String> getGatewayInfo();
}

​ 重启80微服务,再次测试

无网关的直接500了

image-20240316203856756

image-20240316203921219

再把网关打开就成功访问了

image-20240316204039951

目前我们的配置还是有问题的,比如我们网关中的uri地址,本地加端口号写的死死的,一改就无了…

image-20240316204432016

6、GateWay高级特性

6.1、Route以微服务名动态获取服务URI

image-20240317152134870

​ 我们之前给URI写死了,现在可以修改成它的微服务名称,这样就可以动态的从服务注册中心拿到具体的地址加端口,即使端口更改了也没有问题。URL中添加一个lb代表SpringCloud的负载均衡ReactorLoadBalancer。

​ 将9527的配置文件再来修改一下

gateway:
  routes:
    - id: pay_routh1 #pay_routh1                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
      #uri: http://localhost:8001                #匹配后提供服务的路由地址
      uri: lb://cloud-payment-service               #匹配后提供服务的路由地址
      predicates:
        - Path=/pay/gateway/get/**              # 断言,路径相匹配的进行路由
    - id: pay_routh2 #pay_routh2                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
     # uri: http://localhost:8001                #匹配后提供服务的路由地址
      uri: lb://cloud-payment-service
      predicates:
        - Path=/pay/gateway/info/**              # 断言,路径相匹配的进行路由

​ 修改以后可以重启一下网关测试。

image-20240317152817083

​ 测试没有问题,我们把8001的端口给改成8003再测试一下。

image-20240317153154286

image-20240317152941868

​ 依然可用。

6.2、Predicate断言(谓词)

​ Spring Cloud Gateway将路由匹配作为Spring WebFlux HandlerMapping 基础设施的一部分。Spring Cloud Gateway包括许多内置的路由谓词工厂。所有这些谓词都与HTTP请求的不同属性相匹配。你可以用逻辑 and 语句组合多个路由谓词工厂。

​ 我们可以查看9527后台输出的默认路由谓词是和官网的那12个默认路由谓词一一对应着的

image-20240317154302793

两种配置方式

  • Shortcut Configuration,快捷方式配置由过滤器名称(filter name),后面跟一个等号 (=),然后是用逗号 (,) 分隔的参数值。

    image-20240317174758368

  • Fully Expanded Arguments,完全展开的参数看起来更像标准的yaml配置,有名称/值对。一般来说,会有一个 name key和一个 args key。args key是一个键值对的映射,用于配置谓词或过滤器。

    image-20240317174817002

常用的默认路由谓词解释

  1. After 路由谓词工厂需要一个参数,即一个日期时间(这是一个java ZonedDateTime),这个谓词匹配发生在指定日期时间之后的请求。

    那么这个日期怎么获得?

    写一个普通的java类来获得。

    public class ZonedDateTimeDemo
    {
        public static void main(String[] args)
        {
            System.out.println(ZonedDateTime.now());
        }
    }
    

    获得时间格式:2024-03-17T20:25:30.218129200+08:00[Asia/Shanghai]

    开始配置

    gateway:
      routes:
        - id: pay_routh1 #pay_routh1                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
          #uri: http://localhost:8001                #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service               #匹配后提供服务的路由地址
          predicates:
            - Path=/pay/gateway/get/**              # 断言,路径相匹配的进行路由
            - After=2024-03-17T20:30:30.218129200+08:00[Asia/Shanghai]  #在这个时间之后才能够访问,到点放票
        - id: pay_routh2 #pay_routh2                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
         # uri: http://localhost:8001                #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/info/**              # 断言,路径相匹配的进行路由
    

    image-20240317203441613

    在我设置的8:30:30之前是不能访问的,8点半之后就可以访问了。

  2. Before 路由谓词工厂只需要一个参数,即 datetime,这个谓词匹配发生在指定日期时间datetime之前的请求。

    gateway:
      routes:
        - id: pay_routh1 #pay_routh1                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
          #uri: http://localhost:8001                #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service               #匹配后提供服务的路由地址
          predicates:
            - Path=/pay/gateway/get/**              # 断言,路径相匹配的进行路由
            - After=2024-03-17T20:30:30.218129200+08:00[Asia/Shanghai]  #在这个时间之后才能够访问,到点放票
        - id: pay_routh2 #pay_routh2                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
         # uri: http://localhost:8001                #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/info/**              # 断言,路径相匹配的进行路由
            - Before=2024-03-17T20:39:00.218129200+08:00[Asia/Shanghai] 
    

    image-20240317205203281

    可以看到8.38还能正常访问的39就不能了。

  3. Between 路由谓词工厂需要两个参数,datetime1datetime2,它们是java ZonedDateTime 对象。这个谓词匹配发生在 datetime1 之后和 datetime2 之前也就是这个时间段的请求。

    gateway:
      routes:
        - id: pay_routh1 #pay_routh1                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
          #uri: http://localhost:8001                #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service               #匹配后提供服务的路由地址
          predicates:
            - Path=/pay/gateway/get/**              # 断言,路径相匹配的进行路由
            - Between=2024-03-17T21:00:00.218129200+08:00[Asia/Shanghai],2024-03-17T21:01:00.218129200+08:00[Asia/Shanghai]  #在这个时间段才能够访问,现时秒杀
        - id: pay_routh2 #pay_routh2                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
         # uri: http://localhost:8001                #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/info/**              # 断言,路径相匹配的进行路由
            - Before=2024-03-17T20:39:00.218129200+08:00[Asia/Shanghai]
    

    image-20240317210332707

    可以看到,这个只能在9点到9点零1才能访问到。

  4. Cookie 路由谓词工厂接受两个参数,即 cookie name 和一个 regexp(这是一个Java正则表达式)。这个谓词匹配具有给定名称且其值符合正则表达式的cookie。

    gateway:
      routes:
        - id: pay_routh1 #pay_routh1                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
          #uri: http://localhost:8001                #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service               #匹配后提供服务的路由地址
          predicates:
            - Path=/pay/gateway/get/**              # 断言,路径相匹配的进行路由
            - Between=2024-03-17T21:00:00.218129200+08:00[Asia/Shanghai],2024-03-17T21:01:00.218129200+08:00[Asia/Shanghai]  #在这个时间之后才能够访问,到点放票
        - id: pay_routh2 #pay_routh2                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
         # uri: http://localhost:8001                #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/info/**              # 断言,路径相匹配的进行路由
            - Cookie=username, zm
    

    只有加了cookie才能访问,而且键值(kv)要匹配

    有三种可以带cookie访问的方法

    • 方法1,使用原生命令,cmd打开运行框

      不带cookie的不能访问

      image-20240317211459459

      带了cookie可以访问

      image-20240317211449603

    • 方法2,使用postman测试工具测试

      不带cookie的不能访问

      image-20240317212021172

      带了cookie可以访问

      image-20240317212052694

    • 方法3,使用浏览器控制台实现

      不带cookie或者故意填错误的不能访问

      image-20240317212605610

      带了正确的cookie可以访问

    image-20240317212750797

  5. Header 路由谓词工厂需要两个参数,header 和一个 regexp(这是一个Java正则表达式)。这个谓词与具有给定名称且其值与正则表达式相匹配的 header 匹配。

    gateway:
      routes:
        - id: pay_routh1 #pay_routh1                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
          #uri: http://localhost:8001                #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service               #匹配后提供服务的路由地址
          predicates:
            - Path=/pay/gateway/get/**              # 断言,路径相匹配的进行路由
            #- Between=2024-03-17T21:00:00.218129200+08:00[Asia/Shanghai],2024-03-17T21:01:00.218129200+08:00[Asia/Shanghai]  #在这个时间之后才能够访问,到点放票
            - Header=X-Request-Id, \d+  #只有X-Request-Id属性为正整数才能访问
        - id: pay_routh2 #pay_routh2                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
         # uri: http://localhost:8001                #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/info/**              # 断言,路径相匹配的进行路由
            - Cookie=username, zm
    

    测试方法1,使用原生命令

    不是正整数

    image-20240318091813792

    是正整数

    image-20240318091840840

    测试方法2,使用postman测试

    不是正整数

    image-20240318092512234

    是正整数

    image-20240318092420045

  6. Host 路由谓语工厂接受一个参数:一个主机(Host)名称的 patterns 列表。该pattern是Ant风格的模式,以 . 为分隔符。这个谓词匹配符合该pattern的Host header。

    gateway:
      routes:
        - id: pay_routh1 #pay_routh1                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
          #uri: http://localhost:8001                #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service               #匹配后提供服务的路由地址
          predicates:
            - Path=/pay/gateway/get/**              # 断言,路径相匹配的进行路由
            #- Between=2024-03-17T21:00:00.218129200+08:00[Asia/Shanghai],2024-03-17T21:01:00.218129200+08:00[Asia/Shanghai]  #在这个时间之后才能够访问,到点放票
            - Header=X-Request-Id, \d+  #只有X-Request-Id属性为正整数才能访问
        - id: pay_routh2 #pay_routh2                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
         # uri: http://localhost:8001                #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/info/**              # 断言,路径相匹配的进行路由
            #- Cookie=username, zm
            - Host=**.zmblog.com,**.zm.cn  #请求host的header值带zmblog.com或zm.cn的可以匹配
    

    使用原命令测试

    正确符合规则的可以访问

    image-20240318093306903

    不符合规则的拒绝访问

    image-20240318093534851

    使用postman测试

    正常匹配规则的

    image-20240318094042987

​ 不符合规则的

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

7.Path 路由谓词工厂需要两个参数:一个Spring PathMatcher patterns 的list和一个可选的flag matchTrailingSlash(默认为 true)。我们最开始用的就是这个,就不再看了。

8.Query 路由谓词工厂需要两个参数:一个必需的 param 和一个可选的 regexp(这是一个Java正则表达式)。

gateway:
  routes:
    - id: pay_routh1 #pay_routh1                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
      #uri: http://localhost:8001                #匹配后提供服务的路由地址
      uri: lb://cloud-payment-service               #匹配后提供服务的路由地址
      predicates:
        - Path=/pay/gateway/get/**              # 断言,路径相匹配的进行路由
        #- Between=2024-03-17T21:00:00.218129200+08:00[Asia/Shanghai],2024-03-17T21:01:00.218129200+08:00[Asia/Shanghai]  #在这个时间之后才能够访问,到点放票
        #- Header=X-Request-Id, \d+  #只有X-Request-Id属性为正整数才能访问
        - Query=username,\d+  #要有参数名称为username并且值还要是整数才能路由
    - id: pay_routh2 #pay_routh2                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
     # uri: http://localhost:8001                #匹配后提供服务的路由地址
      uri: lb://cloud-payment-service
      predicates:
        - Path=/pay/gateway/info/**              # 断言,路径相匹配的进行路由
        #- Cookie=username, zm
        - Host=**.zmblog.com,**.zm.cn  #请求host的header值带zmblog.com或zm.cn的可以匹配

​ 测试:

​ 按照规则访问

image-20240318103245369

​ 不按规则不能访问

image-20240318103401271

9.RemoteAddr 路由谓词工厂接受一个 sources 集合(最小长度为1),它是CIDR注解(IPv4或IPv6)字符串,如 192.168.0.1/16(其中 192.168.0.1 是一个IP地址,16 是一个子网掩码)。

gateway:
  routes:
    - id: pay_routh1 #pay_routh1                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
      #uri: http://localhost:8001                #匹配后提供服务的路由地址
      uri: lb://cloud-payment-service               #匹配后提供服务的路由地址
      predicates:
        - Path=/pay/gateway/get/**              # 断言,路径相匹配的进行路由
        #- Between=2024-03-17T21:00:00.218129200+08:00[Asia/Shanghai],2024-03-17T21:01:00.218129200+08:00[Asia/Shanghai]  #在这个时间之后才能够访问,到点放票
        #- Header=X-Request-Id, \d+  #只有X-Request-Id属性为正整数才能访问
        #- Query=username,\d+  #要有参数名称为username并且值还要是整数才能路由
        - RemoteAddr=10.2.142.1/24  # 外部访问我的IP限制,最大跨度不超过32,目前是1~24它们是 CIDR 表示法。
    - id: pay_routh2 #pay_routh2                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
     # uri: http://localhost:8001                #匹配后提供服务的路由地址
      uri: lb://cloud-payment-service
      predicates:
        - Path=/pay/gateway/info/**              # 断言,路径相匹配的进行路由
        #- Cookie=username, zm
        - Host=**.zmblog.com,**.zm.cn  #请求host的header值带zmblog.com或zm.cn的可以匹配

​ 测试,重启服务按照原来的localhost地址访问一下不能访问。

image-20240318103903777

​ 换成我们本地的IP就可以了

image-20240318104012582

10.Method 路由谓词工厂接受一个 methods 参数,它是一个或多个参数:要匹配的HTTP方法。

​ 我们可以限制只能使用GET或者POST方法才能访问。

- Method=GET,POST   #只能是get或post请求方法

image-20240318104342742

6.3、自定义断言XXXRoutePredicateFactory规则

​ 原有的默认的路由断言规则不够用,我们就自定义一个。

​ 查看AfterRoutePredicateFactory它就是继承了AbstractRoutePredicateFactory

public class AfterRoutePredicateFactory extends AbstractRoutePredicateFactory<Config>

​ 然后再点AbstractRoutePredicateFactory里面它又实现了RoutePredicateFactory接口

image-20240318105516535

​ 所以我们要想自定义一个路由断言就需要这几点

  1. 要么继承AbstractRoutePredicateFactory抽象类;
  2. 要么实现RoutePredicateFactory接口;
  3. 开头的名字任意取,但是必须以RoutePredicateFactory为后缀结尾;
  4. 由于AbstractRoutePredicateFactory里面就实现类RoutePredicateFactory接口我们就直接继承它即可,一举两得;
  • 新建类名XXX需要以RoutePredicateFactory为后缀结尾并继承AbstractRoutePredicateFactory抽象类;

    @Component
    public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config>
    
  • 重写apply方法;

    @Override
    public Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config) {
        return null;
    }
    
  • 新建apply方法所需要的静态内部类MyRoutePredicateFactory.Config,这个Config类就是我们的路由断言规则,非常重要

    @Validated 
    public static class Config{
        @Setter@Getter@NotNull
        private String userType;  //会员类型
    }
    
  • 空参构造方法,内部调用super;

    //空参构造函数
    public MyRoutePredicateFactory() {
        super(Config.class);
    }
    
  • 重写apply方法第二版;

    @Override
    public Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config) {
        return new GatewayPredicate() {
            @Override
            public boolean test(ServerWebExchange serverWebExchange) {
                String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType");
                if (userType == null) {
                    return false;
                }
                //这个config是从配置文件中拿到的如果和传来的相匹配就通过
                if (userType.equals(config.getUserType())){
                    return true;
                }
                return false;
            }
        };
    }
    
  • 完整方法

    //业务需求说明:自定义配置会员等级userType,按照钻石/黄金/青铜的等级,yml配置会员等级
    @Component
    public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config> {
    
        @Override
        public Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config) {
            return new GatewayPredicate() {
                @Override
                public boolean test(ServerWebExchange serverWebExchange) {
                    String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType");
                    if (userType == null) {
                        return false;
                    }
                    //这个config是从配置文件中拿到的如果和传来的相匹配就通过
                    if (userType.equals(config.getUserType())){
                        return true;
                    }
                    return false;
                }
            };
        }
        @Validated 
        public static class Config{
            @Setter@Getter@NotNull
            private String userType;  //会员类型
        }
        //空参构造函数
        public MyRoutePredicateFactory() {
            super(Config.class);
        }
    }
    
  • 把自定义的断言配置到yml文件

    gateway:
      routes:
        - id: pay_routh1 #pay_routh1                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
          #uri: http://localhost:8001                #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service               #匹配后提供服务的路由地址
          predicates:
            - Path=/pay/gateway/get/**              # 断言,路径相匹配的进行路由
            #- Between=2024-03-17T21:00:00.218129200+08:00[Asia/Shanghai],2024-03-17T21:01:00.218129200+08:00[Asia/Shanghai]  #在这个时间之后才能够访问,到点放票
            #- Header=X-Request-Id, \d+  #只有X-Request-Id属性为正整数才能访问
            #- Query=username,\d+  #要有参数名称为username并且值还要是整数才能路由
            #- RemoteAddr=10.2.142.1/24  # 外部访问我的IP限制,最大跨度不超过32,目前是1~24它们是 CIDR 表示法。
            #- Method=GET,POST   #只能是get或post请求方法
            - My=diamond  #自定义的断言
        - id: pay_routh2 #pay_routh2                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
         # uri: http://localhost:8001                #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/info/**              # 断言,路径相匹配的进行路由
            #- Cookie=username, zm
            - Host=**.zmblog.com,**.zm.cn  #请求host的header值带zmblog.com或zm.cn的可以匹配
    

​ 重启测试发现报错了

image-20240318113921720

​ 报错说没绑定快捷方式的配置

Caused by: org.springframework.boot.context.properties.bind.validation.BindValidationException: Binding validation errors on 

​ 那我们就用完全展开的配置方式再次重写yml

gateway:
  routes:
    - id: pay_routh1 #pay_routh1                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
      #uri: http://localhost:8001                #匹配后提供服务的路由地址
      uri: lb://cloud-payment-service               #匹配后提供服务的路由地址
      predicates:
        - Path=/pay/gateway/get/**              # 断言,路径相匹配的进行路由
        #- Between=2024-03-17T21:00:00.218129200+08:00[Asia/Shanghai],2024-03-17T21:01:00.218129200+08:00[Asia/Shanghai]  #在这个时间之后才能够访问,到点放票
        #- Header=X-Request-Id, \d+  #只有X-Request-Id属性为正整数才能访问
        #- Query=username,\d+  #要有参数名称为username并且值还要是整数才能路由
        #- RemoteAddr=10.2.142.1/24  # 外部访问我的IP限制,最大跨度不超过32,目前是1~24它们是 CIDR 表示法。
        #- Method=GET,POST   #只能是get或post请求方法
       # - My=diamond  #自定义的断言
        - name: My
          args:
            userType: diamond     #使用完全展开的方式配置

​ 重启不报错,测试一下

​ 带正确参数的访问

image-20240318123002448

​ 不带参数或者不是配置文件中写的userType就不能访问

image-20240318123132117

​ 修改一下自定义断言MyRoutePredicateFactory,添加方法shortcutFieldOrder能使用短配置

//实现短配置方式
public List<String> shortcutFieldOrder() {
    return Collections.singletonList("userType");
}

​ 现在的完整MyRoutePredicateFactory代码

//业务需求说明:自定义配置会员等级userType,按照钻石/黄金/青铜的等级,yml配置会员等级
@Component
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config> {

    @Override
    public Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config) {
        return new GatewayPredicate() {
            @Override
            public boolean test(ServerWebExchange serverWebExchange) {
                String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType");
                if (userType == null) {
                    return false;
                }
                //这个config是从配置文件中拿到的如果和传来的相匹配就通过
                if (userType.equals(config.getUserType())){
                    return true;
                }
                return false;
            }
        };
    }
    @Validated 
    public static class Config{
        @Setter@Getter@NotNull
        private String userType;  //会员类型
    }
    //空参构造函数
    public MyRoutePredicateFactory() {
        super(Config.class);
    }
    //实现短配置方式
    public List<String> shortcutFieldOrder() {
        return Collections.singletonList("userType");
    }

}

​ 到配置文件中使用短配置方式配置测试

            - My=diamond  #自定义的断言
#            - name: My
#              args:
#                userType: diamond     #使用完全展开的方式配置

image-20240318123536823

6.4、Filter过滤

6.4.1、Filter概述

​ 路由(Route)过滤器(Filter)允许以某种方式修改传入的 HTTP 请求或传出的 HTTP 响应。路由过滤器的范围是一个特定的路由。Spring Cloud Gateway 包括许多内置的 GatewayFilter 工厂。

​ 中文官网:Spring Cloud Gateway 中文文档 (springdoc.cn)

​ 其实就相当于SpringMVC里面的拦截器interceptor,Servlet过滤器。"pre"和"post"分别会在请求被执行前调用和被执行后调用,用来修改请求和响应信息。

能干嘛?

  • 请求鉴权;
  • 异常处理;
  • 记录接口调用时长统计(重点)

类型

  • 全局默认过滤器Global Filters;

    gateway出厂默认已经有的,直接使用,主要作用于所有的路由,不需要在配置文件中配置,作用在所有的路由上,实现GlobalFilter接口即可。

  • 单一内置过滤器GatewayFilter;

    也可以称为网关过滤器,这种过滤器主要是作用于单一路由或者某个路由分组

  • 自定义过滤器;

6.4.2、Gateway内置Filter

一些常用的

  1. 请求头(RequestHeader)相关组;

    对应着官网的

    6.1.The AddRequestHeader GatewayFilter Factory

    指定请求头内容ByName

    在8001微服务的GateWayController中新增方法

    //测试The AddRequestHeader GatewayFilter Factory
    @GetMapping("/pay/gateway/filter")
    public ResultData<String> getGatewayFilter(HttpServletRequest request){
        String result = "";
        Enumeration<String> headers = request.getHeaderNames();
        while (headers.hasMoreElements()) {
            String headName = headers.nextElement();
            String headerValue = request.getHeader(headName);
            System.out.println("请求头名:"+headName+"\t\t\t"+"请求值:"+headerValue);
            if (headName.equalsIgnoreCase("X-Request-zm1") || headName.equalsIgnoreCase("X-Request-zm2")){
                result = result + headName + "\t" + headerValue +" ";
            }
        }
        return ResultData.success("getGatewayFilter过滤器test:"+result+" \t "+ DateUtil.now());
    }
    

    9527配置文件添加过滤内容

    - id: pay_routh3
      uri: lb://cloud-payment-service
      predicates:
        - Path=/pay/gateway/filter/**
      filters:
        - AddRequestHeader=X-Request-zm1,zmValue1  #请求头kv,如果一头含有多参数则重写一行设置
        - AddRequestHeader=X-Request-zm2,zmValue2  #请求头kv,如果一头含有多参数则重写一行设置
    

    启动9527和8001并再次调用localhost:9527/pay/gateway/filter

    image-20240319090930206

    查看后台

    image-20240319091102055

    6.18.The RemoveRequestHeader GatewayFilter Factory

    可以删除请求头的内容根据请求头名

    我们把名为sec-fetch-site的请求头删除掉

    - id: pay_routh3
      uri: lb://cloud-payment-service
      predicates:
        - Path=/pay/gateway/filter/**
      filters:
        - AddRequestHeader=X-Request-zm1,zmValue1  #请求头kv,如果一头含有多参数则重写一行设置指定请求头内容    
        - AddRequestHeader=X-Request-zm2,zmValue2  #请求头kv,如果一头含有多参数则重写一行设置
        - RemoveRequestHeader= sec-fetch-site #删除请求头sec-fetch-site
    

    重启测试

    image-20240319092117450

    6.29.The SetRequestHeader GatewayFilter Factory

    可以修改请求头的值,那我们把请求头名为sec-fetch-mode的请求值修改成myNavigate

    - id: pay_routh3
      uri: lb://cloud-payment-service
      predicates:
        - Path=/pay/gateway/filter/**
      filters:
        - AddRequestHeader=X-Request-zm1,zmValue1  #请求头kv,如果一头含有多参数则重写一行设置指定请求头内容    
        - AddRequestHeader=X-Request-zm2,zmValue2  #请求头kv,如果一头含有多参数则重写一行设置
        - RemoveRequestHeader= sec-fetch-site #删除请求头sec-fetch-site
        - SetRequestHeader=sec-fetch-mode,myNavigate  #把请求头名为sec-fetch-mode的请求值修改成myNavigate
    

    重启测试

    image-20240319093810850

  2. 请求参数(RequestParameter)相关组;

    此组分别对应着

    6.3、The AddRequestParameter GatewayFilter Factory,在请求中添加参数

    6.19、The RemoveRequestParameter GatewayFilter Factory删除请求中的参数值

    这两个同时演示,在配置文件中配置

    filters:
      - AddRequestHeader=X-Request-zm1,zmValue1  #请求头kv,如果一头含有多参数则重写一行设置指定请求头内容    
      - AddRequestHeader=X-Request-zm2,zmValue2  #请求头kv,如果一头含有多参数则重写一行设置
      - RemoveRequestHeader= sec-fetch-site #删除请求头sec-fetch-site
      - SetRequestHeader=sec-fetch-mode,myNavigate  #把请求头名为sec-fetch-mode的请求值修改成myNavigate
      - AddRequestParameter=customerID,9527 #新增请求参数的kv
      - RemoveRequestParameter=customerName        # 删除url请求参数customerName,你传递过来也是null
    

    修改一下GateWayController,获取请求的参数并输出。

    //测试The AddRequestHeader GatewayFilter Factory
    @GetMapping("/pay/gateway/filter")
    public ResultData<String> getGatewayFilter(HttpServletRequest request){
        String result = "";
        Enumeration<String> headers = request.getHeaderNames();
        while (headers.hasMoreElements()) {
            String headName = headers.nextElement();
            String headerValue = request.getHeader(headName);
            System.out.println("请求头名:"+headName+"\t\t\t"+"请求值:"+headerValue);
            if (headName.equalsIgnoreCase("X-Request-zm1") || headName.equalsIgnoreCase("X-Request-zm2")){
                result = result + headName + "\t" + headerValue +" ";
            }
        }
        System.out.println("----------------------------------");
        String customerID = request.getParameter("customerID");
        System.out.println("参数customerID:"+customerID);
        System.out.println("----------------------------------");
        String customerName = request.getParameter("customerName");
        System.out.println("参数customerName:"+customerName);
        System.out.println("----------------------------------");
        
        return ResultData.success("getGatewayFilter过滤器test:"+result+" \t "+ DateUtil.now());
    }
    

    启动测试,我们先不添加参数直接发请求。

    image-20240319162652008

    然后在请求中添加参数http://localhost:9527/pay/gateway/filter?customerID=6666&customerName=zzzmmm

    image-20240319162857384

    原先在配置文件中写死的customerID会被你的请求覆盖掉,而参数customerName是要被移除的参数,所以你传入什么都会被移除为null

  3. 响应头(ResponseHeader)相关组;

    • 6.4.The AddResponseHeader GatewayFilter Factory添加响应头
    • 6.30.The SetResponseHeader GatewayFilter Factory修改响应头
    • 6.20.The RemoveResponseHeader GatewayFilter Factory移除响应头

    配置文件修改,新增一个响应头X-Response-zm并设值为ZMResponse,修改响应头时间Date为2066-12-25,然后将自带的Content-Type 响应属性删除。

    filters:
      - AddRequestHeader=X-Request-zm1,zmValue1  #请求头kv,如果一头含有多参数则重写一行设置指定请求头内容    
      - AddRequestHeader=X-Request-zm2,zmValue2  #请求头kv,如果一头含有多参数则重写一行设置
      - RemoveRequestHeader= sec-fetch-site #删除请求头sec-fetch-site
      - SetRequestHeader=sec-fetch-mode,myNavigate  #把请求头名为sec-fetch-mode的请求值修改成myNavigate
      - AddRequestParameter=customerID,9527 #新增请求参数的kv
      - RemoveRequestParameter=customerName        # 删除url请求参数customerName,你传递过来也是null
      - AddResponseHeader=X-Response-zm,ZMResponse #新增一个响应头X-Response-zm并设值为ZMResponse
      - SetResponseHeader=Date,2066-12-25 #修改响应头时间Date为2066-12-25
      - RemoveResponseHeader=Content-Type #将自带的Content-Type 响应属性删除
    

    重启服务查看效果

    image-20240319165448513

  4. 前缀和路径相关组;

    • 6.14.The PrefixPath GatewayFilter Factory自动添加路劲前缀

      之前正确的地址http://localhost:9527/pay/gateway/filter

      - id: pay_routh3
        uri: lb://cloud-payment-service
        predicates:
         # - Path=/pay/gateway/filter/**
          - Path=/gateway/filter/**    #实际上你应该在浏览器输入http://localhost:9527/gateway/filter
        filters:
           - PrefixPath=/pay   #和上面的Path拼在一起就是http://localhost:9527/pay/gateway/filter
      

      现在的访问地址就是http://localhost:9527/gateway/filte了

      image-20240320081009664

      如果还访问原来的地址就是404了

      image-20240320080947339

    • 6.29.The SetPath GatewayFilter Factory修改访问路劲

      此时访问的地址就变成了localhost:9527/ZM/abcd/filter才能正确访问

      - id: pay_routh3
        uri: lb://cloud-payment-service
        predicates:
         # - Path=/pay/gateway/filter/**
         # - Path=/gateway/filter/**    #实际上你应该在浏览器输入http://localhost:9527/gateway/filter
           - Path=/ZM/abcd/{segment}  #修改访问路径,{segment}占位符的内容会和SetPath的{segment}保持一致
        filters:
           - SetPath=/pay/gateway/{segment}
      

      image-20240320081923958

      最后的那个占位符写错你就访问不了了

      image-20240320082034296

      那如果把占位符的写对,前面自己改的写错呢?

      image-20240320082131464

      还是不能访问的连前面都不匹配。

    • 6.16. The RedirectTo GatewayFilter Factory重定向到某界面

      - id: pay_routh3
        uri: lb://cloud-payment-service
        predicates:
           - Path=/pay/gateway/filter/**
         # - Path=/gateway/filter/**    #实际上你应该在浏览器输入http://localhost:9527/gateway/filter
           #- Path=/ZM/abcd/{segment}  #修改访问路径,{segment}占位符的内容会和SetPath的{segment}保持一致
      
        filters:
           - RedirectTo=302,http://www.zmblog.vip #访问localhost:9527/pay/gateway/filter/ 直接跳转到我的个人博客上
      

      直接跳转过来

      image-20240320083229810

  5. 其它

    6.38.Default Filters配置在此处相当于全局通用,自定义秒变Global。

    GlobalFilter 接口的签名与 GatewayFilter 相同。这些是特殊的过滤器,有条件地应用于所有路由。

6.4.3、Gateway自定义Filter

自定义全局Filter

​ 对于我们之前说的那个面试题,解决统计接口调用耗时情况的,怎么实现,就可以使用自定义全局过滤器的方式搞定。

官网说明:

image-20240320084532478

​ 我们参照官网的示例写出自己的全局Filter

@Component
@Slf4j
public class MyGlobal implements GlobalFilter, Ordered {
    public static final String START_TIME = "start_time";
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
         //先记录访问接口开始的时间
         exchange.getAttributes().put(START_TIME,System.currentTimeMillis());
         //调用chain.filter(exchange),将请求传递给下一个过滤器或最终的目标服务。
        //使用then方法确保在请求处理完成后执行一些操作
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            Long startTime = exchange.getAttribute(START_TIME);
            if (startTime != null) {
                log.info("访问接口的主机:{}",exchange.getRequest().getURI().getHost());
                log.info("访问接口端口:{}",exchange.getRequest().getURI().getPort());
                log.info("访问接口URL:{}",exchange.getRequest().getURI().getPath());
                log.info("访问接口的URL参数:{}",exchange.getRequest().getURI().getRawQuery());
                log.info("访问接口的时长:{}",(System.currentTimeMillis()-startTime)+"毫秒");
                System.out.println("----------------------分割线---------------------------");
            }
        }));
    }
    //返回的值越小优先级别越高
    @Override
    public int getOrder() {
        return -1;
    }
}

​ 配置文件yml

   gateway:
      routes:
        - id: pay_routh1 #pay_routh1                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
          #uri: http://localhost:8001                #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service               #匹配后提供服务的路由地址
          predicates:
            - Path=/pay/gateway/get/**              # 断言,路径相匹配的进行路由
                
        - id: pay_routh2 #pay_routh2                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
         # uri: http://localhost:8001                #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/info/**              # 断言,路径相匹配的进行路由
        - id: pay_routh3
          uri: lb://cloud-payment-service
          predicates:
             - Path=/pay/gateway/filter/**
          filters:
            - AddRequestHeader=X-Request-zm1,zmValue1  #请求头kv,如果一头含有多参数则重写一行设置指定请求头内容
            - AddRequestHeader=X-Request-zm2,zmValue2  

​ 重启测试一下

image-20240320094239399

自定义条件Filter

​ 自定义,单一内置过滤器GatawayFilter,可以先参考GateWay内置出厂默认的然后照猫画虎,具体的内容感觉还有点像前面自定义的断言。

image-20240320103244916

自定义网关过滤器规则

  1. 新建类名XXX需要以GatewayFilterFactory结尾并继承AbstractGatewayFilterFactory类;

  2. 新建MyGatewayFilterFactory.Config内部类;

    public static class Config{
        @Getter@Setter
        private String status;//设置一个状态值或者标志位,它等于多少,匹配上才能访问
    }
    
  3. 重写apply方法;

    @Override
    public GatewayFilter apply(Config config) {
        
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                ServerHttpRequest request = exchange.getRequest();
                System.out.println("进入了自定义网关过滤器MyGatewayFilterFactory,status:-->"+config.getStatus());
                if (request.getQueryParams().containsKey("zm")) {
                    return chain.filter(exchange);
                }else {
                    exchange.getResponse().setStatusCode(HttpStatus.BAD_GATEWAY);
                    return exchange.getResponse().setComplete();
                }
            }
        };
    }
    
  4. 重写shortcutFieldOrder方法;

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("status");
    }
    
  5. 空参构造方法,内部调用super;

    public MyGatewayFilterFactory(){
        super(MyGatewayFilterFactory.Config.class);
    }
    
  6. 完整MyGatewayFilterFactory类;

    @Component
    public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config> {
    
        public MyGatewayFilterFactory(){
            super(MyGatewayFilterFactory.Config.class);
        }
    
        @Override
        public GatewayFilter apply(Config config) {
            
            return new GatewayFilter() {
                @Override
                public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                    ServerHttpRequest request = exchange.getRequest();
                    System.out.println("进入了自定义网关过滤器MyGatewayFilterFactory,status:-->"+config.getStatus());
                    if (request.getQueryParams().containsKey("zm")) {
                        return chain.filter(exchange);
                    }else {
                        exchange.getResponse().setStatusCode(HttpStatus.BAD_GATEWAY);
                        return exchange.getResponse().setComplete();
                    }
                }
            };
        }
    
        @Override
        public List<String> shortcutFieldOrder() {
            return Arrays.asList("status");
        }
    
        public static class Config{
            @Getter@Setter
            private String status;//设置一个状态值或者标志位,它等于多少,匹配上才能访问
        }
    
    }
    
  7. 全局配置文件中配置yml;

    - id: pay_routh3
      uri: lb://cloud-payment-service
      predicates:
         - Path=/pay/gateway/filter/**
       # - Path=/gateway/filter/**    #实际上你应该在浏览器输入http://localhost:9527/gateway/filter
         #- Path=/ZM/abcd/{segment}  #修改访问路径,{segment}占位符的内容会和SetPath的{segment}保持一致
    
      filters:
         -  My=zm #自定义的过滤器
    
  8. 重启9527测试

    先还按照原来的不带任何参数看能不能访问

    image-20240320110429964

    当然不能访问,然后添加参数的,参数的value是什么我们就没有做限制随便。

    image-20240320110345209

    带着我们设置的参数就可以访问了。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HarmonyOS/OpenHarmony应用开发-HDC环境变量设置

hdc&#xff08;HarmonyOS Device Connector&#xff09;是 HarmonyOS 为开发人员提供的用于调试的命令行工具&#xff0c;通过该工具可以在 windows/linux/mac 系统上与真实设备或者模拟器进行交互。 hdc 工具通过 HarmonyOS SDK 获取&#xff0c;存放于 /Huawei/Sdk/openhar…

责任链模式(处理逻辑解耦)

前言 使用设计模式的主要目的之一就是解耦&#xff0c;让程序易于维护和更好扩展。 责任链则是将处理逻辑进行解耦&#xff0c;将独立的处理逻辑抽取到不同的处理者中&#xff0c;每个处理者都能够单独修改而不影响其他处理者。 使用时&#xff0c;依次调用链上的处理者处理…

RK3399 android10 移植SiS-USB触摸驱动

一&#xff0c;SiS USB触摸简介 SiS USB 触摸屏通常是一种外接式触摸屏设备&#xff0c;通过 USB 接口连接到计算机或其他设备上。这种触摸屏设备可以提供触摸输入功能&#xff0c;用户可以通过手指或触控笔在屏幕上进行操作&#xff0c;实现点击、拖动、缩放等操作。 SiS USB…

双向链表增删改查、遍历、倒置、销毁等——数据结构——day3

首先&#xff0c;我先把我的头节点写出来&#xff0c;里面有整理好的结构体 #ifndef __DOULINK_H__ #define __DOULINK_H__#include<stdio.h> #include<stdlib.h> #include<string.h>typedef struct student {int id;char name[50];int score; }DATA_TYPE; …

【数据可视化】Echarts中的其它图表

个人主页 &#xff1a; zxctscl 如有转载请先通知 文章目录 1. 前言2. 绘制散点图2.1 绘制基本散点图2.2 绘制两个序列的散点图2.3 绘制带涟漪特效的散点图 3. 绘制气泡图3.1 绘制标准气泡图3.2 绘制各国人均寿命与GDP气泡图3.3 绘制城市A、城市B、城市C三个城市空气污染指数气…

负数,小数转换二进制

负数转换二进制 例&#xff1a;在带符号整数signed char的情况下&#xff0c;-57如何被表示成负数呢&#xff1f;在计算机中又是如何计算66-57呢&#xff1f; 解析 考虑int占有32位太长&#xff0c;因此使用只占8位的signed char类型来举例。57用二进制表示位00111001&#…

28-5 文件上传漏洞 - 图片马

一、文件内容检测 解析漏洞定义 控制文件是否被当做后端脚本处理 二、图片马绕过 图片马;在图片中包含一句话木马。利用解析漏洞如.htaccess 或文件包含漏洞,对图片马进行解析,执行其中的恶意代码。优势在于可以绕过多种防护机制。 三、图片马制作方法: # 一句话马示例…

nRF Sniffer在wireshark下的环境搭建

一、准备 nRF Sinffer 安装包&#xff1a; 直接下载&#xff1a;https://nsscprodmedia.blob.core.windows.net/prod/software-and-other-downloads/desktop-software/nrf-sniffer/sw/nrf_sniffer_for_bluetooth_le_4.1.1.zip 官网下载&#xff1a; nRF Sniffer for Bluetooth…

Elasticsearch - Docker安装Elasticsearch8.12.2

前言 最近在学习 ES&#xff0c;所以需要在服务器上装一个单节点的 ES 服务器环境&#xff1a;centos 7.9 安装 下载镜像 目前最新版本是 8.12.2 docker pull docker.elastic.co/elasticsearch/elasticsearch:8.12.2创建配置 新增配置文件 elasticsearch.yml http.host…

实现防抖函数并支持第一次立刻执行(vue3 + ts环境演示)

1、先看一效果&#xff1a; 2、实现思路&#xff1a; 使用定时器setTimeout和闭包实现常规防抖功能&#xff1b;增加immediate字段控制第一次是否执行一次函数&#xff08;true or false&#xff09;&#xff1b;增加一个flag标识&#xff0c;在第一次执行时&#xff0c;将标…

Aspose.PDF功能演示:在 JavaScript 中优化 PDF 文件

PDF 文件是一种普遍存在的文档共享格式&#xff0c;但它们有时可能会很大&#xff0c;导致加载时间变慢并增加存储要求。优化 PDF 文件对于确保无缝的用户体验至关重要&#xff0c;尤其是在 Web 应用程序中。因此&#xff0c;在这篇博文中&#xff0c;我们将探讨如何使用 JavaS…

软考网工学习笔记(6) 广域通信网

公共交换电话网&#xff08;pstn&#xff09; 在pstn是为了语音通信而建立的网络。从20世纪60你年代开始用于数据传输 电话网有三个部分组成&#xff1a; 本地回路 &#xff0c;干线 和 交换机 。 干线 和 交换机 一般采用数字传输和交换技术 &#xff0c;而 本地回路基本采…

Ubuntu18.04桌面版设置静态IP地址

引用: Ubuntu配置静态IP_ubuntu配置静态ip地址-CSDN博客 正文 默认Unbuntu 18.04 Desktop桌面版使用 netplan 管理网卡网络地址。使用Unbuntu 18.04 桌面版配置&#xff0c;可以通过桌面上的设置图标配置网卡的静态IP地址。 点击桌面右上角下拉框&#xff0c;点击“设置”按…

流畅的 Python 第二版(GPT 重译)(六)

第三部分&#xff1a;类和协议 第十一章&#xff1a;一个 Python 风格的对象 使库或框架成为 Pythonic 是为了让 Python 程序员尽可能轻松和自然地学会如何执行任务。 Python 和 JavaScript 框架的创造者 Martijn Faassen。 由于 Python 数据模型&#xff0c;您定义的类型可以…

【Linux】进程详解

目录 一、进程基本概念&#xff1a; 二、进程的五种基本状态&#xff1a; 三、Linux的进程状态&#xff1a; 四、控制进程状态相关指令&#xff1a; 五、僵尸进程与孤儿进程&#xff1a; 六、进程的优先级&#xff1a; 七、进程的四个重要特性&#xff1a; 八、环境变量…

ping 通ip,ping 不通域名

在linux 系统中&#xff0c;ping 通ip,ping 不通对应的域名时&#xff0c;可直接修改系统配置文件 vi /etc/hosts 加入 ip 域名

好就业三种专业#信息安全#云计算#网络工程

一、信息安全专业 根据2021年网络安全宣传周白皮书的观察结果&#xff0c;网络安全产业对于人才的需求正以高速增长的趋势呈现&#xff0c;当前网络安全行业存在着巨大的人才缺口&#xff0c;平均供求比例约为1:2。这一现象导致了资深人才的储备不足&#xff0c;并且新人才的培…

智慧校园数据可视化有什么好处?怎么推进数字化校园方案?

在当今数字化时代&#xff0c;越来越多学校开始实施智慧校园计划&#xff0c;旨在为学生和教师提供更高效、便捷的学习和教学环境。智慧校园运用互联网、大数据、人工智能等技术&#xff0c;对校园内各信息进行收集、整合、分析和应用&#xff0c;实现教学、管理、服务等多方面…

MySQL 索引的分类和优化

​ 优质博文&#xff1a;IT-BLOG-CN 索引是什么 &#xff1a; MySQL 官方对索引的定义&#xff1a;索引&#xff08;Index&#xff09;是帮助 MySQL 高效获取数据的数据结构。可以得到索引的本质&#xff1a;索引是数据结构。索引的目的在于提高查询效率。可以简单理解为&#…

AI PPT生成工具 V1.0.0

AI PPT是一款高效快速的PPT生成工具&#xff0c;能够一键生成符合相关主题的PPT文件&#xff0c;大大提高工作效率。生成的PPT内容专业、细致、实用。 软件特点 免费无广告&#xff0c;简单易用&#xff0c;快速高效&#xff0c;提高工作效率 一键生成相关主题的标题、大纲、…