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:记录所有请求和响应的明细,包括头信息、请求体、元数据。

自定义日志级别方式一

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都继承该接口
  • 优缺点

    • 优点
      • 简单且实现了代码共享
    • 缺点
      • 服务提供方与服务消费方紧耦合
      • 参数列表中的注解映射并不会继承,因此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)→NewFile,创建配置文件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);}
    }
    
  • 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:编写处理逻辑

快速入门

本快速入门示例已上传至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_VALUEvalue属性值越小,优先级越高。所以我们可以通过设置该注解的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.comwww.taobao.orgwww.jd.commiaosha.jd.com
    • 域名相同,端口不同:localhost:8080localhost: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 # 配置跨域请求的有效期(单位:秒)
      

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

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

相关文章

[Java]泛型(一)泛型类

1. 什么是泛型类&#xff1f; 泛型类是指类中使用了占位符类型&#xff08;类型参数&#xff09;的类。通过使用泛型类&#xff0c;你可以编写可以处理多种数据类型的代码&#xff0c;而无需为每种类型编写单独的类。泛型类使得代码更具通用性和可重用性&#xff0c;同时可以保…

react native在windows环境搭建并使用脚手架新建工程

截止到2024-1-11&#xff0c;使用的主要软件的版本如下&#xff1a; 软件实体版本react-native0.77.0react18.3.1react-native-community/cli15.0.1Android Studio2022.3.1 Patch3Android SDKAndroid SDK Platform 34 35Android SDKAndroid SDK Tools 34 35Android SDKIntel x…

GESP2023年12月认证C++六级( 第三部分编程题(1)闯关游戏)

参考程序代码&#xff1a; #include <cstdio> #include <cstdlib> #include <cstring> #include <algorithm> #include <string> #include <map> #include <iostream> #include <cmath> using namespace std;const int N 10…

UE学习日志#15 C++笔记#1 基础复习

1.C20的import 看看梦开始的地方&#xff1a; import <iostream>;int main() {std::cout << "Hello World!\n"; } 经过不仔细观察发现梦开始的好像不太一样&#xff0c;这个import是C20的模块特性 如果是在VS里编写的话&#xff0c;要用这个功能需要新…

深入解析 C++17 中的 std::not_fn

文章目录 1. std::not_fn 的定义与目的2. 基本用法2.1 基本示例2.2 使用 Lambda 表达式2.3 与其他函数适配器的比较3. 在标准库中的应用3.1 结合标准库算法使用3.1.1 std::find_if 中的应用3.1.2 std::remove_if 中的应用3.1.3 其他标准库算法中的应用4. 高级技巧与最佳实践4.1…

AI大模型开发原理篇-2:语言模型雏形之词袋模型

基本概念 词袋模型&#xff08;Bag of Words&#xff0c;简称 BOW&#xff09;是自然语言处理和信息检索等领域中一种简单而常用的文本表示方法&#xff0c;它将文本看作是一组单词的集合&#xff0c;并忽略文本中的语法、词序等信息&#xff0c;仅关注每个词的出现频率。 文本…

创建前端项目的方法

目录 一、创建前端项目的方法 1.前提&#xff1a;安装Vue CLI 2.方式一&#xff1a;vue create项目名称 3.方式二&#xff1a;vue ui 二、Vue项目结构 三、修改Vue项目端口号的方法 一、创建前端项目的方法 1.前提&#xff1a;安装Vue CLI npm i vue/cli -g 2.方式一&…

INCOSE需求编写指南-附录 D: 交叉引用矩阵

附录 Appendix D: 交叉引用矩阵 Cross Reference Matrices Rules to Characteristics Cross Reference Matrix NRM Concepts and Activities to Characteristics Cross Reference Matrix Part 1 NRM Concepts and Activities to Characteristics Cross Reference Matrix Part…

案例研究丨浪潮云洲通过DataEase推进多维度数据可视化建设

浪潮云洲工业互联网有限公司&#xff08;以下简称为“浪潮云洲”&#xff09;成立于2018年&#xff0c;定位于工业数字基础设施建设商、具有国际影响力的工业互联网平台运营商、生产性互联网头部服务商。截至目前&#xff0c;浪潮云洲工业互联网平台连续五年入选跨行业跨领域工…

基于Python的人工智能患者风险评估预测模型构建与应用研究(下)

3.3 模型选择与训练 3.3.1 常见预测模型介绍 在构建患者风险评估模型时,选择合适的预测模型至关重要。不同的模型具有各自的优缺点和适用场景,需要根据医疗数据的特点、风险评估的目标以及计算资源等因素进行综合考虑。以下详细介绍几种常见的预测模型。 逻辑回归(Logisti…

灰色预测模型

特点&#xff1a; 利用少量、不完全的信息 预测的是指数型的数值 预测的是比较近的数据 灰色生成数列原理&#xff1a; 累加生成&#xff1a; 累减生成&#xff1a;通过累减生成还原成原始数列。 加权相邻生成&#xff1a;&#xff08;会更接近每月中旬&#xff0c;更推荐…

golang通过AutoMigrate方法自动创建table详解

一.AutoMigrate介绍 1.介绍 在 Go 语言中&#xff0c;GORM支持Migration特性&#xff0c;支持根据Go Struct结构自动生成对应的表结构,使用 GORM ORM 库的 AutoMigrate 方法可以自动创建数据库表&#xff0c;确保数据库结构与定义的模型结构一致。AutoMigrate 方法非常方便&am…

宝塔mysql数据库容量限制_宝塔数据库mysql-bin.000001占用磁盘空间过大

磁盘空间占用过多&#xff0c;排查后发现网站/www/wwwroot只占用7G&#xff0c;/www/server占用却高达8G&#xff0c;再深入排查发现/www/server/data目录下的mysql-bin.000001和mysql-bin.000002两个日志文件占去了1.5G空间。 百度后学到以下知识&#xff0c;做个记录。 mysql…

Case逢无意难休——深度解析JAVA中case穿透问题

Case逢无意难休——深度解析JAVA中case穿透问题~ 不作溢美之词&#xff0c;不作浮夸文章&#xff0c;此文与功名进取毫不相关也&#xff01;与大家共勉&#xff01;&#xff01; 更多文章&#xff1a;个人主页 系列文章&#xff1a;JAVA专栏 欢迎各位大佬来访哦~互三必回&#…

decison tree 决策树

熵 信息增益 信息增益描述的是在分叉过程中获得的熵减&#xff0c;信息增益即熵减。 熵减可以用来决定什么时候停止分叉&#xff0c;当熵减很小的时候你只是在不必要的增加树的深度&#xff0c;并且冒着过拟合的风险 决策树训练(构建)过程 离散值特征处理&#xff1a;One-Hot…

研发的立足之本到底是啥?

0 你的问题&#xff0c;我知道&#xff01; 本文深入T型图“竖线”的立足之本&#xff1a;专业技术 技术赋能业务能力。研发在学习投入精力最多&#xff0c;也误区最多。 某粉丝感发展遇到瓶颈&#xff0c;项目都会做&#xff0c;但觉无提升&#xff0c;想跳槽。于是&#x…

WPF基础 | 深入 WPF 事件机制:路由事件与自定义事件处理

WPF基础 | 深入 WPF 事件机制&#xff1a;路由事件与自定义事件处理 一、前言二、WPF 事件基础概念2.1 事件的定义与本质2.2 常见的 WPF 事件类型 三、路由事件3.1 路由事件的概念与原理3.2 路由事件的三个阶段3.3 路由事件的标识与注册3.4 常见的路由事件示例 四、自定义事件处…

DeepSeekMoE:迈向混合专家语言模型的终极专业化

一、结论写在前面 论文提出了MoE语言模型的DeepSeekMoE架构&#xff0c;目的是实现终极的专家专业化(expert specialization)。通过细粒度的专家分割和共享专家隔离&#xff0c;DeepSeekMoE相比主流的MoE架构实现了显著更高的专家专业化和性能。从较小的2B参数规模开始&#x…

机器人抓取与操作经典规划算法(深蓝)——2

1 经典规划算法 位姿估计&#xff1a;&#xff08;1&#xff09;相机系位姿 &#xff08;2&#xff09;机器人系位姿 抓取位姿&#xff1a;&#xff08;1&#xff09;抓取位姿计算 &#xff08;2&#xff09;抓取评估和优化 路径规划&#xff1a;&#xff08;1&#xff09;笛卡…

【Qt】06-对话框

对话框 前言一、模态和非模态对话框1.1 概念1.2 模态对话框1.2.1 代码QAction类 1.2.2 模态对话框运行分析 1.3 非模态对话框1.3.1 代码局部变量和成员变量setAttribute 类 1.3.2 现象解释 二、标准对话框2.1 提示对话框 QMessageBox2.1.1 现象及解释 2.2 问题对话框2.2.1 现象…