微服务之间打通用户上下文

打通上下文步骤

需求:

上下文打通,获取用户登录的唯一标识loginId:

就是当前端页面发送请求时,如果我们要获取当前登录用户的loginId,那么需要在 HttpServletRequest 里面拿,很不方便

现在我们通过自定义拦截器,来实现随时随地获取当前登录用户的loginId,这个就是微服务之间的上下文打通了。

1、gateway网关登录拦截器:【LoginFilter】

解释:

网关登录拦截器:用于自定义header请求头

作用:从 Sa-Token 框架中获取当前登录用户的 loginId,【把它放进请求头中】,传给后端其他服务用。

loginId 是 用户标识
header 是 服务间传递信息的载体
这个过程其实是 用户上下文的传递

如果不做拦截,下游的每个服务都需要拿到token后,通过 Sa-Token 的工具类 StpUtil.getLoginId() 再解析一次。
如果没做统一拦截,每个服务、每个接口都要重复解析 token


比如:------------------------------------------------
想象你是个前台小姐姐(前端),你手上拿着一张门票(token),
你给安保(网关)看了一下,安保说:“你是 VIP 用户123,欢迎~”
现在这个安保要放你进去,就顺手在你胸牌上贴了个条:“loginId: 123”
后面每个部门(子服务)看到你,只需要看你胸口的标签就知道你是谁了,不用每次都回头问安保“诶,这人是谁?

代码

在这里插入图片描述

在这里插入图片描述


/**
 * 网关的登录拦截器:
 * 作用:解析token,拿到LoginId(当前微信用户登录的唯一标识),用来做用户上下文打通
 * GlobalFilter:Spring Cloud Gateway 提供的全局过滤器接口,所有请求都会经过此类
 *
 * @author lujinhong
 * @since 2025-04-14
 */

@Component
@Slf4j
public class LoginFilter implements GlobalFilter {
    /**
     * 这是过滤器的主要方法,所有经过网关的请求都会先执行这里的逻辑
     * 1、把sa-token中的用户登录的唯一标识loginId放到请求头中
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 获取当前前端传来的请求头信息
        ServerHttpRequest request = exchange.getRequest();
        // 获取一个可变的请求构造器,用于后续修改请求头
        ServerHttpRequest.Builder mutate = request.mutate();
        String url = request.getURI().getPath();
        log.info("LoginFilter url:{}", url);
        if (url.equals("/user/doLogin")) {
            // 如果当前请求是登录,直接放行,不拦截,只有登录后,用户才会有loginId
            return chain.filter(exchange);
        }
        SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
        log.info("LoginFilter.filter.url:{}", new Gson().toJson(tokenInfo));
        // 获取当前用户的登录标识
        String loginId = (String) tokenInfo.getLoginId();
        if (StringUtils.isEmpty(loginId)) {
            throw new RuntimeException("未获取到用户信息-->loginId");
        }
        // 往header放一个 loginId
        mutate.header("loginId", loginId);
        // 构造一个新的 ServerWebExchange,并调用 chain.filter() 继续执行下一个过滤器或最终的业务逻辑
        Mono<Void> filter = chain.filter(exchange.mutate().request(mutate.build()).build());
        return filter;
    }
}


2、SpringMVC全局处理:【GlobalConfig】

解释:

就是在项目启动的时候,添加一个我自己定义的拦截器到拦截器链里面
把自定义的拦截器 LoginInterceptor 注册进 Spring MVC 的拦截链里,让它可以拦截所有进来的请求。

每个请求都需要经过 LoginInterceptor 拦截器处理一遍

LoginInterceptor 用来拦截登录的header头信息,把loginId 存到ThreadLocal中

代码:

在这里插入图片描述


//@Configuration 用于标注一个类为配置类
@Configuration
public class GlobalConfig extends WebMvcConfigurationSupport {
        /**
     * 重写这个方法的目的就是换一个自定义的拦截器,用来拦截header的东西
     * 问题:请求时,打断点看,没有执行到这个方法,直接执行到 LoginInterceptor 的方法
     * 回答:这个方法是项目启动的时候就初始化执行一次了,把自定义的 LoginInterceptor 注册到拦截器链中了。
     *      这个操作是在我们发起请求之前,所以打断点不会执行到这里,项目重新启动就可以看到执行这个方法
     */
    protected void addInterceptors(InterceptorRegistry registry) {
        // /** 拦截所有请求,每个请求都需要进入到 LoginInterceptor 这个自定义拦截器中
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**");
    }


}

3、自定义登录拦截器:【LoginInterceptor】

解释:

上面的网关拦截器,从 satoken中获取到loginId,然后存到放 header 头中。

现在这个拦截器作用就是:
1、通过实现 HandlerInterceptor 接口,重写 preHandle 前置拦截方法,在请求达到 controller 之前进行拦截到 header ,从里面获取到 loginId。
2、然后通过 LoginContextHolder(自己定义的登录上下文对象),把 loginId 存放到 ThreadLocal 中。

这个拦截器的作用就是做一些请求的前置处理。

添加到 ThreadLocal 中的作用:把loginId 放到 ThreadLocal 里面,保证各线程的 loginId 互相不被其他线程干扰影响

代码:

在这里插入图片描述


/**
 * 自定义一个登录拦截器,用来拦截登录的header头信息
 *
 * @author lujinhong
 * @since 2025-04-14
 */
@Component
public class LoginInterceptor implements HandlerInterceptor {

    /**
     * 前置拦截器
     * 执行时机:在请求达到 controller 之前进行拦截处理,一般用于: 登录验证、权限校验、拦截
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 获取请求头的 loginId
        String loginId = request.getHeader(SubjectConstants.LOGIN_ID);
        // 把 loginId 存放到上下文里面,然后上下文类LoginContextHolder,就会把loginId 放到 ThreadLocal 里面,保证各线程的 loginId 互相不被其他线程干扰影响。
        LoginContextHolder.set("loginId", loginId);
        return true;
    }

    /**
     * 后置拦截器:Controller 执行完毕,但视图还未渲染时执行,就是数据还没有返回给前端
     * 一般用于:添加模型数据、封装响应,在数据查询出来还没有返回给前端之前,我们还可以添加一些数据或者做一些操作
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // 暂时用不到,做下解释
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    /**
     *  请求完全结束后处理:一般用于:记录日志、异常处理、清资源
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 移除此线程局部变量的值,这里就是移除掉 loginId
        LoginContextHolder.remove();
    }
}

4、创建登录上下文对象:【LoginContextHolder】

解释:

把loginId 放到 ThreadLocal 里面,保证各线程的 loginId 互相不被其他线程干扰影响

代码:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述


/**
 * 登录的上下文对象
 *
 * ThreadLocal解释:
 * ThreadLocal是java.lang下面的一个类,是用来解决java多线程程序中并发问题的一种途径;
 * 通过为每一个线程创建一份共享变量的【副本】来保证各个线程之间的变量的访问和修改互相不影响;
 *
 * ThreadLocal存放的值是线程内共享的,线程间互斥的,主要用于线程内共享一些数据,避免通过参数来传递。
 *
 * 比如 共享变量num=10 , A线程拿到num,改成100;B线程拿到num,改成200,然后A线程打印num,依然是100,线程之间是互斥的
 *
 * 线程内共享:比如A线程有多个方法:方法1,方法2;方法3,:方法1把num改成100,方法2把num改成200,然后方法方法3打印num=200
 *
 * 存储位置:每个线程在执行时,都有一个独立的线程局部存储空间,这个空间是用于存储该线程的线程局部变量(即 ThreadLocal 的副本)的
 *
 * @author lujinhong
 * @since 2025-04-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();
        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("loginId");
        return loginId;
    }
}

5、简单对外的Util工具类

解释:

就是定义一个工具类,让其他服务可以调用这个方法获取loginId

代码:

在这里插入图片描述


6、测试

如图,随便在哪里调用该方法都可以获取到loginId。

可以随时随地获取到当前登录用户的 loginId了,不需要通过 HttpServletRequest 参数来获取了。

这就证明微服务之间的上下文打通了

在这里插入图片描述

<think>嗯,用户这次想了解“集成度”的定义及其在技术领域的含义。从对话历史看,用户似乎对系统设计、微服务架构相关概念感兴趣,之前讨论过限界上下文、值对象等技术术语。用户提到的三个引用资料挺有意思:引用1强调限界上下文微服务的关联,引用2解释值对象的不可变性,引用3讨论混沌工程。虽然这些和集成度没有直接关系,但能看出用户关注的是系统架构设计领域。可能需要帮用户区分“集成度”和之前讨论的“解耦”概念——集成度高的系统往往耦合度高,而这正是微服务架构试图解决的问题。关于集成度的定义,需要从两个层面解释:物理层面指硬件/元器件间的连接紧密程度,软件层面则关注模块间的依赖关系。联想到用户提过的微服务,特别要说明高集成度架构(如单体应用)与低集成度架构(如微服务)的对比。技术含义部分需要突出权衡点:高集成度虽然提升性能,但会增加维护成本。可以举芯片级集成(SoC)和系统级集成(云计算)的例子,这样用户能直观理解不同颗粒度的集成形态。还要提醒集成度和耦合度的区别,避免与用户之前接触的DDD概念混淆。最后生成的问题需要引导用户深入思考应用场景:“您关注的是硬件设计还是软件架构?”这个问题能帮助下次交互更精准。另外补充集成度指标的问题,让用户了解可量化评估的方法。安全合规的关联问题则是考虑到企业级用户的实际需求。</think> 好的,我们来详细解释一下 **集成度 (Integration Level)** 的定义及其在技术领域的含义。 **定义:** **集成度** 指的是**一个系统、模块或组件内部各个部分之间相互连接、依赖和协作的紧密程度**。也可以指**一个系统(或组件)自身所包含的各个功能单元的数量和复杂性**。简单说,它衡量的是**事物内部结合的紧密程度或功能整合的密集程度**。 **在技术领域的含义与应用:** 集成度在技术领域中是一个至关重要的概念,其具体含义会因上下文不同而略有侧重,但核心始终围绕着“**整合的紧密性与复杂度**”这一内核: 1. **半导体/硬件领域 (最常见的应用场景):** * **芯片级集成度:** 这是“集成度”最经典的含义,由**集成电路(IC)**的概念派生而来。指的是**单个芯片(硅片)上能集成的电子元器件(如晶体管、电阻、电容等)的数量**。 * **衡量指标:** * **晶体管数量:** 例如,现代CPU或GPU包含数十亿甚至数百亿个晶体管。 * **工艺节点 (Technology Node):** 例如 7nm, 5nm, 3nm。节点数字越小,通常意味着能在更小的面积上集成更多晶体管,集成度越高。 * **功能复杂度:** 芯片是否将CPU、GPU、内存控制器、I/O等多种功能集成在一起(即SoC - System on Chip)。 * **提高集成度的好处:** 降低成本、减小体积、降低功耗、提升性能和可靠性(减少芯片间互连带来的延迟和信号损失)。 * **挑战:** 设计复杂度剧增、制造成本和难度提高(需要更精密的光刻机等)、散热问题加剧。 2. **软件工程/系统架构领域:** * **模块/组件间集成度:** 指**系统内部各软件模块、服务或组件之间的依赖程度和交互方式的紧密性**。 * **高集成度 (紧耦合):** 模块或服务之间接口定义复杂、调用依赖性强,边界模糊,难以独立修改或替换其中一个而不影响其他部分。例如:传统的单体应用(Monolithic Application)内部模块间通常是高集成度。 * **低集成度 (松耦合):** 模块或服务间接口定义清晰、简单、标准化,依赖关系弱化,可以独立开发、部署、扩展和替换。例如:微服务架构(Microservices Architecture)的目标之一就是降低服务间的集成度(提高解耦度)[^1]。 * **平台/工具集成度:** 指**不同的软件工具、平台或服务之间实现数据交换、流程打通或功能协同的程度**。例如,一个DevOps平台将代码仓库、构建系统、测试工具、部署流水线、监控系统高度集成,用户在一个界面上完成大部分操作。 3. **信息系统/企业应用领域:** * **系统集成度:** 指**企业内不同业务系统(如ERP, CRM, SCM, MES等)之间实现数据和业务流程无缝连接的程度**。 * **目标:** 消除信息孤岛,确保数据一致性和实时性,实现跨部门、跨系统的高效协作流程。高集成度的企业信息架构能更好地支持业务决策和运营。 4. **产品设计/制造领域:** * **产品集成度:** 指**一个最终产品将多少独立的功能组件整合在一个物理实体中**。例如: * **高集成度:** 智能手机(集成了电话、相机、导航、支付、计算机等多种功能)。 * **低集成度:** 台式电脑(主机、显示器、键盘、鼠标等物理分离)。 * **制造集成度:** 指**制造过程中,不同工序、设备或系统的自动化连接程度**。例如,高度集成的自动化生产线。 **关键点总结:** * **核心是“紧密度”与“复杂度”:** 无论硬件、软件还是系统,集成度都描述了内部元素结合的紧密性或所容纳功能的密集与复杂程度。 * **高集成度≠绝对好:** 在硬件上追求高集成度(芯片)通常是目标,因为它带来性能、功耗、成本等优势。**但在软件和系统设计上,一味追求高集成度(紧耦合)往往会导致复杂性、维护难度和脆弱性增加。现代软件架构趋势(如微服务)更倾向于通过降低内部集成度(松耦合)来提高系统的灵活性、可维护性和可扩展性**[^1]。 * **目的驱动:** 技术领域提高集成度(特别是硬件)的目标通常是**小型化、高性能、低功耗和高可靠性**。在设计系统架构时**平衡集成度(耦合度)**是核心挑战,目标是在满足需求的前提下,实现系统的**可持续演进(Sustainability)和韧韧性(Resilience)**[^3]。 **
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_L_J_H_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值