当前位置:   article > 正文

Redis实战案例及问题分析之redis实现短信登陆_stringredistemplate.opsforvalue().set(login_code_k

stringredistemplate.opsforvalue().set(login_code_key + phone, code, login_co

短信登陆

基于session短信的登陆存在的问题:

集群的session共享问题:多台tomcat并不共享session存储空间,当请求切换到不同tomcat服务时导致数据丢失的问题。

session的替代方案应该满足:

  • 数据共享
  • 内存数据:读写效率高
  • key、value结构:方便存储方案

使用redis代替session:

  • redis是tomcat以外的存储方案,所以任意一台tomcat都能访问到redis拿到数据实现数据共享
  • redis是内存存储
  • redis是key、value结构

 基于redis实现共享session登录

发送验证码功能:

生成验证码之后,以手机号为key,验证码为value,类型为string存入redis。

  1. @Resource
  2. private StringRedisTemplate stringRedisTemplate;
  3. public Result sendcode(String phone, HttpSession session){
  4. //1.判断手机是否正确
  5. if (RegexUtils.isPhoneInvalid(phone)) {
  6. //验证失败,返回错误信息
  7. return Result.fail("手机格式输入错误!");
  8. }
  9. //2.生成验证码
  10. String code = RandomUtil.randomNumbers(6);
  11. //3.把验证码保存到redis,设置短信验证码有效期
  12. stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone,code,LOGIN_CODE_TTL, TimeUnit.MINUTES);
  13. //4.发送验证码
  14. log.debug("发送短信验证码成功,验证码{}",code);
  15. return Result.ok();
  16. }

短信验证码登陆注册:

以手机号码为key去redis读取验证码,校验是否一致,如果一致以随机生成的token为key,用户信息以hash类型为value存入redis数据库,并返回给客户端(一般是浏览器)

  1. @Override
  2. public Result login(LoginFormDTO loginForm, HttpSession session) {
  3. //1.校验手机
  4. String phone = loginForm.getPhone();
  5. if (RegexUtils.isPhoneInvalid(phone)) {
  6. //验证失败,返回错误信息
  7. return Result.fail("手机格式输入错误!");
  8. }
  9. //TODO 2.从redis获取验证码并校验验证码
  10. String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
  11. if (cacheCode == null || !cacheCode.equals(loginForm.getCode())){
  12. return Result.fail("验证码错误请重试!");
  13. }
  14. // 3.查询手机是否在数据库里
  15. User user = query().eq("phone", phone).one();
  16. //4.如果不在就把该用户注册进数据库
  17. if (user == null){
  18. user = creatUserByPhone(phone);
  19. }
  20. // 5.把用户放入redis
  21. //5.1随机生成token,作为登录令牌
  22. String token = UUID.fastUUID().toString(true);
  23. // 5.2将user对象转换为Hashmap存储
  24. UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
  25. Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(), CopyOptions.create().
  26. setIgnoreNullValue(true).setFieldValueEditor((fieldName,fieldValue) -> fieldValue.toString()));
  27. // 5.3存储到redis
  28. stringRedisTemplate.opsForHash().putAll(LOGIN_USER_KEY + token,userMap);
  29. //5.4设置token有效期
  30. stringRedisTemplate.expire(LOGIN_USER_KEY+token,LOGIN_USER_TTL,TimeUnit.MINUTES);
  31. // 6返回token给客户端
  32. return Result.ok(token);
  33. }

 校验登录功能:

因为在短信验证码登陆注册功能中把用户的token保存在了客户端(一般是浏览器),那么用户每次访问都会携带这个token,就可以根据这个token去redis里面查询用户信息。

拦截器优化:

一开始的拦截器设置是要用户有操作才会刷新用户的token保存时间,优化拦截器之后无论访问的是不是需要用户登录的页面都可以刷新token有效期

  1. public class RefreshTokenInterceptor implements HandlerInterceptor {
  2. /*
  3. 因为这个类RefreshTokenInterceptor对象是我们自己在需要使用到它的时候手动New出来的,
  4. 不是由spring创建的,所以没办法用@Resource/@Autowired这些注解来注入StringRedisTemplate属性
  5. 那谁来注入这个StringRedisTemplate呢?就看是谁用到了RefreshTokenInterceptor类的对象,
  6. 就在用到的那里注入那谁来注入这个StringRedisTemplate,比如这里是在MvcConfig类中
  7. */
  8. private StringRedisTemplate stringRedisTemplate;
  9. public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {
  10. this.stringRedisTemplate = stringRedisTemplate;
  11. }
  12. @Override
  13. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  14. //获取请求头中的token,根据前端代码中定义的变量名来取
  15. String token = request.getHeader("authorization");
  16. //判断token是否为空
  17. if (StrUtil.isBlank(token)) {
  18. return true;
  19. }
  20. //基于token获取redis用户
  21. Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(LOGIN_USER_KEY + token);
  22. //判断用户是否存在
  23. //如果不存在
  24. if (userMap.isEmpty()) {
  25. return true;
  26. }
  27. //将查询到的Hash数据转化为UserDTO对象
  28. UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
  29. //用户存在,保存到threadlocal
  30. UserHolder.saveUser(userDTO);
  31. //刷新token有效期
  32. stringRedisTemplate.expire(LOGIN_USER_KEY + token,LOGIN_USER_TTL, TimeUnit.MINUTES);
  33. return true;
  34. }
  35. @Override
  36. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
  37. //移除用户
  38. UserHolder.removeUser();
  39. }
  40. }
  1. public class LoginInterceptor implements HandlerInterceptor {
  2. @Override
  3. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  4. //判断是否需要拦截(ThreadLocal中是否有用户)
  5. if(UserHolder.getUser() == null){
  6. response.setStatus(401);
  7. return false;
  8. }
  9. return true;
  10. }
  11. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/448500
推荐阅读
相关标签
  

闽ICP备14008679号