2025-02-28 09:29:43 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace traits;
|
|
|
|
|
|
|
|
|
|
use bw\Redis;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 缓存
|
|
|
|
|
*/
|
|
|
|
|
trait CacheTrait
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private $cache_lock_error_msg = "获取锁失败!";
|
|
|
|
|
|
|
|
|
|
private $cache_lock_thorws_excption = true;
|
|
|
|
|
|
|
|
|
|
public function setCacheLockThorwsExcption($cache_lock_thorws_excption){
|
|
|
|
|
$this->cache_lock_thorws_excption = $cache_lock_thorws_excption;
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public function getCacheLockThorwsExcption(){
|
|
|
|
|
return $this->cache_lock_thorws_excption;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public function hasRedis($is_interrupt = false) {
|
|
|
|
|
$error_msg = '';
|
|
|
|
|
try {
|
|
|
|
|
$redis = $this->getRedis();
|
|
|
|
|
|
|
|
|
|
// 检测连接是否正常
|
|
|
|
|
$redis->ping();
|
|
|
|
|
} catch (\BadFunctionCallException $e) {
|
|
|
|
|
// 缺少扩展
|
|
|
|
|
$error_msg = $e->getMessage() ? $e->getMessage() : "缺少 redis 扩展";
|
|
|
|
|
} catch (\RedisException $e) {
|
|
|
|
|
// 连接拒绝
|
|
|
|
|
\think\Log::write('redis connection redisException fail: ' . $e->getMessage());
|
|
|
|
|
$error_msg = $e->getMessage() ? $e->getMessage() : "redis 连接失败";
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
// 异常
|
|
|
|
|
\think\Log::write('redis connection fail: ' . $e->getMessage());
|
|
|
|
|
$error_msg = $e->getMessage() ? $e->getMessage() : "redis 连接异常";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($error_msg) {
|
|
|
|
|
if ($is_interrupt || $this->cache_lock_thorws_excption) {
|
|
|
|
|
throw new \Exception($error_msg);
|
|
|
|
|
} else {
|
|
|
|
|
$this->setCacheLockErrorMsg($error_msg);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public function getRedis() {
|
|
|
|
|
if (!isset($GLOBALS['SPREDIS'])) {
|
|
|
|
|
$GLOBALS['SPREDIS'] = (new Redis())->getRedis();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $GLOBALS['SPREDIS'];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-03-06 08:59:31 +08:00
|
|
|
|
public function getLock($key, $suffix = "-lock-suffix", $timeout = 120, $lock_num = 1) {
|
2025-02-28 09:29:43 +08:00
|
|
|
|
$this->hasRedis(true);
|
|
|
|
|
$redis = $this->getRedis();
|
2025-03-06 08:59:31 +08:00
|
|
|
|
$hashKey = $key . $suffix;
|
|
|
|
|
|
|
|
|
|
if ($lock_num == 1) { // 单令牌
|
|
|
|
|
$nxLock = $redis->set($hashKey, 1, ['nx', 'ex' => $timeout]);
|
|
|
|
|
if ($nxLock == 1) {
|
|
|
|
|
return true;
|
|
|
|
|
} else {
|
|
|
|
|
if ($this->cache_lock_thorws_excption) {
|
|
|
|
|
throw new \Exception($this->cache_lock_error_msg);
|
|
|
|
|
} else {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-02-28 09:29:43 +08:00
|
|
|
|
} else {
|
2025-03-06 08:59:31 +08:00
|
|
|
|
// 多令牌
|
|
|
|
|
$lua = 'local hash_key = KEYS[1]
|
|
|
|
|
local max_tokens = tonumber(ARGV[3])
|
|
|
|
|
local token_value = ARGV[1]
|
|
|
|
|
local token_timeout = tonumber(ARGV[2])
|
|
|
|
|
|
|
|
|
|
-- 获取当前令牌数
|
|
|
|
|
local current_value = redis.call("hget", hash_key, "count")
|
|
|
|
|
|
|
|
|
|
if not current_value then
|
|
|
|
|
-- 锁不存在,初始化锁并设置过期时间
|
|
|
|
|
redis.call("hset", hash_key, "count", 1)
|
|
|
|
|
redis.call("expire", hash_key, token_timeout)
|
|
|
|
|
return 1
|
|
|
|
|
else
|
|
|
|
|
-- 锁存在,增加计数器
|
|
|
|
|
local new_value = tonumber(current_value) + 1
|
|
|
|
|
if new_value > max_tokens then
|
|
|
|
|
return 0
|
|
|
|
|
else
|
|
|
|
|
redis.call("hset", hash_key, "count", new_value)
|
|
|
|
|
redis.call("expire", hash_key, token_timeout)
|
|
|
|
|
return new_value
|
|
|
|
|
end
|
|
|
|
|
end';
|
|
|
|
|
|
|
|
|
|
$result = $redis->eval($lua, [$hashKey, $timeout, $lock_num], 1);
|
|
|
|
|
if ($result >= 1 && $result <= $lock_num) {
|
|
|
|
|
return true;
|
|
|
|
|
} else {
|
|
|
|
|
if ($this->cache_lock_thorws_excption) {
|
|
|
|
|
throw new \Exception($this->cache_lock_error_msg);
|
|
|
|
|
} else {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2025-02-28 09:29:43 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public function setCacheLockErrorMsg($cache_lock_error_msg){
|
|
|
|
|
$this->cache_lock_error_msg = $cache_lock_error_msg;
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public function getCacheLockErrorMsg(){
|
|
|
|
|
return $this->cache_lock_error_msg;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-03-06 08:59:31 +08:00
|
|
|
|
public function freeLock($key, $suffix = "-lock-suffix", $lock_num = 1, $timeout = 120) {
|
2025-02-28 09:29:43 +08:00
|
|
|
|
$this->hasRedis(true);
|
|
|
|
|
$redis = $this->getRedis();
|
2025-03-06 08:59:31 +08:00
|
|
|
|
if (!$key || !$suffix) return true;
|
|
|
|
|
$hashKey = $key . $suffix;
|
2025-02-28 09:29:43 +08:00
|
|
|
|
|
2025-03-06 08:59:31 +08:00
|
|
|
|
if ($lock_num == 1) { // 单令牌
|
|
|
|
|
// 执行lua脚本,确保删除锁是原子操作
|
|
|
|
|
$lua = 'if redis.call("get", KEYS[1]) == ARGV[1]
|
2025-02-28 09:29:43 +08:00
|
|
|
|
then
|
2025-03-06 08:59:31 +08:00
|
|
|
|
return redis.call("del", KEYS[1])
|
2025-02-28 09:29:43 +08:00
|
|
|
|
else
|
|
|
|
|
return 0
|
|
|
|
|
end';
|
2025-03-06 08:59:31 +08:00
|
|
|
|
$result = $redis->eval($lua, [$hashKey, 1], 1);
|
2025-02-28 09:29:43 +08:00
|
|
|
|
|
2025-03-06 08:59:31 +08:00
|
|
|
|
if ('1' == $result) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
} else { // 多令牌
|
|
|
|
|
$lua = 'local hash_key = KEYS[1]
|
|
|
|
|
local token_value = ARGV[1]
|
|
|
|
|
local token_timeout = tonumber(ARGV[2])
|
|
|
|
|
|
|
|
|
|
-- 获取当前令牌数
|
|
|
|
|
local current_value = redis.call("hget", hash_key, "count")
|
|
|
|
|
|
|
|
|
|
if not current_value then
|
|
|
|
|
-- 锁不存在,无需释放
|
|
|
|
|
return 0
|
|
|
|
|
else
|
|
|
|
|
-- 锁存在,减少计数器
|
|
|
|
|
local new_value = tonumber(current_value) - 1
|
|
|
|
|
if new_value < 1 then
|
|
|
|
|
-- 计数器小于1,删除锁
|
|
|
|
|
local result = redis.call("del", hash_key)
|
|
|
|
|
return result
|
|
|
|
|
else
|
|
|
|
|
-- 计数器大于等于1,更新锁
|
|
|
|
|
redis.call("hset", hash_key, "count", new_value)
|
|
|
|
|
redis.call("expire", hash_key, token_timeout)
|
|
|
|
|
return new_value
|
|
|
|
|
end
|
|
|
|
|
end';
|
|
|
|
|
|
|
|
|
|
$result = $redis->eval($lua, [$hashKey, $timeout, $lock_num], 1);
|
|
|
|
|
if ($result >= 1 && $result <= $lock_num) {
|
|
|
|
|
return true;
|
|
|
|
|
} else {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2025-02-28 09:29:43 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|