当随着业务的开发,越来越多的业务都需要去校验用户的登录,我们应该考虑把用户登录验证功能的逻辑抽取到一个地方,就是SpringMVC的拦截器,它可以在请求所有Controller之前去做,用户的所有请求必须先经过拦截器,再由拦截器判断是否放行到对应的Controller。
第二个问题是,拦截器帮我们完成了对用户的校验,拿到了用户信息,那对应的Controller如何拿到用户信息呢?因此我们应该设计一个方案,将拦截器里得到的用户信息传递到Controller,在传递过程中需要保证线程安全问题。这个方案就是将用户信息保存到ThreadLocal中。
关于ThreadLocal:
ThreadLocal是一个线程域对象,每一个进入Tomcat的请求都是一个独立的线程,ThreadLocal会在当前用户线程内开辟一块独立的内存空间,保存信息到对应的ThreadLocalMap,保证每个线程互相不干扰。在ThreadLocal的源码中,无论是它的put方法还是get方法, 都是先从获得当前用户的线程,然后从线程中取出线程的成员变量map,只要线程不一样,map就不一样,所以可以通过这种方式来做到线程隔离。
存入ThreadLocal中的主要原因:
- (1)避免后续业务操作频繁向session域中存取数据,减少session的访问开销。
- (2)避免线程安全问题。

登录验证功能页面请求

拦截器:
java
public class LoginInterceptor implements HandlerInterceptor {
/**
* 执行preHandle相关业务逻辑。
*
* 作用:
* 1.获取Session;
* 2.判断用户是否在存在;
* 3.不存在,拦截,响应状态码401:未授权;
* 4.存在,保存用户信息到ThreadLocal;
* 5.放行;
*
* @param request request参数
* @param response response参数
* @param handler handler参数
* @return处理结果
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//获取session
HttpSession session = request.getSession();
Object user=session.getAttribute("user");
//判断用户是否在存在
if(user==null){
//不存在,拦截,响应状态码401:未授权
response.setStatus(401);
return false;
}
//存在,保存用户信息到ThreadLocal
UserHolder.saveUser((UserDTO)user);
//放行
return true;
}
/**
* 执行postHandle相关业务逻辑。
*
* @param request request参数
* @param response response参数
* @param handler handler参数
* @param modelAndView modelAndView参数
* @return无返回值
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
/**
* 执行afterCompletion相关业务逻辑。
*
* 作用:
* 1.移除用户,防止内存泄露;
*
* @param request request参数
* @param response response参数
* @param handler handler参数
* @param ex ex参数
* @return无返回值
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//移除用户,防止内存泄露
UserHolder.removeUser();
}
}注册拦截器:
java
@Configuration
public class MvcConfig implements WebMvcConfigurer {
/**
* 注册登录拦截器。
*
* @param registry registry参数
* @return无返回值
*/
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.excludePathPatterns(
"/user/code",
"/user/login",
"/blog/hot",
"/shop/**",
"/shop-type/**",
"/voucher/**",
"/upload/**"
);
}
}用户DTO,隐藏用户敏感信息:
java
public class UserDTO {
private Long id;
private String nickName;
private String icon;
}ThreadLocal工具类:
java
public class UserHolder {
private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();
/**
* 保存用户信息到ThreadLocal。
*
* @param user user参数
* @return无返回值
*/
public static void saveUser(UserDTO user){
tl.set(user);
}
/**
* 从ThreadLocal中获取当前用户。
*
* @return处理结果
*/
public static UserDTO getUser(){
return tl.get();
}
/**
* 清理ThreadLocal中的用户信息。
*
* @return无返回值
*/
public static void removeUser(){
tl.remove();
}
}查询登陆状态:
java
/**
* 获取当前登录用户信息。
*
* @return处理结果
*/
public Result me(){
// TODO获取当前登录的用户并返回
UserDTO user= UserHolder.getUser();
return Result.ok(user);
}