• Java/Kotlin 使用Redis模拟发送验证码


    本文为作者原创,允许转载,不过请在文章开头明显处注明链接和出处!!! 谢谢配合~
    作者:stars-one
    链接:https://www.cnblogs.com/stars-one/p/17070550.html

    本篇大约有4533个字,阅读预计需要5.67分钟


    原文地址: Java/Kotlin 使用Redis模拟发送邮件验证码 - Stars-One的杂货小窝

    Java中常用语连接Redis的库有lettucejredis,一般是推荐lettuce,其具有异步性,下面两种都简单来使用如何实现功能

    jredis

    1.引入依赖

    <dependency>
        <groupId>redis.clientsgroupId>
        <artifactId>jedisartifactId>
        <version>3.2.0version>
    dependency>
    

    脚本使用:

    fun main() {
        //1.测试连接
        val jedis = Jedis("127.0.0.1", 6379)
        val resp = jedis.ping()
        //为pong即为可用的
        if (resp == "PONG") {
            val key = "mykey"
            val value = "hello world"
    
            //写入数据
            jedis[key]=value
            //读数据
            val result = jedis[key]
            println(result)
            //  删除指定key
            val row = jedis.del(key)
            //影响的行数
            println(row)
            
            //设置60s后过期
            jedis.setex(key,60,value)
            //设置60ms后过期
            jedis.psetex(key,60,value)
            
            //剩余的过期时间,ttl返回时间单位为s,pttl则是ms
            val time = jedis.ttl(key)
            val time = jedis.pttl(key)
        }
    }
    

    通过setexpsetex方法来设置过期时间后,当数据过期后,再次去查询该数据,就会得到null(即redis将数据删除了)

    上述也是简单演示了redis数据库的增删改查功能,下面就利用此数据库来实现发送验证码的功能。

    2.发送验证码

    这里我是实现了邮箱发送验证码的功能,验证码定为6位纯数字随机数,当然,你也可以加上大小写字母来提高复杂性。

    之后我们将邮箱和验证码存储到redis中,并设置十分钟过期时间,随后通过调用邮箱发送邮件的方法,将验证码发送出去(这里详见JavaXMail发送邮件功能实现

    下面是验证码生成方法:

    //生成验证码
    fun randomCode(): String {
        val sb = StringBuffer()
        repeat(6) {
            //0-9范围
            val num = Random.nextInt(0, 10)
            sb.append(num)
        }
        return sb.toString()
    }
    
    //发送验证码方法
    fun sendCode(email: String) {
        val code = randomCode()
        
        //先判断redis是否有记录
        val oldCode = RedisUtil.getValue(email)
        val action = {
            RedisUtil.setKeyValue(email, code)
            //调用邮箱发送邮件方法
            sendEmail(email, code)
        }
        if (oldCode.isBlank()) {
            action.invoke()
        } else {
            //判断是否已过1分钟
            //已过一分钟,重新发送,否则不做操作
            val flag = RedisUtil.isGtOneMinutes(email)
            if (flag) {
                action.invoke()
            }
        }
    }
    
    object RedisUtil {
        private val url = "127.0.0.1"
    
        //10分钟
        private const val expiredTime = 10 * 60
        private val redis by lazy {
            val jedis = Jedis(url, 6379)
            //如果有设置密码
            //    jedis.auth("")
            jedis
        }
    
        /**
         * 获取数据
         */
        fun getValue(key: String): String {
            return redis[key] ?: ""
        }
    
        /**
         * 存储邮箱和验证码
         */
        fun setKeyValue(key: String, value: String) {
            redis.setex(key, 10 * 60, value)
        }
    
        /**
         * 获取指定key的剩余时间(s)
         */
        fun getSurplusTime(key: String): Long {
            return redis.ttl(key)
        }
    
        /**
         * key是否已过1分钟
         */
        fun isGtOneMinutes(key: String): Boolean {
            val time = getSurplusTime(key)
            //小于九分钟(说明已过1分钟)
            return time <= expiredTime - 60
        }
    
    }
    

    这里补充下,由于邮箱为用户输入,永远不要对用户输入抱有期待,用户可能输入不是个email地址或者输了个不存在的email地址,对于前者问题,我们可以通过在前端和后台增加一个邮箱格式验证,对于后者问题(不存在的email地址),没有什么验证办法,只有发送了才知道这个邮箱地址是否可用(可以使用try catch来捕获异常来处理)

    所以如果发送邮件出现错误,我们需要进行对应的处理,把那条存储到redis数据删除,然后接口返回一个错误提示信息即可。

    而且,为了考虑到恶意用户频繁操作,导致我们邮箱服务频繁发送邮件,我们也需要进行对应的考虑设置,这里只能顾全用户频繁输入单个邮箱的情况,如果是同个邮箱,我们设置验证码过了1分钟的时间,才给重新发送(即现在各大APP手机验证码的操作一样),前端和后台接口都是需要做限制。

    如果是重新发送的话,我们需要重新setex方法设置一下验证码,同时这步也将过期时间重置了。

    3.校验验证码

    之后就是考虑校验验证码的情况了,这里也是比较简单,通过拿到用户输入的验证码和redis里面的进行比对就可校验。

    但可能会有特殊情况,比如redis验证码已经过期了,需要进行判断,并自动重新发送邮件,且接口返回提示信息

    fun checkCode(email: String, code: String):Boolean {
        val dbCode = RedisUtil.getValue(email)
        if (dbCode.isBlank()) {
            //重新发送邮件,并发送提示(这里省略了发送提示)
            sendCode(email)
            return false
        } else {
            if (dbCode==code) {
                //验证通过
                return true
            }
            return false
        }
    }
    

    lettuce

    Lettuce是一个高性能基于Java编写的Redis驱动框架,底层集成了Project Reactor提供天然的反应式编程,通信框架集成了Netty使用了非阻塞IO,5.x版本之后融合了JDK1.8的异步编程特性,在保证高性能的同时提供了十分丰富易用的API,5.1版本的新特性如下:

    • 支持Redis的新增命令ZPOPMIN, ZPOPMAX, BZPOPMIN, BZPOPMAX。
    • 支持通过Brave模块跟踪Redis命令执行。
    • 支持Redis Streams。
    • 支持异步的主从连接。
    • 支持异步连接池。
    • 新增命令最多执行一次模式(禁止自动重连)。
    • 全局命令超时设置(对异步和反应式命令也有效)。

    下面这里就稍微贴下代码就好,具体的思路上面已经都有提及了,就不再过多赘述了。

    1.引入依赖

    如果项目为Spring Boot,只需要引用spring-data-redis依赖即可,其内置默认使用lettuce此库来连接redis

    <dependency>
        <groupId>org.springframework.datagroupId>
        <artifactId>spring-data-redisartifactId>
        <version>2.0.5.RELEASEversion>
    dependency>
    

    或者是单独使用,则直接引用lettuce库即可

    <dependency>
      <groupId>io.lettucegroupId>
      <artifactId>lettuce-coreartifactId>
      <version>5.0.2.RELEASEversion>
    dependency>
    

    2.使用

    val redisUri = RedisURI.builder() // <1> 创建单机连接的连接信息
        .withHost("localhost")
        .withPort(6379)
        .withTimeout(Duration.of(10, ChronoUnit.SECONDS))
        .build()
    
    val redisClient = RedisClient.create(redisUri) // <2> 创建客户端
    val connection = redisClient.connect() // <3> 创建线程安全的连接
    val redisCommands = connection.sync() // <4> 创建同步命令
    
    
    //这里的参数说明可以访问http://redis.io/commands/set查看
    //ex就是设置5s的过期时间
    val setArgs = SetArgs.Builder.nx().ex(5)
    
    //获取剩余过期时间
    redisCommands.ttl("name")
    
    //设置数据
    val result = redisCommands.set("name", "throwable", setArgs)
    if (result.toLowerCase() == "ok") {
        println("成功插入数据")
    }
    
    connection.close() // <5> 关闭连接
    
    redisClient.shutdown() // <6> 关闭客户端
    

    Lettuce结构比较复杂,上面罗列的基本使用已经够用了,就没有深入研究下去了...

    其他

    不过最近找了一款后台框架,写的时候发现,它是用的RedisTemplate,似乎比Lettuce要早一些的技术栈了,稍微摸索了下也能使用,也没去替换了那个后台框架里的东西了

    //存入数据并设置时间
    stringRedisTemplate.opsForValue().set(key, value, timeout, TimeUnit.HOURS);
    
    //删除
    stringRedisTemplate.delete(key);
    
    //获取剩余到期时间
    redisTemplate.getExpire(key, TimeUnit.MINUTES);
    

    参考

  • 相关阅读:
    浅谈一级机电管道设计中的压力与介质温度
    SpringBoot源码解读系列三——引导注解
    Django——模板层、模型层
    Nginx的index.html文件内容+格式语法
    面试官:连 INSERT INTO SET 都不知道怎么用,你这3年都干些什么了?
    Go语言中gin+gorm开发前端端分离博客时遇到的问题,gorm执行查询时如何选中特定字段?
    2022年国家高新企业认定申报最全问答-财务数据篇
    初识AJAX基础(一)
    阿里云对象存储oss——对象储存原子性和强一致性
    深度学习-第三章概率与信息论
  • 原文地址:https://www.cnblogs.com/stars-one/p/17070550.html