• springcloudalibaba架构(31):SpringCloud实现用户信息在微服务之间传递


    前言

    微服务间如何进行用户信息传递?只能依靠接口调用时显式通过参数传递吗?能否在传递过程中无感知呢?

    本章代码已分享至Gitee: https://gitee.com/lengcz/springcloudalibaba01.git

    第一节 微服务间用户信息传递问题

    微服务由于跨服务了,我们如何在中间的某个服务知道当前的用户信息呢?
    例如下面,用户下单的请求,用户的请求经过了 gateway>> order >> product,如果在order的时候,我们怎么样知道用户是谁?难道要请求接口传参时传递参数(用户身份信息)吗?
    在这里插入图片描述

    第二节 解决思路

    请求流程

    1. 用户通过header传递用户身份信息,请求发送到gateway
    2. gateway 将请求路由到 order
    3. order调用product

    我们知道一个微服务的请求有入口,也有出口(调用)其它微服务,请求进入时,可以通过filter进行拦截,
    而调用其它请求时,我们可以将用户信息传递给被调用者(微服务),我们只需要通过AOP的思想,在入口处捕获用户信息,在出口时将用户信息传递出去,这样就保证了全链路都能获取到客户端的身份信息。当我们所有的微服务都在前后插入了接收和传出的操作,那么全链路就能够获取到用户信息了。

    在这里插入图片描述

    第三节 实践操作(基于Feign)

    我们的用户信息传递是所有微服务的,因为所有微服务模块都需要进行用户信息传递,因为我们将实现定义在common模块
    在这里插入图片描述
    order,product,user均引用common

    1. 调用链路说明

    Feign的远程调用是Http的方式,所以我们只需要Filter 在入口取数据,在RequestInterceptor 出口时传递数据即可。

    2. shop-common改造

    1. 引入依赖
     <dependency>
                <groupId>commons-langgroupId>
                <artifactId>commons-langartifactId>
                <version>2.6version>
            dependency>
            
            <dependency>
                <groupId>javax.servletgroupId>
                <artifactId>javax.servlet-apiartifactId>
                <version>4.0.1version>
                <scope>providedscope>
            dependency>
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-openfeignartifactId>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    1. 定义ThreadLocal进行线程共享
    package com.lcz.userinfo;
    public class UserInfoContext {
        private static ThreadLocal<UserInfo> userInfo = new ThreadLocal<>();
    
        public static UserInfo getUser() {
            return (UserInfo) userInfo.get();
        }
    
        public static void setUser(UserInfo user) {
            userInfo.set(user);
        }
        
        public static void remove(){
            userInfo.remove();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    1. 定义用户实体
    package com.lcz.userinfo;
    import lombok.Data;
    import java.io.Serializable;
    
    @Data
    public class UserInfo implements Serializable {
        private Integer uid; //用户id
        private String username;//用户名
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    1. 定义常量类
    package com.lcz.userinfo;
    
    public class UserInfoConstant {
        public static final String DUBBO_USER_KEY="DUBBO_USER_INFO";
    
        public static final String Authorization="USER_INFO";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 编写拦截器
    package com.lcz.userinfo;
    
    import com.alibaba.fastjson.JSON;
    import feign.RequestInterceptor;
    import feign.RequestTemplate;
    import lombok.extern.slf4j.Slf4j;
    
    import java.io.UnsupportedEncodingException;
    import java.net.URLDecoder;
    
    @Slf4j
    public class TransmitUserInfoFeighClientIntercepter implements RequestInterceptor {
    
        public TransmitUserInfoFeighClientIntercepter() {
        }
    
        @Override
        public void apply(RequestTemplate requestTemplate) {
            //从应用上下文中取出user信息,放入Feign的请求头中
            UserInfo user = UserInfoContext.getUser();
            log.info("传递用户信息:"+JSON.toJSONString(user));
            if (user != null) {
                try {
                    String userJson = JSON.toJSONString(user);
                    requestTemplate.header(UserInfoConstant.Authorization,new String[]{URLDecoder.decode(userJson,"UTF-8")});
                } catch (UnsupportedEncodingException e) {
                    log.error("用户信息设置错误",e);
                }finally {
                    UserInfoContext.remove();
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    1. 编写过滤器
    package com.lcz.userinfo;
    
    import com.alibaba.fastjson.JSON;
    import com.lcz.pojo.User;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang.StringUtils;
    
    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import java.io.IOException;
    import java.io.UnsupportedEncodingException;
    import java.net.URLDecoder;
    
    @Slf4j
    public class TransmitUserInfoFilter implements Filter {
    
        public TransmitUserInfoFilter() {
        }
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            this.initUserInfo((HttpServletRequest)request);
            chain.doFilter(request,response);
        }
    
        private void initUserInfo(HttpServletRequest request){
            String userJson = request.getHeader(UserInfoConstant.Authorization);
            log.info("接收用户信息:"+userJson);
            if (StringUtils.isNotBlank(userJson)) {
                try {
                    userJson = URLDecoder.decode(userJson,"UTF-8");
                    UserInfo userInfo = (UserInfo) JSON.parseObject(userJson,UserInfo.class);
                    //将UserInfo放入上下文中
                    UserInfoContext.setUser(userInfo);
                } catch (UnsupportedEncodingException e) {
                    log.error("init userInfo error",e);
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    1. 编写注解实现类
    package com.lcz.userinfo;
    
    import org.springframework.context.annotation.Bean;
    public class EnableUserInfoTransmitterAutoConfiguration {
    
        public EnableUserInfoTransmitterAutoConfiguration() {
        }
    
        @Bean
        public TransmitUserInfoFeighClientIntercepter transmitUserInfo2FeighHttpHeader(){
            return new TransmitUserInfoFeighClientIntercepter();
        }
    
        @Bean
        public TransmitUserInfoFilter transmitUserInfoFromHttpHeader(){
            return new TransmitUserInfoFilter();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    8 . 编写Enable注解,实现注解式注入

    package com.lcz.userinfo;
    
    import org.springframework.context.annotation.Import;
    
    import java.lang.annotation.*;
    
    @Documented
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Import({EnableUserInfoTransmitterAutoConfiguration.class})
    public @interface EnableUserInfoTransmitter {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    3. 开启注解

    在order,product,user模块开启用户信息传递,使用@EnableUserInfoTransmitter
    在这里插入图片描述

    在这里插入图片描述

    注意: common公共模块修改了代码,注意将common模块重新maven install发布到本地仓库

    4. 启动服务器测试

    回到我们的流程图,下单流程有这些微服务参与了,我们启动这些服务。只要我们在product中能够获取到用户信息,就证明用户信息传递成功了。
    在这里插入图片描述
    发起下单请求,由于我们的身份信息通过header传递,我们需要先生成一个格式正确的身份,再写入到header进行传递

        @Test
        public void testUser() throws UnsupportedEncodingException {
            UserInfo user=new UserInfo();
            user.setUid(111);
            user.setUsername("xiaowang");
            String str=  URLEncoder.encode(JSONObject.toJSONString(user),"UTF-8");
            System.out.println(str);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    生成的信息

    %7B%22uid%22%3A111%2C%22username%22%3A%22xiaowang%22%7D
    
    • 1

    将这个token(身份信息)放在header中发送下单请求
    在这里插入图片描述

    我们再product微服务的日志中,可以输出用户信息,表明了用户信息从api-gateway >> order >> product 这个流程调用中,用户信息传递成功了。
    在这里插入图片描述

    第四节 dubbo传递用户信息

    在第三节,我们的改造只适合微服务之间基于feign的调用,才能传递用户信息到其它微服务。而事实上,我们现在很多微服务之间的调用是基于dubbo的,微服务之间如何通过dubbo 传递用户信息呢?
    我们的解决思路依然一样的,需要微服务的前后进行插板,这样就能保证微服务通过dubbo调用时,也能实现跨服务传递用户身份了。

    1. 调用链路说明

    通过官方的代码架构调用链路图,我们可以知道dubbo远程调用都需要经过Filter(全类名org.apache.dubbo.rpc.Filter),因此我们只需要在消费方和生产方两边的Filter进行附带数据传输,即可实现信息的隐式传递。
    在这里插入图片描述

    我们利用org.apache.dubbo.rpc.Filter来拦截信息,并通过其传递用户信息,而接收方可以通过org.apache.dubbo.rpc.Filter接收信息

    2. shop-common 改造

    基于前面的代码,我们继续改造。

    1. 引入dubbo的依赖
    
            <dependency>
                <groupId>com.alibaba.cloudgroupId>
                <artifactId>spring-cloud-starter-dubboartifactId>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 消费者传入用户信息
    package com.lcz.userinfo;
    
    import com.alibaba.dubbo.common.extension.Activate;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.dubbo.rpc.*;
    
    @Slf4j
    @Activate(group = {UserInfoConstant.CONSUMER})
    public class UserInfoConsumerFilter implements org.apache.dubbo.rpc.Filter {
    
    
        @Override
        public Result invoke(Invoker<?> invoker, org.apache.dubbo.rpc.Invocation invocation) throws RpcException {
    //        log.info("消费方:------");
            try{
                UserInfo userInfo = UserInfoContext.getUser();
                if (null == userInfo) {
                    return invoker.invoke(invocation);
                }
                invocation.getObjectAttachments().put(UserInfoConstant.DUBBO_USER_KEY, userInfo);
                return invoker.invoke(invocation);
            }finally {
                UserInfoContext.remove();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    1. 生产者接收用户信息
    package com.it.userinfo;
    
    import com.alibaba.dubbo.common.extension.Activate;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.dubbo.common.constants.CommonConstants;
    import org.apache.dubbo.rpc.Invocation;
    import org.apache.dubbo.rpc.Invoker;
    import org.apache.dubbo.rpc.Result;
    import org.apache.dubbo.rpc.RpcException;
    
    @Slf4j
    @Activate(group = CommonConstants.PROVIDER)
    public class UserInfoProviderFilter implements org.apache.dubbo.rpc.Filter {
    
        @Override
        public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
    //        log.info("生成方:------");
            Object userInfo = invocation.getObjectAttachment(UserInfoConstant.DUBBO_USER_KEY);
            if (null != userInfo) {
                UserInfoContext.setUser((UserInfo) userInfo);
            }
            return invoker.invoke(invocation);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    1. 添加配置文件,META-INF/dubbo目录下,创建com.alibaba.dubbo.rpc.Filter文件,内容如下
      在这里插入图片描述
    userInfoConsumerFilter=com.lcz.userinfo.UserInfoConsumerFilter
    userInfoProviderFilter=com.lcz.userinfo.UserInfoProviderFilter
    
    • 1
    • 2

    3. 测试

    在这里插入图片描述

    第五节 Feign和Dubbo跨服务传递用户信息

    基于我们上面的改造,各微服务不管使用Feign调用还是Dubbo调用,都可以实现用户信息的跨服务传递,解决了无法获取用户身份的问题。让跨服务之间,可以直接使用UserInfoContext.getUser(),具有单体应用的信息获取能力

    UserInfo userInfo = UserInfoContext.getUser(); //获取用户信息
    
    • 1

    同时,直接传递用户身份信息,基于这个用户信息的传递思想,我们可以实现链路跟踪(Sleuth),也可以在gateway网关封装好某些内容,传递一些request信息,比如客户端IP,用户的权限。

    当然了,上面的demo直接传递用户信息是不具有防伪性,我们可能使用JWT相关的技术进行鉴权等处理,在穿越网关时,将其转换为明文可操作的UserInfo,这样就保证了传递的用户信息的安全可靠性。

    关于错误

    如果打包common时,提示repackage failed: Unable to find main class
    可以配置

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
                <configuration>
                    <mainClass>nonemainClass>     
                    <classifier>executeclassifier>    
                configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackagegoal>
                        goals>
                    execution>
                executions>
            plugin>
        plugins>
    build>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    dubbo 隐式传参的其他方式

    消费方

    RpcContext.getClientAttachment().setAttachment("index", "1"); // 隐式传参,后面的远程调用都会隐式将这些参数发送到服务器端,类似cookie,用于框架集成,不建议常规业务使用
    xxxService.xxx(); // 远程调用
    
    • 1
    • 2

    服务提供方

    public class XxxServiceImpl implements XxxService {
     
        public void xxx() {
            // 获取客户端隐式传入的参数,用于框架集成,不建议常规业务使用
            String index = RpcContext.getServerAttachment().getAttachment("index");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    服务方回写

    public class XxxServiceImpl implements XxxService {
     
        public void xxx() {
            String index = xxx;
            RpcContext.getServerContext().setAttachment("result", index);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    消费方获取

    xxxService.xxx(); // 远程调用
    String result = RpcContext.getServerContext().getAttachment("result");
    
    • 1
    • 2

    dubbo隐式传参
    注意:dubbo 2.7和dubbo 3.0 有些不一样。

  • 相关阅读:
    基于编辑距离纯逻辑实现相似地址聚类
    YOLOv8-Seg改进:Backbone改进 |Next-ViT堆栈NCB和NTB 构建先进的CNN-Transformer混合架构
    torchtext0.14 实践手册(0.12版本同理)
    【javaSE】 Lambda表达式与Lambda表达式的使用
    JavaScript关于==隐式转换的判断
    三维度:专业、展现与连接
    keepalived群集
    golang语言_2
    12、建立健全人员培训体系
    Opengl实例7:glm(0.9.8.5)库 +矩阵旋转+课后作业
  • 原文地址:https://blog.csdn.net/u011628753/article/details/126944684