mirror of
https://devops.lemonos.cn/lawson/FendxPHP.git
synced 2026-06-15 23:12:49 +08:00
232 lines
6.4 KiB
PHP
232 lines
6.4 KiB
PHP
|
|
<?php
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
namespace Fendx\Cache;
|
||
|
|
|
||
|
|
use Redis;
|
||
|
|
use RedisException;
|
||
|
|
use Fendx\Core\Config\Config;
|
||
|
|
use Fendx\Common\Exception\BusinessException;
|
||
|
|
|
||
|
|
final class Cache
|
||
|
|
{
|
||
|
|
private static ?Redis $redis = null;
|
||
|
|
private static array $config = [];
|
||
|
|
private static array $localCache = [];
|
||
|
|
private static bool $localCacheEnabled = false;
|
||
|
|
|
||
|
|
public static function configure(array $config): void
|
||
|
|
{
|
||
|
|
self::$config = $config;
|
||
|
|
self::$localCacheEnabled = $config['local_cache'] ?? false;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static function getRedis(): Redis
|
||
|
|
{
|
||
|
|
if (self::$redis === null) {
|
||
|
|
self::$redis = self::createRedisConnection();
|
||
|
|
}
|
||
|
|
return self::$redis;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static function createRedisConnection(): Redis
|
||
|
|
{
|
||
|
|
$redis = new Redis();
|
||
|
|
|
||
|
|
$host = self::$config['host'] ?? '127.0.0.1';
|
||
|
|
$port = self::$config['port'] ?? 6379;
|
||
|
|
$password = self::$config['password'] ?? null;
|
||
|
|
$database = self::$config['database'] ?? 0;
|
||
|
|
$timeout = self::$config['timeout'] ?? 3.0;
|
||
|
|
|
||
|
|
try {
|
||
|
|
$redis->connect($host, $port, $timeout);
|
||
|
|
|
||
|
|
if ($password) {
|
||
|
|
$redis->auth($password);
|
||
|
|
}
|
||
|
|
|
||
|
|
$redis->select($database);
|
||
|
|
|
||
|
|
return $redis;
|
||
|
|
} catch (RedisException $e) {
|
||
|
|
throw new BusinessException(500, 'REDIS_CONNECT_FAILED', [
|
||
|
|
'message' => $e->getMessage()
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function get(string $key, mixed $default = null): mixed
|
||
|
|
{
|
||
|
|
// 先检查本地缓存
|
||
|
|
if (self::$localCacheEnabled && isset(self::$localCache[$key])) {
|
||
|
|
$item = self::$localCache[$key];
|
||
|
|
if ($item['expires'] > time()) {
|
||
|
|
return $item['value'];
|
||
|
|
}
|
||
|
|
unset(self::$localCache[$key]);
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
$value = self::getRedis()->get($key);
|
||
|
|
|
||
|
|
if ($value === false) {
|
||
|
|
return $default;
|
||
|
|
}
|
||
|
|
|
||
|
|
$decoded = json_decode($value, true);
|
||
|
|
$result = is_array($decoded) ? $decoded : $value;
|
||
|
|
|
||
|
|
// 更新本地缓存
|
||
|
|
if (self::$localCacheEnabled) {
|
||
|
|
$ttl = self::getRedis()->ttl($key);
|
||
|
|
if ($ttl > 0) {
|
||
|
|
self::$localCache[$key] = [
|
||
|
|
'value' => $result,
|
||
|
|
'expires' => time() + $ttl
|
||
|
|
];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return $result;
|
||
|
|
} catch (RedisException $e) {
|
||
|
|
throw new BusinessException(500, 'CACHE_GET_FAILED', [
|
||
|
|
'key' => $key,
|
||
|
|
'message' => $e->getMessage()
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function set(string $key, mixed $value, int $ttl = 3600): bool
|
||
|
|
{
|
||
|
|
try {
|
||
|
|
$serialized = is_scalar($value) ? $value : json_encode($value);
|
||
|
|
|
||
|
|
$result = self::getRedis()->setex($key, $ttl, $serialized);
|
||
|
|
|
||
|
|
// 更新本地缓存
|
||
|
|
if (self::$localCacheEnabled && $result) {
|
||
|
|
self::$localCache[$key] = [
|
||
|
|
'value' => $value,
|
||
|
|
'expires' => time() + $ttl
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
return $result;
|
||
|
|
} catch (RedisException $e) {
|
||
|
|
throw new BusinessException(500, 'CACHE_SET_FAILED', [
|
||
|
|
'key' => $key,
|
||
|
|
'message' => $e->getMessage()
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function delete(string $key): bool
|
||
|
|
{
|
||
|
|
try {
|
||
|
|
$result = self::getRedis()->del($key);
|
||
|
|
|
||
|
|
// 清除本地缓存
|
||
|
|
if (self::$localCacheEnabled) {
|
||
|
|
unset(self::$localCache[$key]);
|
||
|
|
}
|
||
|
|
|
||
|
|
return $result > 0;
|
||
|
|
} catch (RedisException $e) {
|
||
|
|
throw new BusinessException(500, 'CACHE_DELETE_FAILED', [
|
||
|
|
'key' => $key,
|
||
|
|
'message' => $e->getMessage()
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function has(string $key): bool
|
||
|
|
{
|
||
|
|
try {
|
||
|
|
return self::getRedis()->exists($key) > 0;
|
||
|
|
} catch (RedisException $e) {
|
||
|
|
throw new BusinessException(500, 'CACHE_HAS_FAILED', [
|
||
|
|
'key' => $key,
|
||
|
|
'message' => $e->getMessage()
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function clear(): bool
|
||
|
|
{
|
||
|
|
try {
|
||
|
|
$result = self::getRedis()->flushDB();
|
||
|
|
|
||
|
|
// 清除本地缓存
|
||
|
|
if (self::$localCacheEnabled) {
|
||
|
|
self::$localCache = [];
|
||
|
|
}
|
||
|
|
|
||
|
|
return $result;
|
||
|
|
} catch (RedisException $e) {
|
||
|
|
throw new BusinessException(500, 'CACHE_CLEAR_FAILED', [
|
||
|
|
'message' => $e->getMessage()
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function increment(string $key, int $value = 1): int
|
||
|
|
{
|
||
|
|
try {
|
||
|
|
$result = self::getRedis()->incrBy($key, $value);
|
||
|
|
|
||
|
|
// 清除本地缓存
|
||
|
|
if (self::$localCacheEnabled) {
|
||
|
|
unset(self::$localCache[$key]);
|
||
|
|
}
|
||
|
|
|
||
|
|
return $result;
|
||
|
|
} catch (RedisException $e) {
|
||
|
|
throw new BusinessException(500, 'CACHE_INCREMENT_FAILED', [
|
||
|
|
'key' => $key,
|
||
|
|
'message' => $e->getMessage()
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function decrement(string $key, int $value = 1): int
|
||
|
|
{
|
||
|
|
try {
|
||
|
|
$result = self::getRedis()->decrBy($key, $value);
|
||
|
|
|
||
|
|
// 清除本地缓存
|
||
|
|
if (self::$localCacheEnabled) {
|
||
|
|
unset(self::$localCache[$key]);
|
||
|
|
}
|
||
|
|
|
||
|
|
return $result;
|
||
|
|
} catch (RedisException $e) {
|
||
|
|
throw new BusinessException(500, 'CACHE_DECREMENT_FAILED', [
|
||
|
|
'key' => $key,
|
||
|
|
'message' => $e->getMessage()
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function remember(string $key, callable $callback, int $ttl = 3600): mixed
|
||
|
|
{
|
||
|
|
$value = self::get($key);
|
||
|
|
|
||
|
|
if ($value === null) {
|
||
|
|
$value = $callback();
|
||
|
|
self::set($key, $value, $ttl);
|
||
|
|
}
|
||
|
|
|
||
|
|
return $value;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function close(): void
|
||
|
|
{
|
||
|
|
if (self::$redis !== null) {
|
||
|
|
self::$redis->close();
|
||
|
|
self::$redis = null;
|
||
|
|
}
|
||
|
|
self::$localCache = [];
|
||
|
|
}
|
||
|
|
}
|