SpringCloud基础二(完结)
HTTP客户端Feign
-
在SpringCloud基础一中,我们利用RestTemplate结合服务注册与发现来发起远程调用的代码如下:
String url = "http://userservice/user/" + order.getUserId(); User user = restTemplate.getForObject(url, User.class);
- 以上代码就存在几个问题:
- 代码可读性差,编程体验不统一
- 若遇到参数较多,则此时复杂的url就难以维护
- 为解决以上问题就引入了Fegin来代替RestTemplate,可帮助我们解决以上问题
- 以上代码就存在几个问题:
-
Fegin是一个声明式的http客户端,可优雅的实现http请求的发送
快速入门
本快速入门示例的初始项目fegin-demo的具体搭建过程可详见SpringCloud项目快速搭建部分内容
本快速入门已上传至Gitee的主分支master中,可自行下载
本快速入门省略了服务注册与发现部分的代码搭建,具体搭建过程可详见SpringCloud基础一中的内容。(本示例以Nacos为基础进行演示)
背景说明
注意:背景说明可详见SpringCloud基础一的微服务远程调用中的背景说明
Fegin搭建
-
Step1: 在服务消费者(即order-service模块)的pom文件中引入依赖
-
openfeign依赖
-
loadBalancer依赖
由于SpringCloud2020之后的版本不在提供默认的负载均衡器,所以需要引入loadBalancer依赖来负载均衡
若使用的是SpringCloud2020之前的版本则可以不引入该依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
-
-
Step2: 在服务消费者(即order-service模块)的启动类
OrderApplication
上添加开启Fegin功能的注解@EnableFeignClients
package at.guigu;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients;@Slf4j @EnableFeignClients @SpringBootApplication public class OrderApplication {public static void main(String[] args) {SpringApplication.run(OrderApplication.class, args);log.info("OrderApplication Running");} }
-
Step3: 在服务消费者(即order-service模块)中创建一个与三层架构包同级的
clients
包,并在该包下创建一个接口UserClient
来编写Fegin客户端,代码如下:-
Step3-1: 给该接口添加一个
@FeignClient
注解,并给该注解的name或value属性的值设为要使用的服务提供者的服务名 -
Step3-2: 在该接口内部自定义方法来返回自己想要的结果
package at.guigu.clients;import at.guigu.po.User;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;@FeignClient(name = "userservice")public interface UserClient {@GetMapping("/user/{id}")User findById(@PathVariable("id") Long id);}
-
三层架构包代码更改
-
Step1: 修改业务层代码
-
Step1-1: 在
IOrderService
接口中添加方法queryOrderById
,代码如下:注意:接口中的方法默认就是
public abstract
,此处写出来只是为了演示,实际项目中可详略package at.guigu.service;import at.guigu.po.Order; import com.baomidou.mybatisplus.extension.service.IService;public interface IOrderService extends IService<Order> {// 返回包含用户信息的订单信息public abstract Order queryOrderById(Long orderId); }
-
Step1-2: 在
IOrderService
接口中添加方法queryOrderById
,代码如下:package at.guigu.service.impl;import at.guigu.clients.UserClient; import at.guigu.mapper.OrderMapper; import at.guigu.po.Order; import at.guigu.po.User; import at.guigu.service.IOrderService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service;@Service @RequiredArgsConstructor public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {// 构造器依赖注入UserClient接口的beanprivate final UserClient userClient;/*** 获取包含用户信息的订单信息* @param orderId* @return Order*/@Overridepublic Order queryOrderById(Long orderId) {// 1 查询订单信息Order order = this.getById(orderId);// 2 利用Feign发起Http请求来获取用户数据,实现远程调用User user = userClient.findById(order.getUserId());// 3 封装用户数据存储到订单信息中order.setUser(user);return order;} }
-
-
Step2: 表现层
OrderController
类中的queryOrderById
方法代码更改如下package at.guigu.controller;import at.guigu.po.Order; import at.guigu.service.IOrderService; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;@RestController @RequestMapping("order") @RequiredArgsConstructor public class OrderController {private final IOrderService orderService;@GetMapping("{orderId}")public Order queryOrderById(@PathVariable("orderId") Long orderId) {// 根据id查询订单并返回return orderService.queryOrderById(orderId);} }
服务注册与发现
注意:服务注册与发现的这一部分,博主采用了Nacos,其搭建详细步骤可详见Nacos注册中心的搭建步骤,此处不在演示步骤
- 为了方便后续演示,本项目创建了两个服务提供者(即user-service模块)的服务实例来模拟多实例部署,端口分别为8081和8082
- 同一服务的多个服务实例的创建过程可详见Eureka服务注册快速入门,其中提到了如何模拟多实例部署的过程
启动演示
-
以上快速入门步骤全部搭建完成之后,运行服务提供者以及服务消费者的启动类,然后通过PostMan进行测试,结果如下
-
博主共进行了四次请求测试,由以上测试结果可知,Feign不仅成功代替了RestTemplate实现了微服务远程调用,而且也实现了负载均衡
-
完整pom文件代码如下
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>cn.itcast</groupId><artifactId>fegin-demo</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>order-service</artifactId><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><!--LoadBalancer依赖--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency><!--nacos客户端服务管理依赖(即Nacos服务发现依赖)--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><!--openfeign依赖--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency></dependencies></project>
自定义配置
-
Feign可以支持很多的自定义配置,部分常用的自定义配置如下
类型 作用 说明 feign.Logger.Level 修改日志级别 包含四种不同的级别:NONE(默认)、BASIC、HEADERS、FULL feign.codec.Decoder 响应结果的解析器 http远程调用的结果做解析,例如解析json字符串为java对象 feign.codec.Encoder 请求参数编码 将请求参数编码,便于通过http请求发送 feign. Contract 支持的注解格式 默认是SpringMVC的注解 feign. Retryer 失败重试机制 请求失败的重试机制,默认是没有,不过会使用Ribbon的重试 - Feign的日志级别共分为四种:
NONE
:不记录任何日志信息,这是默认值。BASIC
:仅记录请求的方法,URL以及响应状态码和执行时间HEADERS
:在BASIC的基础上,额外记录了请求和响应的头信息FULL
:记录所有请求和响应的明细,包括头信息、请求体、元数据。
- Feign的日志级别共分为四种:
自定义日志级别方式一
SpringCloud2020版本之前
-
全局配置------针对所有服务提供者
feign: client:config: default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置loggerLevel: FULL # 日志级别
-
局部配置------针对单个服务提供者
feign: client:config: userservice: # 针对某个微服务的配置loggerLevel: FULL # 日志级别
SpringCloud2020版本之后
-
全局配置------针对所有服务提供者
spring:cloud:openfeign:client:config:default:logger-level: FULL
-
局部配置------针对单个服务提供者
spring:cloud:openfeign:client:config:userservice:logger-level: FULL
注意:不论是SpringCloud哪个版本,在设置完Fegin的日志级别之后,必须通过logging.level来设置指定feign包的日志级别,否则不会生效,代码如下
logging:level:# 配置指定包及其子包下的所有类的日志级别为debug---会输出大于等于该级别的日志信息at.guigu.clients: debug
-
由于在创建该项目时,就已经通过logging.level设置at.guigu包下的所有包及其子包下的日志级别为debug,而clients包在at.guigu包下,所以博主在yml配置文件中并未在通过logging.level设置指定feign包的日志级别
-
logging.level的日志级别共分为七种,从低到高依次为:
Trace
<Debug
<Info
<Warn
<Error
<Fatal
<OFF
- 设置日志级别为degug时,会输出大于等于该级别的日志信息
自定义日志级别方式二
方式一采用的是配置文件的形式,方式二采用配置类的形式
-
Step1: 在服务消费者(即order-service模块)中创建一个与三层架构包同级的config包,并在该包下创建一个类
DefaultFeignConfiguration
,代码如下:package at.guigu.config;import feign.Logger; import org.springframework.context.annotation.Bean;public class DefaultFeignConfiguration {@Beanpublic Logger.Level feignLogLevel(){return Logger.Level.BASIC; // 日志级别为BASIC} }
-
Step2: 使该配置类生效
-
全局配置方式 :为服务消费者(即order-service模块)的启动类中的
@EnableFeignClients
注解添加defaultConfiguration
属性,且属性值为Feign日志级别配置类的类类对象,代码如下:package at.guigu;import at.guigu.config.DefaultFeignConfiguration; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients;@Slf4j @EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class) @SpringBootApplication public class OrderApplication {public static void main(String[] args) {SpringApplication.run(OrderApplication.class, args);log.info("OrderApplication Running");} }
-
局部配置方式(以user-service模块为例) :在clients包下找到对应的服务消费者的Feign客户端接口,然后为该接口中的
@FeignClient
注解添加configuration
属性,且属性值为Feign日志级别配置类的类类对象,代码如下:package at.guigu.clients;import at.guigu.config.DefaultFeignConfiguration; import at.guigu.po.User; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable;@FeignClient(name = "userservice", configuration = DefaultFeignConfiguration.class) public interface UserClient {@GetMapping("/user/{id}")User findById(@PathVariable("id") Long id); }
-
-
注意:不论是全局配置方式还是局部配置方式,最后都要通过logging.level来设置指定feign包的日志级别,否则不会生效
- 通过logging.level来设置指定feign包的日志级别,可详见自定义日志级别的方式一
Feign使用优化
- Feign底层发起http请求,主要依赖于其它的框架,其底层客户端实现包括:
- URLConnection:默认实现,不支持连接池
- Apache HttpClient :支持连接池
- OKHttp:支持连接池
- 由其底层客户端实现可知,提高Feign的性能主要手段就是使用 连接池 代替默认的URLConnection
- 本小节将对HttpClient以及OKHttp都进行演示,实际项目中选择其中一个即可
Apache HttpClient
博主使用的SpringCloud版本为2022,对应的OpenFeign版本为4.x
Spring Cloud2022版本之前或OpenFeign4.x版本之前
-
Step1: 在服务消费者(即order-service模块)的pom文件中引入Apache的HttpClient依赖
<!--httpClient的依赖 --> <dependency><groupId>io.github.openfeign</groupId><artifactId>feign-httpclient</artifactId> </dependency>
-
Step2: 在服务消费者(即order-service模块)的application.yml配置文件中配置连接池
-
SpringCloud2020版本之前
feign:httpclient:enabled: true # 开启feign对HttpClient的支持max-connections: 200 # 最大的连接数max-connections-per-route: 50 # 每个路径的最大连接数
-
SpringCloud2020版本之后
spring:cloud:openfeign:httpclient:enabled: truemax-connections: 200max-connections-per-route: 50
-
-
Step3: 打断点测试是否生效
-
Step3-1: 在
FeignClientFactoryBean
类中的loadBalance
方法中打断点 -
Step3-2: DEBUG运行服务消费者的启动类进行测试,前端发送请求后会自动跳转到该断点处,然后查看delegate属性值来判断是否设置成功
注意:测试时要保证服务提供者的启动类处于运行中
-
Spring Cloud2022版本之后或OpenFeign4.x版本之后
注意:
从Spring Cloud OpenFeign 4开始,Feign Apache HttpClient 4不再被支持,而使用Apache HttpClient 5代替。
-
Step1: 在服务消费者(即order-service模块)的pom文件中引入Apache的HttpClient依赖
<!--httpClient5的依赖--> <dependency><groupId>org.apache.httpcomponents.client5</groupId><artifactId>httpclient5</artifactId><version>5.3.1</version> </dependency> <!--feign-hc5依赖--> <dependency><groupId>io.github.openfeign</groupId><artifactId>feign-hc5</artifactId><!--若为SpringBoot2.x版本,则该依赖版本改为11.x--><version>13.5</version> </dependency>
-
Step2: 在服务消费者(即order-service模块)的application.yml配置文件中配置连接池
spring:cloud:openfeign:httpclient:hc5:enabled: true
-
Step3: 打断点测试是否生效
-
Step3-1: 找到
Client
类,然后找到该类中的execute
接口,单击该接口左侧的符号,然后找到对应的实现类FeignBlockingLoadBalancerClient
,进入到该实现类中 -
Step3-2: 在
FeignBlockingLoadBalancerClient
类的execute
方法内部打上断点,然后DEBUG运行服务消费者的启动类进行测试,前端发送请求后会自动跳转到该断点处,然后查看delegate属性值来判断是否设置成功注意:测试时要保证服务提供者的启动类处于运行中
-
OKHttp
-
Step1: 在服务消费者(即order-service模块)的pom文件中引入Apache的HttpClient依赖
<!--OK http 的依赖 --> <dependency><groupId>io.github.openfeign</groupId><artifactId>feign-okhttp</artifactId> </dependency>
-
Step2: 在服务消费者(即order-service模块)的application.yml配置文件中配置连接池
-
SpringCloud2020版本之前
feign:okhttp:enabled: true
-
SpringCloud2020版本之后
spring:cloud:openfeign:okhttp:enabled: true
-
-
打断点测试是否生效
SpringCloud2020版本之前 :断点测试可详见Apache HttpClient中 Spring Cloud2022版本之前或OpenFeign4.x版本之前 的断点测试步骤
SpringCloud2020版本之后的断点测试步骤如下:
-
Step3-1: 找到
Client
类,然后找到该类中的execute
接口,单击该接口左侧的符号,然后找到对应的实现类FeignBlockingLoadBalancerClient
,进入到该实现类中 -
Step3-2: 在
FeignBlockingLoadBalancerClient
类的execute
方法内部打上断点,然后DEBUG运行服务消费者的启动类进行测试,前端发送请求后会自动跳转到该断点处,然后查看delegate属性值来判断是否设置成功注意:测试时要保证服务提供者的启动类处于运行中
-
Feign最佳实践
最佳实践代码示例已上传至Gitee的分支feign-practice中,可自行下载
思路分析
服务消费者(即order-service模块)中关于服务提供者(即user-service模块)的Feign客户端(即
UserClient
接口)的代码如下
服务提供者(即user-service模块)中对应的
UserController
类的代码如下
分析服务消费者(即order-service模块)中的
UserClient
接口和服务提供者(即user-service模块)中的UserController
类的代码可知它们的代码非常相似,关系图如下
- 因此我们可以对其进行实践优化,共有两种方式:
- 继承方式:将一样的代码封装到接口中,然后通过继承来共享
- 抽取方式:将Feign的Client抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用
继承方式
-
原理:给服务消费者的FeignClient和服务提供者的controller类定义统一的API接口作为标准,然后让eignClient和controller类继承该接口
- 解释:给服务消费者(即order-service模块)中关于服务提供者(即user-service模块)的Feign客户端(即
UserClient
接口)以及服务提供者(即user-service模块)中对应的controller方法定义一个统一的父接口,然后服务提供者中的Feign客户端和服务消费者中的Controller都继承该接口
- 解释:给服务消费者(即order-service模块)中关于服务提供者(即user-service模块)的Feign客户端(即
-
优缺点
- 优点
- 简单且实现了代码共享
- 缺点
- 服务提供方与服务消费方紧耦合
- 参数列表中的注解映射并不会继承,因此Controller中必须再次声明方法、参数列表、注解
- 优点
-
Step1:
抽取方式
进一步分析:
在服务消费者(即order-service模块)中远程调用服务提供者(即user-service模块)中的相关controller类时,会在服务消费者(即order-service模块)内部创建一个Feign客户端来实现远程调用;假设现在有多个服务消费者来远程调用服务提供者(即user-service模块)中的相关controller类,就需要创建多个Feign客户端来实现远程调用,这样就会导致冗余度较高,更不用说在实际项目中了
因此我们可以将Feign客户端抽取成一个独立模块,并把Feign客户端中的方法所返回的实体类以及Feign的默认配置均抽取到这个独立模块中,供所有服务消费者使用。这样就解决了冗余度过高的问题
-
原理:将Feign的Client抽取为独立模块,并且把接口有关的POJO、Feign的默认配置都放到这个模块中,提供给所有消费者使用
- 解释:将UserClient、User、Feign的默认配置都抽取到一个feign-api包中,所有微服务引用该依赖包,即可直接使用
-
Step1: 在当前聚合工程(即父工程)fegin-demo下创建一个新的module,命名为feign-api
注意:该步骤可详见SpringCloud项目搭建快速入门
-
Step2: 在该feign-api模块的pom文件中引入feign的起步依赖(即feign的starter依赖)
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
-
Step3: 在feign-api模块的java包下创建at.guigu包,并在该包下创建feign包
-
Step4: 将服务消费者(即order-service模块)中的feign包、config包、po包以及包中的对应类均剪切到feign包下
-
剪切完成后,包结构如下图所示
-
-
Step5: 由于Feign客户端对应的接口剪切到feign-api模块下后,该接口(即
UserClient
)中使用到的User类的包就需要重新导入原因:原来的User类在at.guigu.po包下,现在剪切过来的User类在at.guigu.feign包下
在服务消费者(即order-service模块)中的相关操作步骤
-
Step1: 在服务消费者(即order-service模块)的pom文件中引入feign-api模块的依赖
<!--feign-api模块的依赖--> <dependency><groupId>cn.itcast</groupId><artifactId>feign-api</artifactId><version>1.0-SNAPSHOT</version> </dependency>
-
Step2: 由于
User
类现在不在服务消费者(即order-service模块)的po包下,而是在feign-api模块下,所以需要在使用到User类的类中导入feign-api模块的相关类 -
Step3: 由于在服务消费者(即order-service模块)的业务层的相关实现类
OrderServiceImpl
中调用了Feign客户端(即UserClient
接口),并且也使用了User
类,所以需要在该类中导入feign-api模块的相关类 -
Step4: 由于在服务消费者(即order-service模块)的启动类进行了Feign的全局配置,有因为Feign的配置类已剪切到feign-api模块下,所以需要在启动类中导入feign-api模块的相关配置类
-
Step5: 重新启动服务消费者(即order-service模块)的启动类进行测试
抽取方式相关问题解决
服务消费者(即order-service模块)的启动类被
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class)
注解修饰,由于该启动类在at.guigu包下,并且服务消费者(即order-service模块)的pom文件中引入了feign-api模块的依赖。所以该注解能够扫描到本模块和feign-spi模块在at.guigu包下的Feign客户端,从而可以在服务消费者(即order-service模块)的业务层相关实现类中成功依赖注入对应Feign客户端(即
UserClient
接口)的Bean由于feign-spi模块下的Feign客户端(即
UserClient
接口)在at.guigu.feign包下,所以能够扫描到
-
假设feign-spi模块下的Feign客户端不在at.guigu包下,而是在其它包下(比如
com.heima.feign
包),则运行服务消费者的启动类后会报错:Field userClient in at.guigu.service.OrderService required a bean of type 'com.heima.feign.clients.UserClient' that could not be found
,即找不到com.heima.feign.clients包下的bean(即包扫描问题),解决方法有两种 -
方法一:指定FeignClient所在包 在服务消费者的启动类中利用
@EnableFeignClients
注解中的basePackages
属性指定Feign应该扫描的包(即feign-api模块的Feign客户端所在包)package at.guigu;import at.guigu.feign.clients.UserClient; import at.guigu.feign.config.DefaultFeignConfiguration; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients;@Slf4j // 方式一:指定FeignClient所在包 @EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class, basePackages = "at.guigu.feign") @SpringBootApplication public class OrderApplication {public static void main(String[] args) {SpringApplication.run(OrderApplication.class, args);log.info("OrderApplication Running");} }
-
方法二:指定FeignClient字节码 在服务消费者的启动类中利用
@EnableFeignClients
注解中的clients
属性指定要加载的Feign客户端(即Client接口)package at.guigu;import at.guigu.feign.clients.UserClient; import at.guigu.feign.config.DefaultFeignConfiguration; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients;@Slf4j // 方式二:指定FeignClient字节码 @EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class, clients = {UserClient.class}) @SpringBootApplication public class OrderApplication {public static void main(String[] args) {SpringApplication.run(OrderApplication.class, args);log.info("OrderApplication Running");} }
网关路由
注意:本项目案例已上传至Gitee的分支gateway中,可自行下载
认识网关
-
网关就是网络的关口 ,是服务的守门神,是所有微服务的统一入口
- 数据在网络间传输,从一个网络传输到另一网络时就需要经过网关来做数据的路由和转发以及数据安全的校验。
-
更通俗的来讲,网关就像是以前园区传达室的大爷。
-
外面的人要想进入园区,必须经过大爷的认可,如果你是不怀好意的人,肯定被直接拦截。外面的人要传话或送信,要找大爷。大爷帮你带给目标人。
-
微服务网关就起到同样的作用。前端请求不能直接访问微服务,而是要请求网关:
-
网关可以做安全控制,也就是登录身份认证和权限校验,校验通过才放行
-
通过认证后,网关再根据请求判断应该访问哪个微服务,将请求转发过去
-
-
-
网关原理
- 前端请求到后端后首先会到达网关,网关首先会对用户请求进行身份认证和权限校验,两者都通过后,网关会将其请求放行
- 然后网关会通过服务路由和负载均衡来为其分配一个微服务,并将请求转发到指定微服务
- 在此同时,若前端请求过多时,则网关会根据下流的微服务所能接受的请求速度来进行放行请求,以此来避免服务压力过大导致系统崩溃
-
网关作用
- 权限控制:网关作为微服务入口,需要校验用户是是否有请求资格,如果没有则进行拦截。
- 路由和负载均衡:一切请求都必须先经过gateway,但网关不处理业务,而是根据某种规则,把请求转发到某个微服务,这个过程叫做路由。当然路由的目标服务有多个时,还需要做负载均衡。
- 限流:当请求流量过高时,在网关中按照下流的微服务能够接受的速度来放行请求,避免服务压力过大。
-
在SpringCloud当中,提供了两种网关实现方案:
-
Netflix Zuul:是基于Servlet实现,属于阻塞式编程,目前已经淘汰
-
SpringCloudGateway:是基于Spring的WebFlux技术,完全支持(或属于)响应式编程,吞吐能力更强,属于非阻塞式编程。具体可详见官方网站
-
快速入门
-
Step1: 在当前聚合工程(即父工程)fegin-demo下创建一个新的module,命名为gateway
注意:该步骤可详见SpringCloud项目搭建快速入门
-
Step2: 在该gateway模块的pom文件中引入相关依赖
-
Nacos服务注册与发现的依赖:spring-cloud-starter-alibaba-nacos-discovery
-
负载均衡依赖:spring-cloud-starter-loadbalancer
Nacos 自2021版本开始已经没有自带ribbon的负载均衡整合,所以就需要引入另一个支持的jar包loadbalancer来实现对
@LoadBalanced
注解以及LoadBalancer负载均衡的支持 -
网关依赖:spring-cloud-starter-gateway
-
初始完整pom文件代码如下
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>cn.itcast</groupId><artifactId>fegin-demo</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>gateway</artifactId><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><!--LoadBalancer依赖--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency><!--nacos客户端服务管理依赖(即Nacos服务注册与发现依赖)--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><!--gateway网关依赖--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency></dependencies> </project>
-
-
Step3: 在该gateway模块的java目录下创建at.guigu.gateway包,并在该包下创建gateway网关服务的启动类,代码如下
package at.guigu.gateway;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @Slf4j @SpringBootApplication public class GatewayApplication {public static void main(String[] args) {SpringApplication.run(GatewayApplication.class, args);log.info("GatewayApplication Running");} }
-
Step4: 右键源代码配置文件目录(即资源文件
resources
)→New
→File
,创建配置文件application.yml,代码如下:-
配置Nacos服务注册与发现的地址
-
配置路由,主要包括
-
路由id:路由的唯一标识
-
路由目标(uri):路由的目标地址,http代表固定地址,lb表示根据服务名负载均衡
-
路由断言(predicates):判断路由的规则,
-
路由过滤器(filters):对请求或响应做处理
-
server:port: 10010 spring:application:name: gatewaycloud:nacos:discovery:server-addr: localhost:8848 #Nacos地址enabled: truegateway:routes:- id: user-service # 自定义路由的唯一标识(此处自定义为模块名)uri: lb://userservice #路由的目标地址predicates: # 路由断言,判断请求是否符合指定规则- Path=/user/** # 路径断言,判断路径是否以/user开头;若是,则符合制定规则- id: order-serviceuri: lb://orderservicepredicates:- Path=/order/**main:web-application-type: reactive logging:level:# 配置指定包及其子包下的所有类的日志级别为debug---会输出大于等于该级别的日志信息at.guigu: DEBUG# 配置日志输出的时间戳格式pattern:dateformat: MM-dd HH:mm:ss:SSS
-
-
Step5: 运行该gateway模块的启动类
GatewayApplication
,然后利用PostMan测试gateway服务是否能够正常运行
可能出现的问题
-
运行gateway网关服务的启动类若报如下错误
原因:SpringCloudGateway属于非阻塞式响应编程;而SpringBoot默认是基于Servlet实现的,属于阻塞式编程
-
方法一: 在application.yml配置文件中设置
spring.main.web-application-type=reactive
使SpringBoot在启动时初始化一个响应式的Web应用环境,而不是默认的Servlet环境server:port: 10010 spring:application:name: gatewaycloud:nacos:discovery:server-addr: localhost:8848 #Nacos地址enabled: truegateway:routes:- id: user-service # 自定义路由的唯一标识(此处自定义为模块名)uri: lb://userservice #路由的目标地址predicates: # 路由断言,判断请求是否符合指定规则- Path=/user/** # 路径断言,判断路径是否以/user开头;若是,则符合制定规则- id: order-serviceuri: lb://orderservicepredicates:- Path=/order/**main:web-application-type: reactive logging:level:# 配置指定包及其子包下的所有类的日志级别为debug---会输出大于等于该级别的日志信息# 由于clients包在at.guigu包下,所以并未单独在logging.level下设置feigen包的日志级别at.guigu: DEBUG# 配置日志输出的时间戳格式pattern:dateformat: MM-dd HH:mm:ss:SSS
-
方法二: 排除spring-boot-starter-web dependency依赖
注意:由于该依赖是在聚合工程(即父工程)的pom文件中,且该聚合工程下的子模块user-service、order-service模块均需要使用该依赖,所以此处暂时无法排除该依赖,只能使用第一种方法
-
-
运行gateway网关服务的启动类若报如下错误
原因:未配置数据源相关的url
注意:gateway网关服务是不需要配置数据源的,但是 Spring Boot 自动配置机制 默认需要一个数据源配置,所以解决方式如下:
-
方法:在gateway服务的启动类
GatewayApplication
中,给@SpringBootApplication
注解添加exclude
属性且属性值为DataSourceAutoConfiguration.class
,以此来排除数据源自动配置package at.guigu.gateway;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;@Slf4j @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) public class GatewayApplication {public static void main(String[] args) {SpringApplication.run(GatewayApplication.class, args);log.info("GatewayApplication Running");} }
-
网关路由流程图
-
网关路由流程图解释
-
前端发送请求
http://127.0.0.1:10010/user/1
后会到达Gateway网关服务原因:Gateway服务的网关端口为10010,前端发送的请求的端口就是10010,所以前端请求一定会到Gateway网关服务中去
-
Gateway网关服务会根据路由断言来判断当前路径对应的微服务名,然后Gateway网关服务会从Nacos注册中心拉取对应服务的列表,若拉取下来的服务列表中只有一个则会直接发送到该服务中去
- 由于前端请求的路径为
/user/1
对应的路径断言为/user/**
,所以当前请求会被转发到userservice服务中去
- 由于前端请求的路径为
-
若对应的微服务有多个,则会通过负载均衡策略选择其中一个,然后将当前请求转发到对应的userservice服务中去
-
断言工厂(Route Predicate Factory)
-
predicates
:即路由断言,判断当前请求是否符合要求,若符合则转发到路由目标地址。代码示例如下:spring:cloud:gateway:routes:- id: user-service # 自定义路由的唯一标识(此处自定义为模块名)uri: lb://userservice #路由的目标地址predicates: # 路由断言,判断请求是否符合指定规则- Path=/user/** # 路径断言,判断路径是否以/user开头;若是,则符合制定规则- id: order-serviceuri: lb://orderservicepredicates:- Path=/order/**
注意:在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件
比如:Path=/user/**是按照路径匹配,该规则是由
org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory
类来处理的
-
在SpringCloudGateway中的断言工厂有十几个:
注意:只需掌握Path这种路由工程就可以了
名称 说明 示例 After 是某个时间点后的请求 - After=2037-01-20T17:42:47.789-07:00[America/Denver]
Before 是某个时间点之前的请求 - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai]
Between 是某两个时间点之前的请求 - Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver]
Cookie 请求必须包含某些cookie - Cookie=chocolate, ch.p
Header 请求必须包含某些header - Header=X-Request-Id, \d+
Host 请求必须是访问某个host(域名) - Host=**.somehost.org,**.anotherhost.org
Method 请求方式必须是指定方式 - Method=GET,POST
Path 请求路径必须符合指定规则 - Path=/red/{segment},/blue/**
Query 请求参数必须包含指定参数 - Query=name, Jack或者- Query=name
RemoteAddr 请求者的ip必须是指定范围 - RemoteAddr=192.168.1.1/24
Weight 权重处理 -
以上断言工厂的使用可详见官网示例,仿照即可,此处以After为例
spring:cloud:gateway:routes:- id: user-serviceuri: lb://userservicepredicates:- Path=/user/**- After=2035-01-27-T15:14:47.433+08:00[Asia/Shanghai]
- 解释:若前端发来的请求路径是
/user/**
且发送的请求时间在指定时区时间之后,此时才满足路由规则,Gateway网关服务才会将其发送到指定的服务中去;若不满足路由规则,则前端报错404
- 解释:若前端发来的请求路径是
路由过滤器GatewayFilter
-
GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理,如下图所示
-
Spring提供了31种不同的路由过滤器工厂,具体可详见官网。此处只进行部分示例
名称 说明 AddRequestHeader
给当前请求添加一个请求头 RemoveRequestHeader
移除请求中的一个请求头 AddResponseHeader
给响应结果中添加一个响应头 RemoveResponseHeader
从响应结果中移除有一个响应头 RequestRateLimiter
限制请求的流量 -
过滤器作用:对路由的请求或响应进行加工处理,比如添加请求头
-
配置在某个路由下的过滤器只对当前路由的请求生效;若想要对所有路由均生效,则需要使用defaultFilters
局部过滤器
此处以请求头过滤器
AddRequestHeader GatewayFilter Factory
来演示需求:给所有进入userservice服务的请求添加一个请求头:Truth=nihao
-
Step1: gateway网关服务的配置文件application.yml代码如下:
- 在自定义的路由user-service中配置请求头过滤器
server:port: 10010 spring:application:name: gatewaycloud:nacos:discovery:server-addr: localhost:8848 #Nacos地址enabled: truegateway:routes:- id: user-service # 自定义路由的唯一标识(此处自定义为模块名)uri: lb://userservice #路由的目标地址predicates: # 路由断言,判断请求是否符合指定规则- Path=/user/** # 路径断言,判断路径是否以/user开头;若是,则符合制定规则filters: # 配置指定路由的过滤器# 配置请求头过滤器---设置请求头Truth=nihao- AddRequestHeader=Truth, nihao- id: order-serviceuri: lb://orderservicepredicates:- Path=/order/**main:web-application-type: reactive logging:level:# 配置指定包及其子包下的所有类的日志级别为debug---会输出大于等于该级别的日志信息at.guigu: DEBUG# 配置日志输出的时间戳格式pattern:dateformat: MM-dd HH:mm:ss:SSS
-
Step2: 更改服务提供者(即user-service模块)的表现层
UserController
类中的代码,以便于测试请求头是否添加成功,UserController
类代码如下- Step2-1: 给
queryById
方法添加一个获取请求头信息的参数truth
- Step2-2: 给该参数添加
@RequestHeader
注解,并在该注解中利用value
指明要获取到的请求头的名称,同时将该注解中的required
设为false
,以此避免未配置请求头过滤器导致的获取请求头信息失败
package at.guigu.controller;import at.guigu.po.User; import at.guigu.service.IUserService; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*;@RestController @RequiredArgsConstructor @RequestMapping("/user") public class UserController {private final IUserService userService;@GetMapping("/{id}")public User queryById(@PathVariable("id") Long id,@RequestHeader(value = "Truth", required = false) String truth) {System.out.println("userservice服务中请求头truth值为:" + truth);return userService.getById(id);} }
- Step2-1: 给
-
Step3: 更改服务消费者(即order-service模块)的表现层
OrderController
类中的代码,以便于测试请求头是否添加成功,OrderController
类代码如下-
Step3-1: 给
queryOrderById
方法添加一个获取请求头信息的参数truth
-
Step3-2: 给该参数添加
@RequestHeader
注解,并在该注解中利用value
指明要获取到的请求头的名称,同时将该注解中的required
设为false
,以此避免未配置请求头过滤器导致的获取请求头信息失败package at.guigu.controller;import at.guigu.po.Order; import at.guigu.service.IOrderService; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*;@RestController @RequestMapping("order") @RequiredArgsConstructor public class OrderController {private final IOrderService orderService;@GetMapping("{orderId}")public Order queryOrderById(@PathVariable("orderId") Long orderId,@RequestHeader(value = "Truth", required = false) String truth) {System.out.println("orderservice服务中请求头truth值为:" + truth);// 根据id查询订单并返回return orderService.queryOrderById(orderId);} }
-
-
Step4: 重新启动Gateway网关服务以及服务提供者(即user-service模块)、服务消费者(即order-service模块)的启动类,然后利用PostMan进行测试
注意:
在以上测试中,前端请求是
localhost:10010/user/1
,此时后端控制台会显示出请求头信息。但如果前端请求是localhost:10010/order/101
呢?,此时结果如下图所示可知,虽然运行成功,但未能获取到请求头信息原因:
当前过滤器只写在了与userservice服务相关的路由下(即只写在了指定路由下),因此仅仅对访问userservice服务的请求有效。
全局路由的默认过滤器
-
若要对所有的路由都生效,则可以将过滤器工厂写到default下。此时gateway网关服务的配置文件application.yml代码如下:
server:port: 10010 spring:application:name: gatewaycloud:nacos:discovery:server-addr: localhost:8848 #Nacos地址enabled: truegateway:routes:- id: user-service # 自定义路由的唯一标识(此处自定义为模块名)uri: lb://userservice #路由的目标地址predicates: # 路由断言,判断请求是否符合指定规则- Path=/user/** # 路径断言,判断路径是否以/user开头;若是,则符合制定规则- id: order-serviceuri: lb://orderservicepredicates:- Path=/order/**default-filters: # 配置路由默认的过滤器# 配置请求头过滤器---设置请求头Truth=nihao- AddRequestHeader=Truth, nihaomain:web-application-type: reactive logging:level:# 配置指定包及其子包下的所有类的日志级别为debug---会输出大于等于该级别的日志信息at.guigu: DEBUG# 配置日志输出的时间戳格式pattern:dateformat: MM-dd HH:mm:ss:SSS
-
重新启动Gateway网关服务以及服务提供者(即user-service模块)、服务消费者(即order-service模块)的启动类,然后利用PostMan进行测试
-
前端请求是
localhost:10010/user/1
-
前端请求是
localhost:10010/order/101
-
全局过滤器GlobalFilter
Gateway网关服务虽然提供了31种路由过滤器,但是每一种过滤器的作用都是固定的,所以还存在较大局限性
-
作用:处理一切进入网关的请求和微服务响应,与路由过滤器
GatewayFilter
作用一样 -
与路由过滤器GatewayFilter的区别
- 路由过滤器GatewayFilter是通过配置文件定义的,处理逻辑是固定的
- 全局过滤器GlobalFilter需要自己手写代码实现
GlobalFilter
接口
-
该接口代码如下
public interface GlobalFilter {/*** 处理当前请求,有必要的话通过{@link GatewayFilterChain}将请求交给下一个过滤器处理** @param exchange 请求上下文,里面可以获取Request、Response等信息* @param chain 用来把请求委托给下一个过滤器 * @return {@code Mono<Void>} 返回标示当前过滤器业务结束*/Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain); }
- 在
filter
方法中编写自定义逻辑,可以实现下列功能:- 登录状态判断
- 权限校验
- 请求限流等
- 在
-
全局过滤器GlobalFilter实现步骤
- Step1:实现
GlobalFilter
接口 - Step2:添加@Order注解或实现
Order
接口 - Step3:编写处理逻辑
- Step1:实现
快速入门
本快速入门示例已上传至Gitee的分支global-demo中,可自行下载
需求:定义全局过滤器GlobalFilter来拦截请求,并判断请求的参数是否同时满足以下两个条件。若是则放行;反之则拦截
1.参数中是否有authorization
2.authorization参数值是否为admin
-
Step1: 在gateway模块的gateway包下创建一个globalfilter包,并在该包下创建一个实现
GlobalFilter
接口的实现类AuthorizeFilter
-
Step2: 重写
GlobalFilter
接口中的filter
方法,代码如下:- Step2-1: 获取请求对象
- Step2-2: 获取包含所有请求参数的Map集合
- Step2-3: 获取集合中第一个参数为authorization的值
- Step2-4: 校验,若校验通过则放行;反之则进行拦截处理
- Step2-5: 拦截处理:获取响应对象
- Step2-6: 拦截处理:设置状态码为HttpStatus.FORBIDDEN(即401),代表请求被拦截
- Step2-7: 拦截处理:结束处理
- Step2-8: 给该类添加
@Component
注解以及@Order
注解@Component
注解:使用在类上,用于实例化bean@Order
注解:用于设置过滤器的执行顺序。该注解中有一个value
属性,默认值为Integer.MAX_VALUE
;value
属性值越小,优先级越高。所以我们可以通过设置该注解的value
属性值来设置不同全局过滤器GlobalFilter的执行顺序。博主在此处将其设置为了-1
package at.guigu.gateway.globalfilter;import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.util.MultiValueMap; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @Order(value = -1) @Component public class AuthorizeFilter implements GlobalFilter {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 1 获取请求对象ServerHttpRequest request = exchange.getRequest();// 2 获取包含所有请求参数的Map集合MultiValueMap<String, String> params = request.getQueryParams();// 3 获取集合中第一个参数为authorization的值String auth = params.getFirst("authorization");// 4 校验if ("admin".equals(auth)) {// 放行return chain.filter(exchange);}// 5.拦截// 5.1 获取响应对象ServerHttpResponse response = exchange.getResponse();// 5.2 设置状态码为HttpStatus.FORBIDDEN(即403),代表请求被拦截response.setStatusCode(HttpStatus.FORBIDDEN);// 5.3 结束处理return response.setComplete();} }
注意:除了使用
@Order
注解来设置全局过滤器GlobalFilter的执行顺序外,还可以通过继承Ordered
接口并重写其中的getOrder
方法来实现,此时代码如下:package at.guigu.gateway.globalfilter;import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.util.MultiValueMap; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono;@Component public class AuthorizeFilter implements GlobalFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 1 获取请求对象ServerHttpRequest request = exchange.getRequest();// 2 获取包含所有请求参数的Map集合MultiValueMap<String, String> params = request.getQueryParams();// 3 获取集合中第一个参数为authorization的值String auth = params.getFirst("authorization");// 4 校验if ("admin".equals(auth)) {// 放行return chain.filter(exchange);}// 5.拦截// 5.1 获取响应对象ServerHttpResponse response = exchange.getResponse();// 5.2 设置状态码为HttpStatus.FORBIDDEN(即403),代表请求被拦截response.setStatusCode(HttpStatus.FORBIDDEN);// 5.3 结束处理return response.setComplete();}// 设置过滤器的执行顺序------等同于@Order注解@Overridepublic int getOrder() {return -1;} }
-
Step3: 重新运行gateway网关服务的启动类,然后进行测试
-
测试服务提供者(即user-service模块)
-
前端请求为
localhost:10010/user/1
------未添加指定的请求参数authorization=admin
-
前端请求为
localhost:10010/user/1?authorization=admin
------添加了指定的请求参数
-
-
测试服务消费者(即order-service模块)
-
前端请求为
localhost:10010/order/101
------未添加指定的请求参数authorization=admin
-
前端请求为
localhost:10010/order/101?authorization=admin
------添加了指定的请求参数
-
-
过滤器执行顺序
-
请求进入网关会碰到三类过滤器:
- 当前路由的过滤器(即GatewayFilter)
- 路由的默认过滤器(即DefaultFilter)
- 全局过滤器(即GlobalFilter)
前端请求路由后,Gateway网关服务会将三类过滤器合并到一个过滤器链(也就是一个List集合)中,排序后依次执行每个过滤器,如下图所示
-
排序的规则
-
每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前。
-
GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定
-
当前路由过滤器(即GatewayFilter)和路由默认过滤器(即DefaultFilter)的order值由Spring指定,默认是按照声明顺序从1递增。
如下示例代码所示,Truth=nihao的order值为1;Truth=haoa的order值为2。所以Truth=nihao的优先级高
spring:cloud:gateway:routes:- id: user-serviceuri: lb://userservicepredicates:- Path=/user/**default-filters: - AddRequestHeader=Truth, nihao- AddRequestHeader=Truth, haoa
-
当过滤器的order值一样时,会按照
defaultFilter > 路由过滤器 > GlobalFilter
的顺序执行。
-
跨域问题处理
-
域名不一致就是跨域,主要包括:
- 域名不同:
www.taobao.com
和www.taobao.org
和www.jd.com
和miaosha.jd.com
- 域名相同,端口不同:
localhost:8080
和localhost:8081
- 域名不同:
-
在我们的服务示例中,服务消费者(即order-service模块)、服务提供者(user-service模块)的端口分别为8080和8081,它们也是跨域。但是为什么没有产生跨域问题呢?
- 跨域问题指的是:浏览器 禁止请求的发起者与服务端发生 跨域ajax请求 ,请求被浏览器拦截的问题
- 而服务消费者调用服务提供者并未用到浏览器,所以也就未发生跨域问题
-
若直接通过浏览器进行跨域访问,则会产生跨域问题(报错如图所示),则网关服务处理跨域问题的解决方案是使用CORS,CORS内容具体可详见https://www.ruanyifeng.com/blog/2016/04/cors.html
-
在gateway网关服务的配置文件application.yml中配置如下内容
-
老版本配置
spring:cloud:gateway:globalcors: # 全局的跨域处理add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题(默认为false)corsConfigurations:'[/**]': # 配置要拦截哪些请求来进行跨域处理,此处代表拦截一切请求allowedOrigins: # 配置允许跨域请求的网站 - "http://localhost:8090"allowedMethods: # 配置允许的跨域ajax请求的方式- "GET"- "POST"- "DELETE"- "PUT"- "OPTIONS"allowedHeaders: "*" # 配置允许在请求中携带的头信息allowCredentials: true # 是否允许携带cookiemaxAge: 360000 # 配置跨域请求的有效期(单位:秒)
-
新版本配置
spring:cloud:gateway:globalcors: # 全局的跨域处理add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题(默认为false)cors-configurations:'[/**]': # 配置要拦截哪些请求来进行跨域处理,此处代表拦截一切请求allowed-origins:- "http://localhost:8090"allowed-methods: # 配置允许跨域请求的网站 - "GET"- "POST"- "DELETE"- "PUT"- "OPTIONS"allowed-headers: "*" # 配置允许在请求中携带的头信息allow-credentials: true # 是否允许携带cookiemax-age: 360000 # 配置跨域请求的有效期(单位:秒)
-
相关文章:
SpringCloud基础二(完结)
HTTP客户端Feign 在SpringCloud基础一中,我们利用RestTemplate结合服务注册与发现来发起远程调用的代码如下: String url "http://userservice/user/" order.getUserId(); User user restTemplate.getForObject(url, User.class);以上代码就…...
云原生时代,如何构建高效分布式监控系统
文章目录 一.监控现状二.Thanos原理分析SidecarQuerierStoreCompactor 三.Sidecar or ReceiverThanos Receiver工作原理 四.分布式运维架构 一.监控现状 Prometheus是CNCF基金会管理的一个开源监控项目,由于其良好的架构设计和完善的生态,迅速成为了监控…...
WordPress使用(1)
1. 概述 WordPress是一个开源博客框架,配合不同主题,可以有多种展现方式,博客、企业官网、CMS系统等,都可以很好的实现。 官网:博客工具、发布平台和内容管理系统 – WordPress.org China 简体中文,这里可…...
小白爬虫冒险之反“反爬”:无限debugger、禁用开发者工具、干扰控制台...(持续更新)
背景浅谈 小白踏足JS逆向领域也有一年了,对于逆向这个需求呢主要要求就是让我们去破解**“反爬机制”**,即反“反爬”,脚本处理层面一般都是decipher网站对request设置的cipher,比如破解一个DES/AES加密拿到key。这篇文章先不去谈…...
Time Constant | RC、RL 和 RLC 电路中的时间常数
注:本文为 “Time Constant” 相关文章合辑。 机翻,未校。 How To Find The Time Constant in RC and RL Circuits June 8, 2024 💡 Key learnings: 关键学习点: Time Constant Definition: The time constant (τ) is define…...
Python爬虫学习第三弹 —— Xpath 页面解析 实现无广百·度
早上好啊,大佬们。上回使用 Beautiful Soup 进行页面解析的内容是不是已经理解得十分透彻了~ 这回我们再来尝试使用另外一种页面解析,来重构上一期里写的那些代码。 讲完Xpath之后,小白兔会带大家解决上期里百度搜索的代码编写,保…...
JS 正则表达式 -- 分组【详解】含普通分组、命名分组、反向引用
普通分组 使用圆括号 () 来创建分组捕获匹配的内容,通过正则表达式匹配结果的数组来访问这些捕获的内容。 const str "Hello, World!"; const regex /(Hello), (World)!$/; const match str.match(regex);if (match) {console.log("完整匹配结果…...
Leetcode刷题-不定长滑动窗口
分享丨【题单】滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环) - 力扣(LeetCode) 3090 class Solution:def maximumLengthSubstring(self, s: str) -> int:c Counter()res 0rk -1for i in range(len(s)):i…...
【Rust自学】15.6. RefCell与内部可变性:“摆脱”安全性限制
题外话,这篇文章一共4050字,是截止到目前为止最长的文章,如果你能坚持读完并理解,那真的很强! 喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以…...
护眼好帮手:Windows显示器调节工具
在长时间使用电脑的过程中,显示器的亮度和色温对眼睛的舒适度有着重要影响。传统的显示器调节方式不仅操作繁琐,而且在低亮度下容易导致色彩失真。因此,今天我想为大家介绍一款适用于Windows系统的护眼工具,它可以帮助你轻松调节显…...
使用 OpenResty 构建高效的动态图片水印代理服务20250127
使用 OpenResty 构建高效的动态图片水印代理服务 在当今数字化的时代,图片在各种业务场景中广泛应用。为了保护版权、统一品牌形象,动态图片水印功能显得尤为重要。然而,直接在后端服务中集成水印功能,往往会带来代码复杂度增加、…...
36、【OS】【Nuttx】OSTest分析(2):环境变量测试
背景 2025.1.29 蛇年快乐! 接之前wiki 35、【OS】【Nuttx】OSTest分析(1):stdio测试(五) 已经分析完了第一个测试项,输入输出端口测试,接下来分析下环境变量测试,也比较…...
C++并发编程指南04
文章目录 共享数据的问题3.1.1 条件竞争双链表的例子条件竞争示例恶性条件竞争的特点 3.1.2 避免恶性条件竞争1. 使用互斥量保护共享数据结构2. 无锁编程3. 软件事务内存(STM) 总结互斥量与共享数据保护3.2.1 互斥量使用互斥量保护共享数据示例代码&…...
Java实现LRU缓存策略实战
实现LRU模型选择LRU缓存回收算法集成Google Guava(LRU缓存策略)插件Google Guava(LRU策略)缓存示例总结LRU(Least Recently Used,最近最少使用)缓存是一种常见的缓存淘汰策略。它的基本思想是优先保留最近被访问过的数据,淘汰最久未被访问的数据。这种策略的目的是为了…...
三个不推荐使用的线程池
线程池的种类 其实看似这么多的线程池,都离不开ThreadPoolExecutor去创建,只不过他们是简化一些参数 newFixedThreadPool 里面全是核心线程 有资源耗尽的风险,任务队列最大长度为Integer.MAX_VALUE,可能会堆积大量的请求ÿ…...
Golang 并发机制-1:Golang并发特性概述
并发是现代软件开发中的一个基本概念,它使程序能够同时执行多个任务,从而提高效率和响应能力。在本文中,我们将探讨并发性在现代软件开发中的重要性,并深入研究Go处理并发任务的独特方法。 并发的重要性 增强性能 并发在提高软…...
Flink中的时间和窗口
在批处理统计中,我们可以等待一批数据都到齐后,统一处理。但是在实时处理统计中,我们是来一条就得处理一条,那么我们怎么统计最近一段时间内的数据呢?引入“窗口”。 所谓的“窗口”,一般就是划定的一段时…...
Alfresco Content Services dockerCompose自动化部署详尽操作
Alfresco Content Services docker社区部署文档 Alfresco Content Services简介 官方说明书 https://support.hyland.com/r/Alfresco/Alfresco-Content-Services-Community-Edition/23.4/Alfresco-Content-Services-Community-Edition/Using/Content/Folder-rules/Defining-…...
吴恩达深度学习——深层神经网络
来自https://www.bilibili.com/video/BV1FT4y1E74V,仅为本人学习所用。 符号约定 对于该深层网络,有四层,包含三个隐藏层和一个输出层。 隐藏层中,第一层有五个单元、第二层有五个单元,第三层有三个单元。标记 l l l…...
【算法设计与分析】实验1:字符串匹配问题的算法设计与求解
目录 一、实验目的 二、实验环境 三、实验内容 四、核心代码 五、记录与处理 六、思考与总结 七、完整报告和成果文件提取链接 一、实验目的 给定一个文本,在该文本中查找并定位任意给定字符串。 1、深刻理解并掌握蛮力法的设计思想; 2、提高应用…...
C语言二级题解:查找字母以及其他字符个数、数字字符串转双精度值、二维数组上下三角区域数据对调
目录 一、程序填空题 --- 查找字母以及其他字符个数 题目 分析 二、程序修改 --- 数字字符串转双精度值 题目 分析 小数位字符串转数字 三、程序设计 --- 二维数组上下三角区域数据对调 题目 分析 前言 本文来讲解: 查找字母以及其他字符个数、数字字符串…...
Git进阶之旅:Git 配置信息 Config
Git 配置级别: 仓库级别:local [ 优先级最高 ]用户级别:global [ 优先级次之 ]系统级别:system [ 优先级最低 ] 配置文件位置: git 仓库级别对应的配置文件是当前仓库下的 .git/configgit 用户级别对应的配置文件时用…...
Qwen2-VL:在任何分辨率下增强视觉语言模型对世界的感知 (大型视觉模型 核心技术 分享)
摘要 我们推出了Qwen2-VL系列,这是对之前Qwen-VL模型的高级升级,重新定义了视觉处理中的常规预设分辨率方法。Qwen2-VL引入了Naive Dynamic Resolution机制,使模型能够动态地将不同分辨率的图像转换为不同的视觉令牌数量。这种方法允许模型生成更高效和准确的视觉表示,紧密…...
【C语言】在Windows上为可执行文件.exe添加自定义图标
本文详细介绍了在 Windows 环境下,如何为使用 GCC 编译器编译的 C程序 添加自定义图标,从而生成带有图标的 .exe 可执行文件。通过本文的指导,读者可以了解到所需的条件以及具体的操作步骤,使生成的程序更具专业性和个性化。 目录 1. 准备条件2. 具体步骤步骤 1: 准备资源文…...
记录 | Docker的windows版安装
目录 前言一、1.1 打开“启用或关闭Windows功能”1.2 安装“WSL”方式1:命令行下载方式2:离线包下载 二、Docker Desktop更新时间 前言 参考文章:Windows Subsystem for Linux——解决WSL更新速度慢的方案 参考视频:一个视频解决D…...
FortiOS 存在身份验证绕过导致命令执行漏洞(CVE-2024-55591)
免责声明: 本文旨在提供有关特定漏洞的深入信息,帮助用户充分了解潜在的安全风险。发布此信息的目的在于提升网络安全意识和推动技术进步,未经授权访问系统、网络或应用程序,可能会导致法律责任或严重后果。因此,作者不对读者基于本文内容所采取的任何行为承担责任。读者在…...
系统思考—心智模式
“我们的大脑对连贯性的渴望远胜于对准确性的追求。”—诺贝尔经济学得主丹尼尔卡尼曼 在面对复杂的决策时,我们往往更倾向于寻找那些能够迅速串联起来的信息,而非深入挖掘每一个细节的真实性。这种倾向在日常生活中或许能帮助我们迅速作出决策…...
【信息系统项目管理师-选择真题】2008上半年综合知识答案和详解
更多内容请见: 备考信息系统项目管理师-专栏介绍和目录 文章目录 【第1题】【第2题】【第3题】【第4题】【第5题】【第6题】【第7~8题】【第9题】【第10题】【第11题】【第12题】【第13题】【第14题】【第15题】【第16~20题】【第21题】【第22题】【第23题】【第24题】【第25题…...
深入理解三高架构:高可用性、高性能、高扩展性的最佳实践
引言 在现代互联网环境下,随着用户规模和业务需求的快速增长,系统架构的设计变得尤为重要。为了确保系统能够在高负载和复杂场景下稳定运行,"三高架构"(高可用性、高性能、高扩展性)成为技术架构设计中的核…...
从 SAP 功能顾问到解决方案架构师:破茧成蝶之路
目录 行业瞭望:架构师崭露头角 现状剖析:功能顾问的局限与机遇 能力跃迁:转型的核心要素 (一)专业深度的掘进 (二)集成能力的拓展 (三)知识广度的延伸 ࿰…...
《从因果关系的角度学习失真不变表示以用于图像恢复》学习笔记
paper:2303.06859 GitHub:lixinustc/Causal-IR-DIL: Distortion invariant feature learning for image restoration from a causality perspective 2023 CVPR 目录 摘要 1、介绍 1.1 图像修复任务 1.2 失真不变表示学习 1.3 因果效应估计的挑战…...
STM32 PWM驱动直流电机
接线图: 代码配置: 根据驱动舵机的代码来写,与舵机不同的是,这次的引脚接到了PA2上,所以需要改一下引脚以及改为OC3通道。 另外还需在配置两个GPIO引脚,来控制电机的旋转方向,这里连接到了PA4与…...
【Hadoop】Hadoop 概述
Hadoop 概述 Hadoop 是什么Hadoop 发展历史Hadoop 三大发行版本Hadoop 优势(4 高)Hadoop 组成(面试重点)HDFS 架构概述YARN 架构概述MapReduce 架构概述HDFS、YARN、MapReduce 三者关系 大数据技术生态体系 Hadoop 是什么 Hadoop…...
【仪器分析】FACTs-幅度
** 当然,这回是一篇没有插图的文章,但是有足够多的描述可以用来想象。 我拿这个系列当作前传试试水 引言。正弦信号可能会发生怎样的变化? ** 近日学FACTs,险些成为传函丁真, 如果从仪器角度考察正弦信号的测量&…...
deepseek R1的确不错,特别是深度思考模式
deepseek R1的确不错,特别是深度思考模式,每次都能自我反省改进。比如我让 它写文案: 【赛博朋克版程序员新春密码——2025我们来破局】 亲爱的代码骑士们: 当CtrlS的肌肉记忆遇上抢票插件,当Spring Boot的…...
Unity敌人逻辑笔记
写ai逻辑基本上都需要状态机。因为懒得手搓状态机,所以选择直接用动画状态机当逻辑状态机用。 架构设计 因为敌人的根节点已经有一个animator控制动画,只能增加一个子节点AI,给它加一个animator指向逻辑“动画”状态机。还有一个脚本&#…...
C++,STL 简介:历史、组成、优势
文章目录 引言一、STL 的历史STL 的核心组成三、STL 的核心优势四、结语进一步学习资源: 引言 C 是一门强大且灵活的编程语言,但其真正的魅力之一在于其标准库——尤其是标准模板库(Standard Template Library, STL)。STL 提供了…...
【事务管理】
目录 一. 介绍与操作二. Spring事务管理三. 事务四大特性 \quad 一. 介绍与操作 \quad \quad 二. Spring事务管理 \quad 推荐加在经常进行增删改的方法上 \quad 三. 事务四大特性 \quad ctrlaltt...
ERP革新:打破数据壁垒,重塑市场竞争
标题:ERP革新:打破数据壁垒,重塑市场竞争 文章信息摘要: Operator和Computer Use等工具通过模拟用户交互和自动化数据提取,绕过了传统ERP系统的API限制,打破了其数据护城河。这种技术革新降低了企业切换软…...
小阿卡纳牌
小阿卡纳牌 风:热湿 火:热干 水:冷湿 土:冷干 火风:温度相同,但是湿度不同,二人可能会在短期内十分热情,但是等待热情消退之后,会趋于平淡。 湿度相同、温度不同&#x…...
android的gradle
资料: GitHub - ChenSWD/CopyGradleInAction: 备份《Gradle IN Action》书中的源码,添加了部分注释 //github上一个开源项目,外加pdf书 Gradle User Manual gradle官网 讲的挺好的博客 Gradle之重新认识Gradle(项目结构、命令行、tas…...
时间轮:XXL-JOB 高效、精准定时任务调度实现思路分析
大家好,我是此林。 定时任务是我们项目中经常会遇到的一个场景。那么如果让我们手动来实现一个定时任务框架,我们会怎么做呢? 1. 基础实现:简单的线程池时间轮询 最直接的方式是创建一个定时任务线程池,用户每提交一…...
【愚公系列】《循序渐进Vue.js 3.x前端开发实践》029-组件的数据注入
标题详情作者简介愚公搬代码头衔华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,CSDN商业化专家,阿里云专家博主,阿里云签约作者,腾讯云优秀博主&…...
「 机器人 」扑翼飞行器控制的当前挑战与后续潜在研究方向
前言 在扑翼飞行器设计与控制方面,虽然已经取得了显著的进步,但在飞行时间、环境适应性、能量利用效率及模型精度等方面依旧存在亟待解决的挑战。以下内容概括了这些挑战和可能的改进路径。 1. 当前挑战 1.1 飞行时间短 (1)主要原因 能源存储有限(电池容量小)、驱动系…...
ICSE‘25 LLM Assistance for Memory Safety
不知道从什么时候开始,各大技术社区,技术群聊流行着 “用Rust重写!” ,放一张图(笑死… 这不, 随着大模型技术的流行,大家都在探索如何让大模型自动完成仓库级别(全程序)的代码重构,代码变换(Refactor&…...
使用 Docker 运行 Oracle Database 23ai Free 容器镜像并配置密码与数据持久化
使用 Docker 运行 Oracle Database 23ai Free 容器镜像并配置密码与数据持久化 前言环境准备运行 Oracle Database 23ai Free 容器基本命令参数说明示例 注意事项高级配置参数说明 总结 前言 Oracle Database 23ai Free 是 Oracle 提供的免费版数据库,基于 Oracle …...
在Linux系统上安装.NET
测试系统:openKylin(开放麒麟) 1.确定系统和架构信息: 打开终端(Ctrl Alt T),输入cat /etc/os-release查看系统版本相关信息。 输入uname -m查看系统架构。确保你的系统和架构符合.NET 的要求,如果架构…...
C++ unordered_map和unordered_set的使用,哈希表的实现
文章目录 unordered_map,unorder_set和map ,set的差异哈希表的实现概念直接定址法哈希冲突哈希冲突举个例子 负载因子将关键字转为整数哈希函数除法散列法/除留余数法 哈希冲突的解决方法开放定址法线性探测二次探测 开放定址法代码实现 哈希表的代码 un…...
星火大模型接入及文本生成HTTP流式、非流式接口(JAVA)
文章目录 一、接入星火大模型二、基于JAVA实现HTTP非流式接口1.配置2.接口实现(1)分析接口请求(2)代码实现 3.功能测试(1)测试对话功能(2)测试记住上下文功能 三、基于JAVA实现HTTP流…...
数据结构——二叉树——堆(1)
今天,我们来写一篇关于数据结构的二叉树的知识。 在学习真正的二叉树之前,我们必不可少的先了解一下二叉树的相关概念。 一:树的概念 树是一种非线性的数据结构,它是由n(n>0)个有限结点组成一个具有层…...