系列文章目录
前言
在之前的政企系统中,我们随时都可能需要获取当前操作的用户信息。针对这个需求,我们使用
ThreadLocal
来解决。若使用了SpringSecurity,则SpringSecurity中提供了对应的解决方案(大概也是用ThreadLocal 实现的)。
此模块中演示ThreadLocal web项目中的使用姿势。
将token解析后,得到对应的用户信息User对象。把User对象存入ThreadLocal中,
那么我们就可以在一次请求的生命周期中(一次线程声明周期中)可以很方便的拿到这个User对象。
然后可以做成一个工具类如:UserUtils.getCurrentUser()来使用。
1. ThreadLocal在web项目中的使用姿势
由于程序是运⾏在web容器中,每⼀个HTTP请求都是⼀个独⽴线程,也就是可以理解成我们编写的应⽤程序运⾏在⼀个多线程的环境中,那么我们就可以使⽤
ThreadLocal
在HTTP请求的⽣命周期内进⾏存值、取值操作。
我们想要知道 执行当前请求的用户信息,如何知道呢?
即:我们可以在拦截器中解析token获取到用户的信息User对象,存入到
ThreadLocal
对象中,那么我们就可以随时随地的获取到 执行当前请求的用户信息User对象了。如下图:我们可以随时随地的获取到当前用户的信息。
我们对上图进行一个说明:
- ⽤户的每⼀个请求,都是⼀个独⽴的线程。
- 图中的TL就是ThreadLocal,⼀旦将数据绑定到ThreadLocal中,那么在整个请求的⽣命周期内都可以随时拿到ThreadLocal中当前线程的数据。
根据上⾯的分析,我们只需要在
Controller请求之前
进⾏对token做校验,如果token有效,则会拿到User对象,然后
将该User对象保存到ThreadLocal中即可
,最后放⾏请求,在后续的各个环节中都可以获取到该数据了。
如果token⽆效,给客户端响应401状态码,拦截请求,不再放⾏到Controller中。
由此可⻅,这个校验的逻辑是⽐较适合放在拦截器中完成的。
2. 代码实现
2.1 编写ThreadLocal工具类
public class UserUtils {
private static final ThreadLocal<User> LOCAL = new ThreadLocal<>();
private UserThreadLocal() {
}
/**
* 将对象放⼊到ThreadLocal
*
* @param user
*/
public static void set(User user) {
LOCAL.set(user);
}
/**
* 返回当前线程中的User对象
*
* @return
*/
public static User get() {
return LOCAL.get();
}
/**
* 删除当前线程中的User对象
*/
public static void remove() {
LOCAL.remove();
}
}
2.2 编写拦截器
编写拦截器:
@Component
public class UserTokenInterceptor implements HandlerInterceptor {
@Autowired
private UserService userService;
/**
* 请求前拦截
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse
response, Object handler) throws Exception {
/*//校验handler是否是HandlerMethod
if (!(handler instanceof HandlerMethod)) {
return true;
}
//判断是否包含@NoAuthorization注解,如果包含,直接放⾏
if (((HandlerMethod)
handler).hasMethodAnnotation(NoAuthorization.class)) {
return true;
}*/
//从请求头中获取token
String token = request.getHeader("Authorization");
if (!StringUtils.isEmpty(token)) {
// 解析Token,并查库获取User对象
User user = this.userService.queryUserByToken(token);
if (user != null) {
//token有效
//将User对象放⼊到ThreadLocal中
UserUtils.set(user);
return true;
}
}
//token⽆效,响应状态为401
response.setStatus(401); //⽆权限
return false;
}
/**
* 清理资源
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse
response, Object handler, Exception ex) throws Exception {
//从ThreadLocal中移除User对象
UserThreadLocal.remove();
}
}
注册拦截器:WebConfig
@Configuration
public class WebConfig implements WebMvcConfigurer {
//@Autowired
//private RedisCacheInterceptor redisCacheInterceptor;
@Autowired
private UserTokenInterceptor userTokenInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//需要考虑拦截器的顺序
registry.addInterceptor(this.userTokenInterceptor).addPathPatterns("/**");
//registry.addInterceptor(this.redisCacheInterceptor).addPathPatterns("/**");
}
}
然后在需要使用当前用户信息的地方直接:
User currentUser = UserUtils.get();