Files
baodev0andClaude Opus 5.5 94614d9cb3 feat: khởi tạo/huỷ domain Plesk qua job, gửi mail hoá đơn qua queue, đính kèm triển khai
- 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>
2026-10-11 09:56:18 +07:00

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];
}
}