thinkphp:redis+lua实现短信发送频率限制(thinkphp v6.0.12LTS)

发布时间 2023-04-16 19:12:46作者: 刘宏缔的架构森林

一,配置:

.env中

[REDIS0]
TYPE = redis
HOST = 127.0.0.1
PORT = 6379
PASSWORD =

二,php代码:

1,lib\util\SmsRateUtil.php

<?php
namespace app\lib\util;

//短信验证码发送频率
class SmsRateUtil {
    //redis连接
    private $redis;
    //60秒内不允许重复发送
    private $sendSeconds = "60";
    //一天内最多发10条
    private $dayCount = "10";

    //构造
    public function __construct($redis){
        $this->redis = $redis;
    }

    /*
    用lua脚本实现访问redis保存访问次数
    KEYS[1] : 主键,此处是手机号
    KEYS[2] :  主键过期的秒数,如果主键存在,表示不允许再次发送
    KEYS[3]:   每手机号每天时间内最多允许发送的条数 
    */
    function setRate($mobile) {
        $lua = <<<SCRIPT
local key = KEYS[1]
local keyseconds = tonumber(KEYS[2])
local daycount = tonumber(KEYS[3])
local keymobile = 'SmsAuthKey:'..key
local keycount = 'SmsAuthCount:'..key
--redis.log(redis.LOG_NOTICE,' keyseconds: '..keyseconds..';daycount:'..daycount)
local current = redis.call('GET', keymobile)
--redis.log(redis.LOG_NOTICE,' current: keymobile:'..current)
if current == false then
   --redis.log(redis.LOG_NOTICE,keymobile..' is nil ')
   local count = redis.call('GET', keycount)
   if count == false then
      redis.call('SET', keycount,1)
      redis.call('EXPIRE',keycount,86400)
      redis.call('SET', keymobile,1)
      redis.call('EXPIRE',keymobile,keyseconds)
      return '1'
   else
      local num_count = tonumber(count)
      if num_count+1 > daycount then
         return '2'
      else
         redis.call('INCRBY',keycount,1)
         redis.call('SET', keymobile,1)
         redis.call('EXPIRE',keymobile,keyseconds)
         return '1'
      end
   end
else
   --redis.log(redis.LOG_NOTICE,keymobile..' is not nil ')
   return '0'
end
SCRIPT;

        $key = $mobile;
        //执行lua脚本
        $sequence = $this->redis->eval($lua, [$key,$this->sendSeconds,$this->dayCount], 3);
        //检查是否出错
        $luaError = $this->redis->getLastError();
        if (isset($luaError)) {
           // print_r($luaError);
        }
        //返回
        return $sequence;
    }
}

2,从controller中调用:

class Sms extends BaseController
{
   //redis连接
    private $redis;
    //构造
    public function __construct(App $app = null){
         //继承父类的构造方法
         parent::__construct($app);
        //创建连接
        $redis = new \Redis();
        $port = env('redis0.port', '6379');
        $port = intval($port);
        $redis->connect(env('redis0.host', '127.0.0.1'),$port);
        $redis->select(0);
        $this->redis = $redis;
    }
    //判断是否可以发送短信验证码
    public function rate() {
          //得到参数
          $mobile = $this->request->param('mobile','','string');
          echo "mobile:".$mobile.":<br/>";
          //判断是否可以发送
          $rate = new SmsRateUtil($this->redis);
          $ret = $rate->setRate($mobile);
          echo "ret:".$ret.":<br/>";
          //返回的说明信息
          $res = "";
          if ($ret == '1') {
             $res = "可以发短信";
          } else if ($ret == '0') {
             $res = "请超过60秒之后再发短信";
          } else if ($ret == '2') {
             $res = "当前手机号本日内发送数量已超限制";
          } else {
             $res = "发生错误";
          }
          echo "res:".$res.":<br/>";
    }

三,测试效果

说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest

         对应的源码可以访问这里获取: https://github.com/liuhongdi/
         或: https://gitee.com/liuhongdi

说明:作者:刘宏缔 邮箱: 371125307@qq.com

四,查看thinkphp的版本:

liuhongdi@lhdpc:/data/php/imgtouch$ php think version
v6.0.12LTS