微服务参数透传实现

说明:在微服务架构中,用户身份经网关验证后,我们可以将用户信息,如ID加入到请求头上。后面的微服务中,可以设置一个拦截器,拦截请求,获取请求头上的用户ID,加入到ThreadLocal中。

最重要的是,在当前微服务发起请求,调用其他微服务时,需要将发出去的请求再进行拦截,把本地线程池中的用户ID再次放入到请求头中,这样就实现了用户信息在微服务中的流转,称为参数透传。本文介绍,上述参数透传的实现。

在这里插入图片描述

在微服务系统中,一般都会有一个共同模块,是所有微服务共同引用的模块,比如模块名为Base、Util、Feign-API,我们可以把参数透传的拦截器写在这个模块里面,这样其他微服务就不用再写拦截器的配置类了。

比如,我当前的这个系统,Feign-API模块是所有微服务共同引用的。里面写了一些共同的DTO对象,以及Feign-Client类对象;

在这里插入图片描述

实现

第一步:创建ThreadLocal类

在FeignAPI模块中,创建一个本地线程池对象,用于存储当前用户的ID;

/**
 * 本地线程池
 */
public class TokenThreadLocal {
    
    
    
    private static final ThreadLocal<Long> THREAD_LOCAL= new ThreadLocal<>();

    /**
     * 设置
     * @param tokenId
     */
    public static void set(Long tokenId){
    
    
        THREAD_LOCAL.set(tokenId);
    }

    /**
     * 获取
     * @return
     */
    public static Long get(){
    
    
        return THREAD_LOCAL.get();
    }

    /**
     * 移除
     */
    public static void remove(){
    
    
        THREAD_LOCAL.remove();
    }
}

第二步:创建MVC拦截器

在FeignAPI模块中,创建MVC拦截器(HandlerInterceptor),拦截路径为所有路径;

package com.hzy.interceptor;

import com.hzy.config.TokenThreadLocal;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 接收请求拦截器
 */
public class AuthorizationInterceptor implements HandlerInterceptor {
    
    

    /**
     * 收到请求会执行的方法
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
        
        // 获取请求头中的用户ID
        String id = request.getHeader("authorization");

        // 判断用户ID是否为空
        if (id != null && id != ""){
    
    
            // 将字符串转换为Long类型
            long l = Long.parseLong(id);
            
            // 将用户ID存入ThreadLocal中
            TokenThreadLocal.set(l);
        }else {
    
    
            responseHandler(response);
            return false;
        }

        // 放行
        return true;
    }

    /**
     * 请求返回客户端之前会执行的方法
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    
    
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    /**
     * 请求到客户端之后会执行的方法
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    
    
        TokenThreadLocal.remove();
    }

    /**
     * 用户尚未登录的返回信息
     * @param response
     * @return
     * @throws IOException
     */
    private boolean responseHandler(HttpServletResponse response) throws IOException {
    
    
        // 设置响应状态码 401 如果用户未登录 状态码都返回401
        response.setStatus(HttpStatus.UNAUTHORIZED.value());

        // 响应的数据类型和编码格式
        response.setContentType("application/json;charset=UTF-8");

        // 提示信息
        response.getWriter().write("用户未登录");

        return false;
    }
}

拦截器配置类,设置为所有路径请求都拦截;

import com.hzy.interceptor.AuthorizationInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 接收请求拦截器配置类
 */
public class MVCInterceptorConfig implements WebMvcConfigurer {
    
    

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    
    
        registry.addInterceptor(new AuthorizationInterceptor())
                .addPathPatterns("/**");
    }
}

第三步:创建Feign拦截器

创建Feign的拦截器(RequestInterceptor),该拦截器用于拦截当前微服务发出去的请求,代码内容写把当前本地线程池的用户ID再次放入到请求头中,传给下游微服务解析;

import com.hzy.config.TokenThreadLocal;
import feign.RequestInterceptor;
import feign.RequestTemplate;

/**
 * 发送请求拦截器
 */
public class AuthorizationRequestInterceptor implements RequestInterceptor {
    
    

    @Override
    public void apply(RequestTemplate requestTemplate) {
    
    
        // 从本地线程池中获取用户ID,放入请求头中
        requestTemplate.header("authorization",TokenThreadLocal.get().toString());
    }
}

测试

测试订单服务,生成订单中,会调用库存服务,查询库存信息。在MVC、Feign拦截器、调用库存服务行分别打断点,发送请求,查看执行过程;

在这里插入图片描述

请求通过网关,先被订单服务的MCV拦截器拦截到,可以看到当前用户的ID是2,放行……

在这里插入图片描述

然后,断点来到订单服务的,调用库存服务这里,放行,查看发出去的请求会不会被拦截住;

在这里插入图片描述

断点被Feign拦截器(RequestInterceptor)拦住,将参数放入到请求头中,放行……

在这里插入图片描述

请求又来到了MVC拦截器,注意哦,这里不是订单服务,是库存服务的拦截器;

itemId

这样,就实现了用户参数在微服务系统中的流转。

总结

使用一前一后两个拦截器,对微服务接受请求前,发送请求前分别进行拦截,实现参数透传。但是需要注意以下两点:

  • 两个拦截器所依赖的是本地线程池,所以如果微服务发出的是异步消息(如MQ),就行不通(参考:http://t.csdn.cn/wWsb7);

  • 拦截器是写在共同模块中的,所以参数得以流转的前提是当前微服务引用了共同模块

猜你喜欢

转载自blog.csdn.net/qq_42108331/article/details/132293110