短信验证清理验证码

java后台
关于短信验证码的问题本想把验证码放到session中,一提出就被否决了。说不好管理。因为时效只有两分钟。
现在方案是这样的 两个map 以输入文本框的手机号码为key 值。 分别放入 截至时间 和 验证码。然后通过第三短信平台发送的验证码和现在对比做逻辑。 问题是。假日2分钟后没人输入验证码。重新发送的验证码要失效才行。我本想写个定时器的 但是2分钟内它一直开着。如果同一时间。其他人就没法操作了 。希望给位指导下 别说redis。不会它。或者大家给个解决方案吧 什么时候清除合适

阅读 13k
4 个回答
  1. 设置一个有效期长量,如 120 秒

  2. 用户申请短信验证码

  3. 服务端根据用户的手机号创建记录:创建时间、手机号、验证码、激活状态(默认为否)

  4. 提交第 3 方短信平台(如果有必要等结果再对条目的状态做标记,那就另说)

  5. 用户提交验证,服务端读取『手机号+验证码』及创建时间倒序的第 1 条,,

    1. 如不存在,直接驳回(可能被删了,也可能是手机号码、验证码之一不正确)

    2. 如已存在,检查过期时间(用户提交时间-条目创建时间是否小于等于 120 秒)

      1. 过期,直接驳回

      2. 没过期,继续下面的业务,将数据库里条目状态标记为已激活

与此同时,跑一个定时脚本,每隔 10 分钟啊 20 分钟去删数据:

  1. 已验证的。

  2. 距离脚本操作时间大于 120 秒或更长时间的;

希望有用。

class VerificationEntity {
    /**
     * 自增编号
     * @var integer 
     */
    public $id;

    /**
     * 键名
     * @var string 
     */
    public $name;

    /**
     * 码值
     * @var string 
     */
    public $code;

    /**
     * 创建时间
     * @var integer 
     */
    public $createdAt;

    /**
     * 过期时间
     * @var integer 
     */
    public $expiredAt;
}
class VerificationFilter
{

    /**
     * 检查手机号码
     * @param string $mobile
     * @throws AppException
     */
    public function checkMobile($mobile)
    {
        if (!Validator::email($mobile)) {
            throw new AppException($this->translate->_('invalidEmail'));
        }
    }

    /**
     * 检查短信间隔
     * @param string $name
     * @throws AppException
     */
    public function checkSMSInterval($name)
    {
        $limit = $this->config->smsCode->interval;
        $record = Repository::getInstance('Verification')->findByName($name);

        if ($record && (time() - $record->createdAt > $limit)) {
            $message = $this->translate->_('reachSMSIntervalLimit', ['limit' => $limit]);
            throw new AppException($message);
        }
    }

    /**
     * 检查验证码
     * @param string $name
     * @param string $code
     */
    public function checkCode($name, $code)
    {
        $record = Repository::getInstance('Verification')->findByName($name);

        if (!$record || $code != $record->code || $record->expiredAt < time()) {
            throw new AppException($this->translate->_('invalidVerifyCode'));
        }
    }

}
class VerificationService
{

    /**
     * 发送短信验证码
     * @param string $mobile
     * @param string $tpl
     */
    public function sendSMSCode($mobile, $tpl)
    {
        Filter::getInstance('Verification')->checkMobile($mobile);
        Filter::getInstance('Verification')->checkSMSInterval($mobile);

        $config = $this->getDI()->get('config');
        $smser = $this->getDI()->get('smser');

        $code = rand(100000, 999999);
        $lifetime = $config->smsCode->lifetime;
        $expiration = time() + $lifetime * 60;
        $placeholders = ['code' => $code, 'lifetime' => $lifetime];

        $content = $this->parseSMSTemplate($tpl, $placeholders);

        $smser->to($mobile)->content($content)->send();

        Repository::getInstance('Verification')->remove($mobile);
        Repository::getInstance('Verification')->create($mobile, $code, $expiration);
    }

    /**
     * 删除验证码
     * @param string $name
     */
    public function remove($name)
    {
        Repository::getInstance('Verification')->remove($name);
    }

    /**
     * 删除过期验证码
     */
    public function removeExpired()
    {
        Repository::getInstance('Verification')->removeExpired();
    }

    /**
     * 解析短信模板
     * @param stirng $tpl
     * @param array $placeholders
     * @return string
     */
    private function parseSMSTemplate($tpl, $placeholders)
    {
        $translate = $this->getDI()->get('translate', 'sms.php');

        if ($translate->exists($tpl)) {
            $template = $translate->_($tpl, $placeholders);
        } else {
            $template = $translate->_('default', $placeholders);
        }

        return $template;
    }

}

大概就这么个思路,为了方便代码有些未删减,看看就行。

Redis清除过期缓存就两种办法:

  1. 根据key获取value时判断是否过期

  2. 通过一个过期队列(根据过期时间排序)来清除过期数据

所以对于验证码这种也可以考虑这么做:
方案一: 在输入验证码之后,判断验证码是否正确的时候,如果验证码正确,判断一下验证码是否过期。
方案二: 将手机号码,验证码存储到一个Map中,同时将手机号码,过期时间存储到DelayQueue中。另起一个线程,不断地从DelayQueue中获取元素,将获取到的元素从Map删除。

这两个方案可以同时都用,或者只用其一。

DelayQueue: 延时队列,Blocking队列,内部实现是用一个Priority队列,以延迟时间作为比较的依据。只有达到延迟时间之后元素才可以被获得,可用于清除空闲连接,过期缓存,超时任务等(例如缓存,将缓存对象放到DelayQueue中,delay时间等于缓存过期时间,运行一个deamon线程从DelayQueue中获取元素,然后将其清除)。

新手上路,请多包涵

你直接试试SUBMAIL短信平台吧。在线发送就可以,他们这个平台本身就能设置定时。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题