- ProvisionDnsDomainJob/SuspendDnsDomainJob: tạo domain admin lên Plesk, huỷ = suspend (is_created=3) - Khách xem zone DNS được gán ở /dich-vu/domain, quản lý bản ghi A/AAAA/CNAME/TXT/MX/SRV - send-mail hoá đơn đẩy SendInvoiceMailJob, preview + sửa nội dung, ghi mail.order_id - invoice_attachments.type (payment_proof/deployment) - CacheService xoá theo SCAN pattern, Redis không persistent mặc định - Backfill transactions.public_code Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
117 lines
3.2 KiB
PHP
117 lines
3.2 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Cache;
|
|
|
|
use Closure;
|
|
use Illuminate\Cache\RedisStore;
|
|
use Illuminate\Contracts\Cache\Repository as CacheRepository;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Support\Str;
|
|
use stdClass;
|
|
|
|
class CacheService implements CacheServiceInterface
|
|
{
|
|
private const TTL_SECONDS = [
|
|
self::TTL_SHORT => 300, // 5 minutes
|
|
self::TTL_MEDIUM => 3600, // 1 hour
|
|
self::TTL_LONG => 86400, // 24 hours
|
|
];
|
|
|
|
private const TAGGABLE_DRIVERS = ['redis', 'memcached', 'dynamodb'];
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Interface implementation
|
|
// -------------------------------------------------------------------------
|
|
|
|
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);
|
|
|
|
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;
|
|
}
|
|
|
|
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 Cache::get($key, $default);
|
|
}
|
|
|
|
public function forget(string $key): void
|
|
{
|
|
Cache::forget($key);
|
|
}
|
|
|
|
public function clearByModule(string $module): void
|
|
{
|
|
$this->scanAndDelete($module);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Internals
|
|
// -------------------------------------------------------------------------
|
|
|
|
private function resolveStore(array $tags): CacheRepository
|
|
{
|
|
if (!empty($tags) && $this->supportsTagging()) {
|
|
return Cache::tags($tags);
|
|
}
|
|
|
|
return Cache::store();
|
|
}
|
|
|
|
private function scanAndDelete(string $module): void
|
|
{
|
|
$store = Cache::store()->getStore();
|
|
if (! $store instanceof RedisStore) {
|
|
return;
|
|
}
|
|
|
|
$redis = $store->connection();
|
|
$connectionPrefix = (string) config('database.redis.options.prefix');
|
|
$pattern = $connectionPrefix . $store->getPrefix() . $module . ':*';
|
|
$cursor = '0';
|
|
|
|
do {
|
|
[$cursor, $keys] = $redis->scan($cursor, ['match' => $pattern, 'count' => 500]) ?: ['0', []];
|
|
|
|
foreach ((array) $keys as $key) {
|
|
$redis->del(Str::after($key, $connectionPrefix));
|
|
}
|
|
} 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];
|
|
}
|
|
}
|