Gateway新一代网关
Gateway新一代网关
1、概述
Cloud全家桶中有个很重要的组件就是网关,在1.x版本中都是采用的Zuul网关;
但在2.x版本中,zuul的升级一直跳票,SpringCloud最后自己研发了一个网关SpringCloud Gateway替代Zuul。
官网:Spring Cloud Gateway 中文文档 (springdoc.cn)
微服务架构中网关在哪里?
Spring Cloud Gateway能干什么?
- 反向代理;
- 鉴权;
- 流量控制;
- 熔断;
- 日志监控;
总结:
**Spring Cloud Gateway组件的核心是一系列的过滤器,通过这些过滤器可以将客户端发送的请求转发(路由)到对应的微服务。 Spring Cloud Gateway是加在整个微服务最前沿的防火墙和代理器,隐藏微服务结点IP端口信息,从而加强安全保护。**Spring Cloud Gateway本身也是一个微服务,需要注册进服务注册中心。
2、Gateway三大核心
- Route(路由): 网关的基本构件。它由一个ID、一个目的地URI、一个谓词(Predicate)集合和一个过滤器(Filter)集合定义。如果集合谓词为真,则路由被匹配。
- Predicate(谓词): 这是一个 Java 8 Function Predicate。输入类型是 Spring Framework
ServerWebExchange
。这让你可以在HTTP请求中的任何内容上进行匹配,比如header或查询参数。 - Filter(过滤器): 这些是
GatewayFilter
的实例,已经用特定工厂构建。在这里,你可以在发送下游请求之前或之后修改请求和响应。
- web前端请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。
- predicate就是我们的匹配条件;
- filter,就可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标uri,就可以实现一个具体的路由了
3、Spring Cloud Gateway 工作流程
客户端向 Spring Cloud Gateway 发出请求。如果Gateway处理程序映射确定一个请求与路由相匹配,它将被发送到Gateway Web处理程序。这个处理程序通过一个特定于该请求的过滤器链来运行该请求。过滤器被虚线分割的原因是,过滤器可以在代理请求发送之前和之后运行逻辑。
所有的 "pre"
(前)过滤器逻辑都被执行。然后发出代理请求。在代理请求发出后,"post"
(后)过滤器逻辑被运行。
注意:在路由中定义的没有端口的URI,其HTTP和HTTPS URI的默认端口值分别为80和443。
总结:路由转发+断言判断+执行过滤器链
4、入门配置
新建module,cloud-gateway9527
添加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: 9527spring:application:name: cloud-gateway #以微服务注册进consul或nacos服务列表内cloud:consul: #配置consul地址host: localhostport: 8500discovery:prefer-ip-address: trueservice-name: ${spring.application.name}
主启动类:
@SpringBootApplication
@EnableDiscoveryClient
public class GateWay9527 {public static void main(String[] args) {SpringApplication.run(GateWay9527.class,args);}
}
业务类是不需要写的,网关与业务无关。
先启动Consul,然后启动网关入住进服务注册中心。
5、9527网关如何做路由映射
我们目前不希望暴露8001端口,希望在8001这个真正的支付微服务外面再套一个9527网关。
8001新建GateWayController
@RestController
public class GateWayController {@ResourcePayService 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());}
}
测试一下:
没有问题,下面在网关的配置文件中新增配置,新增路由和断言配置。
server:port: 9527spring:application:name: cloud-gateway #以微服务注册进consul或nacos服务列表内cloud:consul: #配置consul地址host: localhostport: 8500discovery:prefer-ip-address: trueservice-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
目前通过网关访问是成功的,如果通过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
{@Resourceprivate 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();}
}
我们发现是可以访问的,真的走网关了吗?我们的API接口上@FeignClient(value = "cloud-payment-service")
打的可是cloud-payment-service微服务,根本没有网关的事呀,所以,这个时候的网关压根就没有使用,我们把网关的微服务关掉再用80访问一定是可以访问的。
网关的9527关掉了,现在访问
依旧正常访问。我们想一下,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了
再把网关打开就成功访问了
目前我们的配置还是有问题的,比如我们网关中的uri地址,本地加端口号写的死死的,一改就无了…
6、GateWay高级特性
6.1、Route以微服务名动态获取服务URI
我们之前给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-servicepredicates:- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由
修改以后可以重启一下网关测试。
测试没有问题,我们把8001的端口给改成8003再测试一下。
依然可用。
6.2、Predicate断言(谓词)
Spring Cloud Gateway将路由匹配作为Spring WebFlux HandlerMapping
基础设施的一部分。Spring Cloud Gateway包括许多内置的路由谓词工厂。所有这些谓词都与HTTP请求的不同属性相匹配。你可以用逻辑 and
语句组合多个路由谓词工厂。
我们可以查看9527后台输出的默认路由谓词是和官网的那12个默认路由谓词一一对应着的
两种配置方式
-
Shortcut Configuration,快捷方式配置由过滤器名称(filter name),后面跟一个等号 (
=
),然后是用逗号 (,
) 分隔的参数值。 -
Fully Expanded Arguments,完全展开的参数看起来更像标准的yaml配置,有名称/值对。一般来说,会有一个
name
key和一个args
key。args
key是一个键值对的映射,用于配置谓词或过滤器。
常用的默认路由谓词解释
-
After
路由谓词工厂需要一个参数,即一个日期时间(这是一个javaZonedDateTime
),这个谓词匹配发生在指定日期时间之后的请求。那么这个日期怎么获得?
写一个普通的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-servicepredicates:- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由
在我设置的8:30:30之前是不能访问的,8点半之后就可以访问了。
-
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-servicepredicates:- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由- Before=2024-03-17T20:39:00.218129200+08:00[Asia/Shanghai]
可以看到8.38还能正常访问的39就不能了。
-
Between
路由谓词工厂需要两个参数,datetime1
和datetime2
,它们是javaZonedDateTime
对象。这个谓词匹配发生在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-servicepredicates:- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由- Before=2024-03-17T20:39:00.218129200+08:00[Asia/Shanghai]
可以看到,这个只能在9点到9点零1才能访问到。
-
Cookie
路由谓词工厂接受两个参数,即 cookiename
和一个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-servicepredicates:- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由- Cookie=username, zm
只有加了cookie才能访问,而且键值(kv)要匹配
有三种可以带cookie访问的方法
-
方法1,使用原生命令,cmd打开运行框
不带cookie的不能访问
带了cookie可以访问
-
方法2,使用postman测试工具测试
不带cookie的不能访问
带了cookie可以访问
-
方法3,使用浏览器控制台实现
不带cookie或者故意填错误的不能访问
带了正确的cookie可以访问
-
-
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-servicepredicates:- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由- Cookie=username, zm
测试方法1,使用原生命令
不是正整数
是正整数
测试方法2,使用postman测试
不是正整数
是正整数
-
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-servicepredicates:- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由#- Cookie=username, zm- Host=**.zmblog.com,**.zm.cn #请求host的header值带zmblog.com或zm.cn的可以匹配
使用原命令测试
正确符合规则的可以访问
不符合规则的拒绝访问
使用postman测试
正常匹配规则的
不符合规则的
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-servicepredicates:- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由#- Cookie=username, zm- Host=**.zmblog.com,**.zm.cn #请求host的header值带zmblog.com或zm.cn的可以匹配
测试:
按照规则访问
不按规则不能访问
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-servicepredicates:- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由#- Cookie=username, zm- Host=**.zmblog.com,**.zm.cn #请求host的header值带zmblog.com或zm.cn的可以匹配
测试,重启服务按照原来的localhost地址访问一下不能访问。
换成我们本地的IP就可以了
10.Method
路由谓词工厂接受一个 methods
参数,它是一个或多个参数:要匹配的HTTP方法。
我们可以限制只能使用GET或者POST方法才能访问。
- Method=GET,POST #只能是get或post请求方法
6.3、自定义断言XXXRoutePredicateFactory规则
原有的默认的路由断言规则不够用,我们就自定义一个。
查看AfterRoutePredicateFactory它就是继承了AbstractRoutePredicateFactory
public class AfterRoutePredicateFactory extends AbstractRoutePredicateFactory<Config>
然后再点AbstractRoutePredicateFactory里面它又实现了RoutePredicateFactory接口
所以我们要想自定义一个路由断言就需要这几点
- 要么继承AbstractRoutePredicateFactory抽象类;
- 要么实现RoutePredicateFactory接口;
- 开头的名字任意取,但是必须以RoutePredicateFactory为后缀结尾;
- 由于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@NotNullprivate String userType; //会员类型 }
-
空参构造方法,内部调用super;
//空参构造函数 public MyRoutePredicateFactory() {super(Config.class); }
-
重写apply方法第二版;
@Override public Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config) {return new GatewayPredicate() {@Overridepublic 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> {@Overridepublic Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config) {return new GatewayPredicate() {@Overridepublic 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@NotNullprivate 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-servicepredicates:- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由#- Cookie=username, zm- Host=**.zmblog.com,**.zm.cn #请求host的header值带zmblog.com或zm.cn的可以匹配
重启测试发现报错了
报错说没绑定快捷方式的配置
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: Myargs:userType: diamond #使用完全展开的方式配置
重启不报错,测试一下
带正确参数的访问
不带参数或者不是配置文件中写的userType就不能访问
修改一下自定义断言MyRoutePredicateFactory,添加方法shortcutFieldOrder能使用短配置
//实现短配置方式
public List<String> shortcutFieldOrder() {return Collections.singletonList("userType");
}
现在的完整MyRoutePredicateFactory代码
//业务需求说明:自定义配置会员等级userType,按照钻石/黄金/青铜的等级,yml配置会员等级
@Component
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config> {@Overridepublic Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config) {return new GatewayPredicate() {@Overridepublic 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@NotNullprivate String userType; //会员类型}//空参构造函数public MyRoutePredicateFactory() {super(Config.class);}//实现短配置方式public List<String> shortcutFieldOrder() {return Collections.singletonList("userType");}}
到配置文件中使用短配置方式配置测试
- My=diamond #自定义的断言
# - name: My
# args:
# userType: diamond #使用完全展开的方式配置
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
一些常用的
-
请求头(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_routh3uri: lb://cloud-payment-servicepredicates:- Path=/pay/gateway/filter/**filters:- AddRequestHeader=X-Request-zm1,zmValue1 #请求头kv,如果一头含有多参数则重写一行设置- AddRequestHeader=X-Request-zm2,zmValue2 #请求头kv,如果一头含有多参数则重写一行设置
启动9527和8001并再次调用localhost:9527/pay/gateway/filter
查看后台
6.18.The RemoveRequestHeader GatewayFilter Factory
可以删除请求头的内容根据请求头名
我们把名为sec-fetch-site的请求头删除掉
- id: pay_routh3uri: lb://cloud-payment-servicepredicates:- Path=/pay/gateway/filter/**filters:- AddRequestHeader=X-Request-zm1,zmValue1 #请求头kv,如果一头含有多参数则重写一行设置指定请求头内容 - AddRequestHeader=X-Request-zm2,zmValue2 #请求头kv,如果一头含有多参数则重写一行设置- RemoveRequestHeader= sec-fetch-site #删除请求头sec-fetch-site
重启测试
6.29.The SetRequestHeader GatewayFilter Factory
可以修改请求头的值,那我们把请求头名为sec-fetch-mode的请求值修改成myNavigate
- id: pay_routh3uri: lb://cloud-payment-servicepredicates:- 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
重启测试
-
请求参数(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()); }
启动测试,我们先不添加参数直接发请求。
然后在请求中添加参数http://localhost:9527/pay/gateway/filter?customerID=6666&customerName=zzzmmm
原先在配置文件中写死的customerID会被你的请求覆盖掉,而参数customerName是要被移除的参数,所以你传入什么都会被移除为null
-
响应头(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 响应属性删除
重启服务查看效果
-
前缀和路径相关组;
-
6.14.The PrefixPath GatewayFilter Factory自动添加路劲前缀
之前正确的地址http://localhost:9527/pay/gateway/filter
- id: pay_routh3uri: lb://cloud-payment-servicepredicates:# - Path=/pay/gateway/filter/**- Path=/gateway/filter/** #实际上你应该在浏览器输入http://localhost:9527/gateway/filterfilters:- PrefixPath=/pay #和上面的Path拼在一起就是http://localhost:9527/pay/gateway/filter
现在的访问地址就是http://localhost:9527/gateway/filte了
如果还访问原来的地址就是404了
-
6.29.The SetPath GatewayFilter Factory修改访问路劲
此时访问的地址就变成了localhost:9527/ZM/abcd/filter才能正确访问
- id: pay_routh3uri: lb://cloud-payment-servicepredicates:# - Path=/pay/gateway/filter/**# - Path=/gateway/filter/** #实际上你应该在浏览器输入http://localhost:9527/gateway/filter- Path=/ZM/abcd/{segment} #修改访问路径,{segment}占位符的内容会和SetPath的{segment}保持一致filters:- SetPath=/pay/gateway/{segment}
最后的那个占位符写错你就访问不了了
那如果把占位符的写对,前面自己改的写错呢?
还是不能访问的连前面都不匹配。
-
6.16. The RedirectTo GatewayFilter Factory重定向到某界面
- id: pay_routh3uri: lb://cloud-payment-servicepredicates:- 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/ 直接跳转到我的个人博客上
直接跳转过来
-
-
其它
6.38.Default Filters配置在此处相当于全局通用,自定义秒变Global。
GlobalFilter
接口的签名与GatewayFilter
相同。这些是特殊的过滤器,有条件地应用于所有路由。
6.4.3、Gateway自定义Filter
自定义全局Filter
对于我们之前说的那个面试题,解决统计接口调用耗时情况的,怎么实现,就可以使用自定义全局过滤器的方式搞定。
官网说明:
我们参照官网的示例写出自己的全局Filter
@Component
@Slf4j
public class MyGlobal implements GlobalFilter, Ordered {public static final String START_TIME = "start_time";@Overridepublic 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("----------------------分割线---------------------------");}}));}//返回的值越小优先级别越高@Overridepublic 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-servicepredicates:- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由- id: pay_routh3uri: lb://cloud-payment-servicepredicates:- Path=/pay/gateway/filter/**filters:- AddRequestHeader=X-Request-zm1,zmValue1 #请求头kv,如果一头含有多参数则重写一行设置指定请求头内容- AddRequestHeader=X-Request-zm2,zmValue2
重启测试一下
自定义条件Filter
自定义,单一内置过滤器GatawayFilter,可以先参考GateWay内置出厂默认的然后照猫画虎,具体的内容感觉还有点像前面自定义的断言。
自定义网关过滤器规则
-
新建类名XXX需要以GatewayFilterFactory结尾并继承AbstractGatewayFilterFactory类;
-
新建MyGatewayFilterFactory.Config内部类;
public static class Config{@Getter@Setterprivate String status;//设置一个状态值或者标志位,它等于多少,匹配上才能访问 }
-
重写apply方法;
@Override public GatewayFilter apply(Config config) {return new GatewayFilter() {@Overridepublic 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();}}}; }
-
重写shortcutFieldOrder方法;
@Override public List<String> shortcutFieldOrder() {return Arrays.asList("status"); }
-
空参构造方法,内部调用super;
public MyGatewayFilterFactory(){super(MyGatewayFilterFactory.Config.class); }
-
完整MyGatewayFilterFactory类;
@Component public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config> {public MyGatewayFilterFactory(){super(MyGatewayFilterFactory.Config.class);}@Overridepublic GatewayFilter apply(Config config) {return new GatewayFilter() {@Overridepublic 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();}}};}@Overridepublic List<String> shortcutFieldOrder() {return Arrays.asList("status");}public static class Config{@Getter@Setterprivate String status;//设置一个状态值或者标志位,它等于多少,匹配上才能访问}}
-
全局配置文件中配置yml;
- id: pay_routh3uri: lb://cloud-payment-servicepredicates:- Path=/pay/gateway/filter/**# - Path=/gateway/filter/** #实际上你应该在浏览器输入http://localhost:9527/gateway/filter#- Path=/ZM/abcd/{segment} #修改访问路径,{segment}占位符的内容会和SetPath的{segment}保持一致filters:- My=zm #自定义的过滤器
-
重启9527测试
先还按照原来的不带任何参数看能不能访问
当然不能访问,然后添加参数的,参数的value是什么我们就没有做限制随便。
带着我们设置的参数就可以访问了。
相关文章:

Gateway新一代网关
Gateway新一代网关 1、概述 Cloud全家桶中有个很重要的组件就是网关,在1.x版本中都是采用的Zuul网关; 但在2.x版本中,zuul的升级一直跳票,SpringCloud最后自己研发了一个网关SpringCloud Gateway替代Zuul。 官网&…...
Simulink中Scope图像导出在MATLAB上重新画
在Simulink中,Scope是一个常用的可视化工具,用于实时显示仿真过程中的信号波形。 1. 从Simulink Scope中导出数据 首先,您需要在Simulink的Scope中捕获或记录想要导出的数据。这通常通过配置Scope的“Logging”选项来实现。确保在仿真过程中…...
利用opencv获取系统时间
前一篇《c获取系统时间的方法-CSDN博客》博客介绍了如何在不同系统中获取系统时间的方法,但这些方法受系统的限制,如time.h就只能在Linux系统中使用。而opencv则不受系统限制,示例代码如下, #include <opencv2/opencv.hpp>…...

Go环境变量配置,及GOROOT、GOPATH的区别
一、安装Go go下载地址: https://golang.google.cn/dl/ windows下载安装,有两种方式。解压和直接安装 方式一:直接下载安装包。以.msi结尾的文件。例如: go1.22.1.windows-amd64.msi 下载后,双击后一直点下一步即…...

爬虫系列-CSS基础语法
🌈个人主页:会编程的果子君 💫个人格言:“成为自己未来的主人~” CSS全称层叠样式表 ,主要用来定义页面内容展示效果的一门语言,HTML:页面骨架,素颜CSS:页面效果美化:…...
获取比特币和莱特币的实时价格
数据来源: https://datacenter.jin10.com/reportType/dc_bitcoin_current 代码: import akshare as ak import pandas as pd pd.set_option(display.max_columns, None) pd.set_option(display.max_rows, None) pd.set_option(display.width, 1000)cr…...

Axure案例分享—折叠面板(附下载地址)
今天和大家分享的Axure案例是折叠面板 折叠面板是移动端APP中常见的组件之一,有时候也称之为手风琴。咱们先看下Axure画出的折叠面板原型效果,然后再对该组件进行详细讲解。 一、功能介绍 折叠或展开多个面板内容,默认为展开一项内容&…...

SQLiteC/C++接口详细介绍sqlite3_stmt类(五)
返回:SQLite—系列文章目录 上一篇:SQLiteC/C接口详细介绍sqlite3_stmt类(四)- 下一篇: 无 12. sqlite3_bind_text16函数 sqlite3_bind_text16函数用于将UTF-16编码的文本数据(字符串)绑定…...

单片机-- 数电(3)
编码器与译码器 译码 :将二进制代码转化为其他进制的代码 编码 :就是将其他代码转换为二进制码 编码器的类型 1二进制编码器 用n位二进制数码对2的n次方个输入信号进行编码的电路 2二-十进制编码器 将0到9十个十进制数转化为二进制代码的电路 2…...

基于Java中的SSM框架实现在线通用旅游平台网站系统项目【项目源码+论文说明】计算机毕业设计
基于Java中的SSM框架实现在线通用旅游平台网站系统演示 摘要 近几年来,计算机网络的发展得到了飞速的提升,由此展开的一系列行业大洗牌也由此开始。早些年只是人们只是对于计算机和互联网有了些基础的认识,现在它正在悄悄的改变着我们生活的…...

「数据分析」之零基础入门数据挖掘
摘要:对于数据挖掘项目,本文将学习应该从哪些角度分析数据?如何对数据进行整体把握,如何处理异常值与缺失值,从哪些维度进行特征及预测值分析? 探索性数据分析(Exploratory Data Analysis&#…...
【力扣】383.赎金信
题目描述 给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。 如果可以,返回 true ;否则返回 false 。 magazine 中的每个字符只能在 ransomNote 中使用一次。 示例 1: …...

【Linux】传输层协议:TCP/UDP
目录 netstat pidof UDP协议 TCP协议 TCP协议段格式 TCP协议的相关机制 确认应答(ACK)机制 超时重传机制 连接管理机制 服务端状态转换 客户端状态转化 流量控制 流量控制常见问题: 滑动窗口 拥塞控制 延迟应答 面向字节流…...
Linux运维_Bash脚本_构建安装Systemd-250
Linux运维_Bash脚本_构建安装Systemd-250 Bash (Bourne Again Shell) 是一个解释器,负责处理 Unix 系统命令行上的命令。它是由 Brian Fox 编写的免费软件,并于 1989 年发布的免费软件,作为 Sh (Bourne Shell) 的替代品。 您可以在 Linux 和…...

中国城市统计年鉴、中国县域统计年鉴、中国财政统计年鉴、中国税务统计年鉴、中国科技统计年鉴、中国卫生统计年鉴
统计年鉴是指以统计图表和分析说明为主,通过高度密集的统计数据来全面、系统、连续地记录年度经济、社会等各方面发展情况的大型工具书来获取统计数据资料。 统计年鉴是进行各项经济、社会研究的必要前提。而借助于统计年鉴,则是研究者常用的途径。目前国…...

C++:继承:面向对象编程的重要特性
(❁◡❁)(●◡●)╰(*▽*)╯(*/ω\*)(^///^)(❁◡❁)(❁◡❁)(●◡●)╰(*▽*)╯(*/ω\*)(❁◡❁)(●’◡’●)╰(▽)╯(/ω\)(///) C:继承:面向对象编程的重要特性 前言**继承**1.继承的概念及定义1.1继承的概念1.2继…...

oneMKL--FFT 基本使用
oneMKL–FFT 基本使用 本人基于官方文档的摘录与理解 oneMKL--FFT基本使用 oneMKL--FFT 基本使用1. Both FFT and Cluster FFT functions compute an FFT in five steps2 Computing an FFT2.1 缺省值2.2 Fourier Transform Funcions Code Examples2.2.1 One_dimentional In-p…...
软件测试工程师面试汇总Linux篇
Linux 命令篇 cd:切换目录 cd / #进入到系统根目录 cd . #进入到当前目录 cd .. #返回上层目录 cd /tmp #进入指定目录/tmp cd ~ #进入当前用户的家目录 2ls:列出当前目录的所有文件、文件夹(目录)信息; -l 列出目录或…...
【python】使用代理IP爬取猫眼电影专业评分数据
前言 我们为什么需要使用IP代理服务? 在编写爬虫程序的过程中,IP封锁无疑是一个常见且棘手的问题。尽管网络上存在大量的免费IP代理网站,但其质量往往参差不齐,令人堪忧。许多代理IP的延迟过高,严重影响了爬虫的工作…...
C/C++中枚举(enum)和结构体(struct)的异同
一、枚举 enum 1.普通枚举,枚举在C中使用比C使用简单 C语言: enum Color {red,green,blue }; enum Color c red;C语言 enum Color {red,green,blue }; Color c red;C认为这种枚举方式会污染名字,即:枚举使用的名字,在同一个作…...

全球首个30米分辨率湿地数据集(2000—2022)
数据简介 今天我们分享的数据是全球30米分辨率湿地数据集,包含8种湿地亚类,该数据以0.5X0.5的瓦片存储,我们整理了所有属于中国的瓦片名称与其对应省份,方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...
多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验
一、多模态商品数据接口的技术架构 (一)多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如,当用户上传一张“蓝色连衣裙”的图片时,接口可自动提取图像中的颜色(RGB值&…...

C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...

关键领域软件测试的突围之路:如何破解安全与效率的平衡难题
在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)
上一章用到了V2 的概念,其实 Fiori当中还有 V4,咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务),代理中间件(ui5-middleware-simpleproxy)-CSDN博客…...

短视频矩阵系统文案创作功能开发实践,定制化开发
在短视频行业迅猛发展的当下,企业和个人创作者为了扩大影响力、提升传播效果,纷纷采用短视频矩阵运营策略,同时管理多个平台、多个账号的内容发布。然而,频繁的文案创作需求让运营者疲于应对,如何高效产出高质量文案成…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...
多模态图像修复系统:基于深度学习的图片修复实现
多模态图像修复系统:基于深度学习的图片修复实现 1. 系统概述 本系统使用多模态大模型(Stable Diffusion Inpainting)实现图像修复功能,结合文本描述和图片输入,对指定区域进行内容修复。系统包含完整的数据处理、模型训练、推理部署流程。 import torch import numpy …...

android13 app的触摸问题定位分析流程
一、知识点 一般来说,触摸问题都是app层面出问题,我们可以在ViewRootImpl.java添加log的方式定位;如果是touchableRegion的计算问题,就会相对比较麻烦了,需要通过adb shell dumpsys input > input.log指令,且通过打印堆栈的方式,逐步定位问题,并找到修改方案。 问题…...

通过MicroSip配置自己的freeswitch服务器进行调试记录
之前用docker安装的freeswitch的,启动是正常的, 但用下面的Microsip连接不上 主要原因有可能一下几个 1、通过下面命令可以看 [rootlocalhost default]# docker exec -it freeswitch fs_cli -x "sofia status profile internal"Name …...