每日学习Java之一万个为什么(待完善)

发布于:2025-05-09 ⋅ 阅读:(18) ⋅ 点赞:(0)

文章目录

SpringMVC复习

SpringCloud 带动了 SpringBoot,SpringBoot成就了SpringCloud。从微软热词的反馈中可以直观感受到它们两的热度是一致的。

微服务本身是一种架构风格。相比于单体架构,扩展性更高,上限更高。单体架构稳定,成本低。


@Import

在 Spring 框架中,@Import 注解是一个核心机制,用于将外部类、配置类或动态注册的 Bean 导入到 Spring 应用上下文中。如果一个注解本身带有 @Import 注解(即作为元注解使用),它的作用通常是封装配置逻辑,简化其他注解的使用。以下从多个角度解析 @Import 的作用和导入的类模板的角色:

1.1 导入配置类
  • 功能:将其他 @Configuration 类导入到当前配置类中,实现模块化配置。
  • 示例
    @Configuration
    @Import({DataConfig.class, SecurityConfig.class})
    public class AppConfig {
        // AppConfig 中会包含 DataConfig 和 SecurityConfig 中定义的 Bean
    }
    
  • 技术原理
    • @Import 会将指定的类注册为 Spring 容器中的 Bean。
    • 如果目标类是 @Configuration,则其 @Bean 方法也会被处理。
1.2 导入普通类
  • 功能:将普通类(未标注 @Component 等注解的类)注册为 Spring 容器中的 Bean。
  • 示例
    @Configuration
    @Import(MyService.class)
    public class AppConfig {
        // MyService 会被注册为 Bean,Bean 名称为全限定类名
    }
    
  • 注意事项
    • 默认情况下,Bean 名称为类的全限定名(如 com.example.MyService)。
    • 如果需要自定义 Bean 名称,需通过 @Bean@Component 注解。
1.3 动态导入类(ImportSelector
  • 功能:通过实现 ImportSelector 接口,在运行时动态决定导入哪些类。
  • 示例
    public class MyImportSelector implements ImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            return new String[] { "com.example.MyDynamicBean" };
        }
    }
    
    @Configuration
    @Import(MyImportSelector.class)
    public class AppConfig {
        // AppConfig 会动态导入 MyDynamicBean
    }
    
  • 技术原理
    • selectImports 方法返回的类名数组会被注册为 Spring 容器中的 Bean。
    • 适用于根据条件(如环境变量)动态加载不同配置。
1.4 自定义 Bean 注册(ImportBeanDefinitionRegistrar
  • 功能:通过实现 ImportBeanDefinitionRegistrar 接口,手动注册 Bean。
  • 示例
    public class MyRegistrar implements ImportBeanDefinitionRegistrar {
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(MyCustomBean.class);
            registry.registerBeanDefinition("myCustomBean", beanDefinition);
        }
    }
    
    @Configuration
    @Import(MyRegistrar.class)
    public class AppConfig {
        // AppConfig 会手动注册 MyCustomBean
    }
    
  • 技术原理
    • 直接操作 BeanDefinitionRegistry,灵活控制 Bean 的注册逻辑。
    • 适用于需要深度定制 Bean 创建场景(如动态代理、AOP)。

2. @Import 作为元注解的作用

如果一个注解本身带有 @Import,则它可以通过封装配置逻辑,简化其他注解的使用。例如:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MyImportSelector.class)  // 将 @MyAnnotation 标注的类自动导入 MyImportSelector
public @interface MyAnnotation {
}
  • 作用

    • 使用 @MyAnnotation 注解的类会自动触发 MyImportSelector 的执行。
    • 将复杂的配置逻辑封装到元注解中,提升代码复用性。
  • 示例

    @MyAnnotation
    @Configuration
    public class MyConfig {
        // 自动导入 MyImportSelector 指定的类
    }
    

3. 导入的类模板角色

导入的类模板在 Spring 容器中扮演不同的角色,具体取决于其类型:

3.1 配置类(@Configuration
  • 角色:定义 Bean 的创建逻辑(通过 @Bean 方法)。
  • 示例
    @Configuration
    public class DataConfig {
        @Bean
        public DataSource dataSource() {
            return new HikariDataSource();  // 注册 DataSource Bean
        }
    }
    
3.2 普通类
  • 角色:直接注册为 Spring 容器中的 Bean。
  • 示例
    public class MyService {
        public void doSomething() {
            // ...
        }
    }
    
3.3 ImportSelector 实现类
  • 角色:动态决定导入哪些类。
  • 示例
    public class EnvironmentBasedImportSelector implements ImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata metadata) {
            if (Environment.isProduction()) {
                return new String[] { "com.example.ProductionConfig" };
            } else {
                return new String[] { "com.example.DevConfig" };
            }
        }
    }
    
3.4 ImportBeanDefinitionRegistrar 实现类
  • 角色:手动注册 Bean,支持复杂逻辑。
  • 示例
    public class CustomBeanRegistrar implements ImportBeanDefinitionRegistrar {
        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            // 动态创建 BeanDefinition 并注册
            registry.registerBeanDefinition("customBean", new RootBeanDefinition(CustomBean.class));
        }
    }
    

4. @Import@ComponentScan 的区别

功能 @ComponentScan @Import
作用 扫描并注册包路径下的组件(如 @Component 显式导入特定类或动态注册类
适用场景 自动发现组件(推荐用于通用组件) 精确控制配置(推荐用于模块化或第三方集成)
灵活性 依赖包路径,灵活性较低 完全灵活,支持动态逻辑

5. 实际应用中的典型场景

  1. 模块化配置

    • 将数据库、安全、缓存等配置拆分为独立的 @Configuration 类,通过 @Import 组合。
    @Configuration
    @Import({DatabaseConfig.class, SecurityConfig.class})
    public class AppConfig {}
    
  2. 集成第三方库

    • 通过 @Import 导入第三方库的配置类,无需修改原始代码。
    @Configuration
    @Import(ThirdPartyLibraryConfig.class)
    public class MyConfig {}
    
  3. 环境隔离

    • 使用 ImportSelector 根据环境变量动态加载不同配置。
    @Configuration
    @Import(EnvironmentBasedImportSelector.class)
    public class AppConfig {}
    
  4. 自定义 Bean 注册

    • 通过 ImportBeanDefinitionRegistrar 注册动态生成的 Bean(如代理对象)。
    @Configuration
    @Import(MyBeanRegistrar.class)
    public class AppConfig {}
    

6. 总结

  • @Import 的核心作用:显式导入类或动态注册 Bean,实现模块化配置和灵活扩展。
  • 导入的类模板角色
    • 配置类:定义 Bean 的创建逻辑。
    • 普通类:直接注册为容器管理的 Bean。
    • 动态选择类:通过 ImportSelector 动态决定导入哪些类。
    • 手动注册类:通过 ImportBeanDefinitionRegistrar 精确控制 Bean 的注册。
  • 最佳实践
    • 对通用组件使用 @ComponentScan
    • 对模块化配置或第三方集成使用 @Import
    • 复杂场景下结合 ImportSelectorImportBeanDefinitionRegistrar 实现动态配置。

通过合理使用 @Import,可以显著提升 Spring 应用的可维护性和扩展性。

自定义SpringMVC

WebMvcAutoConfigurer

WebMvcConfigurer

Servlet容器

Redis 与 MySQL数据不一致

写操作不一致
在这里插入图片描述
读写不一致
在这里插入图片描述

Redis中的布隆过滤器

微服务组件复习

SpringCloud-Alibaba-Nacos

服务注册

导入依赖后自动注册,需要添加应用名称

服务发现

需要拉取注册中心的服务实例
在这里插入图片描述

配置中心

将公共配置放置到nacos中,由项目的bootstrap自己拉取


OpenFeign

在这里插入图片描述

Feign客户端发请求的大致流程

Feign客户端发送请求的完整流程可以分为以下几个阶段,每个阶段由不同的组件或模块负责完成:


1. 创建代理对象并收集参数

  • 责任方FeignClientFactoryBeanFeign 的动态代理机制
  • 流程
    1. 代理对象创建
      • 通过 @FeignClient 注解定义的接口在 Spring 容器启动时被 FeignClientFactoryBean 扫描。
      • FeignClientFactoryBean 使用 Java 动态代理为接口生成代理对象(ReflectiveFeign),代理对象负责拦截接口方法调用。
    2. 参数收集
      • 当调用接口方法时,动态代理会捕获方法参数(如 @PathVariable@RequestParam 等注解标注的参数),并收集到方法上下文中。

2. 整理参数并组合 URI

  • 责任方ContractEncoder
  • 流程
    1. 注解解析
      • Contract(默认 SpringMvcContract)解析接口方法上的注解(如 @GetMapping@PostMapping@PathVariable 等),生成请求模板(RequestTemplate),包括 HTTP 方法、路径、Header 等。
    2. 参数编码
      • Encoder(默认 SpringEncoder)将方法参数编码到请求模板中:
        • @PathVariable:替换路径中的占位符(如 /user/{id} 替换为 /user/123)。
        • @RequestParam:添加查询参数(如 ?id=123)。
        • @RequestBody:序列化对象为 JSON/表单数据并填充到请求体中。

3. 根据服务名拉取实例并发送请求(第一次发起某个请求的时候,也就比较慢(懒加载开启))

  • 责任方LoadBalancerClient
  • 流程
    1. 服务实例选择
      • 如果接口未显式指定 URL(如 @FeignClient(name = "service-b")),LoadBalancer(默认 RibbonSpring Cloud LoadBalancer)会从注册中心(如 Nacos、Eureka)获取目标服务的所有实例。
      • LoadBalancer 根据配置的策略(如轮询、随机)选择一个实例,确定最终的请求地址(如 http://service-b:8080)。
    2. 请求发送
      • Client(默认 HttpURLConnection,可替换为 OkHttpApache HttpClient)将 RequestTemplate 转换为实际的 HTTP 请求(如 GET http://service-b:8080/user/123)并发送。
    3. 拦截器处理
      • 在请求发送前,RequestInterceptor(如添加认证 Token)会修改请求头或内容。
      • 在请求失败或响应返回后,Retryer(默认重试机制)决定是否重试请求。

4. 接收响应并处理结果

  • 责任方DecoderErrorDecoder
  • 流程
    1. 响应解码
      • Decoder(默认 SpringDecoder)将 HTTP 响应体(如 JSON)反序列化为 Java 对象,匹配方法的返回值类型。
    2. 异常处理
      • ErrorDecoder 处理 HTTP 错误状态码(如 404、500),抛出业务异常或触发熔断逻辑(如 Hystrix/Sentinel 的降级)。
    3. 日志记录
      • Logger(默认 NoOpLogger)记录请求和响应的详细信息(如 URL、参数、耗时),用于调试和监控。

关键组件总结

阶段 责任方 核心功能
代理对象创建 FeignClientFactoryBean 扫描 @FeignClient 接口,生成动态代理对象。
参数收集与注解解析 Contract(如 SpringMvcContract 解析注解生成请求模板,Encoder 编码参数到请求中。
服务实例选择 LoadBalancer(Ribbon/LoadBalancer) 根据服务名从注册中心拉取实例,并选择目标实例。
请求发送 ClientHttpURLConnection 等) 将请求模板转换为 HTTP 请求并发送。
响应处理与异常处理 DecoderErrorDecoder 反序列化响应体,处理 HTTP 错误,触发熔断降级。
日志与拦截器 LoggerRequestInterceptor 记录请求日志,添加认证 Token、分布式追踪 ID 等。

补充说明

  1. 动态代理的灵活性
    Feign 通过 ReflectiveFeign 实现动态代理,开发者无需手动编写 HTTP 调用逻辑,只需关注接口定义。
  2. 负载均衡的可扩展性
    负载均衡策略可通过 IRule(Ribbon)或 LoadBalancer(Spring Cloud LoadBalancer)自定义,支持权重、区域隔离等策略。
  3. 性能优化
    默认的 HttpURLConnection 性能较差,建议替换为连接池实现(如 OkHttp)以提高吞吐量。
  4. 熔断与降级
    集成 Hystrix 或 Sentinel 后,Feign 可在服务不可用时自动触发降级逻辑(通过 fallback 配置)。

注解

@EnableFeignClients
@FeignClient

SpringCloud-Gateway

在这里插入图片描述

网关处理请求的流程

Spring Cloud Gateway 处理请求的流程可以分为以下几个阶段,每个阶段由特定的组件负责:


1. 请求分发(DispatcherHandler)

负责人DispatcherHandler
作用

  • 作为网关的入口,负责接收所有 HTTP 请求。
  • 通过 HandlerMapping(如 RoutePredicateHandlerMapping)将请求分发到对应的处理器。
    流程
  • DispatcherHandler 调用 handle(ServerWebExchange exchange) 方法,依次遍历所有 HandlerMapping 实现类,找到能处理当前请求的 Handler
  • 如果未找到匹配的处理器,返回错误响应(如 404)。

代码示例

public class DispatcherHandler implements WebHandler {
    @Override
    public Mono<Void> handle(ServerWebExchange exchange) {
        return Flux.fromIterable(this.handlerMappings)
                .concatMap(mapping -> mapping.getHandler(exchange))
                .next()
                .switchIfEmpty(Mono.error(HANDLER_NOT_FOUND_EXCEPTION))
                .flatMap(handler -> invokeHandler(exchange, handler))
                .flatMap(result -> handleResult(exchange, result));
    }
}

2. 路由匹配(RoutePredicateHandlerMapping)

负责人RoutePredicateHandlerMapping
作用

  • 根据 Predicates(断言)匹配请求到对应的路由规则(Route)。
  • RouteLocator 获取所有路由配置,逐个测试断言是否满足。

流程

  1. 通过 RouteLocator 获取所有路由规则(如 YAML 配置或代码定义的 RouteDefinition)。
  2. 使用断言(如 Path=/api/**)判断请求是否符合路由条件。
  3. 返回第一个匹配的 Route

代码示例

public Route route(ServerWebExchange exchange) {
    return this.routeLocator.getRoutes()
            .filter(route -> route.getPredicate().test(exchange))
            .next()
            .block();
}

3. 过滤器链处理(FilteringWebHandler + GatewayFilterChain)

负责人FilteringWebHandlerGatewayFilterChain
作用

  • 执行过滤器链(GatewayFilter),对请求和响应进行预处理或后处理。
  • 过滤器分为 Pre(前置)Post(后置) 阶段。

流程

  1. Pre 过滤器:在请求转发前执行(如鉴权、日志记录)。
    • 示例:AddRequestHeader 添加请求头,RequestRateLimiter 限流。
  2. 路由转发:将请求转发到目标服务(见下一阶段)。
  3. Post 过滤器:在响应返回前执行(如修改响应头、记录日志)。
    • 示例:AddResponseHeader 添加响应头,NettyWriteResponseFilter 写入响应。

代码示例

public Mono<Void> filter(ServerWebExchange exchange) {
    return this.filterChain.filter(exchange)
            .then(Mono.fromRunnable(() -> {
                // 后置处理逻辑
            }));
}

4. 服务发现与负载均衡

负责人DiscoveryClientLoadBalancer(如 ReactorLoadBalancer
作用

  • 服务发现:通过 DiscoveryClient(如 Nacos、Eureka)获取目标服务的可用实例列表。
  • 负载均衡:根据配置的策略(如轮询、随机)选择一个实例。

流程

  1. DiscoveryClient 从注册中心拉取目标服务的实例列表。
  2. LoadBalancer 根据策略选择一个实例(如 RoundRobinRule)。
  3. 构建目标 URI(如 http://192.168.1.10:8080)。

代码示例

public URI selectInstance(String serviceId) {
    List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
    ServiceInstance instance = loadBalancer.choose(instances);
    return instance.getUri();
}

5. 请求转发(WebClient)

负责人WebClient(基于 Netty 的非阻塞 HTTP 客户端)
作用

  • 将请求转发到目标服务实例,并返回响应。
  • 支持异步、非阻塞的请求处理(基于 Reactor 模型)。

流程

  1. 构建 ClientRequest,复制原始请求的 headers、body 等信息。
  2. 使用 WebClient 发送请求到目标 URI。
  3. 将目标服务的响应返回给客户端。

代码示例

public Mono<Void> forward(ServerWebExchange exchange, URI url) {
    ClientRequest clientRequest = ClientRequest.create(
        exchange.getRequest().getMethod(), url)
        .headers(headers -> headers.addAll(exchange.getRequest().getHeaders()))
        .build();
    return webClient.exchange(clientRequest);
}

6. 响应返回

负责人NettyWriteResponseFilter(默认的 Post 过滤器)
作用

  • 将目标服务的响应写回客户端。
  • 在 Post 阶段处理响应数据(如添加自定义头、记录日志)。

流程

  1. 目标服务返回响应体后,NettyWriteResponseFilter 将响应写入客户端。
  2. 执行其他 Post 过滤器(如日志记录)。

总结:完整流程图

Client 请求
  ↓
DispatcherHandler(分发请求)
  ↓
RoutePredicateHandlerMapping(路由匹配)
  ↓
FilteringWebHandler + GatewayFilterChain(过滤器链处理)
  ↓
DiscoveryClient(服务发现) + LoadBalancer(负载均衡)
  ↓
WebClient(请求转发)
  ↓
NettyWriteResponseFilter(响应返回)
  ↓
Client 响应

关键组件职责对照表

阶段 负责人 核心功能
请求分发 DispatcherHandler 接收请求并匹配处理器。
路由匹配 RoutePredicateHandlerMapping 根据断言规则匹配路由。
过滤器链处理 GatewayFilterChain 执行 Pre/Post 过滤器(如鉴权、限流、日志)。
服务发现 DiscoveryClient 从注册中心获取目标服务的实例列表。
负载均衡 LoadBalancer 根据策略选择一个服务实例。
请求转发 WebClient 非阻塞式转发请求到目标服务实例。
响应返回 NettyWriteResponseFilter 将目标服务的响应写回客户端,并执行 Post 过滤器逻辑。

网关配置

以下是对 Gateway 配置 的详细解析,涵盖 sentinelmain 配置项的功能、作用及技术原理:


1. sentinel 配置解析

1.1 eager: true
  • 作用:取消 Sentinel 控制台(Dashboard)的懒加载。
  • 默认行为:Sentinel 控制台默认在首次请求时初始化(懒加载),eager: true 会强制在应用启动时主动连接控制台。
  • 适用场景
    • 确保应用启动后能立即与 Sentinel 控制台通信。
    • 避免因懒加载导致的首次请求延迟或连接失败问题。

1.2 transport.dashboard
transport:
  dashboard: 101.42.185.156:8858
  • 作用:指定 Sentinel 控制台的地址和端口。
  • 技术原理
    • 应用启动后,会通过 101.42.185.156:8858 向 Sentinel 控制台发送心跳、监控数据(如流量、QPS)。
    • 控制台可基于这些数据进行实时监控和规则推送。
  • 注意事项
    • 确保 IP 和端口可访问(需检查防火墙、网络策略)。
    • 控制台服务需已启动(如使用 Docker 或独立部署)。

1.3 datasource.ds1 配置
datasource:
  ds1:
    nacos:
      server-addr: 101.42.185.156:8848
      dataId: sentinel-spzx-gateway
      groupId: DEFAULT_GROUP
      data-type: json
      rule-type: gw-flow
  • 作用:通过 Nacos 实现 Sentinel 规则的 持久化动态更新

  • 技术原理

    1. 规则持久化
      • Sentinel 默认将规则存储在内存中,应用重启后规则会丢失。
      • 通过 Nacos 将规则存储到 sentinel-spzx-gatewaydataId 文件中,实现规则持久化。
    2. 动态更新
      • 当 Nacos 中的规则文件内容变更时,Sentinel 会自动拉取新规则并生效。
    3. 规则类型 gw-flow
      • 表示当前规则类型是 网关流量控制规则(Gateway Flow Rule)。
      • 适用于 Spring Cloud Gateway 或 Dubbo 网关场景。
  • 配置参数说明

    参数名 说明
    server-addr Nacos 服务地址(IP + 端口)。
    dataId Nacos 中存储规则的配置文件 ID(需与 Nacos 中实际配置匹配)。
    groupId Nacos 中的分组(默认为 DEFAULT_GROUP)。
    data-type 规则文件格式(json 表示 JSON 格式的规则文件)。
    rule-type 规则类型(gw-flow 表示网关流量控制规则,其他类型如 flow 是普通流量控制规则)。
  • 注意事项

    • 需确保 Nacos 服务已启动,且 sentinel-spzx-gateway 对应的 JSON 文件内容符合 Sentinel 规则格式。
    • 示例 JSON 规则文件(网关流量控制):
      [
        {
          "resource": "/api/**",
          "resourceType": "gateway-api",
          "count": 100,
          "grade": 1,
          "strategy": 0,
          "controlBehavior": 0,
          "clusterMode": false
        }
      ]
      

2. main.web-application-type: reactive

  • 作用:指定 Spring Boot 应用的 Web 类型为 响应式(Reactive)
  • 技术原理
    • 使用 Spring WebFlux 替代传统的 Spring MVC
    • 基于非阻塞式 I/O(Netty、Undertow 等),支持异步、背压(Backpressure)和高并发。
  • 适用场景
    • 高并发、低延迟的网关或微服务场景。
    • 需要处理大量长连接(如 WebSocket)或异步请求的场景。
  • 注意事项
    • 需确保依赖库兼容响应式编程(如 Spring Cloud Gateway 本身基于 WebFlux)。
    • 避免在响应式应用中混用阻塞式代码(如直接调用 Thread.sleep()ResultSet.next())。

3. 整体配置的协同作用

  1. Sentinel 与 Nacos 集成
    • 应用通过 sentinel.datasource 配置从 Nacos 拉取网关流量控制规则。
    • 规则变更后无需重启应用,Sentinel 会自动生效新规则。
  2. Sentinel 与控制台联动
    • 应用将监控数据上报到 101.42.185.156:8858 的 Sentinel 控制台。
    • 控制台可实时查看流量、QPS、线程池等指标,并通过规则管理界面动态调整规则。
  3. 响应式网关架构
    • web-application-type: reactive 确保网关基于响应式编程模型,适配高并发场景。
    • 与 Sentinel 的非阻塞限流机制兼容,避免因限流导致线程阻塞。

4. 验证配置是否生效

4.1 检查 Sentinel 控制台连接
  • 访问 Sentinel 控制台(http://101.42.185.156:8858)。
  • 登录后查看是否出现当前应用的节点(名称由 spring.application.name 决定)。
4.2 检查 Nacos 规则加载
  • 在 Nacos 控制台(http://101.42.185.156:8848)中查找 sentinel-spzx-gatewaydataId
  • 确保规则内容正确,并观察 Sentinel 是否成功加载规则(可通过日志或控制台验证)。
4.3 测试限流效果
  • 发送请求到网关的 /api/** 路径。
  • 当 QPS 超过规则中的 count(如 100)时,应触发限流(返回 HTTP 429 或自定义错误响应)。

5. 总结

配置项 作用
sentinel.eager 取消 Sentinel 控制台懒加载,确保启动时连接。
sentinel.transport.dashboard 指定 Sentinel 控制台地址,用于监控和规则推送。
sentinel.datasource.ds1.nacos 通过 Nacos 实现规则持久化和动态更新。
main.web-application-type 启用响应式编程模型,适配高并发网关场景。

此配置适用于需要 动态限流高可用监控响应式架构 的 Spring Cloud Gateway 应用,结合 Sentinel 和 Nacos 可实现灵活的流量治理能力。

Rou-Yi 框架学习

注解

@EnableCustomConfig

@EnableRyFeignClients

Ry如何传递用户信息

RuoYi-Cloud 微服务框架中,用户信息的传递主要通过 网关(Gateway)OpenFeign 拦截器ThreadLocal 三部分协作完成。以下是详细的实现流程和关键代码示例:


1. 网关层传递用户信息

目标:在请求进入业务微服务前,网关将用户信息写入请求头中。
实现方式

  • 解析 Token:网关(如 Spring Cloud Gateway)从请求头中提取 JWT Token,解析出用户信息(如用户ID、用户名等)。
  • 写入请求头:将用户信息以自定义 Header 的形式(如 X-User-Info)附加到请求中,传递给下游服务。

代码示例(网关层的 AuthFilter

@Component
public class AuthFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1. 从请求头中提取 JWT Token
        String token = exchange.getRequest().getHeaders().getFirst("Authorization");
        
        // 2. 验证 Token 并解析用户信息(假设解析后的用户信息为 userInfo)
        UserInfo userInfo = parseUserInfoFromToken(token);
        
        // 3. 将用户信息写入请求头(自定义 Header)
        ServerHttpRequest request = exchange.getRequest().mutate()
            .header("X-User-Info", JSON.toJSONString(userInfo))
            .build();
        
        return chain.filter(exchange.mutate().request(request).build());
    }
}

2. 业务微服务接收用户信息

目标:业务微服务接收到请求后,从请求头中提取用户信息并存储到当前线程的 ThreadLocal 中。
实现方式

  • 拦截器处理:通过自定义拦截器(如 HeaderInterceptor)拦截请求,解析请求头中的用户信息并存入 ThreadLocal

代码示例(业务微服务的拦截器)

@Component
public class HeaderInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 1. 从请求头中提取用户信息
        String userInfoStr = request.getHeader("X-User-Info");
        UserInfo userInfo = JSON.parseObject(userInfoStr, UserInfo.class);
        
        // 2. 存入 ThreadLocal
        UserContext.setUser(userInfo);
        
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        // 3. 请求结束后清除 ThreadLocal,避免内存泄漏
        UserContext.removeUser();
    }
}

3. 微服务间调用传递用户信息

目标:当业务微服务之间通过 OpenFeign 调用时,将当前线程的用户信息传递到下游服务。
实现方式

  • Feign 拦截器:通过自定义 RequestInterceptor,在 Feign 请求中自动附加用户信息到请求头。

代码示例(Feign 拦截器)

@Component
public class FeignRequestInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate template) {
        // 1. 从 ThreadLocal 中获取当前用户信息
        UserInfo userInfo = UserContext.getUser();
        
        // 2. 将用户信息写入 Feign 请求头
        if (userInfo != null) {
            template.header("X-User-Info", JSON.toJSONString(userInfo));
        }
    }
}

4. 用户信息在业务代码中的使用

目标:通过工具类(如 UserContext)在业务代码中直接获取用户信息。
实现方式

  • ThreadLocal 存取UserContext 使用 ThreadLocal 存储和获取用户信息,确保线程安全。

代码示例(工具类 UserContext

public class UserContext {
    private static final ThreadLocal<UserInfo> currentUser = new ThreadLocal<>();

    public static void setUser(UserInfo user) {
        currentUser.set(user);
    }

    public static UserInfo getUser() {
        return currentUser.get();
    }

    public static void removeUser() {
        currentUser.remove();
    }
}

业务代码中使用

// 直接获取当前用户信息
UserInfo currentUser = UserContext.getUser();
System.out.println("当前用户ID:" + currentUser.getUserId());

5. 关键注意事项

  1. 线程安全问题

    • ThreadLocal 在异步调用(如使用 @Async)或线程池中可能失效。需使用 TransmittableThreadLocal 或在异步任务中手动传递上下文。
    • 示例(使用 TransmittableThreadLocal):
      import com.alibaba.ttl.TransmittableThreadLocal;
      private static final TransmittableThreadLocal<UserInfo> currentUser = new TransmittableThreadLocal<>();
      
  2. Token 过期处理

    • 网关需校验 Token 有效性,若 Token 过期或非法,需返回 401 错误。
  3. 性能优化

    • 用户信息尽量精简(如只传递用户ID),避免频繁序列化/反序列化。

6. 整体流程图

客户端请求 → 网关(解析 Token 并写入 X-User-Info) → 
业务服务A(拦截器读取 X-User-Info 存入 ThreadLocal) → 
服务A调用服务B(Feign 拦截器附加 X-User-Info) → 
服务B(拦截器读取 X-User-Info 存入 ThreadLocal) → 
业务代码通过 UserContext 获取用户信息

7. 总结

RuoYi-Cloud 通过以下机制实现用户信息传递:

  1. 网关层:解析 Token 并注入自定义 Header。
  2. 业务服务:拦截器将 Header 信息存入 ThreadLocal
  3. 服务间调用:Feign 拦截器将 ThreadLocal 数据附加到下游请求中。
  4. 工具类:通过 UserContext 简化用户信息的获取和管理。

这种方式确保了用户信息在微服务架构中透明传递,同时保持了代码的简洁性和可维护性。

RBAC

代码生成

数据权限

定时任务


网站公告

今日签到

点亮在社区的每一天
去签到