PHP 实现防抖功能(防重复请求)
原理
- 使用Redis做接口访问锁,访问前加锁,访问结束后解锁。
- 使用 域名+antiRepeat+模块+控制器+方法名称+用户ID”作为唯一key值,用户ID默认自动获取,也可以由参数传入
基础代码
Larave或Lumen框架
/**
* 通用接口防抖函数
*/
function antiRepeat($unique_id = false)
{
if (!$unique_id && !auth()->id()) {
fail('该方法使用“域名+模块+控制器+方法名称+用户ID”作为唯一key值,无法使用auth()->id()的接口请传入unique_id参数代替 用户ID');
}
// 获取请求的模块、控制器、方法名称
list($class, $method) = explode('@', request()->route()[1]['uses']);
// 模块名
$modules = str_replace(
'\\',
'.',
str_replace(
'App\\Http\\Controllers\\',
'',
trim(
implode('\\', array_slice(explode('\\', $class), 0, -1)),
'\\'
)
)
);
// 控制器名称
$controller = str_replace(
'Controller',
'',
substr(strrchr($class, '\\'), 1)
);
// 传入$unique_id则优先使用$unique_id
if ($unique_id) {
$key = $_SERVER['HTTP_HOST'] . '-antiRepeat-' . $modules . '.' . $controller . '.' . $method . '.' . $unique_id;
} else {
$key = $_SERVER['HTTP_HOST'] . '-antiRepeat-' . $modules . '.' . $controller . '.' . $method . '.' . auth()->id();
}
// 访问进行中
if (Redis::get($key) == 1) {
fail('请勿重复操作');
} else {
Redis::set($key, 1);
register_shutdown_function(function () use ($key) {
// Redis::set($key, 0);
Redis::del($key);
});
}
}
/**
* 通用接口防抖函数
*/
function antiRepeatTime($unique_id = false, $seconds = 8)
{
if (!$unique_id && !auth()->id()) {
fail('该方法使用“域名+模块+控制器+方法名称+用户ID”作为唯一key值,无法使用auth()->id()的接口请传入unique_id参数代替 用户ID');
}
// 获取请求的模块、控制器、方法名称
list($class, $method) = explode('@', request()->route()[1]['uses']);
// 模块名
$modules = str_replace(
'\\',
'.',
str_replace(
'App\\Http\\Controllers\\',
'',
trim(
implode('\\', array_slice(explode('\\', $class), 0, -1)),
'\\'
)
)
);
// 控制器名称
$controller = str_replace(
'Controller',
'',
substr(strrchr($class, '\\'), 1)
);
// 传入$unique_id则优先使用$unique_id
if ($unique_id) {
$key = $_SERVER['HTTP_HOST'] . '-antiRepeatTime-' . $modules . '.' . $controller . '.' . $method . '.' . $unique_id;
} else {
$key = $_SERVER['HTTP_HOST'] . '-antiRepeatTime-' . $modules . '.' . $controller . '.' . $method . '.' . auth()->id();
}
// 访问进行中
if (Redis::get($key) == 1) {
fail('请求过于频繁');
} else {
Redis::setex($key, $seconds, 1);
}
}
ThinkPHP框架
/**
* 通用接口防抖函数
*/
function antiRepeat($unique_id = false)
{
$auth = Auth::instance();
if (!$unique_id && !$auth->id) {
fail('该方法使用“域名+模块+控制器+方法名称+用户ID”作为唯一key值,无法使用auth()->id()的接口请传入unique_id参数代替 用户ID');
}
// 这里可以换成其他缓存机制
$redis = Cache::store('redis');
// 模块名
$modules = request()->module();
// 控制器名称
$controller = request()->controller();
// 方法名称
$method = request()->action();
// 传入$unique_id则优先使用$unique_id
if ($unique_id) {
$key = $_SERVER['HTTP_HOST'] . '-antiRepeat-' . $modules . '.' . $controller . '.' . $method . '.' . $unique_id;
} else {
$key = $_SERVER['HTTP_HOST'] . '-antiRepeat-' . $modules . '.' . $controller . '.' . $method . '.' . $auth->id;
}
// 访问进行中
if ($redis->get($key) == 1) {
fail('请勿重复操作');
} else {
$redis->set($key, 1);
register_shutdown_function(function () use ($key, $redis) {
$redis->set($key, 0);
});
}
}
使用示例
- 如果是能使用auth()->id()获得用户ID的接口
antiRepeat();
...业务代码
- 如果是不能使用auth()->id()获得用户ID的接口
$user_id = 1;// 自行设置用户ID
antiRepeat($user_id);
...业务代码
by 同事 - kaivin99