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

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

需求:手机验证码功能

  该功能有以下3个要求:

  • 输入手机号,点击发送后随机生成6位数字验证码,2分钟内有效
  • 输入验证码,点击验证,返回成功或失败
  • 每个手机号每24小时内只会生成3次验证码

测试环境

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

代码实现

添加依赖

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


      
      javax.servlet
      javax.servlet-api
      3.1.0
    
    
    
      redis.clients
      jedis
      2.9.0
    

Jsp页面编写

  其次为jsp页面,该页面实现了一个简单的验证码页面,当用户填写手机号并点击发送验证码按钮后,页面出现120秒的倒计时,并将该表单提交以ajax方式提交给CodeSenderServlet

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>



    
    
    
    验证码

    
    
    



效果图

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文件加入下面依赖:

    
    
      com.aliyun
      aliyun-java-sdk-core
      4.1.0
    

  之后写一个工具类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应用场景之验证码)