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

openfeign 拦截器实现微服务上下文打通

目录

  • openfeign 拦截器实现微服务上下文打通
    • 需求分析:
    • 代码实现:
      • subject 服务:
        • controller
        • Feign 拦截器:
          • 将 Feign 拦截器注册为一个Bean:
      • auth 鉴权服务:
        • 全局配置类:
        • 登录拦截器:
        • 上下文对象:
    • 测试:
      • subject 服务的拦截器:
      • auth 服务的前置拦截:
      • 上下文对象:
      • 成功实现远程调用时,携带 loginId
    • 备注:
      • auth 对外提供的 feign 远程调用接口

openfeign 拦截器实现微服务上下文打通

需求分析:

subject 服务 通过 openfeign 调用 auth 鉴权服务 :subject 服务接收到前端发来的请求,这个请求需要远程调用 auth 鉴权服务,所以我们需要发送一个 feign 请求 进行远程调用 auth 鉴权服务的某一个接口:
期间需要携带 loginId(用户唯一标识id)思路:subject 服务:
1、需要在 subject 服务中 定义一个 feign 拦截器,把当前请求中携带的 loginId ,放入到 Feign 请求中,用来传递给下游服务auth 鉴权服务:2、auth 鉴权服务 定义一个 LoginInterceptor 拦截器,在执行 preHandle 前置拦截时,将 loginId 存放到 LoginContextHolder 这个自定义的登录上下文对象中。
3、这个 LoginContextHolder 登录上下文对象,主要通过 ThreadLocal 来实现,为每个线程存储数据。
4、在原有的 GlobalConfig (spring mvc 的全局处理 -- 消息转换器)中,重写 addInterceptors 方法,拦截每一个请求,让所有请求都进入到 LoginInterceptor 登录拦截器里面

代码实现:

subject 服务:

controller

首先写一个简单的测试 controller ,这个方法会进行远程调用 auth 服务

在这里插入图片描述

Feign 拦截器:

作用:在当前服务使用 Feign 发送请求时,自动将前端传来的 loginId 添加到 Feign 请求的 header 中,实现用户身份的“透传”

在这里插入图片描述


/*** Feign 请求拦截器: 把当前请求中的 loginId 自动加入 Feign 的请求头,实现跨服务身份传递** 在微服务中,用户请求经过多个服务(A 调 B,再调 C),每一层都需要知道当前用户是谁(loginId),所以需要透传这个字段** @author lujinhong* @since 2025-05-14*/@Component
public class FeignRequestInterceptor implements RequestInterceptor {/*** 在当前服务使用 Feign 发送请求时,自动将前端传来的 loginId 添加到 Feign 请求的 header 中,实现用户身份的“透传”*/@Overridepublic void apply(RequestTemplate requestTemplate) {// 从当前线程上下文中拿到原始的 HTTP 请求(比如前端请求当前服务时的请求)ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = requestAttributes.getRequest();if (Objects.nonNull(request)) {// 获取请求头中的 loginId。String loginId = request.getHeader(SubjectConstants.LOGIN_ID);if (StringUtils.isNotBlank(loginId)) {// 将这个 loginId 放入 Feign 请求的 header 中,传递给下游服务requestTemplate.header(SubjectConstants.LOGIN_ID, loginId);}}}
}
将 Feign 拦截器注册为一个Bean:

在这里插入图片描述

/*** 将自定义的 FeignRequestInterceptor 注册为 Spring Bean,使其在 Feign 请求中生效** 让自定义的 Feign 拦截器真正生效,自动为每次 Feign 调用加上想加的 header,比如 loginId** @author lujinhong* @since 2025-05-14*/
//声明一个配置类,用于配置 Feign 相关内容
@Configuration
public class FeignConfiguration {/*** 将 FeignRequestInterceptor 注册为一个 Bean*/@Beanpublic RequestInterceptor requestInterceptor() {return new FeignRequestInterceptor();}}

auth 鉴权服务:

全局配置类:

在这里配置这个 addInterceptors 添加拦截器的方法,让每个请求都进入到 LoginInterceptor 拦截器里面

在这里插入图片描述


/*** spring mvc 的全局处理 -- 消息转换器** @author lujinhong* @since 2025-01-09* <p>* WebMvcConfigurationSupport 提供了一些默认的 Spring MVC 配置方法,包括消息转换器、视图解析器、拦截器等。* 通过继承这个类,可以修改默认配置,来满足项目特定的需求*///@Configuration 用于标注一个类为配置类
@Configuration
public class GlobalConfig extends WebMvcConfigurationSupport {/*** 该是一个用于配置消息转换器的方法,消息转换器用于将请求的 JSON 或 XML 数据转换为 Java 对象,或者将 Java 对象转换为 JSON 或 XML 数据*/@Overrideprotected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {/** super 关键字用于访问当前类的父类(超类)成员(字段、方法或构造器)* 重写父类的这个方法,仍然推荐再调用父类的这个configureMessageConverters方法,是为了保留继承和扩展父类未来可能增加的任何配置的潜力,即使现在这个父类的这个方法是空的。*/super.configureMessageConverters(converters);//在原有的消息转换器列表基础上,添加自定义的 MappingJackson2HttpMessageConverter。该转换器的作用是处理 JSON 数据,将其与 Java 对象进行相互转换converters.add(mappingJackson2HttpMessageConverter());}/*** 这是 Spring MVC 中重写 WebMvcConfigurer 的方法,用来注册拦截器。** addInterceptor(...):在拦截器链路中 添加自己写的 LoginInterceptor 拦截器。* addPathPatterns("/**"):给 LoginInterceptor 这个拦截器设置拦截规则 --> 拦截所有路径,/** 表示所有接口(包括子路径)。* 每次请求进入 Controller 之前,都会先执行 LoginInterceptor 中的 preHandle() 方法。*/@Overrideprotected void addInterceptors(InterceptorRegistry registry) {// /** 拦截所有请求,每个请求都需要进入到 LoginInterceptor 这个自定义拦截器中registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**");}/*** 自定义 mappingJackson2HttpMessageConverter,用于配置 JSON 数据的处理方式* 作用:空值忽略,空字段可返回* 空值忽略:指的是如果字段的值是 null,该字段不会出现在 JSON 中。* 空字段返回:比如一些空的list集合,虽然也没值,但是会被序列化后返回一个{} ,该list集合类型的字段依然会在 JSON 输出中出现*/private MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {//ObjectMapper 是 Jackson 序列化和反序列化 JSON 数据的核心类ObjectMapper objectMapper = new ObjectMapper();//作用:在序列化一个 空的 Java 对象(即没有任何属性或字段的对象)时,不会抛出异常,而是直接返回一个空的 JSON 对象({})//适用于处理一些空的 Bean 对象,避免因空对象序列化失败而导致异常objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);//这个配置会使得在序列化对象为 JSON 时,忽略值为 null 的字段。也就是说,如果某个字段的值是 null,它不会出现在最终的 JSON 输出中objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);return new MappingJackson2HttpMessageConverter(objectMapper);}
}
登录拦截器:

LoginInterceptor 登录拦截器:在前置拦截方法中进行处理,将 loginId 存到 当前线程的上下文对象中

在这里插入图片描述


/*** 自定义一个登录拦截器,用来拦截登录的header头信息** @author lujinhong* @since 2025/5/14  星期三*/
@Component
public class LoginInterceptor implements HandlerInterceptor {/*** 前置拦截器* 执行时机:在请求达到 controller 之前进行拦截处理,一般用于: 登录验证、权限校验、拦截*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 获取请求头的 loginIdString loginId = request.getHeader(AuthConstants.LOGIN_ID);// 把 loginId 存放到上下文里面,然后上下文类LoginContextHolder,就会把loginId 放到 ThreadLocal 里面,保证各线程的 loginId 互相不被其他线程干扰影响。LoginContextHolder.set("loginId", loginId);return true;}/*** 后置拦截器:Controller 执行完毕,但视图还未渲染时执行,就是数据还没有返回给前端* 一般用于:添加模型数据、封装响应,在数据查询出来还没有返回给前端之前,我们还可以添加一些数据或者做一些操作*/@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {// 暂时用不到,做下解释HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);}/*** 请求完全结束后处理:一般用于:记录日志、异常处理、清资源*/@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 移除此线程局部变量的值,这里就是移除掉 loginIdLoginContextHolder.remove();}
}
上下文对象:

在这里插入图片描述

package cn.ljh.auth.application.context;import cn.ljh.auth.application.constants.AuthConstants;import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;/*** 登录的上下文对象* <p>* ThreadLocal解释:* ThreadLocal是java.lang下面的一个类,是用来解决java多线程程序中并发问题的一种途径;* 通过为每一个线程创建一份共享变量的【副本】来保证各个线程之间的变量的访问和修改互相不影响;* <p>* ThreadLocal存放的值是线程内共享的,线程间互斥的,主要用于线程内共享一些数据,避免通过参数来传递。* <p>* 比如 共享变量num=10 , A线程拿到num,改成100;B线程拿到num,改成200,然后A线程打印num,依然是100,线程之间是互斥的* <p>* 线程内共享:比如A线程有多个方法:方法1,方法2;方法3,:方法1把num改成100,方法2把num改成200,然后方法方法3打印num=200* <p>* 存储位置:每个线程在执行时,都有一个独立的线程局部存储空间,这个空间是用于存储该线程的线程局部变量(即 ThreadLocal 的副本)的** @author lujinhong* @since 2025/5/14  星期三*/public class LoginContextHolder {// 只对当前线程有效,子线程无法访问,线程池更不行。// private static final ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal<>();// 只适合临时新建的子线程,缺点:线程池中的线程是复用的,容易导致数据泄露private static final InheritableThreadLocal<Map<String, Object>> THREAD_LOCAL = new InheritableThreadLocal<>();// 线程池场景专用: 作用:比如num=10,A线程拿到num=10,B线程拿到num后改成15,当线程切换回A线程时,A线程持有的num依然是10// 实际项目用的一定是这个TransmittableThreadLocal,不过视频演示先用InheritableThreadLocal了解一下// private static final TransmittableThreadLocal<Map<String,Object>> transmitThreadLocal = new TransmittableThreadLocal<>();/*** 将此线程局部变量的当前线程副本中的值设置为指定值。(就是会将当前线程的 ThreadLocal 变量副本的值设置为你传入的指定值)* 当前线程副本中的值是存储在“局部变量”中的,不过这个局部变量是线程局部的,即它只属于当前线程,并且由 ThreadLocal 管理,而不是普通的局部变量* 许多应用程序不需要这项功能,它们只依赖于initialValue()方法来设置线程局部变量的值*/public static void set(String key, Object value) {Map<String, Object> map = getThreadLocalMap();// concurrenthashmap 的value不能为nullif (value != null) {map.put(key, value);}}/*** 返回此线程局部变量的当前线程副本中的值。如果这是线程第一次调用该方法,则创建并初始化此福副本。*/public static Object get(String key) {Map<String, Object> map = getThreadLocalMap();Object object = map.get(key);return object;}/*** 获取当前线程的局部变量的值,做判断*/public static Map<String, Object> getThreadLocalMap() {// get() : 返回此线程局部变量的当前线程副本中的值。如果这是线程第一次调用该方法,则创建并初始化此副本。Map<String, Object> map = THREAD_LOCAL.get();if (Objects.isNull(map)) {// 保证线程安全map = new ConcurrentHashMap<>();THREAD_LOCAL.set(map);}return map;}/*** 移除此线程局部变量的值*/public static void remove() {THREAD_LOCAL.remove();}/*** 获取当前线程的loginId*/public static String getLoginId() {String loginId = (String) getThreadLocalMap().get(AuthConstants.LOGIN_ID);return loginId;}}

测试:

subject 服务的拦截器:

先走 subject 服务的拦截器,把请求的 loginId 塞到 feign 请求的 请求头中

在这里插入图片描述

auth 服务的前置拦截:

来到 auth 服务的登录拦截器的 前置拦截

在这里插入图片描述

上下文对象:

正常将数据存到上下文对象中

在这里插入图片描述

成功实现远程调用时,携带 loginId

最后成功实现远程调用,把数据从 auth 服务中查询出来了

在这里插入图片描述

备注:

auth 对外提供的 feign 远程调用接口

在这里插入图片描述

相关文章:

  • 【机器人】复现 SG-Nav 具身导航 | 零样本对象导航的 在线3D场景图提示
  • react中安装依赖时的问题 【集合】
  • FPGA:Xilinx Kintex 7实现DDR3 SDRAM读写
  • b站视频如何下载到电脑——Best Video下载器
  • 昆士兰科技大学无人机自主导航探索新框架!UAVNav:GNSS拒止与视觉受限环境中的无人机导航与目标检测
  • 算法第十八天|530. 二叉搜索树的最小绝对差、501.二叉搜索树中的众数、236. 二叉树的最近公共祖先
  • Agent Builder API - Agent Smith 扩展的后端服务(开源代码)
  • 学习机器学习的体会与姓名性别预测案例分析
  • 智能工具协同赋能STEM教育科研|探索LLM大语言模型和数学软件Maple的创新实践
  • 反向操作:如何用AI检测工具优化自己的论文“人味”?
  • 华为云Flexus+DeepSeek征文|基于华为云ModelArts Studio平台体验DeepSeek-V3大模型
  • idea中编写spark程序
  • npm install 报错
  • CMD(Command Prompt)和 Anaconda 的不同
  • c# 倒序方法
  • 数据结构(八)——查找
  • 2025-5-14渗透测试:利用Printer Bug ,NTLMv2 Hash Relay(中继攻击),CVE-2019-1040漏洞复现
  • 环境配置与MySQL简介
  • css设置文字两端对齐text-align:justify不起作用的解决方法
  • C++之fmt库介绍和使用(1)
  • 专家:家长要以身作则,孩子是模仿者学习者有时也是评判者
  • 丰富“互换通”产品类型,促进中国金融市场高水平对外开放
  • 丹麦外交大臣拉斯穆森将访华
  • 国际奥委会举办研讨会,聚焦如何杜绝操纵比赛
  • 欠债七十万后,一个乡镇驿站站长的中年心事
  • 美叙领导人25年来首次会面探索关系正常化,特朗普下令解除对叙经济制裁