SpringCloud之Gateway

1. 什么是Gateway

Spring Cloud Gateway是Spring公司基于Spring 5.0,Spring Boot 2.0 和 Project Reactor 等术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。它的目标是替代 Netflflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控和限流。

优点:

l 性能强劲:是第一代网关Zuul的1.6倍

l 功能强大:内置了很多实用的功能,例如转发、监控、限流等

l 设计优雅,容易扩展

缺点:

l 其实现依赖Netty与WebFlux,不是传统的Servlet编程模型,学习成本高

l 不能将其部署在Tomcat、Jetty等Servlet容器里,只能打成jar包执行

l 需要Spring Boot 2.0及以上的版本,才支持

 

2. Gateway--服务网关

大家都都知道在微服务架构中,一个系统会被拆分为很多个微服务。那么作为客户端要如何去调用这么多的微服务呢?如果没有网关的存在,我们只能在客户端记录每个微服务的地址,然后分别去调用。

 

这样的架构,会存在着诸多的问题:

l 客户端多次请求不同的微服务,增加客户端代码或配置编写的复杂性

l 认证复杂,每个服务都需要独立认证。

l 存在跨域请求,在一定场景下处理相对复杂。

上面的这些问题可以借助API网关来解决。

所谓的API网关,就是指系统的统一入口,它封装了应用程序的内部结构,为客户端提供统一服 务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、路由转发等等。 添加上API网关之后,系统的架构图变成了如下所示:

 

3. 使用Gateway

 单独创建GatewayMaven工程

【注:Gateway本质来说也是一个子服务模块】

【注:Gateway是和xw-serve、xw-comm服务模块同级】

在其pom文件加入依赖:

<!--加入gateway的依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

【注:还有在工程中创建启动类】

 

 3.1 进行Gateway配置

创建application.yml配置文件在其中配置子服务模块信息:

【注:可以是yml后缀的配置文件,因为用properties后缀的配置文件比较麻烦】

 

3.1.1 复杂配置设置:

server:
  port: 8851
spring:
  application:
    name: gateway    #服务名称
  # 配置子服务模块api
  #【注:可以配置多个子服务模块】
  cloud:
    gateway:
      routes:
        - id: order   # 路由的唯一标识,只要不重复都可以,如果不写默认会通过UUID产生,一般写成被路由的服务名称
          uri: http://localhost:8082/  # 被路由的地址(去向子服务模块的地址+端口)
          order: 1                     #表示优先级  数字越小优先级越高
          predicates:                  #断言: 执行路由的判断条件
            - Path=/product_serv/**
          filters:                     # 过滤器: 可以在请求前或请求后作一些手脚
            - StripPrefix=1

 

3.1.2 简单配置设置及Gateway前后端跨域:

server:
  port: 8851
spring:
  application:
    name: gateway
  # 配置api
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOriginPatterns: "*"
            allowedHeaders: "*"
            allowedMethods: "*"
            allowCredentials: true
#        default-filters:
#          - DedupeResponseHeader=Vary Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_FIRST
      discovery:
        locator:
          enabled: true

 【注:使用Gateway本身不能跟web依赖共存】

【注:使用Gateway只需要其本身配置跨域信息即可,子服务模块不需要配置跨域信息】

【注:一切都以通过Gateway去访问子服务模块】

 访问路径:ip地址:Gateway端口号/子服务模块微服务微服务名称/要访问的子服务路径

 3.2 自定义Gateway全局拦截器

创建com.aaa包下config包下AuthGlobalFilter 配置类:

com -> aaa -> congif -> AuthGlobalFilter

@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //获取请求头中的token值
        ServerHttpRequest request = exchange.getRequest();
        String token = request.getHeaders().getFirst("token");
        //判断token是否为空
        if (StringUtils.isEmpty(token)){
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                return exchange.getResponse().setComplete();
        }else {
            //不为空则校验token,校验通过将token存入Redis中
            try {
                Claims claims = JwtUtils.parseJWT(token);
                return chain.filter(exchange);
            }catch (Exception e){
                e.printStackTrace();
                throw new RuntimeException("token非法");
            }
        }
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

【注:此配置文件作为Gateway检测前端每次发起请求是否携带token】

【注:此配置文件后期可根据业务需求进行更改拦截条件】

 

4. 将heads注入容器获取token

注:【假如有需求需要使用到用户id或用户信息从token中解析取值】

该如何?

通过注入HTTPServletRequest 来获取request 对象:

@Resource
private HttpServletRequest request;
String token = request.getHeader("token");//从请求头中获取token
Claims claims = JwtUtils.parseJWT(token);//解析token
    // 将这个map转化为对象
    List token1 = (List) claims.get("token");
    Map o = (Map) token1.get(0);
    user user = BeanUtil.fillBeanWithMap(o, new user(), false);//将集合转换对象
    int id = user.getId();//获取到了用户id

 

5. Gateway白名单

通过在application.yml配置文件中定义白名单:

#配置Gateway白名单
whitename:
  /log/login    #要加入白名单的路径 
                #【注:因为是通过Gateway访问,所以路径格式应是:/子服务模块的服务名称/要访问的路径】

在Gateway配置类中进行获取白名单的值:AuthGlobalFilter 

@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    //获取application.yml中定义的白名单且将其注入到whitename
    @Value("${whitename}")
    private String whitename;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //设置白名单
        String[] whitenames = whitename.split(",");
        //  判断请求的路径
        // header
        // 获取请求头中的请求路径
        ServerHttpRequest requestss = exchange.getRequest();
        String path = requestss.getURI().getPath();
        System.out.println("请求的路径:" + path);
        if (ArrayUtil.contains(whitenames, path)) {
            // 放行:如果请求头中的请求路径在白名单中则直接放行
            //否则执行else逻辑
            return chain.filter(exchange);
        } else{
            //获取请求头中的token值
        ServerHttpRequest request = exchange.getRequest();
        String token = request.getHeaders().getFirst("token");
        //判断token是否为空
        if (StringUtils.isEmpty(token)){
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                return exchange.getResponse().setComplete();
        }else {
            //不为空则校验token,校验通过将token存入Redis中
            try {
                Claims claims = JwtUtils.parseJWT(token);
                return chain.filter(exchange);
            }catch (Exception e){
                e.printStackTrace();
                throw new RuntimeException("token非法");
            }
        }
        }
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

   

6. OpenFeign组件之间调度优化

在SpringCloud中,组件与组件之间通过OpenFeign调度实现

那么在我们固有的返回格式中

那么调用者从被调者组件获取数据时返回的是一个固有的result对象

那么我们还需要从result对象中提取数据还要进行对象转换才能获取到我们想要的值:

如下:

OpenFeign层:

@FeignClient(value = "prodect",fallback = ProdectService.class)
public interface OrderService {
     @RequestMapping("/sep/{sid}")
    result GetProdectMsg(@PathVariable Integer sid);
    
}

controller层:

//查询商品
@RequestMapping("/sep/{sid}")
public result GetProdectMsg(@PathVariable Integer sid){
    //将sid通过OpenFeign来传入被调者组件中,且将结果以result对象形式返回
    result forObject = orderService.GetProdectMsg(sid);
    Object data = forObject.getData();//获取result中的data数据
    ObjectMapper objectMapper = new ObjectMapper();
    Prodect prodect = objectMapper.convertValue(data, Prodect.class);//进行数据类别转换得到我们需要的值
    return new result(200,"商品查询成功!!",prodect);
}

 

如此繁琐的操作让我们不得不思考是否更可以进一步对OpenFeign优化:

其实,我们在组件与组件之间的调用时,通常会在被调用者中创建一个api层,专门用来用于组件之间的相互调用:

调用者:

OpenFeign层:

@FeignClient(value = "prodect",fallback = ProdectService.class)
public interface OrderService {

    @RequestMapping("/mypro/{sid}")
    Prodect GetProdectMsg(@PathVariable Integer sid);
    
    }

controller层:

//查询商品
@RequestMapping("/sep/{sid}")
public result GetProdectMsg(@PathVariable Integer sid){
    //将sid通过OpenFeign来传入被调者组件中,且将结果直接以prodect对象形式返回
    Prodect prodect = orderService.GetProdectMsg(sid);
    return new result(200,"商品查询成功!!",prodect);
}

 

被调用者:

 


 

以上便是SpringCloud之Gateway中的内容,如有漏缺请在下方留言告知,我会及时补充

posted @ 2023-09-25 19:28  九极致之术  阅读(87)  评论(0编辑  收藏  举报