当前位置: 首页 > article >正文

网关基础知识

1.网关路由

网关:就是网络的关口,负责请求的路由、转发、身份校验。

SpringCloud中网关的实现包括两种:

1.Spring Cloud Gateway 

        Spring官方出品

        基于WebFlux响应式编程

        无需调优即可获得优异性能

2.Netflix Zuul

        Netflix出品

        基于Servlet的阻塞式编程

        需要调优才能获得与SpringCloudGateway类似的性能

1.基础使用

1.创建新的模块作为网关的模块。

2.导入网关的依赖:(要用到网关以及nacos)

        <!--网关-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!--nacos discovery-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--负载均衡-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>

3.在模块下创建启动类

4.配置路由规则

server:
  port: 8080 #网关对应的端口
spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: 192.168.145.129:8848 #nacos服务注册
    gateway:
      routes:
        - id: item # 路由规则id,自定义,唯一
          uri: lb://item-service # 路由的目标服务,lb代表负载均衡,会从注册中心拉取服务列表
          predicates: # 路由断言,判断当前请求是否符合当前规则,符合则路由到目标服务
            - Path=/items/**,/search/** # 这里是以请求路径作为判断规则
        - id: cart
          uri: lb://cart-service
          predicates:
            - Path=/carts/**
        - id: user
          uri: lb://user-service
          predicates:
            - Path=/users/**,/addresses/**
        - id: trade
          uri: lb://trade-service
          predicates:
            - Path=/orders/**
        - id: pay
          uri: lb://pay-service
          predicates:
            - Path=/pay-orders/**

2.路由属性

网关路由对应的Java类型是RouteDefinition,其中常见的属性有:

        lid:路由唯一标示

        luri:路由目标地址

        lpredicates:路由断言,判断请求是否符合当前路由。

        lfilters:路由过滤器,对请求或响应做特殊处理。

2.网关登录校验

问题:请求发到微服务的每个项目中,不可能每一个项目都做登录校验,因此要在网关统一进行登录校验。

1.自定义过滤器

过滤器执行是有顺序的,执行pre过程后,请求访问完在进行post过程。

网关过滤器有两种,分别是:

        GatewayFilter:路由过滤器,作用于任意指定的路由;默认不生效,要配置到路由后生效。

        GlobalFilter:全局过滤器,作用范围是所有路由;声明后自动生效。

GlobalFilter使用:

自定义GlobalFilter直接实现GlobalFilter即可,而且无法设置动态参数:

@Component
public class PrintAnyGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 编写过滤器逻辑
        ServerHttpRequest request = exchange.getRequest();
        HttpHeaders headers = request.getHeaders();
        System.out.println("请求头是"+headers);
        // 放行
        // return chain.filter(exchange);
        // 拦截
        ServerHttpResponse response = exchange.getResponse();
        response.setRawStatusCode(401);
        return response.setComplete();
    }

    @Override
    public int getOrder() {
        // 过滤器执行顺序,值越小,优先级越高
        return 0;
    }
}

GatewayFilter使用:

自定义GatewayFilter不是直接实现GatewayFilter,而是实现AbstractGatewayFilterFactory。最简单的方式是这样的:

@Component
public class PrintAnyGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {
    @Override
    public GatewayFilter apply(Object config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                // 获取请求
                ServerHttpRequest request = exchange.getRequest();
                // 编写过滤器逻辑
                System.out.println("过滤器执行了");
                // 放行
                return chain.filter(exchange);
            }
        };
    }
}

注意:该类的名称一定要以GatewayFilterFactory为后缀!

然后在yaml配置中这样使用:

spring:
  cloud:
    gateway:
      default-filters:
            - PrintAny # 此处直接以自定义的GatewayFilterFactory类名称前缀类声明过滤器

另外,这种过滤器还可以支持动态配置参数,不过实现起来比较复杂,示例:


@Component
public class PrintAnyGatewayFilterFactory // 父类泛型是内部类的Config类型
                extends AbstractGatewayFilterFactory<PrintAnyGatewayFilterFactory.Config> {

    @Override
    public GatewayFilter apply(Config config) {
        // OrderedGatewayFilter是GatewayFilter的子类,包含两个参数:
        // - GatewayFilter:过滤器
        // - int order值:值越小,过滤器执行优先级越高
        return new OrderedGatewayFilter(new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                // 获取config值
                String a = config.getA();
                String b = config.getB();
                String c = config.getC();
                // 编写过滤器逻辑
                System.out.println("a = " + a);
                System.out.println("b = " + b);
                System.out.println("c = " + c);
                // 放行
                return chain.filter(exchange);
            }
        }, 100);
    }

    // 自定义配置属性,成员变量名称很重要,下面会用到
    @Data
    static class Config{
        private String a;
        private String b;
        private String c;
    }
    // 将变量名称依次返回,顺序很重要,将来读取参数时需要按顺序获取
    @Override
    public List<String> shortcutFieldOrder() {
        return List.of("a", "b", "c");
    }
        // 返回当前配置类的类型,也就是内部的Config
    @Override
    public Class<Config> getConfigClass() {
        return Config.class;
    }

}

然后在yaml文件中使用:

spring:
  cloud:
    gateway:
      default-filters:
            - PrintAny=1,2,3 # 注意,这里多个参数以","隔开,将来会按照shortcutFieldOrder()方法返回的参数顺序依次复制

上面这种配置方式参数必须严格按照shortcutFieldOrder()方法的返回参数名顺序来赋值。

还有一种用法,无需按照这个顺序,就是手动指定参数名:

spring:
  cloud:
    gateway:
      default-filters:
            - name: PrintAny
              args: # 手动指定参数名,无需按照参数顺序
                a: 1
                b: 2
                c: 3

2.实现登录校验

整体思路:微服务的登录校验要在网关统一做拦截处理,在网关中解析jwt令牌判断请求能否转发并得到userid,利用exchange.mutate()函数将userid添加到请求头中转发到微服务中,在common模块中设置拦截器在请求头中获取userid并存入线程中,如此便可在项目中用userid查询数据。要在微服务中传递userid,可以用feign.RequestInterceptor实现将userid添加到请求头中发送给另一个微服务,此微服务解析出userid即可。

登录校验过滤器

@Component
@RequiredArgsConstructor
public class AutoGlobalFilter implements GlobalFilter, Ordered {

    private final AuthProperties authProperties;
    private final JwtTool jwtTool;
    //匹配器,匹配路径是否相符
    private final AntPathMatcher antPathMatcher=new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //获取请求头
        ServerHttpRequest request = exchange.getRequest();
        //判断是否要拦截
        if (isexclude(request.getPath().toString())) {
            //放行
            return chain.filter(exchange);
        }
        System.out.println("判断之后");
        //获取token
        String token = null;
        Long userid = null;
        List<String> list = request.getHeaders().get("authorization");
        if (list != null && !list.isEmpty()) {
            token = list.get(0);
        }
        //解析token
        try {
            userid = jwtTool.parseToken(token);
        } catch (Exception e) {
            //拦截,设置响应码为401
            ServerHttpResponse response = exchange.getResponse();
            //两种方式设置响应码为401
            //response.setRawStatusCode(401);
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }
        //传递用户信息
        String useridString = userid.toString();
        ServerWebExchange serverWebExchange = exchange.mutate()
                .request(builder -> builder.header("user-info", useridString))
                .build();
        //放行
        return chain.filter(serverWebExchange);
    }

    private boolean isexclude(String path) {
        for (String excludePath : authProperties.getExcludePaths()) {
            if (antPathMatcher.match(excludePath,path)){
                System.out.println("路径放行"+excludePath);
                return true;
            }
        }
        return false;
    }

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

AntPathMatcher是spring提供的一个类,可以检验路径是否匹配

3.网关传递用户信息

1.在登录校验中获取到token中的userid,添加到发送到微服务中的请求头中

String useridString = userid.toString();
ServerWebExchange serverWebExchange = exchange.mutate()
         .request(builder -> builder.header("user-info", useridString))
         .build();

2.在微服务中接收userid

在controller中获取userid,代码如下:

    @ApiOperation("查询购物车列表")
    @GetMapping
    public List<CartVO> queryMyCarts(@RequestHeader(value = "user-info",required = false)String userinfo){
        System.out.println("用户id是"+userinfo);
        return cartService.queryMyCarts();
    }

问题:要在每个cintroller中获取,太繁琐,因此要在拦截器中统一获取

在拦截器中获取userid

网关从token中获取到userd后,把它存入到了请求头中,当微服务接收到请求头后,解析出userid,存入线程之中,之后项目即可在线程中获取到userid。

public class interceptors implements HandlerInterceptor{
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //获取用户登录信息
        String userinfo = request.getHeader("user-info");
        //判断是否获取了信息,若获取则存入线程
        if (StrUtil.isNotBlank(userinfo)){
            UserContext.setUser(Long.valueOf(userinfo));
        }
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //清理用户
        UserContext.removeUser();
    }
}

ps:

1.因为此代码编写在common中,微服务引用的common包,因此作为配置类的拦截器无法被微服务spring扫描到。解决办法是在resources目录下创建文件META-INF/spring.factories,如图

在此文件下编写如下代码

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.hmall.common.config.MyBatisConfig,\
  com.hmall.common.config.webinterceptorconfig,\
  com.hmall.common.config.JsonConfig

当别的项目引用common模块时这些配置类即可配别的项目扫描并自动装配

2.要让common在网关不生效,在微服务生效,在注册拦截器的配置类上添加如下注解:

@ConditionalOnClass(DispatcherServlet.class)

@ConditionalOnClass标识在@Configuration类上,只有存在@ConditionalOnClass中value/name配置的类该Configuration类才会生效,而微服务是mvc框架,网关不是,因此上述代码可以解决问题。

4.openfeign传递用户信息

在openfeign中添加userid到请求头,要借助Feign中提供的一个拦截器接口:

feign.RequestInterceptor,实现apply方法,利用RequestTemplate类来添加请求头,从线程中获

取userid,将用户信息保存到请求头中。代码如下:

    @Bean
    public RequestInterceptor requestInterceptor(){
        return new RequestInterceptor() {
            @Override
            public void apply(RequestTemplate requestTemplate) {
                Long userid = UserContext.getUser();
                if (userid!=null){
                    requestTemplate.header("user-info",userid.toString());
                }
            }
        };
    }

保留问题:RequestInterceptor怎么作用于特定的openfeign请求?

3.配置管理

1.配置共享

2.配置热更新

3.动态路由


http://www.kler.cn/news/318554.html

相关文章:

  • 线性判别分析(LDA)中求协方差矩阵示例
  • 配置文件--UmiJs
  • 用Flutter几年了,Flutter每个版本有什么区别?
  • 深入理解前端拖拽:从基础实现到事件冒泡与委托的应用【面试真题】
  • MySQL Performance Schema 详解及运行时配置优化
  • mac-m1安装nvm,docker,miniconda
  • 【shell脚本5】Shell脚本学习--条件控制
  • MyBatis与 Springboot 的集成
  • 【Webpack】使用 Webpack 和 LocalStorage 实现静态资源的离线缓存
  • ubuntu24.04 怎么调整swap分区的大小,调整为16G
  • Spark 任务与 Spark Streaming 任务的差异详解
  • Java毕业设计 基于SpringBoot和Vue自习室管理系统
  • Mybatis-为什么使用Mybatis,它存在哪些优点和缺点?
  • 【后端开发】JavaEE初阶—线程安全问题与加锁原理(超详解)
  • 专业学习|随机规划概观(内涵、分类以及例题分析)
  • Java基础|多线程:多线程分页拉取
  • Python画笔案例-054 绘制流光溢彩动画
  • windows C++-指定特定的计划程序策略
  • unix中如何查询和修改进程的资源限制
  • 2024年中国研究生数学建模竞赛B题 (WLAN组网吞吐量机理建模+决策树 完整建模文章)
  • 基于物联网技术的智能运动检测仪设计(微信小程序)(230)
  • 从零开始,Docker进阶之路(二):Docker安装
  • Leetcode面试经典150题-39.组合总和
  • AMD-9950X(至尊旗舰)对比I9性能如何?孰强孰弱
  • 点云与Open3D
  • PCL 用八叉树方法压缩点云
  • vue中使用exceljs和file-saver插件实现纯前端表格导出Excel(支持样式配置,多级表头)
  • MySQL程序
  • 观后感:《中国数据库前世今生》——时代变迁中的数据库崛起
  • 个性化大语言模型:PPlug——让AI更懂你