您现在的位置是:首页 >技术交流 >微服务-网关路由网站首页技术交流

微服务-网关路由

编程学委 2026-03-18 12:01:04
简介微服务-网关路由

微服务-网关路由

1.什么是网关?

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

在这里插入图片描述

我们可以把

网关------>小区的门卫大爷

各个微服务------>不同的住户

前端的请求------>询问的人

在大爷询问来访人的信息时就是身份验证,告诉询问的人他要找的人在几楼几号就是路由,找不到路大爷带路去就是转发

那么问题来了网关怎么知道所有的服务地址?别忘了我们之前讲过的注册中心,这里网关也作为一个微服务从注册中心拉取服务信息。

在这里插入图片描述

有什么好处?

  • 可以在网关这里进行身份验证并将信息向后传递,不需要每一个微服务都做一次处理
  • 可以不用将自己的微服务地址暴露出来是一种保护
  • 对前端来说只需要向网关发送请求,后端就和原来的单体架构一样没什么区别,开发体验更好了

网关的实现包括两种:

在这里插入图片描述

不过Spring Cloud Gateway性能更优,也是主流的网关组件。

2.网关路由-快速体验

注意:前端发送的请求到底由网关发送给那个服务很重要!所以路由的核心就是去配置路由的规则

1.实现步骤

  1. 创建新模块

  2. 引入网关依赖

    </dependencies>
       <!--网关-->
       <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-nacosdiscovery</artifactId>
       </dependency>
       <!--负载均衡-->
       <dependency>
           <groupId>org.springframework.cloud</groupId>
           <artifactId>spring-cloud-starter-loadbalancer</artifactId>
       </dependency>
    </dependencies>
    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
                <plugin>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
        </plugins>
    </build>
    
  3. 编写启动类

  4. 配置路由规则

    spring:
       cloud:
         gateway:
           routes:
            - id:item-service #路由规则id自定义,唯一,一般和你的服务名称相同
              url:lb://item-service #路由目标微服务,lb代表负载均衡
              predicates: #路由断言,判断请求是否符合规则,符合则路由到目标
                - Path=/items/** #以请求路径做判断,以/items开头的则符合
                #如果有多个路径,路径间用逗号分隔eg:- Path=/items/**,/xxxx/**
             - id:xxxx
               url:lb://xxxx-service
               predicates:
                 - Path=/xx/**     
                
    
  5. 启动服务测试(这里我给出我的测试结果)

    网关端口是8080

    在这里插入图片描述

    微服务端口是8081

    在这里插入图片描述

    可以发现路由成功转发,当你开启服务的多个实例,会发现实现了负载均衡。

3.网关路由-路由属性

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

  • id:路由的唯一标识
  • url:路由的目标地址
  • predicates:路由断言,判断请求是否符合当前路由
  • filters:路由过滤器,对请求或响应做特殊处理

1.路由断言

spring提供了12种基本的RoutePredicatesFactory实现:使用示例可在官网查看

在这里插入图片描述

2.路由过滤器

网关中提供了33种路由过滤器,每种过滤器都有独特的作用。

在这里插入图片描述

3.网关登录校验

1.如何在网关转发前做登录校验?

网关请求处理流程

在这里插入图片描述

从图中发现NettyRoutingFilter这个过滤器负责将请求转发到微服务,所以我们需要在这个过滤器之前自定义一个过滤器,在其中去编写登录校验(JWT校验)的逻辑(在pre阶段)。

2.如何将用户信息传递给微服务?

网关到微服务也是一次Http请求,所以将信息放在请求头中传递给微服务。

在这里插入图片描述

3. 如何在微服务之间传递用户信息?

基于OpenFeign发送Http请求,这里和网关的Http请求不同。

在这里插入图片描述

4.实现登录校验

1.自定义过滤器

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

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

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

    两者用法大致相同,在这里之后的以GlobalFilter示例

    在这里插入图片描述

@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.获取请求
        ServerHttpRequest request = exchange.getRequest();
        //2.过滤器业务处理
        HttpHeaders headers = request.getHeaders();
        System.out.println("请求头信息:" + headers);
        //放行
        return chain.filter(exchange);
    }

    //优先级,数字越小越优先执行,继承Ordered接口即可,保证在NettyRoutingFilter前执行
    @Override
    public int getOrder() {
        return 0;
    }
}
2.实现登录校验
//这里给出的是思路逻辑,具体业务结合实际情况处理
@RequiredArgsConstructor
@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
    private final AuthProperties authProperties;//这个是我自己不需要登录校验的路径,可以自己处理
    private final JwtTool jwtTool;//这里可以使用自己的jwt工具
    private final AntPathMatcher antPathMatcher = new AntPathMatcher();//Spring提供的匹配器
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.获取请求request
        ServerHttpRequest request = exchange.getRequest();
        //2.判断是否需要登录拦截
        //判断是否包含在排除路径中
        if(isExclude(request.getPath().toString())){
           //放行
           return chain.filter(exchange);
       }
        //3.获取token
        String token=null;
        List<String> headers = request.getHeaders().get("authorization");
     if(headers!=null &&!headers.isEmpty()) {
         token = headers.get(0);
     }
        //4.校验并解析token
         Long userId =null;
       try{
           userId = jwtTool.parseToken(token);
       }catch (UnauthorizedException e){
           //拦截设置响应码为401
           ServerHttpResponse response = exchange.getResponse();
           response.setStatusCode(HttpStatus.UNAUTHORIZED);
           return response.setComplete();
       }
        //5.传递用户信息
        System.out.println("用户id"+userId);
        //放行
        return chain.filter(exchange);
    }

    public boolean isExclude(String path) {
        List<String> excludePaths = authProperties.getExcludePaths();
        for(String excludePath : excludePaths){
            if(antPathMatcher.match(excludePath,path)){
                return true;
            }
        }
        return false;
    }
    @Override
    public int getOrder() {
        //优先级,数字越小越优先执行,继承Ordered接口即可,,保证在NettyRoutingFilter前执行
        return 0;
    }
}
3.网关传递信息

添加拦截器获取用户保存到ThreadLocal

在这里插入图片描述

步骤:

  • 完善网关向服务传递用户信息

    // 5. 传递用户信息
            String userInfo = userId.toString();
            // 使用 ServerWebExchange 的 mutate 方法创建一个新的请求对象
            // 在请求头中添加自定义的 "user-Info" 字段,值为用户信息
            ServerWebExchange newRequest = exchange.mutate()
                  .request(builder -> builder.header("user-Info", userInfo)) // 添加请求头
                  .build(); // 构建新的请求对象
            // 6. 放行请求
            // 将修改后的请求对象传递给过滤器链,继续后续的处理
            return chain.filter(newRequest);
    
  • 在微服务共同引入的工具模块中定义拦截器(比如common模块被cart,item…服务引入依赖)

    public class UserInfoIntercepter implements HandlerInterceptor {
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            //获取请求头信息
            String userInfo = request.getHeader("user-Info");
            //判断是否获取了用户,如果有则存入ThreadLocal
            if(StrUtil.isNotBlank(userInfo)) {
                UserContext.setUser(Long.valueOf(userInfo));
            }
            //放行
            return true;
        }
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            //清理
            UserContext.removeUser();
        }
    }
    

    这里我给出UserContext

    public class UserContext {
        private static final ThreadLocal<Long> tl = new ThreadLocal<>();
        /**
         * 保存当前登录用户信息到ThreadLocal
         * @param userId 用户id
         */
        public static void setUser(Long userId) {
            tl.set(userId);
        }
        /**
         * 获取当前登录用户信息
         * @return 用户id
         */
        public static Long getUser() {
            return tl.get();
        }
        /**
         * 移除当前登录用户信息
         */
        public static void removeUser(){
            tl.remove();
        }
    }
    
  • 让拦截器生效

    import com.xxxx.common.interceptors.UserInfoIntercepter;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    @Configuration
    public class MvcConfig implements WebMvcConfigurer {
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new UserInfoIntercepter());
        }
    }
    
  • 配置类需要被Spring扫描到,此时拦截器和其他服务不在一个包怎么办?

    在拦截器模块中的resources中配置spring.factories(老版本),将MvcConfig配置。

    在这里插入图片描述

  • 此时运行可能会报错(具体情况具体分析,我这里网关服务引入了Common模块(放了一些工具类和其他公共属性))

  • 注意:之前写网关服务是非阻塞式编程,底层并不是SpringMvc,而WebMvcConfigurer的底层是SpringMvc,所以会出现报错,怎么处理?

    提示:只要是微服务都有SpringMvc,只要是SpringMvc就都有SpringMvc核心API—>DispatcherServlet,所以我们只需要判断有没有这个类就可以了。

    import com.xxxx.common.interceptors.UserInfoIntercepter;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    @Configuration
    @ConditionalOnClass(DispatcherServlet.class)//这个注解判断某个类是否存在
    public class MvcConfig implements WebMvcConfigurer {
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new UserInfoIntercepter());
        }
    }
    

    更改后就可以正常运行了。

4.OpenFeign传递用户(服务间)

示例:创建完成订单,需要发送用户ID给其他服务来进行处理。

在这里插入图片描述

如图:这里给出示例的运行结果,购物车服务并未删除掉该用户的购物车信息。

在这里插入图片描述

这里大家可能会有疑惑,我们之前不是在common中写的拦截器用来保存用户ID到ThreadLocal中么,并且common被每个服务都引入,不是应该都可以拿到ID吗?

​ 这个就和ThreadLocal的特性有关了:在微服务架构中,前端请求通过网关验证身份后,用户ID存储在 ThreadLocal 中。然而,当一个服务向另一个服务发起请求时,被请求的服务无法获取到用户ID,这是因为 ThreadLocal 的存储是线程隔离的,不会在跨进程或跨线程的调用中自动传播。

解决:Openfeign中提供了一个拦截器接口,所有由Openfeign发起的请求都会先调用拦截器处理请求:

在这里插入图片描述

其中RequestTemplate类包含了一些方法可以帮助我们修改请求头:

在这里插入图片描述

步骤:

  • 在你的公共组件中加入RequestInterceptor
public class DefaultFeignConfig {

    @Bean
    public RequestInterceptor requestInterceptor() {
        return new RequestInterceptor(){
            @Override
            public void apply(RequestTemplate template) {
                Long userId = UserContext.getUser();
                template.header("user-info",userId.toString());
            }
        };
    }
}
注意DefaultFeignConfig需要在相应调用者服务的启动类上声明
eg:
@EnableFeignClients(defaultConfiguration = DefaultFeignConfig.class)

添加RequestInterceptor后

在这里插入图片描述

这里给出微服务登录的简单流程图

在这里插入图片描述

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。