一、前言
项目要实现用户信息全局可取,经百度发现使用ThreadLocal可实现。
每一个请求都是一个线程,将用户信息由session保存,在该请求执行时,将其存入ThreadLocal线程本地中,可保证该请求单一用户。
防止内存泄漏,在请求执行完毕时,将ThreadLocal移除。
二、实现思路
- 登录:session--存放用户信息
- 登出:session--移除用户信息
- 请求拦截:ThreadLocal--拦截器校验session用户,存在则将用户信息存入ThreadLocal中,不存在则跳转登录页面
- 使用:调用ThreadLocal的用户信息
- 请求后:ThreadLocal--remove移除
- 优化:将已登录用户集合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);
}
}
}