微服务架构-网关

目录

1.网关简述

2.网关登陆校验

​编辑

2.1自定义过滤器

2.2实现登录校验

2.3网关传递用户

[2.3.1 请求头接收](#2.3.1 请求头接收)

[2.3.2 在公共类编写拦截器​](#2.3.2 在公共类编写拦截器)

定义拦截器

配置拦截器

自动扫描配置

[2.4 OpenFeign传递用户](#2.4 OpenFeign传递用户)

3.配置管理

3.1配置共享

[3.1.1 添加配置到Nacos](#3.1.1 添加配置到Nacos)

[3.1.2 拉取共享配置](#3.1.2 拉取共享配置)

[3.2 配置热更新](#3.2 配置热更新)

[3.3 动态路由](#3.3 动态路由)


1.网关简述

  1. 客户端 只认网关地址 http://localhost:8080/items

    后面有多少微服务、换不换 IP 它都不用知道。

  2. 网关 收到请求后:

    • 注册中心 拉取当前可用的 item-service 实例列表(服务发现)

    • 身份校验(JWT/登录态/签名)

    • 内置 负载均衡器 挑一台健康实例

    • 路由转发 把请求发过去

  3. 微服务 启动时会把自身地址 注册 到注册中心,并持续 心跳 保活;

    挂掉或网络抖动时注册中心会把它踢出列表,网关就不会再转发流量给它。

维度 Netflix Zuul Spring Cloud Gateway
出身 Netflix 老牌套件 Spring 官方继任者
技术栈 基于 Servlet 2.x,阻塞式(BIO) 基于 WebFlux + Netty,响应式(NIO)
性能 需要调优才能追平 Gateway 不调优也原生高吞吐、低延迟
维护状态 Netflix 已停止维护 Zuul 1,Spring Cloud 2020 起移除 持续迭代,官方主推

2.网关登陆校验

2.1自定义过滤器

自定义登录拦截器:实现全局过滤器接口GlobalFilter 和 排序接口Ordered

"从 exchange 拿请求 → 做业务 → chain.filter(exchange)同一个 exchange 交给下一个过滤器"。

在 Spring Cloud Gateway 里,没有传统的 HttpServletRequest / HttpServletResponse ,而是把一次 HTTP 交互(请求 + 响应 + 上下文)整体封装成一个 ServerWebExchange

  1. ServerWebExchange exchange

    • exchange.getRequest() → 得到 ServerHttpRequest(URI、Header、Body)

    • exchange.getResponse() → 得到 ServerHttpResponse(Status、Header、Body)

    • exchange.getAttributes() → 自定义 KV 上下文(放 traceId、用户ID 等)

      一句话:大容器里装着"请求对象 + 响应对象 + 公共行李"

  2. GatewayFilterChain chain

    责任链模式,每个过滤器必须调 chain.filter(exchange) 才能把"包裹"交给下一个;

    不写这行 = 请求在此中断,网关直接返回当前响应(常用于鉴权失败、限流拒绝)

"下一个"是谁 ≠ 只看你自己的 getOrder(),而是看整个网关里 所有过滤器的 getOrder() 值从小到大排好的顺序表

Spring Cloud Gateway 启动时会一次性把 全局过滤器order 升序排成一条链,你调用 chain.filter(exchange) 只是 把指针往后移一位,并不会动态挑过滤器。

2.2实现登录校验

java 复制代码
@Component
@RequiredArgsConstructor
@EnableConfigurationProperties(AuthProperties.class)
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    private final JwtTool jwtTool;

    private final AuthProperties authProperties;

    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    @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 (!CollUtils.isEmpty(headers)) {
            token = headers.get(0);
        }
        // 4.校验并解析token
        Long userId = null;
        try {
            userId = jwtTool.parseToken(token);
        } catch (UnauthorizedException e) {
            // 如果无效,拦截
            ServerHttpResponse response = exchange.getResponse();
            response.setRawStatusCode(401);
            return response.setComplete();
        }

        //5.传递用户信息
        String userInfo= userId.toString();
        ServerWebExchange ex=exchange.mutate()
                .request(b->b.header("user", userInfo))
                .build();
        // 6.放行
        return chain.filter(ex);
    }

    private boolean isExclude(String antPath) {
        // 如果排除路径列表为空,直接返回false
        if (authProperties.getExcludePaths() == null || authProperties.getExcludePaths().isEmpty()) {
            return false;
        }
        
        for (String pathPattern : authProperties.getExcludePaths()) {
            if (pathPattern == null || pathPattern.isEmpty()) {
                continue;
            }
            // 使用 AntPathMatcher 进行路径匹配
            if(antPathMatcher.match(pathPattern, antPath)){
                return true;
            }
        }
        return false;
    }

    @Override
    public int getOrder() {
        // 设置较高的优先级,确保在CORS过滤器之后执行
        return 0;
    }
}

1.获取request

2.判断是否需要做登录拦截

3.获取token

4.校验并解析token

5.传递用户信息

6.放行

2.3网关传递用户

在网关层中,将token解析完后写入请求头。

此时网关对微服务发送请求时,请求头携带了用户信息。

2.3.1 请求头接收

网关使用 exchange.mutate() 创建一个新的 ServerWebExchange 实例

通过 request(b -> b.header("user", userInfo)) 在请求头中添加一个名为 "user" 的字段

将用户ID字符串作为该请求头的值
使用 @RequestHeader(value="user", required=false) 注解标记参数

value="user" 指定从请求头中名为 "user" 的字段获取值

required=false 表示该请求头不是必需的

2.3.2 在公共类编写拦截器

定义拦截器
java 复制代码
public class UserInfoInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.获取请求头中的用户信息
        String userInfo = request.getHeader("user-info");
        // 2.判断是否为空
        if (StrUtil.isNotBlank(userInfo)) {
            // 不为空,保存到ThreadLocal
                UserContext.setUser(Long.valueOf(userInfo));
        }
        // 3.放行
        return true;
    }

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

实现了 preHandle 方法用于在请求前设置用户信息到 UserContext

实现了 afterCompletion 方法用于在请求完成后清理 UserContext 中的用户信息

配置拦截器
java 复制代码
@Configuration
@ConditionalOnClass(DispatcherServlet.class)
public class MvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new UserInfoInterceptor());
    }
}

1. InterceptorRegistry

  • Spring MVC 的拦截器注册中心

  • 提供链式 API 配置拦截器

  • 底层维护一个拦截器列表,按注册顺序执行

2. addInterceptor() 方法

当前实现的局限性

复制代码
// 当前实现 - 简单但不够完善
registry.addInterceptor(new UserInfoInterceptor());

完整配置应该包含

复制代码
registry.addInterceptor(new UserInfoInterceptor())
    .addPathPatterns("/**")              // 拦截所有路径
    .excludePathPatterns(                // 排除不需要拦截的路径
        "/auth/login",
        "/public/**",
        "/swagger-ui/**",
        "/v3/api-docs/**",
        "/error"
    )
    .order(1);                          // 设置执行顺序
自动扫描配置
java 复制代码
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.hmall.common.config.MyBatisConfig,\
  com.hmall.common.config.MvcConfig,\
  com.hmall.common.config.JsonConfig
组件 作用 类比
MvcConfig 定义拦截器的行为 菜谱:怎么做菜
spring.factories 告诉Spring Boot加载这个配置 菜单:有哪些菜可用
@ConditionalOnClass 决定是否真的执行配置 食材检查:有食材才做菜
复制代码
1. spring.factories 告诉 Spring Boot:"我有这些自动配置类"
2. Spring Boot 检查条件注解(如 @ConditionalOnClass)
3. 条件满足 → 实例化 MvcConfig
4. MvcConfig.addInterceptors() 被调用
5. 拦截器注册到 Spring MVC

没有 spring.factories :再好的配置也不会被加载 → 拦截器不生效
没有 @ConditionalOnClass :可能在不支持 Web 的环境报错
两者结合:实现"智能的、条件化的自动配置"

2.4 OpenFeign传递用户

  1. 网关统一登录

    用户只在 API Gateway 做一次登录(JWT/Session),网关解析出 userId。

  2. 下游服务无需再次鉴权

    业务服务(cart、order...)不直连登录中心 ,只要知道"当前是谁"即可。

    → 必须把 userId 从网关带到每一个服务

  3. Feign 调用同样需要用户

    服务 A → 服务 B 的 OpenFeign 调用也要带用户,否则会丢失上下文。

  4. 线程安全 & 可清理

    一次请求可能在 Tomcat 线程池里切换,需要线程级变量,并在结束后立即清理,防止内存泄漏。

3.配置管理

3.1配置共享

3.1.1 添加配置到Nacos

3.1.2 拉取共享配置

由于启动时拉去Nacos配置比读取application.yml文件早,所以对于Nacos地址的配置放在

bootstrap.yml文件中。

3.2 配置热更新

启动会自动根据 微服务名称+项目profile(可选)+文件后缀 加载Nacos中相关配置

3.3 动态路由

相关推荐
xqqxqxxq3 小时前
Java 集合框架核心用法与实战技术笔记
java·笔记·python
一起养小猫3 小时前
LeetCode100天Day3-判断子序列与汇总区间
java·数据结构·算法·leetcode
山峰哥4 小时前
数据库性能优化实战:从工程架构到SQL调优的深度解析
大数据·数据库·oracle·性能优化·架构·深度优先
程序媛徐师姐4 小时前
Java基于SSM的社会救助信息管理系统,附源码+文档说明
java·社会救助信息管理系统·java社会救助信息管理系统·ssm社会救助信息管理系统·社会救助·java社会救助信息管理·java社会救助管理系统
爱笑的眼睛114 小时前
深度解析现代OCR系统:从算法原理到高可用工程实践
java·人工智能·python·ai
武子康4 小时前
Java-207 RabbitMQ Direct 交换器路由:RoutingKey 精确匹配、队列多绑定与日志分流实战
java·消息队列·rabbitmq·erlang·ruby·java-rabbitmq
2501_916766544 小时前
idea多模块项目运行设置
java·intellij-idea
Knight_AL4 小时前
CMS vs G1 GC 写屏障:拦截时机与漏标的根本原因
java·jvm·算法
陈震_4 小时前
《字节外包二面凉经》
java·字节外包