Completed
Pull Request — 6.0 (#2048)
by
unknown
09:29
created

Throttle   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 141
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 52
c 2
b 0
f 0
dl 0
loc 141
rs 10
wmc 17

7 Methods

Rating   Name   Duplication   Size   Complexity  
A parseRate() 0 6 1
A getCacheKey() 0 19 6
A wait() 0 7 3
A allowRequest() 0 24 3
A __construct() 0 4 1
A setRate() 0 2 1
A handle() 0 8 2
1
<?php
2
0 ignored issues
show
Coding Style introduced by
Missing file doc comment
Loading history...
3
declare (strict_types = 1);
4
5
namespace think\middleware;
6
7
use Closure;
8
use think\Cache;
9
use think\Config;
10
use think\Request;
11
use think\Response;
12
13
/**
14
 * 访问频率限制
15
 * Class Throttle
16
 * @package think\middleware
0 ignored issues
show
Coding Style introduced by
Package name "think\middleware" is not valid; consider "Thinkmiddleware" instead
Loading history...
17
 */
18
class Throttle
19
{
20
    /**
21
     * 缓存对象
22
     * @var Cache
23
     */
24
    protected $cache;
25
26
    /**
27
     * 配置参数
28
     * @var array
29
     */
30
    protected $config = [
31
        // 节流规则 true为自动规则
32
        'key'    => true,
33
        // 节流频率 null 表示不限制 eg: 10/m  20/h  300/d
34
        'visit_rate' => null,
35
        'visit_fail_text' => '访问频率受到限制,请稍等 %s秒 再试',
36
37
    ];
38
39
    protected $wait_seconds = 0;
40
41
    protected $duration = [
42
        's' => 1,
43
        'm' => 60,
44
        'h' => 3600,
45
        'd' => 86400,
46
    ];
47
48
    public function __construct(Cache $cache, Config $config)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __construct()
Loading history...
49
    {
50
        $this->cache  = $cache;
51
        $this->config = array_merge($this->config, $config->get('middleware.throttle', []));
52
    }
53
54
    /**
55
     * 生成缓存的 key
56
     * @param Request $request
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
57
     * @return null|string
58
     */
59
    protected function getCacheKey($request)
60
    {
61
        $key = $this->config['key'];
62
        if ($key instanceof \Closure) {
63
            $key = call_user_func($key, $this, $request);
64
        }
65
66
        if (false === $key || null === $this->config['visit_rate']) {
67
            // 关闭当前限制
68
            return;
69
        }
70
71
        if (true === $key) {
72
            $key = $request->ip();
73
        } elseif (false !== strpos($key, '__IP__')) {
74
            $key = str_replace('__IP__', $request->ip(), $key);
75
        }
76
77
        return md5($key);
78
    }
79
80
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $rate should have a doc-comment as per coding-style.
Loading history...
81
     * 解析频率配置项
82
     * @param $rate
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter name
Loading history...
83
     * @return array
84
     */
85
    protected function parseRate($rate)
86
    {
87
        list($num, $period) = explode("/", $rate);
88
        $num_requests = intval($num);
89
        $duration = $this->duration[$period];
90
        return [$num_requests, $duration];
91
    }
92
93
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $history should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $now should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $num_requests should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $duration should have a doc-comment as per coding-style.
Loading history...
94
     * 计算距离下次合法请求还有多少秒
95
     * @param $history
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter name
Loading history...
96
     * @param $now
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter name
Loading history...
97
     * @param $num_requests
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter name
Loading history...
98
     * @param $duration
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter name
Loading history...
99
     * @return void
100
     */
101
    protected function wait($history, $now, $num_requests, $duration)
0 ignored issues
show
Unused Code introduced by
The parameter $num_requests is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

101
    protected function wait($history, $now, /** @scrutinizer ignore-unused */ $num_requests, $duration)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
102
    {
103
        $wait_seconds = $history ? $duration - ($now - $history[0]) : $duration;
104
        if ($wait_seconds <= 0) {
105
            $wait_seconds = 0;
106
        }
107
        $this->wait_seconds = $wait_seconds;
108
    }
109
110
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $request should have a doc-comment as per coding-style.
Loading history...
111
     * 请求是否允许
112
     * @param $request
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter name
Loading history...
113
     * @return bool
114
     */
115
    protected function allowRequest($request)
116
    {
117
        $key = $this->getCacheKey($request);
118
        if (null === $key) {
119
            return true;
120
        }
121
        list($num_requests, $duration) = $this->parseRate($this->config['visit_rate']);
122
        $history = $this->cache->get($key, []);
123
        $now = time();
124
125
        // 移除过期的请求的记录
126
        $history = array_values(array_filter($history, function($val) use ($now, $duration) {
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
127
            return $val >= $now - $duration;
128
        }));
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
129
130
        if (count($history) <= $num_requests) {
131
            // 允许访问
132
            $history[] = $now;
133
            $this->cache->set($key, $history, $now + $duration);
134
            return true;
135
        }
136
137
        $this->wait($history, $now, $num_requests, $duration);
138
        return false;
139
    }
140
141
    /**
142
     * 处理限制访问
143
     * @param Request $request
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
144
     * @param Closure $next
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
145
     * @return Response
146
     */
147
    public function handle($request, Closure $next)
148
    {
149
        $allow = $this->allowRequest($request);
150
        if (!$allow) {
151
            return Response::create(sprintf($this->config['visit_fail_text'], $this->wait_seconds))->code(403);
152
        }
153
        $response = $next($request);
154
        return $response;
155
    }
156
157
    public function setRate($rate) {
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function setRate()
Loading history...
Coding Style introduced by
Opening brace should be on a new line
Loading history...
158
        $this->config['visit_rate'] = $rate;
159
    }
160
}