(*)Redis应用场景之验证码

2019-04-18  本文已影响0人  纸中圆

  随着技术的不断发展,为了使用户拥有更好的体验,许多网站在登陆界面提供了使用手机验证码的登陆方式。这种功能基本使用Redis数据库实现,不仅提高了效率,还减少了维护量。下面我们通过一个简单的例子来了解一下Redis是怎么实现这种功能的。

需求:手机验证码功能

  该功能有以下3个要求:

测试环境

  IDEA 2019.1+ Maven + Servlet + Jsp(Bootstarp实现) + Redis5.0

代码实现

添加依赖

  首先在pom.xml加入以下依赖:

<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

<%@ 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具体代码如下:

import 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 : wksky
 * @date : 2019-04-21 18:10
 */
@WebServlet("/CodeSenderServlet")
public class CodeSendServlet extends HttpServlet {

    @Override
    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去进行判断,其代码如下:

import 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 : wksky
 * @date : 2019-04-21 18:10
 */
@WebServlet("/CodeVerifyServlet")
public class CodeVerifyServlet extends HttpServlet {

    @Override
    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中的验证码:

127.0.0.1:6379> get 13666666:code
"577737"
127.0.0.1:6379> get 13666666:count
"1"
127.0.0.1:6379> ttl 13666666:code
(integer) 14
127.0.0.1:6379> ttl 13666666:code
(integer) 13
127.0.0.1:6379> ttl 13666666:count
(integer) 86290

  可以看到该验证码随机生成的数字,该验证码生成次数及过期时间

优化:使用阿里云的短信SDK

  前面的代码并没有向指定的手机号发送验证码,所以我们可以通过使用阿里云云通信中的短信服务来提供该服务,在pom.xml文件加入下面依赖:

    <!--阿里云短信sdk-->
    <dependency>
      <groupId>com.aliyun</groupId>
      <artifactId>aliyun-java-sdk-core</artifactId>
      <version>4.1.0</version>
    </dependency>

  之后写一个工具类SendSmsUtil

import com.aliyuncs.CommonRequest;
import com.aliyuncs.CommonResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.exceptions.ServerException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;

/**
 * SendSms接口是短信发送接口,支持在一次请求中向多个不同的手机号码发送同样内容的短信。
 *
 * @author : wksky
 * @date : 2019-07-23 14:18
 */
public class SendSmsUtil {
    public static void sendSms(String phoneNumbers, String code) {
        //地区(可不填使用默认值)及AK
        DefaultProfile profile = DefaultProfile.getProfile("default", "请输入阿里云提供的ID", "请输入阿里云提供的密码");
        IAcsClient client = new DefaultAcsClient(profile);

        CommonRequest request = new CommonRequest();
        request.setMethod(MethodType.POST);
        request.setDomain("dysmsapi.aliyuncs.com");
        request.setVersion("2017-05-25");
        //系统规定参数。取值:SendSms。
        request.setAction("SendSms");
        //接受验证码的手机号
        request.putQueryParameter("PhoneNumbers", phoneNumbers);
        //短信签名名称。请在控制台签名管理页面签名名称一列查看。
        request.putQueryParameter("SignName", "请输入自己的短信签名");
        //短信模板ID。请在控制台模板管理页面模板CODE一列查看。
        request.putQueryParameter("TemplateCode", "请输入自己的短信模版ID");
        //短信模板变量对应的实际值,JSON格式。如:{"code":"1111"}
        request.putQueryParameter("TemplateParam", "{\"code\":\"" + code + "\"}");
        try {
            CommonResponse response = client.getCommonResponse(request);
            response.getData();
            System.out.println(response.getData());
        } catch (ServerException e) {
            e.printStackTrace();
        } catch (ClientException e) {
            e.printStackTrace();
        }
    }
}

  最后将前面的CodeSendServlet中的逻辑代码修改并优化:

import ...

@WebServlet("/CodeSenderServlet")
public class CodeSendServlet extends HttpServlet {

    @Override
    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) {
                generateCodeAndSend(phoneNumber, jedis, codeKey);
                //设置次数重置时间并累加验证码发送次数
                jedis.setex(countKey, 24 * 60 * 60, "1");
                //返回成功的消息
                resp.getWriter().print("true");
            } else if (Integer.valueOf(count) < 3) {//发送次数小于3次
                generateCodeAndSend(phoneNumber, jedis, codeKey);
                //验证码发送次数+1
                jedis.incr(countKey);
                //返回成功的消息
                resp.getWriter().print("true");
            } else {//发送次数过多返回失败的消息
                resp.getWriter().print("limit");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    /**
     * 验证码的操作
     *
     * @param phoneNumber 手机号
     * @param jedis       redis
     * @param codeKey     手机号对应的验证码
     */
    private void generateCodeAndSend(String phoneNumber, Jedis jedis, String codeKey) {
        //生成验证码
        StringBuilder code = new StringBuilder();
        for (int i = 0; i < 6; i++) {
            code.append(new Random().nextInt(10));
        }
        //在缓存数据库中增加验证码
        jedis.setex(codeKey, 120, code.toString());
        //发送验证码到指定手机号
        SendSmsUtil.sendSms(phoneNumber, code.toString());
    }
}

  这样,我们点击按钮后手机就会收到验证码啦。

上一篇下一篇

猜你喜欢

热点阅读