Skip to content
DAILY QUOTE

“ ”

当随着业务的开发,越来越多的业务都需要去校验用户的登录,我们应该考虑把用户登录验证功能的逻辑抽取到一个地方,就是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);
}