71 lines
1.7 KiB
PHP
71 lines
1.7 KiB
PHP
<?php
|
||
namespace app\api\middleware;
|
||
|
||
use think\Request;
|
||
use think\Response;
|
||
use think\exception\HttpResponseException;
|
||
use app\common\server\RedisHelper;
|
||
|
||
class RateLimit
|
||
{
|
||
// 默认配置:每60秒最多20次
|
||
protected $limit = 20;
|
||
protected $ttl = 60;
|
||
|
||
/**
|
||
* 限流处理入口
|
||
* @param Request $request
|
||
* @param \Closure $next
|
||
* @param string $keyPrefix 可传入 'ip' 或 'token' 等标识字段
|
||
* @return Response
|
||
*/
|
||
public function handle($request, \Closure $next, $keyPrefix = 'ip')
|
||
{
|
||
$identifier = $this->getIdentifier($request, $keyPrefix);
|
||
|
||
$redisKey = "rate_limit:{$keyPrefix}:" . $identifier;
|
||
$redis = (new RedisHelper())->getRedis();
|
||
|
||
$count = $redis->incr($redisKey);
|
||
|
||
if ($count == 1) {
|
||
// 第一次设置过期时间
|
||
$redis->expire($redisKey, $this->ttl);
|
||
}
|
||
|
||
if ($count > $this->limit) {
|
||
$this->error("请求频率过高,请稍后再试");
|
||
}
|
||
|
||
return $next($request);
|
||
}
|
||
|
||
/**
|
||
* 获取请求标识符(支持IP/token)
|
||
*/
|
||
protected function getIdentifier(Request $request, string $keyPrefix)
|
||
{
|
||
switch ($keyPrefix) {
|
||
case 'token':
|
||
return $request->header('token') ?? $request->param('token') ?? 'guest';
|
||
case 'ip':
|
||
default:
|
||
return $request->ip();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 错误输出统一格式
|
||
*/
|
||
protected function error($msg, $code = 429)
|
||
{
|
||
$result = [
|
||
'status' => 0,
|
||
'msg' => $msg,
|
||
'data' => null
|
||
];
|
||
$response = Response::create($result, 'json', $code);
|
||
throw new HttpResponseException($response);
|
||
}
|
||
}
|