说明:在开发中,我们经常需要获取当前操作的用户信息,如创建用户、创建订单时,我们需要记录下创建人,本文介绍获取当前用户信息的三种方式。
ThreadLocal本质上是一个Map,键是当前线程,值是存入的信息。我们可以在用户登录,校验用户信息后,将所需要的用户信息存入到ThreadLocal中,如用户ID、用户Token等,然后在需要的时候直接使用即可。
如下,在preHandle()方法中,将当前用户的ID存入到TokenThreadLocal对象中,
@Component
public class TokenInterceptor implements HandlerInterceptor {
@Value("${token.key}")
private String tokenKey;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取请求头 拿到Token
String token = request.getHeader("Token");
String authToken = request.getHeader("authentication");
if (StrUtil.isAllEmpty(token,authToken)){
return responseHandler(response);
}
// 校验Token的合法性和有效性
JWT jwt = JWTUtil.parseToken(StrUtil.isBlank(token) ? authToken : token);
try {
// 校验Token是否合法 校验Token是否过期
if (!(jwt.setKey(tokenKey.getBytes()).verify() && jwt.validate(0))){
return responseHandler(response);
}
} catch (Exception e) {
// 抛出自定义异常 Token是非法的
// throw new RuntimeException(e);
return responseHandler(response);
}
// 把Token的信息解析出来放到ThreadLocal
Long id = Convert.toLong(jwt.getPayload("id"));
// 设置本地线程池中的用户ID
TokenThreadLocal.set(id);
// 放行
return true;
}
本地线程对象,TokenThreadLocal
/**
* 本地线程对象
*
* 存放用户ID
*/
public class TokenThreadLocal {
/**
* 创建一个ThreadLocal对象
*/
private static final ThreadLocal<Long> THREAD_LOCAL= new ThreadLocal<>();
/**
* 添加一个数据
* @param key
*/
public static void set(Long key){
THREAD_LOCAL.set(key);
}
/**
* 获取一个数据
* @return
*/
public static Long get(){
return THREAD_LOCAL.get();
}
/**
* 删除一个数据
*/
public static void remove(){
THREAD_LOCAL.remove();
}
}
需要的时候,直接调用其get()方法,下面是使用AOP+自定义注解实现对创建、更新操作字段的填充;
注意,需要在afterCompletion()方法中调用ThreadLocal的remove()方法,避免内存泄漏;
如果项目中,登录校验框架使用的是Shiro,有一种更方便的方式,如下:
第一步:创建一个自定义注解,如LoginInfo,表示登录用户的信息,注意元注解的属性;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 登录用户
* @author
*/
@Target(value = ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginInfo {
}
第二步:创建MVC的配置类,注入一个在线用户解析对象,后面实现;
import org.decent.modules.integral.resolver.LoginUserArgumentResolver;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;
import java.util.List;
/**
* web设置
* @author
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Resource
private LoginUserArgumentResolver loginUserArgumentResolver;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(loginUserArgumentResolver);
}
}
第三步:创建在线用户解析类,其中获取当前用户的信息使用的是Shiro框架的方法,SecurityUtils.getSubject().getPrincipal()
,该方法返回的是一个Object类型的对象;
import org.apache.shiro.SecurityUtils;
import org.decent.modules.integral.annotation.LoginInfo;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* 登录解析实现
*
* @author
*/
@Component
public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.hasParameterAnnotation(LoginInfo.class);
}
@Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
return SecurityUtils.getSubject().getPrincipal();
}
}
第四步:创建一个在线用户的JavaBean,存放一些可能会用得上的属性;
import lombok.Data;
/**
* 在线用户信息
*/
@Data
public class LoginUser {
/**
* 登录人id
*/
private String id;
/**
* 登录人账号
*/
private String username;
/**
* 登录人名字
*/
private String realname;
}
第五步:在需要使用的接口上,直接使用注解即可。当请求访问该接口时,会被前面的拦截器拦截住,然后把当前用户的信息取出来并封装到JavaBean对象中,非常方便;
@PostMapping(value = "/add")
public Result<?> add(@LoginInfo LoginUser loginUser) {
......
}
这种方式思路和第一种相同,当用户通过校验时,将用户信息查询出来并存起来,需要的时候再取出来用。当然,使用Redis存储比ThreadLocal更灵活一点,可以设置有效时间。实现如下:
第一步:登录验证通过,将用户信息存入Redis;
@PostMapping("/login")
public Result<?> counterLogin(@RequestBody LoginBody LoginUser){
// 登录
LoginUser userInfo = sysLoginService.login(LoginUser.getUsername(), LoginUser.getPassword(),LoginUser.getCounterType());
// 创建Token并返回
return Result.success(tokenService.createToken(userInfo));
}
@Autowired
private RedisService redisService;
// 定义有效时间,为720 * 60 秒,即12小时
private final static long EXPIRE_TIME = 720 * 60;
/**
* 创建令牌
*/
public Map<String, Object> createToken(LoginUser loginUser){
// 生成token
String token = IdUtils.fastUUID();
loginUser.setToken(token);
loginUser.setUserid(loginUser.getSysUser().getUserId());
loginUser.setUsername(loginUser.getSysUser().getUserName());
// 保存用户token
Map<String, Object> map = new HashMap<String, Object>();
map.put("token", token);
map.put("loginUser",loginUser);
// 将该用户的信息存入到Redis中
redisService.setCacheObject(token, loginUser, EXPIRE_TIME, TimeUnit.SECONDS);
return map;
}
RedisService类相关方法
/**
* spring redis 工具类
**/
@Component
public class RedisService{
@Autowired
public RedisTemplate redisTemplate;
/**
* 缓存基本的对象
*/
public <T> void setCacheObject(final String key, final T value, final Long timeout, final TimeUnit timeUnit){
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 获得缓存的基本对象
*/
public <T> T getCacheObject(final String key){
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
}
第三步:需要时,根据当前用户的Token,去Redis中取出该用户的信息;
/**
* 根据用户Token获取用户身份信息
*
* @return 用户信息
*/
public LoginUser getLoginUser(String token){
if (StringUtils.isNotEmpty(token)){
String userKey = getTokenKey(token);
LoginUser user = redisService.getCacheObject(userKey);
return user;
}
return null;
}
用户的Token是需要放在Request对象里面的,所以可以再写一个TokenService对象,用来获取当前用户的Token,并调用RedisService获取当前用户信息,进行进一步的封装。
以上是三种获取当前用户信息的方式,可以根据实际情况选择;