SpringCloud Alibaba 入门到精通 - Gateway

news2024/11/17 16:36:38

SpringCloud Alibaba 入门到精通 - Gateway

  • 一、网关简介
    • 1.简单使用Gateway网关
  • 二、断言
    • 1.After 断言时间
    • 2.Before 断言时间
    • 3.Between 断言时间
    • 4.Cookie 断言Cookie
    • 5.Header 断言请求头
    • 6.Host 断言请求头中的Host
    • 7.Method 断言请求方式
    • 8.Path 断言请求路径
    • 9.Query 断言请求URL参数
    • 10.RemoteAddr 断言客户端地址
    • 11.Weight 设置访问权重,并不会断言
    • 12.XForwarded 断言代理地址
  • 三、GatewayFilter
    • 1.AddRequestHeader 增加请求头
    • 2.AddResponseHeader 增加响应头
    • 3.StripPrefix 去除请求的前缀路径
    • 4.PrefixPath 为请求增加公共前缀
    • 5.RewritePath 重写请求路径
    • 6.Retry 失败重试
    • 7.Default Filters 默认过滤器
  • 四、GlobalFilter
    • 1.简单使用全局过滤器
    • 2.全局过滤器如何实现pre、post方法
    • 3.如何实现异常时返回json给到客户端
  • 五、网关的CORS配置与超时配置
    • 1.全局CORS配置-配置类
    • 2.全局CORS配置-配置文件
    • 3.局部CORS配置作用于路由
    • 4.超时配置全局
    • 5.超时配置局部
  • 六、网关整合Nacos+Loadbalancer实现负载均衡
    • 1.引入依赖
    • 2.配置更改
    • 3.实现负载均衡
    • 4.自动获取nacos服务名实现路由
  • 七、网关整合Sentinel进行流控
    • 1.引入依赖
    • 2.配置更改
    • 3.dashboard配置路由流控
    • 4.dashboard配置api流控
    • 5.api配置路由流控
    • 6.自定义流控、熔断响应信息
    • 7.网关的全局异常处理
  • 总结

一、网关简介

网关的概念从开始有网络时基本就存在了,作用一般是鉴权、白名单、跨域设置、负载均衡、灰度发布、失败重试、路由断言等。简单总结来说就是为了做一些统一的不涉及业务活动的动作。将这些动作和业务隔离开来,解耦了这些无关业务的动作,同时也可以使得业务服务更加清晰、安全。
官方文档:https://docs.spring.io/spring-cloud-gateway/docs/4.0.x-SNAPSHOT/reference/html/#gateway-starter

本位demo使用的主要版本信息
SpringBoot: 2.6.11
SpringCloud: 2021.0.4
SpringCloud-Alibaba: 2021.0.4.0

1.简单使用Gateway网关

注意:不要在网关服务中导入web的starter。因为Gateway本身是已经支持web的
使用很简单,直接引入网关依赖即可,我这里使用的是bootstrap配置文件,所以还需要多引入一个bootstrap的依赖,如果只使用application.yml可以不引入bootstrap的依赖。

 <!-- 网关依赖 -->
 <dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-gateway</artifactId>
 </dependency>

 <!-- 引入bootstrap依赖,不引入这个依赖是无法使用bootstrap配置文件的 -->
 <dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-bootstrap</artifactId>
 </dependency>

然后只需要简单的配置即可开始网关的使用了,这里配置下网关的简单路由到淘宝吧。

server:
  port: 9000
spring:
  cloud:
    gateway:
      routes:
        - id: hello_route
          uri: https://www.taobao.com
          predicates:
            - Path=/**

浏览器输入localhost:9000会被直接路由到淘宝了,这样就可以开始网关的使用了。
在这里插入图片描述

二、断言

基础的网关是由多个路由规则构成的,多个路由规则之间是或的关系,每个路由规则都支持配置断言、网关过滤器等。

  • id:配置路由规则的唯一标识,没有作用,但不可以不设置
  • uri:当请求通过了断言,在经过过滤器以后就会根据uri去转发目标请求了
  • predicates:这里配置断言,多个断言使用杠进行分行设置,gateway提供了12种断言,基本可以满足大部分使用场景了
  • filters: Gatewayfilter 在这里声明,Gateway提供了30多种的过滤器,基本用不完。
    这里着重介绍下网关支持的各种断言,注意
  • 1.不同路由规则之间是或的关系,同一个路由规则下的不同断言是且的关系
  • 2.断言和Gatewayfilter的执行谁寻是:断言–>Gatewayfilter
  • 3.有的断言支持配个多个值,有的不支持配置多个值,可以根据一个规则准确判断:断言中的值本来就是键值对的则不支持声明多个值(一行中声明),比如Cookie、Header、Query 他的值本来就是键值对,则不支持在一行声明多个,他们需要使用逗号分割键和值, 比如Host、Path、Method 等他们的值本来就是字符串,则支持在一行声明多个值,使用逗号分割
  • 4.断言时值是键值对的,可以只断言他们的键而不断言值,这样也是支持的
  • 5.断言失败返回的状态码是404

1.After 断言时间

给定一个时间,请求产生的时间只有在这个时间之后才可以通过。
以下是路由配置:

spring:
  cloud:
    gateway:
      enable: true # 默认值,如果导入了gateway的包却不想用,则需要关闭
      routes:
# 1.时间路由断言:After、Before、Between
        - id: after_route
          uri: https://baidu.com
          predicates:
            - After=2023-11-24T15:00:00+08:00[Asia/Shanghai]
            - Path=/one

2.Before 断言时间

给定一个时间,请求产生的时间只有在这个时间之前才可以通过。
以下是路由配置:

spring:
  cloud:
    gateway:
      enable: true # 默认值,如果导入了gateway的包却不想用,则需要关闭
      routes:
# 1.时间路由断言:After、Before、Between
        - id: before_route
          uri: https://jd.com
          predicates:
            - Before=2023-11-21T15:00:00+08:00[Asia/Shanghai]

3.Between 断言时间

给定两个时间,请求产生的时间只有在这两个时间之间才可以通过,第一个为开始时间,第二个为结束时间,两个时间用逗号隔开。
以下是路由配置:

spring:
  cloud:
    gateway:
      enable: true # 默认值,如果导入了gateway的包却不想用,则需要关闭
      routes:
# 1.时间路由断言:After、Before、Between
        - id: between_route
          uri: https://taobao.com
          predicates:
            - Between=2023-11-21T15:00:00+08:00[Asia/Shanghai],2023-11-24T15:00:00+08:00[Asia/Shanghai]

4.Cookie 断言Cookie

断言请求头中的Cookie信息,Cookie信息通常会携带认证等信息,Cookie信息的设置需要依赖服务端返回的Set-cookie信息进行设置,通常以键值对的形式存在,多个键值对使用逗号隔开。Cookie断言不支持多值的配置,因为他的值本身就是键值对,所以这里不支持多值的声明,逗号前轴是键值对,而不是多值。

spring:
  cloud:
    gateway:
      enable: true # 默认值,如果导入了gateway的包却不想用,则需要关闭
      routes:
# 2.Cookie 路由断言 Cookie 指的就是请求头里的Cookie,等号后面第一个值是Cookie中的键名,逗号之后的是键对应的值也可以是正则表达式
           # 如下的配置要求请求头里有这个token值:Cookie : mycookie=mycookievalue
        - id: cookie_route
          uri: https://taobao.com
          predicates:
            - Cookie=mycookie,mycookievalue

这里的mycookie是Cookie中信息的键,mycookievalue则是Cookie信息中的value,只有完全匹配才会断言通过。

5.Header 断言请求头

断言秦秋头中的信息,请求头中的所有信息都可以断言,不过这里与Cookie类似的是不支持多个值的断言,一次只能断言一个。与其他断言类似的是逗号后支持正则表达式,同样也是支持完全匹配的值。

spring:
  cloud:
    gateway:
      enable: true # 默认值,如果导入了gateway的包却不想用,则需要关闭
      routes:
# 3.请求头 路由断言 写法与Cookie类似,等号后面第一个是值,逗号后面是正则表达式,也可以是完全匹配的值
        # 不过同样的是,不可以在一行写多个请求头信息,必须分开写      
        - id: header_route
          uri: https://jd.com
          predicates:
            - Header=Host,jd.com
            - Header=Referer,https://www.baidu.com

断言请求的Host为jd.com,请求头Host用于标识请求的目标主机信息,Referer用于标识请求的来源信息。这两个两个Header断言的关系是且的关系,也及时只有同时满足时断言才会通过。

6.Host 断言请求头中的Host

这个其实和Header是有些重复的,因为Header时支持断言所有请求头信息的。他的使用支持多值的断言,多值中只要有一个满足,断言即通过。

spring:
  cloud:
    gateway:
      enable: true # 默认值,如果导入了gateway的包却不想用,则需要关闭
      routes:
# 4.请求头Host 路由断言,Host可以使用Header来实现,也可以直接使用Host断言实现,多个值使用逗号隔开
        # 这里的逗号隔开表示多个值,与Cookie和Header不同,Cookie和Header逗号表示分割键值对的
        - id: host_route
          uri: https://taobao.com
          predicates:
            - Host=pdd.com,pdd2.com

如上,当请求信息中Host是pdd.com或者pdd2.com时断言才会通过。

7.Method 断言请求方式

断言请求方式,8种请求方式中基本只会用GET、POST、OPTIONS,其他的基本没有用的,所以这里我们可以针对这三种方式进行断言。
注意请求方式必须大写,小写无效

spring:
  cloud:
    gateway:
      enable: true # 默认值,如果导入了gateway的包却不想用,则需要关闭
      routes:
# 5.请求Method 路由断言,注意必须大写,多个方法使用逗号隔开是或的关系
        - id: method_route
          uri: https://wymdm.sunac.com.cn/#/login
          predicates:
            - Method=GET,POST,OPTIONS

8.Path 断言请求路径

Path断言是必用的断言,他是根据请求路径进行断言,通常情况下不同的业务服务都是具有不同的路径前缀,Path正式根据这个业务前缀来路由请求到对饮的服务的。
特别注意的是:当断言成功以后路由的真正地址是uri+Path的地址,如果Path地址是统配的话加的就是真实的地址,比如如果uri是localhost:8080,Path是/test/one,那么真正路由到的地址是localhost:8080/test/one
Paht同样支持多个值的声明,使用逗号隔开即可。满足其一即可断言成功。

spring:
  cloud:
    gateway:
      enable: true # 默认值,如果导入了gateway的包却不想用,则需要关闭
      routes:
# 6.Path 路由断言,Path是路径的意思,可以匹配请求路径,这也是最常用的一个路由断言
        # 可以声明多个路由,使用逗号隔开,路径支持统配符/taobao*/** 也是可以的,这种写法下,支持taobao路径开头的任何信息,比如/taobao2222/111
        - id: path_route
          uri: https://taobao.com
          predicates:
            - Path=/taobao/**,/taobao2/**,/taobao*/**

以上断言支持路径/taobao/11/11,/taobao2/11/11,/taobao11111/2222/333

9.Query 断言请求URL参数

注意这里只可以断言请求RUL中携带的参数,对于form-data、json等数据中的参数是无法断言的。断言基本都是支持只断言请求参数而不断言参数值的。

spring:
  cloud:
    gateway:
      enable: true # 默认值,如果导入了gateway的包却不想用,则需要关闭
      routes:
# 7.请求参数Query 路由断言: 这里的请求参数指的是请求头中携带的参数,支持GET、POST
        # 如果是form-data、raw等格式的数据,这里是无法断言的
        # 这里可以只断言参数,也可以既断言参数也断言参数的值,同样支持正则
        - id: query_route
          uri: https://jd.com
          predicates:
            - Query=username
            - Query=password,123456

10.RemoteAddr 断言客户端地址

当没有代理服务器时或者中间服务器时,这个IP是远端IP,如果有代理比如用户访问时经过了NG,那么这个地址其实是NG的地址,而不是远端客户端的真实地址,当不存在代理服务器时可以使用RemoteAddr进行获取客户端地址。

spring:
  cloud:
    gateway:
      enable: true # 默认值,如果导入了gateway的包却不想用,则需要关闭
      routes:
        - id: remote_addr_route
          uri: https://taobao.com
          predicates:
            - RemoteAddr=172.17.64.0/20
            - RemoteAddr=172.17.65.124,172.17.65.123

如上,使用了两种方式对ip进行了断言,
第一种(172.17.64.0/20)较多CIDR表示法,172.17.64.0/20表示ip为172.17.64.0的远端地址是可以访问,此外还表示这个网段下的ip都可以访问,不过是这个ip之后的数据也就是172.17.64.0开始一直到这个网段结束。ip是由网络号和主机号组成的,20表示的是网络号的长度,余下的是主机号。如下:第三位主机号最大还可以加15,第四位则是255,所以允许的ip范围是172.17.64.0-172.17.79.255.

 172     .  17      .  64       . 0
 10101100   00010001   01000000   00000000
 其实知道ip和 网络号,也可以推断出他的子网掩码是(网络号全为1就是子网掩码):
 11111111.11111111.11110000.00000000(子网掩码,二进制表示法)
 255.255.240.0(子网掩码,点十表示法)

第二种则是对两个IP进行了断言。

11.Weight 设置访问权重,并不会断言

这是是针对请求进行进行权重控制,并不会对请求信息进行断言,不知道gateway为什么把他放在了这里,感觉放在过滤器里实现更加合理一些。不过真实场景基本没有人使用gateway提供的权重断言,都是使用负载均衡的中间件来进行负载管控,而不是权重断言。
这里的权重断言使用时一定需要注意的是,多个断言声明group名时必须一致,才可以实现权重的路由。

spring:
  cloud:
    gateway:
      enable: true # 默认值,如果导入了gateway的包却不想用,则需要关闭
      routes:
# 9.权重Weight 路由断言,权重路由是权重路由,可以给路由断言设置权重,权重越高,被匹配的概率越大
        # 这里声明多个路由在一个分组的话,只需要将分组名称命名为一样的即可,如果分组名不一样则
        # 他们之间的权重则没有关系,只有同组的权重才会相互影响。
        # 权重一般会配合别的断言一起使用,很少有单独使用的
        - id: weight_route
          uri: https://taobao.com
          predicates:
            - Weight=group1,8
        - id: weight_route2
          uri: https://jd.com
          predicates:
            - Weight=group1,8      

如上多个路由使用了同一个组名group1,他们的权重值是相同的,这样基本就是一个轮询的效果。

12.XForwarded 断言代理地址

RemoteAddr 断言在使用了代理服务器时就无法获取到远端真实的IP了,那怎么才可以断言到真实的客户端的IP呢,就需要使用XForwardedRemoteAddr断言。
特别注意的是:必须代理服务器比如NG进行配置才可以在服务端对信息进行断言,且断言的请求头信息是X-Forwarded-For,而不是和断言类名一样的请求头信息

spring:
  cloud:
    gateway:
      enable: true # 默认值,如果导入了gateway的包却不想用,则需要关闭
      routes:
        - id: x_forwarded_remote_addr_route
          uri: https://taobao.com
          predicates:
            - XForwardedRemoteAddr=172.17.64.0/20

三、GatewayFilter

网关中的过滤器主要是GatewayFilter和GlobalFilter。GatewayFilter必须配合路由使用,他针对路由中的请求信息进行生效,Gateway中支持的GatewayFilter有30+种,这里挑出了比较重要和真正会用到的过滤器进行总结说明。
注意过滤器在断言之后执行

1.AddRequestHeader 增加请求头

顾名思义就是可以为请求头添加信息,感觉一般也用不到,想要对信息进行统一处理还是GlobalFilter更好用一些,加入为请求头添加Referer信息。

spring:
  cloud:
    gateway:
      enable: true # 默认值,如果导入了gateway的包却不想用,则需要关闭
      routes:
# 1.增加请求头的网关过滤器 AddRequestHeader
        - id: add_request_header_route
          uri: http://localhost:8001
          predicates:
            - Path=/order/addOrder
#            - Query=username
          filters:
            - AddRequestHeader=Referer,https://jd.com

注意使用filters配置项进行配置,如上配置表示断言满足的情况下给我请求信息添加Referer。

2.AddResponseHeader 增加响应头

这里是为响应头添加信息

spring:
  cloud:
    gateway:
      enable: true # 默认值,如果导入了gateway的包却不想用,则需要关闭
      routes:
# 2.添加响应头的网关过滤器 AddResponseHeader: 需要注意的是如果请求时是从这个路由走的,那么响应时也可肯定是从这个路由走的
        # 如果想要实现给一个请求头塞入多个值并用逗号隔开,最好使用复杂配置项,直接声明name,value的值,如下,当使用简单配置方式时
        # 多次尝试,是无法正常给一个信息赋多个值的
        - id: add_response_header_route
          uri: http://localhost:8001
          predicates:
            - Path=/order/**
          filters:
            - AddResponseHeader=Access-Control-Allow-Methods,POST

注意若是想要给一个请求头信息添加多个值,最好使用复杂方式配置项,而不要使用这种简略的,笔者尝试了多次简略的配置方式配置多个值均不生效,复杂方式需要我们使用name声明过滤器,使用name和value分别声明键和值,如下:

spring:
  cloud:
    gateway:
      enable: true # 默认值,如果导入了gateway的包却不想用,则需要关闭
      routes:
# 2.添加响应头的网关过滤器 AddResponseHeader: 需要注意的是如果请求时是从这个路由走的,那么响应时也可肯定是从这个路由走的
        # 如果想要实现给一个请求头塞入多个值并用逗号隔开,最好使用复杂配置项,直接声明name,value的值,如下,当使用简单配置方式时
        # 多次尝试,是无法正常给一个信息赋多个值的
        - id: add_response_header_route
          uri: http://localhost:8001
          predicates:
            - Path=/order/**
          filters:
            - name: AddResponseHeader
              args:
                name: Access-Control-Allow-Methods
                value: POST,GET

3.StripPrefix 去除请求的前缀路径

通常有时候会让请求信息中携带前缀路径,比如/v1/customer/**、/api/cusomer/*等等。而前缀api、v1又不需要放入到真实的路径中,就可以使用StripPrefix将前缀路径去掉。

spring:
  cloud:
    gateway:
      enable: true # 默认值,如果导入了gateway的包却不想用,则需要关闭
      routes:
# 3.去除路径中的公共部分,余下部分进行路由: StripPrefix
        - id: strip_prefix_route
          uri: http://localhost:8001
          predicates:
            - Path=/api/order/addOrder
          filters:
          # 表示去除第一段路径,即api
            - StripPrefix=1

StripPrefix=1表示去除路径中第一部分,如果是=2则表示去除路径中前两部分。

4.PrefixPath 为请求增加公共前缀

这个与上面正好相反,有公共前缀的又不喜欢前端知道或者想要统一处理,就可以使用这个过滤器。

spring:
  cloud:
    gateway:
      enable: true # 默认值,如果导入了gateway的包却不想用,则需要关闭
      routes:
        - id: prefix_path_route
          uri: http://localhost:8001
          predicates:
            - Path=/api/addOrder
          filters:
            - StripPrefix=1
            - PrefixPath=/order

如上配置表示先去除api再添加order

5.RewritePath 重写请求路径

StripPrefix和PrefixPath都是只可以操作路径的前缀,对于路径中的其他操作就会比较局限,而Gateway提供了更为强大的路径处理的过滤器RewritePath。他可以实现路径的任意组合。

spring:
  cloud:
    gateway:
      enable: true # 默认值,如果导入了gateway的包却不想用,则需要关闭
      routes:
# 5.重写请求路径 RewritePath 下面的写法效果和StripPrefix一样,只不过StripPrefix是去除,而RewritePath是重写
        # 重写路径的写法: RewritePath=/api/?(?<segment>.*), /$\{segment}
        # (?<segment>.*) 表示匹配的部分将其存入到segment
        # /$\{segment}  表示重写的路径: / + 前面匹配的内容
        - id: rewrite_path_route
          uri: http://localhost:8001
          predicates:
            - Path=/api/order/addOrder
          filters:
#            - StripPrefix=1
            - RewritePath=/api/?(?<segment>.*), /$\{segment}

如上配置是将api后的路径取出放置到segment中,然后使用,相当于StripPrefix=1.

6.Retry 失败重试

这是控制请求失败重试的过滤器,当请求经过当前过滤器且失败时可以触发(有条件),根据配置进行间隔性重新请求。主要配置项,有部分有坑。详见注释:

spring:
  cloud:
    gateway:
      enable: true # 默认值,如果导入了gateway的包却不想用,则需要关闭
      routes:
          - id: retry_route
          uri: http://localhost:8001
          predicates:
            - Path=/api/**
          filters:
            - AddRequestHeader=X-Request-Foo, Bar
            - RewritePath=/api/?(?<segment>.*), /$\{segment}
            - name: Retry
              args:
                retries: 3 # 这是默认值,不设置默认就是三次
#                series: SERVER_ERROR # HttpStatus.Series 默认5开头的都会触发
                series: '' # 这个值默认是 SERVER_ERROR ,表示只要是5开头的异常码都会触发重试,若是想要通过statuses 指定
                           # 状态码触发重试,则需要将这个值置空,注意这个是必须的,不可为null
                statuses: INTERNAL_SERVER_ERROR # HttpStatus
                methods: GET,POST # 只针对指定的请求方式进行重试,其他不进行重试
                backoff:
                  firstBackoff: 1000 # 第一次重试间隔时间1s,单位应该是ms
                  maxBackoff: 10000 # 最大间隔时间不能超过10s
                  factor: 2 # 每次重试时等待时间的增长因子。这里是 2,表示每次等待时间都是上一次的两倍。
                  basedOnPreviousValue: true # 是否根据前一次的等待时间来计算下一次的等待时间。false的话则都是根据第一次的也就是firstBackoff来计算

失败重试的基本都是这些配置没啥大区别。

7.Default Filters 默认过滤器

前面说过GatewayFilter只作用于路由规则内部,若是每个路由规则都需要配置相同的过滤器就会很麻烦,默认过滤器就是用来简化这个操作的。默认过滤器内部支持的过滤器就是GatewayFilter的过滤器,配置在默认过滤器的过滤器作用于所有路由规则。注意默认过滤器的执行顺序依然是断言大于过滤器。

server:
  port: 9000
spring:
  cloud:
    gateway:
      default-filters:
        - RewritePath=/api/?(?<segment>.*), /$\{segment}
        - name: Retry
          args:
            retries: 3 # 这是默认值,不设置默认就是三次
            # series: SERVER_ERROR # HttpStatus.Series 默认5开头的都会触发
            series: '' # 这个值默认是 SERVER_ERROR ,表示只要是5开头的异常码都会触发重试,若是想要通过statuses 指定
            # 状态码触发重试,则需要将这个值置空,注意这个是必须的,不可为null
            statuses: INTERNAL_SERVER_ERROR,BAD_GATEWAY,SERVICE_UNAVAILABLE # HttpStatus
            methods: GET,POST # 只针对指定的请求方式进行重试,其他不进行重试
            backoff:
              firstBackoff: 1000 # 第一次重试间隔时间1s,单位应该是ms
              maxBackoff: 10000 # 最大间隔时间不能超过10s
              factor: 2 # 每次重试时等待时间的增长因子。这里是 2,表示每次等待时间都是上一次的两倍。
              basedOnPreviousValue: true # 是否根据前一次的等待时间来计算下一次的等待时间。false的话则都是根据第一次的也就是firstBackoff来计算
      routes:
        - id: task_route
          uri: http://localhost:8001
          predicates:
            - Path=/api/task/**
        - id: data_route
          uri: http://localhost:8002
          predicates:
            - Path=/api/data/**

如上配置默认过滤器会对所有都生效,当接口发生500或者503时都会触发3次重推。

四、GlobalFilter

网关的主要功能除了路由、重试还有鉴权、白名单、日志等,这些则需要依赖全局过滤器。全局过滤器。
注意全局过滤器的执行顺序:断言>网关过滤器>全局过滤器

1.简单使用全局过滤器

只需要实现GlobalFilter、Ordered两个接口即可,GlobalFilter负责处理请求和响应,Ordered负责处理过滤器的优先级问题,如果不实现Ordered接口,使用Order注解效果也是一样的。如下:

package com.cheng.ebbing.gateway.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * @author pcc
 */
@Slf4j
@Order(0)
@Component
public class LogGlobalFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        exchange.getRequest().getQueryParams().forEach((key, value) -> {
            log.info("收到请求参数:{},{}",key,value);
        });
        exchange.getRequest().getHeaders().forEach((key, value) ->{
            log.info("收到请求头参数:{},{}",key,value);
        });
        return chain.filter(exchange);
    }
}

2.全局过滤器如何实现pre、post方法

从上面的代码可以看到,这里的过滤器和WebFilter是类似的都是类似的(Gateway中不可以些WebFilter注解)。

@WebFilter(urlPatterns = "/test")
public class TestWebFilter implements WebFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        return chain.filter(exchange);
    }
}

很明显默认处理的是请求方法,也就是相当于servlet的pre方法,那post方法怎么声明呢?这个需要借助Mono来处理,Mono提供了一个fromRunnable返回一个Mono对象,如下:

package com.cheng.ebbing.gateway.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * @author pcc
 */
@Slf4j
@Order(0)
@Component
public class LogGlobalFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        final Long startTime= System.currentTimeMillis();
        exchange.getRequest().getQueryParams().forEach((key, value) -> {
            log.info("收到请求参数:{},{}",key,value);
        });
        exchange.getRequest().getHeaders().forEach((key, value) ->{
            log.info("收到请求头参数:{},{}",key,value);
        });

        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            final Long endTime= System.currentTimeMillis();
            // 获取响应状态码
            int statusCode = exchange.getResponse().getStatusCode().value();
            if (statusCode == 200) {
                log.info("响应信息正确");
            } else {
                log.info("响应信息错误,错误码:{},处理用时:{}",statusCode,(endTime-startTime));

            }
        }));
    }
}

3.如何实现异常时返回json给到客户端

网关既然起到了鉴权、白名单等拦截的作用,那异常场景是需要直接返回给客户端的,Json格式的数据是目前最普遍的交互方式。针对上面的代码我们可以做如下改动:

package com.cheng.ebbing.gateway.filter;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * @author pcc
 */
@Slf4j
@Order(0)
@Component
public class LogGlobalFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        final Long startTime= System.currentTimeMillis();
        exchange.getRequest().getQueryParams().forEach((key, value) -> {
            log.info("收到请求参数:{},{}",key,value);
        });
        exchange.getRequest().getHeaders().forEach((key, value) ->{
            log.info("收到请求头参数:{},{}",key,value);
        });
        
        // 假设异常触发了
        if(1>1){
            //组装异常信息
            Map<Object, Object> objectObjectMap = new HashMap<>();
            objectObjectMap.put("code", HttpStatus.BAD_REQUEST.value());
            objectObjectMap.put("message","请求处理异常");
            byte[] bytes = JSON.toJSONString(objectObjectMap).getBytes(StandardCharsets.UTF_8);
            DataBuffer read = exchange.getResponse().bufferFactory().wrap(bytes);
            exchange.getResponse().getHeaders().add("Content-Type", "application/json;charset=UTF-8");
            return exchange.getResponse().writeWith(Mono.just(read));
        }


        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            final Long endTime= System.currentTimeMillis();
            // 获取响应状态码
            int statusCode = exchange.getResponse().getStatusCode().value();
            if (statusCode == 200) {
                log.info("响应信息正确");
            } else {
                log.info("响应信息错误,错误码:{},处理用时:{}",statusCode,(endTime-startTime));
            }
        }));
    }
}

五、网关的CORS配置与超时配置

网关作为系统门户自然需要处理CORS的问题。在不使用网关时我们可以使用注解@CrossOrigin或者利用WebFilter是可以解决这个问题的,在网关里也提供了一套自己的解决方案,不过这些方案都是殊途同归,他们都是通过设置以下请求头属性来声明是否允许跨域的。

  • Origin:允许访问的源
  • Method:允许访问的请求方式
  • Header:允许的请求头
  • Credentials:允许携带认证信息进行访问,即请求头中的Authorization、Cookie等认证信息
    无论使用哪种方式都需要注意,最好Credentials被设为true允许携带认证信息时(这个是必须的),Origin不要设为允许全部来源,这样很容易被CSRF攻击,而且网关在最近的版本中已经禁止了这一操作

1.全局CORS配置-配置类

如下,提供一个Cors的配置类,这些信息都差不多,然后将他交给Gateway的请求拦截即可。
需要注意的是,若是不针对所有请求方式进行放行,则必须包含OPTIONS请求,因为当浏览器跨域时默认发送OPTIONS请求去服务器端检测是否允许跨域,是否允许就是根据响应头中的AllowedOrigin来判定的。

package com.cheng.ebbing.gateway.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 java.util.Arrays;

/**
 * @author pcc
 */
@Configuration
public class GatewayConfig {

    @Bean
    public CorsConfiguration getCorsConfiguration() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowedOriginPatterns(Arrays.asList("http://localhost:*",""));
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.setAllowedMethods(Arrays.asList("GET","POST","OPTIONS"));
        corsConfiguration.setAllowCredentials(true);
        corsConfiguration.setMaxAge(3600L);// 预检请求的有效期,有效期内浏览器可以不用重复预检
        return corsConfiguration;
    }

    @Bean
    public CorsWebFilter corsWebFilter(CorsConfiguration corsConfiguration) {
        UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
        urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
        return new CorsWebFilter(urlBasedCorsConfigurationSource);
    }
}

2.全局CORS配置-配置文件

配置文件的配置位置如下,信息还都是上面那些信息

spring:
  cloud:
    gateway:
      globalcors:
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOrigins: "http://localhost:5173"
            allowedHeaders: "*"
            allowedMethods:
              - OPTIONS  # 注意 跨域设置OPTIONS是必须的,因为前端跨域时浏览器默认发出OPTIONS请求
              - GET
              - POST
            allowCredentials: true
            maxAge: 3000  

3.局部CORS配置作用于路由

Gateway官网说是支持路由中配置跨域的,不过他们给出的配置是错的,目前没有修正,这个场景使用也比较少,可以不用在意,以下是官网给的配置:

spring:
  cloud:
    gateway:
      routes:
      - id: cors_route
        uri: https://example.org
        predicates:
        - Path=/service/**
        metadata:
          cors:
            allowedOrigins: '*'
            allowedMethods:
              - GET
              - POST
            allowedHeaders: '*'
            maxAge: 30

4.超时配置全局

这里httpclient并不是说gateway使用的是httpclient进行请求转发的,事实上底层他使用的是netty进行通讯的,不过这个是可以更改的,可以更改为常用的okhttp等都是支持的,当然修改的话需要提供配置。
注意connect-timeout单位是ms,下面是携带单位的5s

spring:
  cloud:
    gateway:
      httpclient:
        connect-timeout: 1000
        response-timeout: 5s

5.超时配置局部

超时局部配置也是通过元信息的配置设置的,这个是有用的,通常个别接口我们是需要设置较长的响应时间的
注意这里单位都是ms

spring:
  cloud:
    gateway:
      routes:
        - id: per_route_timeouts
          uri: https://example.org
          predicates:
            - Path: /**
          metadata:
            response-timeout: 200
            connect-timeout: 200

六、网关整合Nacos+Loadbalancer实现负载均衡

在SpringCloud中服务都是注册到注册中心的,所以路由时我们直接访问ip是不合适的,所以这里我们需要引入注册中心nacos,使用nacos和OpenFeign进行调用时,负载均衡的中间件是必须引入,否则无法实现正常调用,因为SringCloud默认使用,这里也是一样使用nacos+gateway进行调用,但负载均衡还是得LoadBalancer来。

1.引入依赖

        <!-- nacos注册中心客户端,父工程引入了cloud的包管理,这里无需声明具体包-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <!-- 引入loadbalancer负载均衡,父工程引入了cloud的包管理,这里无需声明具体包-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>

2.配置更改

负载均衡组件是个比较简单的组件,如果不更换负载策略可以不增加任何配置,我们只需要配置nacos的信息即可。Nacos使用也很简单,无需增加额外注解(Cloud帮我们省略了)只需要配置下配置信息即可,如对nacos和LoadBalancer有疑问可以看这里:https://blog.csdn.net/m0_46897923/article/details/132639913。
注意引入nacos必须配置spring.application.name

server:
  port: 9000
spring:
  application:
    name: ebbing-gateway
  cloud:
    nacos:
      server-addr: 192.168.150.197:8848
      user-name: nacos # 这是默认值
      password: nacos # 这是默认值
      discovery:
        namespace: 78ec0572-2091-43de-a708-5978caee7b3a # dev
        watch:
          enable: true
        heart-beat-interval: 5000

不错只需要这些更改就可以集成了,事实上gateway也是标准的boot项目,所以没有任何多余的操作就可以直接引入。

3.实现负载均衡

nacos与loadbalance引入就是这么简单,使用的话我们只需要在uri中更改路由的路径即可,未更改前是:http://localhost:8808,更改后如下:

spring:
  application:
    name: ebbing-gateway
  cloud:
    gateway:
      routes:
        - id: task_route
          uri: lb://ebbing-task
          predicates:
            - Path=/api/task/**

lb是使用负载组件,ebbing-task是注册中心的服务名称。LoadBalancer是客户端负载,原理是从nacos拉取所有实例信息,在本地实现的负载。

4.自动获取nacos服务名实现路由

如果网关中无需为每个项目配置单独的断言或者路由规则,那么我们可以使用gateway提供的自动映射,配置如下所示。
注意:使用自动映射,请求路径的开头信息必须是nacos中的服务名,自动路由的Path断言就是根据服务名进行断言的
注意:默认服务名都是大写,所以我们需要开启小写的支持

spring:
  application:
    name: ebbing-gateway
  cloud:
    nacos:
      server-addr: 192.168.150.197:8848
      user-name: nacos # 这是默认值
      password: nacos # 这是默认值
      discovery:
        locator:
          enabled: true # 开启主动映射,自动映射后可以实现注册中心的服务自动路由,原理是路径必须以nacos服务名开头
          lower-case-service-id: true # 开启小写支持,注册中心服务名默认大写,开启后支持小写
        namespace: 78ec0572-2091-43de-a708-5978caee7b3a # dev
        watch:
          enable: true
        heart-beat-interval: 5000

不过一般不用,大部分长还是需要单独配置路由的,不过即使配置了自动路由,也是支持手写路由的,且手写路由的优先级会更高一些。

七、网关整合Sentinel进行流控

这里不详细介绍Sentinel的配置和使用,Sentinel可以参见这里哦:https://java-dream.blog.csdn.net/article/details/132796974

1.引入依赖

这里除了需要引入sentinel的依赖外,还需要引入一个整合的依赖,不过都不需要我们声明版本号,版本号直接在父工程定义即可,具体版本信息详见文章开头。

        <!-- 网关和sentinel整合包 -->
        <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.配置更改

如果想要使用sentinel的dashboard则需要添加配置,如果不需要使用dashboard而是直接使用api进行流控,则可以任何配置都不添加。以下配置是sentinel的dashboard的配置,与整合没有关系其实。

spring:
  application:
    name: ebbing-gateway
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8088
        port: 8719 # gateway与dashboard通信的本地端口

3.dashboard配置路由流控

Gateway整合Sentinel不支持Sentinel中的授权规则,其他的熔断、流控、热点流控、系统规则基本算是支持,使用与Sentinel的原本配置几乎没有差别。这里要说的是使用dashboard配置路由流控,sentinel支持对gateway的两种流控方式,一种是路由流控,一种是根据请求的接口进行流控,也就是api流控了。
在这里插入图片描述
上图是接口被触发了以后就可以看到路由规则对应的请求链路信息了,点击流控进行配置流控规则:
在这里插入图片描述
添加完规则以后,通过jemter测试,如下:
在这里插入图片描述
可以发现被流控了,不过这里都是抛出的ParamFlowException.

4.dashboard配置api流控

这里的api流控,就是可以根据我们自己添加接口进行流控管理,如下图:
注意:sentinel的执行在网关过滤器之前,如果网关过滤器有对路径进行处理,那在API管理时添加的路径应该是处理前的路径
在这里插入图片描述
在这里插入图片描述
然后还回到请求链路中随意找一个路由点击添加流控,然后操作如下,即可选择到刚刚添加的API了,其他操作和路由流控就没有区别了。
在这里插入图片描述

5.api配置路由流控

上面已经说了dashboard配置流控的方式,那如何使用api进行配置流控呢,这里使用路由规则进行演示API配置流控,代码如下:

package com.cheng.ebbing.auth.config;

import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
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 javax.annotation.PostConstruct;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

/**
 * @author pcc
 */
@Configuration
public class GatewayConfig {

    @PostConstruct
    public void init() {
        // 初始化流控规则
        GatewayFlowRule flowRule = new GatewayFlowRule("task_route");// 路由名
        flowRule.setCount(2);// 并发2
        flowRule.setIntervalSec(1);//统计时间1s
        flowRule.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID);// 默认使用路由模式
        flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);// 默认使用QPS

        Set<GatewayFlowRule> set = new HashSet<GatewayFlowRule>();
        set.add(flowRule);
        GatewayRuleManager.loadRules(set);
    }

}

代码如上,验证都没有区别,这里不重复贴图了,可以看到代码和sentinel本身使用时非常相似,只是类名上都多了个gateway而已。

6.自定义流控、熔断响应信息

流控一直返回系统定义的异常信息肯定是不合适的,我们需要定义自己的响应异常信息,如下:

package com.cheng.ebbing.auth.config;

import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;

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

/**
 * @author pcc
 */
@Configuration
public class GatewayConfig {

    // 定义流控等异常信息
    @PostConstruct
    public void initBlockHanler(){
        GatewayCallbackManager.setBlockHandler((exchange,throwable)->{
            if(throwable instanceof ParamFlowException){

                //组装返回信息
                Map<String,Object> resultMap = new HashMap<String,Object>();
                resultMap.put("code",HttpStatus.TOO_MANY_REQUESTS.value());
                resultMap.put("message",HttpStatus.TOO_MANY_REQUESTS.getReasonPhrase());
                resultMap.put("data",null);

                // 流控异常
                return ServerResponse
                        .status(HttpStatus.TOO_MANY_REQUESTS)
                        .contentType(MediaType.APPLICATION_JSON_UTF8).body(BodyInserters.fromValue(resultMap));

            }else{
                //组装返回信息
                Map<String,Object> resultMap = new HashMap<String,Object>();
                resultMap.put("code",HttpStatus.SERVICE_UNAVAILABLE);
                resultMap.put("message","其他异常");
                resultMap.put("data",null);

                // 其他异常
                return ServerResponse
                        .status(HttpStatus.SERVICE_UNAVAILABLE)
                        .contentType(MediaType.APPLICATION_JSON_UTF8).body(BodyInserters.fromValue(resultMap));
            }
        });
    }
}

可以看到,响应信息是我们自己定义的异常信息了,这样就实现了自定义异常信息。
在这里插入图片描述

7.网关的全局异常处理

会有一些情况网关这里依然获取到了注册中心的实例,但是调用时失败了(并没有到达目标服务),那么此时网关就需要一个类似全局异常处理的机制。怎么做呢,只需要实现ErrorWebExceptionHandler这个函数式接口即可,代码如下:

package com.cheng.ebbing.auth.config;

import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;

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

/**
 * @author pcc
 */
@Configuration
public class GatewayConfig {

    // 全局异常处理
    @Bean
    @Order(-100)
    public ErrorWebExceptionHandler getGlobalErrorHandler(ObjectMapper objectMapper) {
        return (exchange,throwable)->{
            Map<String,Object> resultMap = new HashMap<String,Object>();
            // 获取返回
            ServerHttpResponse response = exchange.getResponse();
            // 响应类型统一设置为json
            response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
            // 信息已经响应了,异步的存在已经响应过的可能
            if(response.isCommitted()){
                return Mono.error(throwable);
            }

            // 这里可以根据异常类型进行处理了
            if(throwable instanceof RuntimeException){
                response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
                return response.writeWith(Mono.fromSupplier(()->{
                    // 这里手动生成一个mono用以组装返回信息
                    DataBufferFactory bufferFactory = response.bufferFactory();
                    resultMap.put("code",HttpStatus.INTERNAL_SERVER_ERROR.value());
                    resultMap.put("message","路由异常了");
                    try {
                        return bufferFactory.wrap(objectMapper.writeValueAsBytes(resultMap));
                    }
                    catch (JsonProcessingException e) {
                        return bufferFactory.wrap(new byte[0]);
                    }
                }));
            }else{
                // 这里简单处理,真实场景可以根据不同异常进行设置
                response.setStatusCode(HttpStatus.BAD_REQUEST);
                return response.writeWith(Mono.fromSupplier(()->{
                    // 这里手动生成一个mono用以组装返回信息
                    DataBufferFactory bufferFactory = response.bufferFactory();
                    resultMap.put("code",HttpStatus.INTERNAL_SERVER_ERROR.value());
                    resultMap.put("message","其他错误");
                    try {
                        return bufferFactory.wrap(objectMapper.writeValueAsBytes(resultMap));
                    }
                    catch (JsonProcessingException e) {
                        return bufferFactory.wrap(new byte[0]);
                    }
                }));
            }

        };
    }
}

以上代码使用Bean方式进行注入,当然也可以写成类使用component进行注入,不过无论哪种方式切记必须加Order注解,不然我们的配置会被其他实现类盖掉,且这个Order的值越小优先级越高,我们需要保证优先级小于其他异常的实现

总结

gateway东西其实不多,不过因为其是响应式编程风格,很多东西和Springmvc差别还是很大的。这里总结了断言、过滤器、cors、以及整合nacos、sentinel等信息,其实没有什么困难信息,都是api,使用时翻起来看看即可,希望可以帮助到路过的朋友。

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

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

相关文章

LIN总线信号串行译码

我们用虹科Pico汽车示波器捕捉了LIN总线信号 &#xff0c;如果想看它对应的报文数据&#xff0c;我们可以应用PicoScope Automotive软件的串行译码功能来对它破译。 使用指导如下&#xff1a; 点击“串行译码”&#xff0c;选择对应的协议&#xff0c;如LIN。 在下面对话框&…

linux高级管理——访问MYSQL数据库

一、认识数据库系统&#xff1a; MySQL数据库系统也是一个典型的C/S(客户端/服务器&#xff09;架构的应用&#xff0c;要访问MySQL数据库需要使用专门的客户端软件。在Linux系统中&#xff0c;最简单、易用的MySQL客户端软件是其自带的mysql命令工具。 1&#xff0e;登录到My…

使用Visual Studio(VS)创建空项目的Win32桌面应用程序【main函数入口变WinMain】

前言 在Visual Studio中直接新建Windows桌面应用程序会有很多多余的代码生成&#xff0c;本文将提供从空项目创建Win32项目的方法&#xff0c;解决新建空项目直接使用WinMain代码编译报错的问题 例如&#xff1a;LNK2019 &#xff1a;无法解析的外部符号 参考博客&#xff1…

使用 Timm 库替换 RT-DETR 主干网络 | 1000+ 主干融合RT-DETR

文章目录 前言版本差异说明替换方法parse_moedl( ) 方法_predict_once( ) 方法修改 yaml ,加载主干论文引用timm 是一个包含最先进计算机视觉模型、层、工具、优化器、调度器、数据加载器、数据增强和训练/评估脚本的库。 该库内置了 700 多个预训练模型,并且设计灵活易用。…

电子取证中Chrome各版本解密Cookies、LoginData账号密码、历史记录

文章目录 1.前置知识点2.对于80.X以前版本的解密拿masterkey的几种方法方法一 直接在目标机器运行Mimikatz提取方法二 转储lsass.exe 进程从内存提取masterkey方法三 导出SAM注册表 提取user hash 解密masterkey文件&#xff08;有点麻烦不太推荐&#xff09;方法四 已知用户密…

el-select与treeselect下拉框重叠、兼容性问题

问题描述&#xff1a; 点击el-select的外部&#xff0c;el-select下拉框会自动收回&#xff0c;此操作触发了el-select的clickoutside事件&#xff0c;但当el-select与treeselect配合使用时&#xff0c;先点击el-select使下拉框出现&#xff0c;后再点击treeselect&#xff0c;…

浅谈 USB Bulk 深入浅出 (3) - USB Bulk 装置传输的注意事项

来源&#xff1a;大大通 作者&#xff1a;冷氣團 1 USB Bulk 是什么 USB 是即插即用使用差动信号的装置界面&#xff0c;是以 端点 ( Endpoint )&#xff0c;做为传输装置的输出入端&#xff0c;透过不同的端点 ( Endpoint ) 和模式&#xff0c;来进行与装置的沟通&#xff…

极狐在广告推广与客服系统中实现API集成,加快电商平台无代码开发进程

无代码开发&#xff1a;电商平台的新时代 随着科技的进步&#xff0c;电商平台正经历着一场变革&#xff0c;那就是无代码开发的兴起。在这个新时代里&#xff0c;企业不再需要耗费大量的时间和资源来进行繁杂的API开发。极狐GitLab作为一站式DevOps平台&#xff0c;提供了这样…

【C语言】动态内存规划# 这一篇就够了

目录 通过本篇文章&#xff0c;你可以了解到&#xff1a; &#xff08;O&#xff09;C/C中程序内存区域划分 &#xff08;一&#xff09;动态内存分配的作用 (二)动态内存函数的原理与使用 1.内存开辟malloc 2.初始并开辟calloc 3.调整已开辟的内存realloc 4.释放空间free …

TMTS汽车电子仿真及测试研讨会笔记请查收!

11月29日&#xff0c;德思特2023年度TMTS汽车电子仿真及测试研讨会圆满结束。感谢大家的观看与支持&#xff01; 在直播间收到一些观众的技术问题&#xff0c;我们汇总了热点问题并请讲师详细解答&#xff0c;在此整理分享给大家&#xff0c;请查收&#xff01; 面向汽车T-BOX…

2023年最佳推荐 | 值得收藏的 5大 SaaS 知识库

随着数字化时代的到来&#xff0c;SaaS&#xff08;软件即服务&#xff09;已经成为企业和个人日常工作中的重要工具。在众多的SaaS应用中&#xff0c;知识库是不可或缺的一部分&#xff0c;它可以帮助我们更好地管理和利用知识&#xff0c;提高工作效率和创新能力。接下来就跟…

小型洗衣机什么牌子好又便宜?内衣洗衣机品牌排行榜前十名

最近这两年在洗衣机中火出圈的内衣洗衣机&#xff0c;它不仅可以清洁我们较难清洗的衣物&#xff0c;自带除菌功能&#xff0c;可以让衣物上的细菌&#xff0c;还能在清洗的过程中呵护我们衣物的面料&#xff0c;虽然说它是内衣洗衣机&#xff0c;它的功能不止可以清洗内衣&…

LED恒流调节器FP7125,应用LED街道照明、调光电源、汽车大灯、T5T8日光灯

目录 一、FP7125概述 二、FP7125功能 三、应用领域 近年来&#xff0c;随着人们环保意识的不断增强&#xff0c;LED照明产品逐渐成为照明行业的主流。而作为LED照明产品中的重要配件&#xff0c;LED恒流调节器FP7125的出现为LED照明带来了全新的发展机遇。 一、FP7125概述 FP…

工业应用新典范,飞凌嵌入式FET-D9360-C核心板发布!

来源&#xff1a;飞凌嵌入式官网 当前新一轮科技革命和产业变革突飞猛进&#xff0c;工业领域对高性能、高可靠性、高稳定性的计算需求也在日益增长。为了更好地满足这一需求&#xff0c;飞凌嵌入式与芯驰科技&#xff08;SemiDrive&#xff09;强强联合&#xff0c;基于芯驰D9…

迅速理解什么是通信前置机

通信前置机设在两个通信对象之间&#xff0c;是实质性的物理服务器&#xff0c;适应不同通信协议或数据格式之间的相互转换。 前置机的作用&#xff1a; 隔离——隔离客户端与服务端&#xff0c;保障后端安全减负——处理非核心业务&#xff0c;分担后端服务器压力&#xff0…

yolov8实时推理目标识别、区域分割、姿态识别 Qt GUI

介绍一个GUI工具&#xff0c;可以实时做yolov8模型推理&#xff0c;包括目标检测、姿态识别、跟踪、区域分割等操作。 可以接入图像、视频或者RTSP视频流进行验证。 推理模型用的是yolov8转onnx之后的。用ultralytics自带的转换即可&#xff0c;不用带NMS。 框架用的是Qt 任…

javaspringboot--AOP

AOP AOP:Aspect Oriented Programming (面向切面编程、面向方面编程)&#xff0c;其实就是面向特定方法编程 依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency…

vue2+Echarts数据可视化 【帕累托图】

接口得到的数据如下 要经过排序 &#xff0c;计算累计百分比得到数据 蓝色 柱状图数据&#xff1a; 取count字段值 横坐标&#xff1a;取 id值 折线图&#xff1a;根据柱状图的数据计算累计百分比 getInterface(data) {getParetoChart(data).then((res) > {if (res) {thi…

qiankun中主系统启动子系统本地静态图片加载404或者跨域问题

由于本地启动主系统和子系统&#xff0c;如果主系统内不放子系统的图片就会导致主系统访问不到子系统的图片 对于这个问题有三种解决方案&#xff0c;这里一一说明 1、第一种也是我常用的&#xff0c;就是把子系统中的静态图片全部转为base64这样通过主系统访问的时候就不是通…

EMC辐射发射RE整改方法?|深圳比创达电子EMC

一、排除外界因素 1、将被测设备关电&#xff0c;确认背景噪声是否满足标准要求(标准要求―电波暗室的背景噪声在限值线以下6dB)&#xff1b; 2、确认测试布置是否满足标准要求。 二、宽带噪声抑制方法 1、谱线问题描述&#xff1a;30&#xff5e;300MHz频段内出现宽带噪声…