序言
随着技术的不断发展,为了使用户拥有更好的体验,许多网站在登陆界面额外提供了使用手机验证码的登陆方式。
该功能基本使用 Redis 数据库实现,不仅能提高效率,还可以减少维护量。
下面我们通过一个简单的例子来了解一下 Redis 是怎么实现这种功能的。
需求:手机验证码功能
该功能有以下 3 个要求:
- 输入手机号,点击发送后随机生成 6 位数字验证码,2 分钟内有效
- 输入验证码,点击验证,返回成功或失败
- 每个手机号每 24 小时内只会生成 3 次验证码
测试环境
IDEA 2019.1+ Maven + Servlet + Js(Bootstarp 实现)+ Redis5.0
代码实现
添加依赖
首先在创建项目的pom.xml
加入以下依赖:1
2
3
4
5
6
7
8
9
10
11
12<dependency>
<!--Servlet相关-->
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<!--Jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
编写 Jsp 页面
其次为 Jsp 页面,该页面实现了一个简单的验证码页面,当用户填写手机号并点击发送验证码
按钮后,页面出现 120 秒的倒计时,并将该表单提交以ajax
方式提交给CodeSenderServlet
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>验证码</title>
<script src="${pageContext.request.contextPath}/static/jquery-3.3.1.min.js"></script>
<link rel="stylesheet" href="${pageContext.request.contextPath}/static/bootstrap.min.css">
<script src="${pageContext.request.contextPath}/static/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</head>
<body>
<div class="container">
<div class="row">
<div id="alertdiv" class="col-md-12">
<form class="navbar-form navbar-left" role="search" id="codeform">
<div class="form-group">
<input type="text" class="form-control" placeholder="填写手机号" name="phone_number">
<button type="button" class="btn btn-default" id="sendCode">发送验证码</button>
<br>
<font id="countdown" color="red"></font>
<br>
<input type="text" class="form-control" placeholder="填写验证码" name="verify_code">
<button type="button" class="btn btn-default" id="verifyCode">确定</button>
<font id="result" color="green"></font>
<font id="error" color="red"></font>
</div>
</form>
</div>
</div>
</div>
</body>
<script type="text/javascript">
var t = 120;//设计倒计时的时间
var interval;
function refer() {
$("#countdown").text("请于" + t + "秒内填写验证码");//显示倒计时
t--;//计数器递减
if (t <= 0) {
clearInterval(interval);
$("#countdown").text("验证码已失效,请重新发送!");
}
}
$(function () {
$("#sendCode").click(function () {
$.post("${pageContext.request.contextPath}/CodeSenderServlet", $("#codeform").serialize(), function (data) {
if (data == "true") {
t = 120;
clearInterval(interval);
interval = setInterval("refer()", 1000);//启动1秒定时
} else if (data == "limit") {
clearInterval(interval);
$("#countdown").text("单日发送超过次数! ");
}
});
});
$("#verifyCode").click(function () {
$.post("${pageContext.request.contextPath}/CodeVerifyServlet", $("#codeform").serialize(), function (data) {
if (data == "true") {
$("#result").attr("color", "green");
$("#result").text("验证成功,即将跳转到下一页面");
clearInterval(interval);
$("#countdown").text("");
} else if (data == "false"){
$("#result").attr("color", "red");
$("#result").text("验证失败,请重新发送验证码");
}
})
})
})
</script>
</html>
编写 Servlet
CodeSenderServlet
具体代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75import redis.clients.jedis.Jedis;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Random;
/**
* 获取验证码
*
* @author Chris Wong
* @date : 2019-04-21 18:10
*/
"/CodeSenderServlet") (
public class CodeSendServlet extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//从表单中获取电话号码
String phoneNumber = req.getParameter("phone_number");
//获取指定的电话号码发送的验证码次数
Jedis jedis = null;
try {
jedis = new Jedis("127.0.0.1", 6379);
/**
* 设置Redis的两个键
* codeKey:该手机号对应的验证码
* countKey:该手机号验证码的获取次数
*/
String codeKey = phoneNumber + ":code";
String countKey = phoneNumber + ":count";
//对次数进行判断
String count = jedis.get(countKey);
//没有发送过验证码
if (count == null) {
//生成验证码
StringBuilder code = new StringBuilder();
for (int i = 0; i < 6; i++) {
code.append(new Random().nextInt(10));
}
//在缓存数据库中增加验证码
jedis.setex(codeKey, 120, code.toString());
//设置次数重置时间并累加验证码发送次数
jedis.setex(countKey, 24 * 60 * 60, "1");
//返回成功的消息
resp.getWriter().print("true");
} else if (Integer.valueOf(count) < 3) {//发送次数小于3次
//生成验证码
StringBuilder code = new StringBuilder();
for (int i = 0; i < 6; i++) {
code.append(new Random().nextInt(10));
}
//在缓存数据库中增加验证码
jedis.setex(codeKey, 120, code.toString());
//验证码发送次数+1
jedis.incr(countKey);
//返回成功的消息
resp.getWriter().print("true");
} else {//发送次数过多返回失败的消息
resp.getWriter().print("limit");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedis != null) {
jedis.close();
}
}
}
}
当我们点击发送验证码
后,会去 Redis 数据库查询该电话号码相关信息,根据代码里的逻辑去生成验证码,次数超过 3 则不会再生成验证码,且该验证码有过期时间。
之后当手机收到验证码(此处未模拟,需自己去 Redis 中查看相关验证码)后,将其填写并点击确定,页面又会通过ajax
的方式提交给CodeVerifyServlet
去进行判断,其代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47import redis.clients.jedis.Jedis;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 校验验证码
*
* @author Chris Wong
* @date : 2019-04-21 18:10
*/
"/CodeVerifyServlet") (
public class CodeVerifyServlet extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取用户填写的电话号码
String phoneNumber = req.getParameter("phone_number");
//获取用户填写的验证码
String verifyCode = req.getParameter("verify_code");
//Redis中的验证码
String codeKey = phoneNumber + ":code";
Jedis jedis = null;
try {
jedis = new Jedis("127.0.0.1", 6379);
//获取redis中的验证码
String redisCode = jedis.get(codeKey);
//对获取结果进行校验
if (verifyCode == null || !verifyCode.equals(redisCode)) {
resp.getWriter().print("false");
} else if (verifyCode.equals(redisCode)) {
resp.getWriter().write("true");
}
} catch (Exception e) {
} finally {
if (jedis != null) {
jedis.close();
}
}
}
}
测试
测试过程如图:
Redis中的验证码:
1 | 127.0.0.1:6379> get 13666666:code |
可以看到该验证码随机生成的数字,该验证码生成次数及过期时间。
项目实战:使用三方短信 SDK
在实际开发过程中,公司会根据客户的需要选择对应的短信提供商,比如阿里云短信、腾讯云短信、梦网短信,开发按对应选型拿到对应的 SDK 接入即可。
文章信息
时间 | 说明 |
---|---|
2019-04-18 | 初稿 |