基于SpringBoot和Redis实现短信验证码功能

基于SpringBoot和Redis实现短信验证码功能

app中使用短信登录,使用redis实现

(一)准备工作

1.1引入相关依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <exclusions>
        <exclusion>
            <artifactId>spring-data-redis</artifactId>
            <groupId>org.springframework.data</groupId>
        </exclusion>
        <exclusion>
            <artifactId>lettuce-core</artifactId>
            <groupId>io.lettuce</groupId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>2.6.2</version>
</dependency>
<dependency>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
    <version>6.1.6.RELEASE</version>
</dependency>

1.2在application.yaml文件中配置相关数据

redis:
  host: 192.168.***.***
  port: 6379
  password: 123456
  lettuce:
    pool:
      max-active: 10
      max-idle: 10
      min-idle: 1
      time-between-eviction-runs: 10s

(二)短信的发送

2.1思路

用户从前端发送手机号到后台服务器,首先需要经过service层验证手机号格式是否正确,将手机号传给某个接口(市面上很多),验证通过,发送验证码。然后service层随机生成一个6位数的随机字符串。之后首先将生成的验证码保存在redis中并设置有效时间(防止redis内存占满的情况),(这里采用String数据结构保存验证码,并且以手机号作为key)。然后向用户提供的手机号上发送本次随机生成的验证码。

2.2代码编写

@Override
public Result sendCode(String phone, HttpSession session) {
    //1校验手机号
    if(RegexUtils.isPhoneInvalid(phone)){
        //2如果不符合,返回错误信息
        return Result.fail("手机号格式错误");
    }
    //3符合生成验证码
    String code = RandomUtil.randomNumbers(6);
    //4保存验证码到redis,并设置有效期(防止内存占满setex)
    stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone,code,LOGIN_CODE_TTL, TimeUnit.MINUTES);
    //5发送验证码(本业务的功能在于学习redis,发送验证码需要整合第三方接口,这里假装发送一下)
    log.debug("发送验证码成功,验证码为{}",code);
    //返回🆗
    return Result.ok();
}

(三)用户登录功能

3.1思路

用户再次提交手机号和验证码,此时再次验证手机号格式是否正确,如果格式正确则去redis里查找该手机号对应的验证码,并与用户提供的验证码对比,如果两个验证码相同,则从数据库中查找到该user,并把用户信息也保存在redis中并设置有效时间以便后续功能的使用。(这里选择hash数据结构存储用户数据,因为考虑到用户的信息时一个个的对象)

3.2代码编写

@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
    //用户提交过来手机号和验证码,对手机号和验证码进行校验
    // 1.1校验手机号
    String phone = loginForm.getPhone();
    if(RegexUtils.isPhoneInvalid(phone)){
        //2如果不符合,返回错误信息
        return Result.fail("手机号格式错误");
    }
    // 1.2校验验证码(从redis里获取)
    String code = loginForm.getCode();//用户提交的验证码
    String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
    // 2不一致,报错
    if(cacheCode == null || !cacheCode.toString().equals(code)){
        return Result.fail("验证码错误");
    }

    // 3一致,根据手机号查询用户
    User user = query().eq("phone", phone).one();
    // 4判断用户是否存在
    // 5不存在,创建新用户
    if (user == null) {
        //创建新用户,并且保存
        user = createUserWithPhone(phone);
    }
    // 6存在,保存用户信息到redis(采用hash作为存储数据结构,用一个随机生成的token作为key,不用手机号是因为最终的token要保存在浏览器,使用手机号的话不安全)

    String token = UUID.randomUUID().toString(true);
    // 6.1将用户对象转为hashmap存储
    UserDTO userDTO = BeanUtil.copyProperties(user,UserDTO.class);
    Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>(),
            CopyOptions.create()
                .setIgnoreNullValue(true)
                .setFieldValueEditor((filedName,filedValue) -> filedValue.toString()));
    //6.2存储到redis并设置有效时间(30min),并且当用户操作浏览器时更新有效期
    stringRedisTemplate.opsForHash().putAll(LOGIN_USER_KEY + token,userMap);
    stringRedisTemplate.expire(LOGIN_USER_KEY + token,LOGIN_USER_TTL,TimeUnit.MINUTES);
    // 6.3通过登录拦截校验
    return Result.ok(token);
}

(四)通过拦截器更新用户信息的有效时间

4.1必要性

由于登录后设置了用户保存在redis中的有效时间为30min,当30min过后,如果用户还在使用该app而信息过期,那么用户还需要再次登录,体验不好,应该是当用户在操作该app时去更新有效时间。保证用户使用时user信息永远不会过期。如何知道用户是否在操作app?可以在拦截器中对用户请求进行拦截,每当用户发送请求时,都会经过拦截器,所以可以在拦截器中更新有效时间

4.2代码编写

拦截器
public class LoginInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate;

    //这个类是我们自己手动写出来的,并没有添加到spring容器中,所以只能用构造放法的方式注入
    public LoginInterceptor(StringRedisTemplate stringRedisTemplate){
        this.stringRedisTemplate = stringRedisTemplate;
    }
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1获取请求头中的token,判断是否登录
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)) {
            response.setStatus(401);
            return false;
        }
        // 2基于token获取redis中的用户
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(RedisConstants.LOGIN_USER_KEY + token);
        // 3判断用户是否存在,主要判断登录是否过期
        if (userMap.isEmpty()) {
            // 4不存在,拦截,返回401
            response.setStatus(401);
            return false;
        }

        // 5将查询到的Hash数据转换为UserDTO对象
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);

        // 6存在,保存用户信息到ThreadLocal
        UserHolder.saveUser(userDTO);

        // 7刷新token有效期
        stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY + token,RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
        // 8放行

        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserHolder.removeUser();
    }
}
配置拦截器并排除一些请求的拦截
@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor(stringRedisTemplate)).excludePathPatterns(
                "/user/code",
                "/user/login",
                "/shop/**",
                "/blog/hot",
                "/upload/**",
                "/shop-type/**",
                "/voucher/**"
        );
    }
}

(五)总结

短信登录功能主要用到redis里的几种数据结构,其中String类型用于存储服务器生成的验证码,Hash用于存储用户的基本信息。这里存储的不需要是用户的完整信息,可以通过声明一个类,里面只有用户的最重要的信息就行。还需要设置这些键值对的过期时间,防止redis内存被占满的情况。还需要通过拦截器拦截用户的请求,以便更新用户信息的有效时间,防止用户在操作过程中信息过期。

  • 11
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值