• Redis实战案例及问题分析之redis实现短信登陆


    短信登陆

    基于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. }

  • 相关阅读:
    Texlive安装
    大数据之hadoop入门
    高等数学刷题
    在CorelDraw中,VBA宏调用是如何执行的?
    【Mysql】mysql | 命令 | 常用命令 | 登录指明端口
    14:00面试,14:06就出来了,问的问题有点变态。。。。。。
    RK3568驱动指南|第六期-平台总线-第51章 注册platform设备实验
    FlinkCDC基础篇章1-安装使用
    langchain介绍之-Prompt
    微信公众号如何通过迁移变更主体?
  • 原文地址:https://blog.csdn.net/PnJgHT/article/details/125436198