一、前言

     项目要实现用户信息全局可取,经百度发现使用ThreadLocal可实现。

每一个请求都是一个线程,将用户信息由session保存,在该请求执行时,将其存入ThreadLocal线程本地中,可保证该请求单一用户。

防止内存泄漏,在请求执行完毕时,将ThreadLocal移除。

二、实现思路

  1. 登录:session--存放用户信息
  2. 登出:session--移除用户信息
  3. 请求拦截:ThreadLocal--拦截器校验session用户,存在则将用户信息存入ThreadLocal中,不存在则跳转登录页面
  4. 使用:调用ThreadLocal的用户信息
  5. 请求后:ThreadLocal--remove移除
  6. 优化:将已登录用户集合list持久化到内存/数据库中,如果登出/session失效,则list.remove

三、关键代码

ThreadLocalHolder:线程本地持有者:存储当前请求线程的登录用户

/**
 * 线程本地持有者:存储当前请求线程的登录用户
 */
public class ThreadLocalHolder{

    private final static ThreadLocal<OnlineUser> ThreadLocalHolder = new ThreadLocal<>();

    /**
     * 重写ThreadLocal的三个方法:set、get、remove
     */
    public static void set(OnlineUser user) {
        ThreadLocalHolder.set(user);
    }

    public static OnlineUser get() {
        return ThreadLocalHolder.get();
    }

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

    /**
     * 获取登录用户的常用信息:id、姓名
     */
    public static Long getId() {
        return ThreadLocalHolder.get().getId();
    }

    public static String getName() {
        return ThreadLocalHolder.get().getName();
    }

}

OnlineUser :全局,当前登录用户信息存储DTO

/**
 * 全局,当前登录用户信息存储DTO
 */
public class OnlineUser implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 用户ID
     */
    private Long id;

    /**
     * 姓名
     */
    private String name;


//***************** get/set **************************
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }


//***************** 构造器 **************************
    public OnlineUser() {
    }

    public OnlineUser(Long id, String name) {
        this.id = id;
        this.name = name;
    }
}

登录校验

/**
 * 登录验证
 */
@Controller
public class SysLoginController extends BaseController {

    @RequestMapping(value = "/login", method = RequestMethod.POST)
    @ResponseBody
    public Result login(HttpServletRequest request, UserVO userVO) {
        User userSession = (User) request.getSession().getAttribute("user");
        //用户未登录
        if(userSession == null){
            //校验:名字是否空、用户是否存在、密码是否空、密码是否正确
            if (密码正确){
                //执行登陆后台的逻辑
                request.getSession().setAttribute("user",user);
                return Result.ok("登录成功");
            }else{
                return Result.fail("密码不正确,请重新输入");
            }
        }else{
            return Result.ok("登录成功");
        }
    }

    @ApiOperation("退出登录")
    @RequestMapping(value = "logout", method = RequestMethod.POST)
    @ResponseBody
    public Result logoutApi(HttpServletRequest request) {
        //销毁session
        request.getSession().invalidate();
        return Result.ok("退出登录成功");
    }

}

SessionInterceptorConfig :session拦截器

/**
 * session拦截器
 */
@Component
public class SessionInterceptorConfig implements HandlerInterceptor {

    private static Logger log = LoggerFactory.getLogger(SessionInterceptorConfig.class);
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        User user = (User) request.getSession().getAttribute("user");
        //如果用户不为空,则通过
        if (user != null) {
            //将在线用户存入线程本地ThreadLocal中
            OnlineUser onlineUser = new OnlineUser(user.getId(), user.getUserName());
            ThreadLocalHolder.set(onlineUser);
            return true;
        } else {
            log.info(">>>session过期, 跳至登录页");
            request.getRequestDispatcher("/login").forward(request, response);
            return false;
        }
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
        //将线程本地ThreadLocal中的在线用户移除(需要手动移除,否则会产生内存泄露)
        ThreadLocalHolder.remove();
    }
}

普及:HandlerInterceptor 

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

使用:

/**
 * 数据库拦截器
 */
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    private static final Logger LOGGER = LoggerFactory.getLogger(MyMetaObjectHandler.class);

    private String createTime = "createTime", createBy = "createBy", updateTime = "updateTime", updateBy = "updateBy";

    /**
     * 新增拦截
     */
    @Override
    public void insertFill(MetaObject metaObject) {
        LOGGER.info("start insert fill ....");
        Date date = new Date(System.currentTimeMillis());
        this.setInsertFieldValByName(createTime, date, metaObject);
        this.setInsertFieldValByName(updateTime, date, metaObject);
        try{
            //获取登录用户信息
            String loginUser = ThreadLocalHolder.getName();
            this.setInsertFieldValByName(createBy, loginUser, metaObject);
            this.setInsertFieldValByName(updateBy, loginUser, metaObject);
        }catch (Exception e){
            LOGGER.error("更新新增时间和人异常:[{}]" , e.getMessage() );
            this.setInsertFieldValByName(createBy, "", metaObject);
            this.setInsertFieldValByName(updateBy, "", metaObject);
        }
    }

    /**
     * 修改拦截
     */
    @Override
    public void updateFill(MetaObject metaObject) {
        LOGGER.info("start update fill ....");
        this.setUpdateFieldValByName(updateTime, new Date(System.currentTimeMillis()), metaObject);
        try{
            //获取登录用户信息
            String loginUser = ThreadLocalHolder.getName();
            this.setUpdateFieldValByName(updateBy, loginUser, metaObject);
        }catch (Exception e){
            LOGGER.error("更新修改时间和人异常:[{}]" , e.getMessage() );
            this.setUpdateFieldValByName(updateBy, "", metaObject);
        }
    }
}