原文地址: Java/Kotlin 使用Redis模擬傳送郵件驗證碼 - Stars-One的雜貨小窩
Java中常用語連線Redis的庫有lettuce
和jredis
,一般是推薦lettuce
,其具有非同步性,下面兩種都簡單來使用如何實現功能
<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)
}
}
通過setex
或psetex
方法來設定過期時間後,當資料過期後,再次去查詢該資料,就會得到null(即redis將資料刪除了)
上述也是簡單演示了redis資料庫的增刪改查功能,下面就利用此資料庫來實現傳送驗證碼的功能。
這裡我是實現了郵箱傳送驗證碼的功能,驗證碼定為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
方法設定一下驗證碼,同時這步也將過期時間重置了。
之後就是考慮校驗驗證碼的情況了,這裡也是比較簡單,通過拿到使用者輸入的驗證碼和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是一個高效能基於Java編寫的Redis驅動框架,底層整合了Project Reactor提供天然的反應式程式設計,通訊框架整合了Netty使用了非阻塞IO,5.x版本之後融合了JDK1.8的非同步程式設計特性,在保證高效能的同時提供了十分豐富易用的API,5.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);