5

Java/Kotlin 使用Redis模拟发送验证码 - Stars-one

 1 year ago
source link: https://www.cnblogs.com/stars-one/p/17070550.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

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


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

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

jredis

1.引入依赖

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.2.0</version>
</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.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>2.0.5.RELEASE</version>
</dependency>

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

<dependency>
  <groupId>io.lettuce</groupId>
  <artifactId>lettuce-core</artifactId>
  <version>5.0.2.RELEASE</version>
</dependency>
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);

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK