1.代码逻辑流程图:
这里存储用户信息使用hash结构进行存储。 使用String和json存储不是那么方便。
因为原本使用session的方案会自动帮我们进行校验,这里我们使用redis进行代替有很多东西都需要我们自己去存储。前端会把token存放在请求头中,我们每次校验的使用从请求头中获取即可。
2.发送验证码的代码:
public Result sendCode(String phone, HttpSession session) {
// 校验手机号
if(RegexUtils.isPhoneInvalid(phone)){
// 如果不符合,返回错误信息
return Result.fail("手机号格式错误");
}
// 符合生成验证码
String code = RandomUtil.randomNumbers(6);
// 保存验证码到redis中
stringRedisTemplate.opsForValue().set(RedisConstants.LOGIN_CODE_KEY + phone, code
,RedisConstants.LOGIN_CODE_TTL
,TimeUnit.MINUTES);
// 发送验证码
log.debug("发送验证码成功,验证码:{}",code);
return Result.ok();
}
3.重新实现登录的代码:
public Result login(LoginFormDTO loginForm, HttpSession session) {
// 校验手机号和验证码
String phone = loginForm.getPhone();
if(RegexUtils.isPhoneInvalid(phone)){
// 如果不符合,返回错误信息
return Result.fail("手机号格式错误");
}
// 从redis中获取验证码并校验验证码
String cacheCode = stringRedisTemplate.opsForValue()
.get(RedisConstants.LOGIN_CODE_KEY + phone);
String code = loginForm.getCode();
if(cacheCode == null || !cacheCode.equals(code)){
return Result.fail("验证码错误");
}
// 根据手机号查询用户
User user = query().eq("phone",phone).one();
if(user == null){
user = createUserWithPhone(phone);
}
//保存用户信息到redis中
String token = UUID.randomUUID().toString(true);
String tokenKey = RedisConstants.LOGIN_USER_KEY + token;
UserDTO userDTO = BeanUtil.copyProperties(user,UserDTO.class);
Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>()
, CopyOptions.create()
.setIgnoreNullValue(true)
.setFieldValueEditor((fieldName,fieldValue) -> fieldValue.toString())); // 把User转换成map
stringRedisTemplate.opsForHash().putAll(tokenKey,userMap);//存储用户信息
stringRedisTemplate.expire(tokenKey,RedisConstants.LOGIN_USER_TTL,TimeUnit.MINUTES); // 设置有效期
return Result.ok(token);
}
4.重新实现登录校验的代码逻辑:
这里就是重新实现拦截器的代码
public class RefreshTokenInterceptor implements HandlerInterceptor {
private StringRedisTemplate stringRedisTemplate;
public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1.获取请求头中的token
String token = request.getHeader("authorization");
if (StrUtil.isBlank(token)) {
return true;
}
// 2.基于TOKEN获取redis中的用户
String key = LOGIN_USER_KEY + token;
Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);
// 3.判断用户是否存在
if (userMap.isEmpty()) {
return true;
}
// 5.将查询到的hash数据转为UserDTO
UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
// 6.存在,保存用户信息到 ThreadLocal
UserHolder.saveUser(userDTO);
// 7.刷新token有效期
stringRedisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.MINUTES);
// 8.放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 移除用户
UserHolder.removeUser();
}
}
这里因为我们设置的token有过期删除时间,所以我们每次操作经过拦截器的时候,都需要刷新过期时间。然后因为有的接口因没有使用拦截器但是我们也需要刷新过期时间,所以建议把更新过期时间和拦截未登录用户写成两个拦截器。但是这里的代码是写在一起的,可以自己改一下。