功能说明
应用场景
数据库高负荷时,在员工账号层级进行限流,目的是让数据库尽早缓冲过来
特色功能
- 支持临时 追加/减少 白名单名额
- 支持重置白名单
- 直接配置化,不需要操作数据库
源代码
// config/white_list.php
<?php
// 抢白名单配置,白名单内的员工允许访问 xxx 系统,其他员工则跳转到登陆界面
// 变更配置后,记得执行 php artisan optimize
return [
// 是否启用
'is_enable' => env('WHITE_LIST_IS_ENABLE', 0),
// 白名单名额,支持临时追加白名单名额,但不支持减少操作
'number' => env('WHITE_LIST_NUMBER', 100),
// 缓存 key 前缀,变更后会重置白名单
'cache_key_prefix' => env('WHITE_LIST_CACHE_KEY_PREFIX', 'first'),
// 缓存 key
'cache_key_list_init' => '_xxx_white_list_init',
'cache_key_list' => '_xxx_white_list',
'cache_key_hash' => '_xxx_white_hash'
'cache_ttl' => 43200,
// 员工没在白名单内的提示文案,并跳转到登陆界面
'error_msg' => 'xxx 系统暂不可用,请稍晚一些再登陆使用,谢谢配合!',
];
// Services/RedisHelper.php
<?php
namespace App\Services;
use Illuminate\Support\Facades\Redis;
class RedisHelper
{
/**
* redis排他锁:拒绝并发相同的请求
* @param string $key
* @param $value
* @param int $ttl
* @return bool
*/
public static function setNxWithTTL(string $key, $value, int $ttl = 300): bool
{
if ((Redis::connection('default'))->set($key, $value, 'ex', $ttl, 'nx')) {
return true;
}
return false;
}
}
/**
* 核心代码~判断该员工是否在白名单内:只允许部分员工可以正常使用 xxx 系统,其他人则跳转到登陆界面
* @param int $staffId
* @return bool 返回 true 表示该员工在白名单内,允许正常使用 xxx 系统
*/
public function isInWhiteList(int $staffId): bool
{
// 是否启用白名单功能
if (!config('white_list.is_enable')) {
return true;
}
// 白名单名额
$whiteListNumber = (int)config('white_list.number');
if ($whiteListNumber <= 0) {
return false;
}
$redis = Redis::connection('default');
$cacheKeyPrefix = config('white_list.cache_key_prefix');
$cacheKeyListInit = $cacheKeyPrefix . config('white_list.cache_key_list_init');
$cacheKeyList = $cacheKeyPrefix . config('white_list.cache_key_list');
$cacheKeyHash = $cacheKeyPrefix . config('white_list.cache_key_hash');
$cacheTTL = config('white_list.cache_ttl');
// 判断该员工是否在白名单中
if ($redis->exists($cacheKeyHash) && $redis->hexists($cacheKeyHash, $staffId)) {
return true;
}
// 初始化令牌队列
if (!$redis->exists($cacheKeyListInit)) {
if (!RedisHelper::setNxWithTTL("{$cacheKeyListInit}_nx", 1, config('cache.one_hour'))) {
return true;
}
$redis->rpush($cacheKeyListInit, array_fill(0, $whiteListNumber, 1));
$redis->expire($cacheKeyListInit, $cacheTTL);
}
// 支持临时追加白名单名额
$length = $redis->llen($cacheKeyListInit);
if ($length < $whiteListNumber) {
if (!RedisHelper::setNxWithTTL("{$cacheKeyListInit}_nx_{$whiteListNumber}", 1, config('cache.one_hour'))) {
return false;
}
$redis->rpush($cacheKeyListInit, array_fill($length - 1, $whiteListNumber - $length, 1));
$redis->expire($cacheKeyListInit, $cacheTTL);
}
// 避免并发情况下,同一员工占用多个白名单名额
if (!$redis->hsetnx($cacheKeyHash, $staffId, 1)) {
return true;
}
$index = $redis->rpush($cacheKeyList, [$staffId]);
$ret = $redis->lindex($cacheKeyListInit, $index - 1);
// 手慢了,白名单名额已被抢完
if (empty($ret)) {
$redis->rpop($cacheKeyList);
$redis->hdel($cacheKeyHash, [$staffId]);
return false;
}
$redis->expire($cacheKeyList, $cacheTTL);
$redis->expire($cacheKeyHash, $cacheTTL);
return true;
}
使用案例
- 启用白名单功能,在 .env 文件配置以下参数:
WHITE_LIST_IS_ENABLE=1
WHITE_LIST_NUMBER=10
- 追加白名单名额,在 .env 文件调整以下参数:
WHITE_LIST_NUMBER=20
- 减少白名单名额,在 .env 文件调整以下参数:
WHITE_LIST_NUMBER=10
WHITE_LIST_CACHE_KEY_PREFIX="second"
- 觉得该换另一批人使用 xxx 系统了,则重置白名单,在 .env 文件调整以下参数:
WHITE_LIST_CACHE_KEY_PREFIX="third"
- 关闭白名单功能,在 .env 文件调整以下参数:
WHITE_LIST_IS_ENABLE=0