美文网首页
(*)Redis应用场景之验证码

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

作者: 纸中圆 | 来源:发表于2019-04-18 07:55 被阅读0次

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

    需求:手机验证码功能

      该功能有以下3个要求:

    • 输入手机号,点击发送后随机生成6位数字验证码,2分钟内有效
    • 输入验证码,点击验证,返回成功或失败
    • 每个手机号每24小时内只会生成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());
        }
    }
    

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

    相关文章

      网友评论

          本文标题:(*)Redis应用场景之验证码

          本文链接:https://www.haomeiwen.com/subject/falsiqtx.html