集群的session共享问题:多台tomcat并不共享session存储空间,当请求切换到不同tomcat服务时导致数据丢失的问题。
session的替代方案应该满足:
使用redis代替session:
生成验证码之后,以手机号为key,验证码为value,类型为string存入redis。
@Resource
private StringRedisTemplate stringRedisTemplate;
public Result sendcode(String phone, HttpSession session){
//1.判断手机是否正确
if (RegexUtils.isPhoneInvalid(phone)) {
//验证失败,返回错误信息
return Result.fail("手机格式输入错误!");
}
//2.生成验证码
String code = RandomUtil.randomNumbers(6);
//3.把验证码保存到redis,设置短信验证码有效期
stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone,code,LOGIN_CODE_TTL, TimeUnit.MINUTES);
//4.发送验证码
log.debug("发送短信验证码成功,验证码{}",code);
return Result.ok();
}
以手机号码为key去redis读取验证码,校验是否一致,如果一致以随机生成的token为key,用户信息以hash类型为value存入redis数据库,并返回给客户端(一般是浏览器)
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
//1.校验手机
String phone = loginForm.getPhone();
if (RegexUtils.isPhoneInvalid(phone)) {
//验证失败,返回错误信息
return Result.fail("手机格式输入错误!");
}
//TODO 2.从redis获取验证码并校验验证码
String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
if (cacheCode == null || !cacheCode.equals(loginForm.getCode())){
return Result.fail("验证码错误请重试!");
}
// 3.查询手机是否在数据库里
User user = query().eq("phone", phone).one();
//4.如果不在就把该用户注册进数据库
if (user == null){
user = creatUserByPhone(phone);
}
// 5.把用户放入redis
//5.1随机生成token,作为登录令牌
String token = UUID.fastUUID().toString(true);
// 5.2将user对象转换为Hashmap存储
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
Map userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(), CopyOptions.create().
setIgnoreNullValue(true).setFieldValueEditor((fieldName,fieldValue) -> fieldValue.toString()));
// 5.3存储到redis
stringRedisTemplate.opsForHash().putAll(LOGIN_USER_KEY + token,userMap);
//5.4设置token有效期
stringRedisTemplate.expire(LOGIN_USER_KEY+token,LOGIN_USER_TTL,TimeUnit.MINUTES);
// 6返回token给客户端
return Result.ok(token);
}
因为在短信验证码登陆注册功能中把用户的token保存在了客户端(一般是浏览器),那么用户每次访问都会携带这个token,就可以根据这个token去redis里面查询用户信息。
一开始的拦截器设置是要用户有操作才会刷新用户的token保存时间,优化拦截器之后无论访问的是不是需要用户登录的页面都可以刷新token有效期
public class RefreshTokenInterceptor implements HandlerInterceptor {
/*
因为这个类RefreshTokenInterceptor对象是我们自己在需要使用到它的时候手动New出来的,
不是由spring创建的,所以没办法用@Resource/@Autowired这些注解来注入StringRedisTemplate属性
那谁来注入这个StringRedisTemplate呢?就看是谁用到了RefreshTokenInterceptor类的对象,
就在用到的那里注入那谁来注入这个StringRedisTemplate,比如这里是在MvcConfig类中
*/
private StringRedisTemplate stringRedisTemplate;
public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//获取请求头中的token,根据前端代码中定义的变量名来取
String token = request.getHeader("authorization");
//判断token是否为空
if (StrUtil.isBlank(token)) {
return true;
}
//基于token获取redis用户
Map
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判断是否需要拦截(ThreadLocal中是否有用户)
if(UserHolder.getUser() == null){
response.setStatus(401);
return false;
}
return true;
}
}