Redis从理论到实战:如何使用redis实现短信登录与注册?_使用redis来做一个短信验证注册账号(5)

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化资料的朋友,可以戳这里获取

加油加油,不要过度焦虑O(∩_∩)O


一、基于session实现短信登录

为了方便理解,先来看看不用redis,基于session来实现登录;然后分析存在的问题缺陷,最后用redis解决存在的问题!

1、实现发送验证码功能

在这里插入图片描述

思路分析:

  • 首先验证输入的手机号格式是否正确;不正确则重新输入手机号,正确则模拟发送6位数的验证码;
  • 最后把手机号和验证码保存到session中。

代码:

    @PostMapping("/code")
    public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
        return userService.generateCode(phone, session);
    }
    @Override
    public Result generateCode(String phone, HttpSession session) {
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号格式错误");
        }
        //手机号格式正确,则生成验证码
        String code = RandomUtil.randomNumbers(6);
        //将验证码和手机号保存到session
        session.setAttribute("code", code);
        session.setAttribute("phone", phone);
        log.info("生成的验证码是:{}", code);
        return Result.ok();
    }

2、实现用户登录和注册功能(小优化)

思路分析:

  • 从session中获取手机号(避免用户获取验证码后修改手机号),如果手机号跟表单中的手机号不一致(即用户修改了手机号),则让用户重新发送验证码;
  • 接着从session中获取验证码,如果表单中的验证码和发送的验证码不一致,则让用户重新输入验证码;
  • 然后从数据库中查询是否存在该手机号(即该用户是否已经注册过),如果不存在,则把该用户插入到数据库;
  • 最后返回用户信息(为了减少Tomcat服务器的内存消耗以及用户信息的安全,会使用UserDTO类来返回用户的部分信息)

代码:

    @PostMapping("/login")
    public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session) {
        return userService.login(loginForm, session);
    }
    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        //校验手机号和验证码
        String phone = (String) session.getAttribute("phone");
        if (!phone.equals(loginForm.getPhone())) {
            return Result.fail("手机号改变,请重新获取验证码");
        }
        String code = (String) session.getAttribute("code");
        String cacheCode = loginForm.getCode();
        if (cacheCode == null || !cacheCode.equals(code)) {
            return Result.fail("验证码错误");
        }
        //判断用户是否存在
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
                .eq(User::getPhone, phone);
        User user = userMapper.selectOne(wrapper);
        if (user == null) {
            user = createUser(phone);
        }
        //优化:减少Tomcat内存的使用并隐藏用户的敏感信息
        session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));
        return Result.ok();
    }
    /\*\*
 \* 创建用户
 \*
 \* @param phone
 \* @return
 \*/
    private User createUser(String phone) {
        User user = new User();
        user.setPhone(phone);
        user.setNickName(USER\_NICK\_NAME\_PREFIX + RandomUtil.randomString(8));
        userMapper.insert(user);
        return user;
    }
	@Data
	public class UserDTO {
	    private Long id;
	    private String nickName;
	    private String icon;
	}


3、实现登录校验拦截器功能

思路分析:

  • 为了避免在多个controller层中实现登录校验功能,可以使用拦截器,在访问controller之前,进行登录校验;
  • 在后续的业务中,因为要用到用户信息,所以要把拦截器中的用户信息传到controller中,并且,为了确保线程安全问题,可以使用ThreadLocal类。我们可以把拦截到的用户信息保存到ThreadLocal对象中。
  • Java中的ThreadLocal详解

public class UserHolder {
    private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();
    public static void saveUser(UserDTO user){
        tl.set(user);
    }
    public static UserDTO getUser(){
        return tl.get();
    }
    public static void removeUser(){
        tl.remove();
    }
}


拦截器配置代码:

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession();
        UserDTO user = (UserDTO) session.getAttribute("user");
        if (user == null) {
            response.setStatus(401);
            return false;
        }
        UserHolder.saveUser(user);
        return true;
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserHolder.removeUser();
    }
}
@Configuration
public class MyConfiguration implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .excludePathPatterns(
                        "/shop/\*\*",
                        "/voucher/\*\*",
                        "/shop-type/\*\*",
                        "/upload/\*\*",
                        "/blog/hot",
                        "/user/code",
                        "/user/login"
                );
    }
}


controller层代码:

    @GetMapping("/me")
    public Result me() {
        UserDTO user = UserHolder.getUser();
        return Result.ok(user);
    }


最后来看看基于session登录的完整流程:

在这里插入图片描述


二、session共享的问题分析

为什么使用Redis实现登录功能,而不使用基于Session实现登录功能?考虑到多台Tomcat并不共享session存储空间(虽然多台Tomcat可以对数据进行拷贝,但是不仅会造成内存空间的浪费,而且还会因为存在数据拷贝时间上的延迟,如果在延迟时间内有使用者来访问,依然会出现数据不一致的情况!),当请求切换到不同tomcat服务时会导致数据丢失!所以,我们的解决方案应该满足:数据共享、内存存储、key-value结构。


三、基于Redis实现短信登录

1、实现发送验证码功能

思路分析:

  • 跟基于session发送验证码不同的是:我们把验证码放到redis数据库中,并设置验证码过期时间;
  • 使用字符串数据结构:以手机号为key,验证码为value。

代码实现:

    @PostMapping("/code")
    public Result sendCode(@RequestParam("phone") String phone) {
        return userService.generateCode(phone);
    }
    @Override
    public Result generateCode(String phone) {
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号格式错误");
        }
        String code = RandomUtil.randomNumbers(6);
        //设置验证码过期时间为2minutes
        redisTemplate.opsForValue().set(LOGIN\_CODE\_KEY + phone, code, LOGIN\_CODE\_TTL, TimeUnit.MINUTES);
        log.info("生成的验证码是:{}", code);
        return Result.ok();
    }


2、实现用户登录和注册功能

思路分析:

  • 跟基于session实现用户登录和注册不同的是:我们把用户对象放到redis数据库中并设置有效期;
  • 对象存储使用哈希结构,随机生成token,作为登录令牌,并以token作为对象存储的key,对象值为value。

代码实现:

    @Override
    public Result login(LoginFormDTO loginForm) {
        String phone = loginForm.getPhone();
        if (!redisTemplate.hasKey(LOGIN\_CODE\_KEY + phone)) {
            return Result.fail("手机号改变,请重新获取验证码");
        }
        String cacheCode = redisTemplate.opsForValue().get(LOGIN\_CODE\_KEY + phone);
        String code = loginForm.getCode();
        if (code == null || !code.equals(cacheCode)) {
            return Result.fail("验证码错误");
        }
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
                .eq(User::getPhone, phone);
        User user = userMapper.selectOne(wrapper);
        if (user == null) {
            user = createUser(phone);
        }
        //保存用户信息到redis中
        String token = UUID.randomUUID().toString(true);
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        HashMap<String, String> hashMap = getHashMap(userDTO);
        token = LOGIN\_USER\_KEY + token;


![img](https://img-blog.csdnimg.cn/img_convert/cb770854f5a597e5c919f337f5f54124.png)
![img](https://img-blog.csdnimg.cn/img_convert/36b989711f937cbbe8d46870f2ce4d9e.png)

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

OGIN\_USER\_KEY + token;


[外链图片转存中...(img-QKT7iZCN-1715337317403)]
[外链图片转存中...(img-raKWm3pL-1715337317403)]

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 23
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值