微服务获取登录用户Id与单体服务下获取用户Id对比(黑马头条Day03)

前置声明

        当前前后端分离开发项目中,后端某个请求向具体某个数据库中的多个表插入数据时,经常需要使用到当前登录用户的Id(唯一标识)。在当前用户线程下以实现变量共享,同时为了避免不同用户线程之间操作变量的影响,往往选择ThreadLocal本地线程变量实现线程内变量的共享读取,同时避免不同线程间操作的影响。

         产生第一个问题?我们如何获取用户的线程Id并将其存入到本地线程变量ThreadLocal中?

        http连接是无状态的,当前请求不会共享上一次请求的数据,所以需要结果Cookie、Session或Token一起使用,同时Token结合JWT令牌尝尝用于用户的登录校验,只有通过验证的连接才可以执行后续请求,没有通过登录验证的连接直接拒绝本次请求,提醒用户进行登录授权在用户首次登陆时服务端会利用JWT令牌技术生成Token,并将其一起返回给用户,随后用户访问服务端时携带第一次生成的Token,服务端可以实现用户登陆校验。在生成Token的同时可以在payLoad中加入用户Id信息,实现每次连接服务端解析Token获取当前登陆用户Id的目的。

 单体服务(苍穹外卖)如何获取用户Id

实现原理

        苍穹外卖作为一个单体项目,整个项目内只有一个启动类,所有可以认为一个连接就是一个线程,项目结构如下:

        在单体项目中获取用户Id并将其存入ThreadLocal变量中可以在项目拦截器中的以实现,具体流程如下:

        在生成JWT Token的过程中返给UserId,在拦截请求验证过程中解析Token获取用户Id。

实现步骤

  • 创建ThreadLocal类,主要功能包括设置UerId,获取UserId,清除UserId。
public class BaseContext {

    public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();

    /**
     * 设置UserId值
     * @param id
     */
    public static void setCurrentId(Long id) {
        threadLocal.set(id);
    }

    /**
     * 获取UserId值
     * @return
     */
    public static Long getCurrentId() {
        return threadLocal.get();
    }

    /**
     * 请求局部变量ThreadLocal中的UserId,防止内存溢出
     */
    public static void removeCurrentId() {
        threadLocal.remove();
    }
}
  • 在拦截器中解析Token获取用户Id,并将其存入上面创建的BaseContext类中。 
    /**
     * 校验jwt
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判断当前拦截到的是Controller的方法还是其他资源
        if (!(handler instanceof HandlerMethod)) {
            //当前拦截到的不是动态方法,直接放行
            return true;
        }

        //1、从请求头中获取令牌
        String token = request.getHeader(jwtProperties.getUserTokenName());

        //2、校验令牌
        try {
            log.info("jwt校验:{}", token);
            Claims claims = JwtUtil.parseJWT(jwtProperties.getUserSecretKey(), token);
            Long userId = Long.valueOf(claims.get(JwtClaimsConstant.USER_ID).toString());
            log.info("当前员工id:{}", userId);

            // 将当前登陆ID放入ThreadLocal中
            BaseContext.setCurrentId(userId);
            //3、通过,放行
            return true;
        } catch (Exception ex) {
            //4、不通过,响应401状态码
            response.setStatus(401);
            return false;
        }
    }
  • 上述代码块中主要使用的如下:直接调用静态方法setCurrentIdUserId添加到创建的ThreadLocal中。

 

  • 此后直接从BaseContext中使用getCurrentId(...)获取当前登陆用户的Id。 

微服务(黑马头条)如何获取用户Id

实现原理

        相信做过黑马头条这个项目的同伴都不会陌生微服务这个概念,在黑马头条这个微服务项目中涉及到多个微服务,如:网关微服务、文章微服务、用户微服务......。各个微服务均有属于各自的启动类,各个微服务通过阿里开源nacos实现服务注册与分发,在这个项目中自媒体网关微服务自媒体微服务通过nacos将其关联起来,自媒体相关请求到达时首先通过Nginx方向代理将其分配到自媒体网关微服务接口,对其进行登陆校验(Token解析相关工作),通过验证的请求,nacos会将其分发到自媒体微服务接口,实现后续操作。 两个微服务各自有各自的启动类,二者之间并不直接联系,对应不同的线程。如何使用TheradLocal实现共享变量的线程内部使用。

        首先在自媒体网关微服务中对Token进行解析,并将其存入Header中,随后路由(重置请求)将其发送到自媒体微服务中,后续逻辑同单体服务。

实现步骤

  • 构建ThreadLocal类。
public class WmThreadLocalUtil {
    private static ThreadLocal<WmUser> WM_USER_THREAD_LOCAL = new ThreadLocal<>();

    /**
     * 设置用户信息
     * @param wmUser
     */
    public static void setUser(WmUser wmUser){
        WM_USER_THREAD_LOCAL.set(wmUser);
    }

    /**
     * 获取用户信息
     * @return
     */
    public static WmUser getUser(){
        return WM_USER_THREAD_LOCAL.get();
    }

    /**
     * 清理用户信息
     */
    public static void clear(){
        WM_USER_THREAD_LOCAL.remove();
    }
}
  • 自媒体网关微服务中对登陆进行校验并获取用户信息。 

  • 自媒体微服务中配置拦截器对header进行拦截并进行存储。
@Slf4j
@Component
public class WmTokenInterceptor implements HandlerInterceptor {


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String userId = request.getHeader("userId");
        if(userId != null){
            log.info("将用户信息存入到ThreadLocal中:{}", userId);
            WmUser wmUser = new WmUser();
            wmUser.setId(Integer.valueOf(userId));
            WmThreadLocalUtil.setUser(wmUser);
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
//        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
        log.info("清理ThreadLocal中的用户信息");
        WmThreadLocalUtil.clear();
    }

}

        在上面代码中重写postHandle(...)方法对ThreadLocal中的变量进行及时清除。防止内存溢出。

参考

        苍穹外卖

        黑马头条

  • 38
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值