spring boot项目使用threadlocal存储用户信息实现数据权限控制

一、博客背景

    项目是前后端分离的spring boot项目,前端将标识用户信息的code存储在cookie中,当实现从cookie中获取到code值后,再查询数据库获取数据用户信息后,如何将用户信息一层一层的往下传递?有人可能将用户信息存储到HttpServletRequest请求中,后续需要用到用户信息的时候,再从HttpServletRequest中获取。如果在dao层需要用户信息,还需要将HttpServletRequest一层一层传递,或者说在controller中获取到用户信息一层一层传递。而我的做法是用ThreadLocal来存储用户信息,这样在后续的代码里可以直接获取用户信息。

二、实现原理

1.对于请求到后端来的请求,每次都是不同的线程执行。即一次请求的发起,就会从线程池中取出一个线程取执行业务。详情可自行了解。

2.ThreadLocal提供线程局部变量;一个变量用在多个线程中分别有独立的值(副本)

三、代码实现

UserContext类,定义ThreadLocal的静态变量,用于存储用户信息

import com.tp.common.exception.TokenException;
import com.tp.common.model.BaseUserDTO;


public class UserContext {
    private static final ThreadLocal<BaseUserDTO> user = new ThreadLocal<>();

    private UserContext() {
    }

    public static Long getUserId() {
        BaseUserDTO baseUserDTO = getUser();
        return baseUserDTO.getId();
    }

    public static BaseUserDTO getUser() {
        BaseUserDTO baseUser = user.get();
        if (null == baseUser) {
            throw new TokenException("登录失效,请重新登录!");
        }
        return baseUser;
    }



    public static void setBaseUser(BaseUserDTO baseUser) {
        user.set(baseUser);
    }

    public static void remove() {
        user.remove();
    }

}

拦截器类,用于设置用户信息到ThreadLocal中,并且在DispatcherServlet完全处理完请求后移除ThreadLoca中的用户信息

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;


import com.tp.common.constant.CacheConstant;
import com.tp.common.constant.CommonConstant;
import com.tp.common.exception.TokenException;
import com.tp.common.model.BaseUserDTO;
import com.tp.common.utils.IpUtils;
import com.tp.dao.model.query.UserQuery;
import com.tp.dao.model.user.User;
import com.tp.service.cache.CacheService;
import com.tp.service.user.UserService;
import com.tp.service.threadlocal.UserContext;
import com.tp.service.utils.JwtUtil;
import com.tp.web.config.ExampleDataIdConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.text.ParseException;
import java.util.List;


@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {

    @Autowired
    private UserService userService;

    @Autowired
    private CacheService cacheService;

    @Autowired
    ExampleDataIdConfig exampleDataIdConfig;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ParseException {
        

        String token = getToken(request);
        log.info("token值:{}", token);
        if (StringUtils.isBlank(token)) {
            log.error("url:{} token is null", request.getRequestURL());
            throw new TokenException(IpUtils.getIpAddr(request), "登录失效,请重新登录!");
        }


        //验证token是否过期
        try {
            DecodedJWT decodedJWT = JwtUtil.parseToken(token);
            Claim userCodeClaim = decodedJWT.getClaim("code");
            String code = userCodeClaim != null ? userCodeClaim.as(String.class) : null;
            if (code == null) {
                log.error("code is null");
                throw new TokenException("登录失效,请重新登录!code is null");
            }

            User user = null;
            String key = CacheConstant.USER_INFO + code;
            JSONObject value = (JSONObject) cacheService.get(key);
            if (value != null) {
                user = JSON.toJavaObject(value, User.class);
            } else {
                UserQuery query = new UserQuery();
                query.setCode(code);
                List<User> userList = userService.getUserListByQuery(query);
                if (CollectionUtils.isEmpty(userList)) {
                    log.error("userList is null");
                    throw new TokenException("登录失效,请重新登录!msg:用户不存在");
                }
                user = userList.get(0);
                cacheService.setExpire(key, user, CacheConstant.KEY_NO_EXIST, CacheConstant.SECOND, CacheConstant.USER_INFO_EXPIRE_TIME);
            }
            BaseUserDTO baseUserDTO = new BaseUserDTO();
            baseUserDTO.setCode(user.getCode());
            baseUserDTO.setId(user.getId());
            baseUserDTO.setPhone(user.getPhone());
            UserContext.setBaseUser(baseUserDTO);
            log.info("登录信息:" + JSON.toJSONString(baseUserDTO));
        } catch (Exception e) {
            log.error("登录失效,请重新登录!", e);
            throw new TokenException("登录失效,请重新登录!", e);
        }
        return true;
    }

    

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                           ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        UserContext.remove();
    }

   
}

service方法中使用

    /**
     * 获取手机号
     *
     * @return 手机号
     */
    private String getPhone() {
        String phone;
        try {
            phone = UserContext.getUser().getPhone();
        } catch (Exception exception) {
            phone = "no login";
        }
        return phone;
    }

四、代码讲解

1、拦截器

preHandle:在业务处理器处理请求之前被调用。预处理,可以进行编码、安全控制、权限校验等处理;
postHandle:在业务处理器处理请求执行完成后,生成视图之前执行。后处理(调用了Service并返回ModelAndView,但未进行页面渲染),有机会修改ModelAndView (这个博主就基本不怎么用了);
afterCompletion:在DispatcherServlet完全处理完请求后被调用,可用于清理资源等。返回处理(已经渲染了页面);


2、ThreadLocal

ThreadLocal的作用主要是做数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的,在多线程环境下,如何防止自己的变量被其它线程篡改。详情可以自行了解

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ThreadLocal是一种线程局部变量,可以在每个线程存储和访问数据,而不会被其他线程访问到。在多线程环境下,可以使用ThreadLocal存储用户信息,以便在后续的代码直接获取用户信息。 具体实现原理如下: 1. 在UserContext类定义了一个静态的ThreadLocal变量,用于存储用户信息\[2\]。 2. 当用户登录成功后,将用户信息存储ThreadLocal,通过调用setBaseUser方法\[2\]。 3. 在后续的代码,可以通过调用getUser方法来获取当前线程存储用户信息\[2\]。 4. 当用户请求处理完成后,可以通过调用remove方法来清除当前线程存储用户信息,以防止内存泄漏\[2\]。 使用ThreadLocal存储用户信息的好处是可以避免在代码一层一层传递HttpServletRequest或其他对象来获取用户信息\[3\]。每个线程都有自己独立的ThreadLocal变量,可以在当前线程直接获取用户信息,提高了代码的简洁性和可读性。 需要注意的是,由于ThreadLocal的特性,需要在适当的时候调用remove方法来清除ThreadLocal数据,以防止内存泄漏\[1\]。在登录拦截,可以在controller执行完成后的afterCompletion方法调用remove方法来清除用户信息\[1\]。 总结起来,ThreadLocal可以用于存储用户信息,通过在每个线程存储和获取数据,避免了在代码一层一层传递对象的繁琐操作,提高了代码的简洁性和可读性。同时,需要注意在适当的时候调用remove方法来清除ThreadLocal数据,以防止内存泄漏。 #### 引用[.reference_title] - *1* [ThreadLocal存储用户登录信息](https://blog.csdn.net/weixin_46000937/article/details/126828778)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v4^insert_chatgpt"}} ] [.reference_item] - *2* *3* [spring boot项目使用threadlocal存储用户信息实现数据权限控制](https://blog.csdn.net/bird_tp/article/details/117297856)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v4^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值