<?php
namespace app\common\util;

/**
 * 登录限流
 *
 * 使用说明,使用前先判断$account是否为空
 * 1.验证:(new ThrottlesUtil())->tooManyAttempts($account,$pre);  true=锁定 false=未锁定
 * 2.登录成功,需要重置登录次数:(new ThrottlesUtil())->resetAttempts($account,$pre);
 * 3.不同体系的账号,可通过设置$pre前缀区分
 */
class ThrottlesUtil
{
    private $limitEnabled = true; // 是否开启限流 false=不开启 true=开启

    private $limitTime = 1*60; // 间隔时长(s)

    private $maxAttempts = 5; // 最大登录次数

    private $lockTime = 30*60; // 锁定时间(s)

    private $pre = "<|login|>"; // 前缀避免账号重复

    public function __construct($config = [])
    {
        if(isset($config['time'])){
            $this->limitTime = (int)$config['time'];
        }
        if(isset($config['attempts'])){
            $this->maxAttempts = (int)$config['attempts'];
        }
        if(isset($config['lock'])){
            $this->lockTime = (int)$config['lock'];
        }
        if(isset($config['enabled'])){
            $this->limitEnabled = $config['enabled'];
        }
    }

    /**
     * 检查是否锁定
     * @param $account 账号
     * @param string $pre // 缓存前缀,根据前缀区分不同类别账号
     * @return boolean false=未锁定 true=已锁定
     */
    public function tooManyAttempts($account,$pre = 'throttles'){
        if(!$this->limitEnabled){
            return false;
        }
        $key = $pre.$this->pre.$account;
        //检查是否存在对应lockout缓存
        if (cache($key.':lockout')) {
            return true;
        }
        //如果没有的话,attempts($key)返回$key对应的缓存中存储的登录次数
        if ($this->attempts($account,$pre) > $this->maxAttempts) {
            //如果已经超出限制,调用add方法添加对应lockout到缓存
            cache($key.':lockout',time() + $this->lockTime, $this->lockTime);
            //重置当前登录次数
            $this->resetAttempts($key);
            return true;
        }
        return false;
    }

    /**
     * 获取登录次数
     * @param $account 账号
     * @param string $pre 缓存前缀
     * @return int
     */
    public function attempts($account,$pre = 'throttles'){
        $key = $pre.$this->pre.$account;
        $times = cache($key); // ["1234567899","1234567892"]存储时间戳
        $ndata = [];
        $count = 0;
        $curTime = time();
        if($times){
            $data = json_decode($times,true);
            if(!empty($data)){
                $count = count($data);
                foreach ($data as $k=>$v){
                    if($curTime - $v < $this->limitTime){ // 间隔小于时长有效
                        $ndata[] = $v;
                    }
                }
            }
        }
        $ndata[] = $curTime;
        cache($key,json_encode($ndata),$this->limitTime);
        return $count + 1;
    }

    /**
     * 重置登录次数
     * @param $account 账号
     * @param string $pre 缓存前缀
     */
    public function resetAttempts($account,$pre = 'throttles'){
        $key = $pre.$this->pre.$account;
        cache($key,NULL);
        cache($key.':lockout',NULL);
    }

}