refactor: implement centralized cache key management and add utility traits for vCenter SSH operations.

This commit is contained in:
pbao9
2026-05-16 16:52:21 +07:00
parent 06dbd54ecd
commit 8a115c5175
36 changed files with 1694 additions and 517 deletions
+107 -36
View File
@@ -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];
}
}