feat : common redis cache and common logger

This commit is contained in:
2025-09-14 15:58:45 +08:00
parent 48ad2ebd1b
commit 0db9995c19
15 changed files with 640 additions and 14 deletions

View File

@@ -0,0 +1,173 @@
<?php
namespace App\Cache\Redis;
use App\Lib\Log\Logger;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Redis\Redis;
use Swoole\Coroutine;
use Throwable;
abstract class BaseScript
{
/**
* @var string|null
*/
protected ?string $sha1 = null;
/**
* @var bool
*/
protected bool $debugMode;
/**
* @var Redis
*/
protected Redis $redis;
/**
* @var ConfigInterface
*/
protected ConfigInterface $config;
protected Logger $logger;
/**
* @param Redis $redis
* @param ConfigInterface $config
* @param Logger $logger
*/
public function __construct(
Redis $redis,
ConfigInterface $config,
Logger $logger
)
{
$this->redis = $redis;
$this->debugMode = $config->get('app_debug',false);
$this->logger = $logger;
}
/**
* 获取脚本名称对应lua文件名
* @return string
*/
abstract public function getName(): string;
/**
* @param array $keys
* @param array $args
* @param int|null $numKeys
* @return mixed
*/
protected function run(
array $keys,
array $args,
?int $numKeys = null
): mixed
{
$numKeys = $numKeys ?? count($keys);
try {
$script = $this->getScriptContent();
// $this->logExecution($keys, $args, true);
return $this->execute($script, $keys, $args, $numKeys);
} catch (Throwable $e) {
$this->logExecution($keys, $args, false, $e);
return false;
}
}
/**
* @return string
*/
protected function getScriptContent(): string
{
// $path = __DIR__.'/Script/'.$this->getName().'.lua';
$path = __DIR__.'/Lua/'.$this->getName().'.lua';
if (!file_exists($path)) echo 1;
$content = file_get_contents($path);
if ($content === false) echo 2;
return $content;
}
/**
* @param string $script
* @param array $keys
* @param array $args
* @param int $numKeys
* @return mixed
*/
protected function execute(
string $script,
array $keys,
array $args,
int $numKeys
): mixed
{
if (
!$this->debugMode &&
$this->sha1
)
{
try {
return $this->redis->evalsha(
$this->sha1,
array_merge($keys, $args),
$numKeys
);
} catch (Throwable $e)
{
// SHA1 不存在时回避
$this->sha1 = null;
}
}
$result = $this->redis->eval(
$script,
array_merge($keys, $args),
$numKeys
);
$this->sha1 = sha1($script);
return $result;
}
/**
* @param array $keys
* @param array $args
* @param bool $success
* @param Throwable|null $e
* @return void
*/
protected function logExecution(
array $keys,
array $args,
bool $success,
?Throwable $e = null,
): void
{
$context = [
'script' => $this->getName(),
'keys' => $keys,
'args' => $args,
'success' => $success,
'error' => $e?->getMessage()
];
$logStrategy = match(true) {
!$success => $this->logger->cache()->error(...),
$this->debugMode => $this->logger->cache()->debug(...),
default => fn() => null // 不记录
};
if (!$logStrategy) return;
$logStrategy('Redis Lua execution', $context);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Cache\Redis\Lua;
use App\Cache\Redis\BaseScript;
class RateLimit extends BaseScript
{
/**
* @return string
*/
public function getName(): string
{
return 'rate_limit';
}
/**
* 限流
* @param string $key
* @param int $limit
* @param int $window
* @return array
*/
public function check(string $key, int $limit, int $window): array
{
$result = $this->run([$key], [$limit, $window]);
if (!$result) return [];
return [(bool)$result[0], (int)$result[1]];
}
}

View File

@@ -0,0 +1,82 @@
<?php
namespace App\Cache\Redis;
use App\Lib\Log\Logger;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Redis\RedisFactory;
use Hyperf\Redis\RedisProxy;
/**
* @mixin RedisProxy
*/
class RedisCache
{
/**
* @var string
*/
private string $poolName = 'default';
/**
* @var array
*/
private array $luaHandlers = [];
/**
* @param RedisFactory $redisFactory
* @param ConfigInterface $config
* @param Logger $logger
*/
public function __construct(
protected readonly RedisFactory $redisFactory,
protected readonly ConfigInterface $config,
protected readonly Logger $logger
) {}
/**
* @param string $poolName
* @return $this
*/
public function with(string $poolName = 'default'): self
{
$new = clone $this;
$new->poolName = $poolName;
return $new;
}
/**
* @return RedisProxy
*/
public function client(): RedisProxy
{
return $this->redisFactory->get($this->poolName);
}
/**
* @param string $scriptClass
* @return mixed
*/
public function lua(string $scriptClass): mixed
{
$poolName = $this->poolName ?? 'default';
$key = $poolName . ':' . $scriptClass;
if (!isset($this->luaHandlers[$key])) {
$this->luaHandlers[$key] = new $scriptClass(
$this->client(),
$this->config,
$this->logger
);
}
return $this->luaHandlers[$key];
}
/**
* 魔术方法代理原生 Redis 命令 (默认连接池)
*/
public function __call(string $method, array $arguments)
{
return $this->client()->{$method}(...$arguments);
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace App\Cache\Redis;
class RedisKey
{
/**
* @param int $id
* @return string
*/
public static function getAdminUserInfoKey(int $id): string
{
return 'admin_user:'.$id;
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace App\Cache\Redis;
use Hyperf\Redis\RedisProxy;
class RedisProxyWrapper
{
public function __construct(
protected readonly RedisProxy $redisProxy,
protected string $poolName
) {}
/**
* @param string $method
* @param array $arguments
* @return mixed
*/
public function __call(string $method, array $arguments)
{
return $this->redisProxy->{$method}(...$arguments);
}
public function lua(string $scriptClass)
{
// 这里实现你的 Lua 脚本逻辑
$key = $this->poolName . ':' . $scriptClass;
// 实际实现根据你的需求
return $this->redisProxy->eval($scriptClass, 0);
}
}

View File

@@ -0,0 +1,18 @@
-- app/Cache/Redis/Script/rate_limit.lua
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local current = redis.call('GET', key)
local remaining = 0
if current then
remaining = tonumber(current) - 1
if remaining >= 0 then
redis.call('DECR', key)
else
remaining = -1
end
else
remaining = limit - 1
redis.call('SET', key, remaining, 'EX', window)
end
return {remaining >= 0, remaining}