refactor: implement centralized cache key management and add utility traits for vCenter SSH operations.
This commit is contained in:
@@ -2,56 +2,127 @@
|
||||
|
||||
namespace App\Services\Cache;
|
||||
|
||||
|
||||
use App\Services\Cache\CacheServiceInterface;
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Cache\Repository as CacheRepository;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
use stdClass;
|
||||
|
||||
class CacheService implements CacheServiceInterface
|
||||
{
|
||||
protected int $ttl;
|
||||
private const TTL_SECONDS = [
|
||||
self::TTL_SHORT => 300, // 5 minutes
|
||||
self::TTL_MEDIUM => 3600, // 1 hour
|
||||
self::TTL_LONG => 86400, // 24 hours
|
||||
];
|
||||
|
||||
public function __construct(int $ttl = 60)
|
||||
{
|
||||
$this->ttl = $ttl;
|
||||
}
|
||||
private const TAGGABLE_DRIVERS = ['redis', 'memcached', 'dynamodb'];
|
||||
|
||||
public function putCache(string $prefix, string $key, mixed $value, ?int $ttl = null): void
|
||||
{
|
||||
$cacheKey = $this->makeKey($prefix, $key);
|
||||
Cache::put($cacheKey, $value, $ttl ?? $this->ttl);
|
||||
}
|
||||
// -------------------------------------------------------------------------
|
||||
// Interface implementation
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function getCache(string $prefix, string $key, mixed $default = null): mixed
|
||||
{
|
||||
$cacheKey = $this->makeKey($prefix, $key);
|
||||
return Cache::get($cacheKey, $default);
|
||||
}
|
||||
public function remember(
|
||||
string $key,
|
||||
Closure $callback,
|
||||
string $ttl = self::TTL_MEDIUM,
|
||||
array $tags = [],
|
||||
): mixed {
|
||||
$store = $this->resolveStore($tags);
|
||||
$sentinel = new stdClass();
|
||||
$cached = $store->get($key, $sentinel);
|
||||
|
||||
public function clearCache(string $prefix, string $key): void
|
||||
{
|
||||
$cacheKey = $this->makeKey($prefix, $key);
|
||||
Cache::forget($cacheKey);
|
||||
}
|
||||
|
||||
public function clearCacheByPrefix(string $prefix): void
|
||||
{
|
||||
|
||||
$cachePrefix = config('cache.prefix');
|
||||
|
||||
$pattern = $cachePrefix . $prefix . '_*';
|
||||
$keys = Redis::connection('cache')->keys($pattern);
|
||||
|
||||
|
||||
foreach ($keys as $k) {
|
||||
Redis::connection('cache')->del($k);
|
||||
if ($cached !== $sentinel) {
|
||||
Log::debug("CACHE HIT: {$key}");
|
||||
return $cached;
|
||||
}
|
||||
|
||||
Log::debug("CACHE MISS: {$key}");
|
||||
$value = $callback();
|
||||
$store->put($key, $value, $this->resolveTtl($ttl));
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
protected function makeKey(string $prefix, string $key = ''): string
|
||||
public function put(
|
||||
string $key,
|
||||
mixed $value,
|
||||
string $ttl = self::TTL_MEDIUM,
|
||||
array $tags = [],
|
||||
): void {
|
||||
$this->resolveStore($tags)->put($key, $value, $this->resolveTtl($ttl));
|
||||
}
|
||||
|
||||
public function get(string $key, mixed $default = null): mixed
|
||||
{
|
||||
return trim($prefix . '_' . $key, '_');
|
||||
return Cache::get($key, $default);
|
||||
}
|
||||
|
||||
public function forget(string $key): void
|
||||
{
|
||||
Cache::forget($key);
|
||||
}
|
||||
|
||||
public function clearByModule(string $module): void
|
||||
{
|
||||
if ($this->supportsTagging()) {
|
||||
Cache::tags([$module])->flush();
|
||||
return;
|
||||
}
|
||||
|
||||
$this->scanAndDelete($module);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Internals
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private function resolveStore(array $tags): CacheRepository
|
||||
{
|
||||
if (!empty($tags) && $this->supportsTagging()) {
|
||||
return Cache::tags($tags);
|
||||
}
|
||||
|
||||
return Cache::store();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all keys for a module using SCAN (safe for production).
|
||||
*
|
||||
* KEYS is O(N) and blocks Redis; SCAN iterates in batches without blocking.
|
||||
* The pattern matches: {cache_prefix}:{module}:*
|
||||
*/
|
||||
private function scanAndDelete(string $module): void
|
||||
{
|
||||
$redis = Redis::connection('cache');
|
||||
$prefix = config('cache.prefix');
|
||||
$pattern = "{$prefix}:{$module}:*";
|
||||
$cursor = '0';
|
||||
|
||||
do {
|
||||
$result = $redis->scan($cursor, 'MATCH', $pattern, 'COUNT', 100);
|
||||
|
||||
if ($result === false) {
|
||||
break;
|
||||
}
|
||||
|
||||
[$cursor, $keys] = $result;
|
||||
|
||||
foreach ((array) $keys as $key) {
|
||||
$redis->del($key);
|
||||
}
|
||||
|
||||
} while ((string) $cursor !== '0');
|
||||
}
|
||||
|
||||
private function supportsTagging(): bool
|
||||
{
|
||||
return in_array(config('cache.default'), self::TAGGABLE_DRIVERS, strict: true);
|
||||
}
|
||||
|
||||
private function resolveTtl(string $ttl): int
|
||||
{
|
||||
return self::TTL_SECONDS[$ttl] ?? self::TTL_SECONDS[self::TTL_MEDIUM];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user