From 63195718a23fa44744677f52972f6159909d2280 Mon Sep 17 00:00:00 2001 From: adonis Date: Sat, 29 Aug 2026 22:39:08 +0330 Subject: [PATCH] fix(settings): drop program-settings last-good cache and prefer montzar Stale Redis overlay and SMS-origin-first fetch kept feature flags like customer_special_pricing false after SuperAdmin turned them on. Always fetch live and try adm.montzar.ir first. Co-authored-by: Cursor --- ...uperAdminProgramSettingsRefreshCommand.php | 12 ++-- .../Controllers/Admin/SettingsController.php | 4 +- .../SuperAdminProgramSettingsClient.php | 53 ++++++++++++-- .../SuperAdminProgramSettingsOverlay.php | 70 ++----------------- 4 files changed, 66 insertions(+), 73 deletions(-) diff --git a/app/Console/Commands/SuperAdminProgramSettingsRefreshCommand.php b/app/Console/Commands/SuperAdminProgramSettingsRefreshCommand.php index 772c262..f5f20eb 100644 --- a/app/Console/Commands/SuperAdminProgramSettingsRefreshCommand.php +++ b/app/Console/Commands/SuperAdminProgramSettingsRefreshCommand.php @@ -8,9 +8,9 @@ use Illuminate\Console\Command; class SuperAdminProgramSettingsRefreshCommand extends Command { protected $signature = 'superadmin:refresh-program-settings - {--clear-only : فقط کش last-good را پاک کن}'; + {--clear-only : فقط memo درخواست را ریست کن (دیگر کش Redis نیست)}'; - protected $description = 'کش overlay تنظیمات سوپرادمین را پاک/تازه می‌کند'; + protected $description = 'تنظیمات دسترسی را دوباره از سوپرادمین می‌خواند (بدون کش)'; public function handle(): int { @@ -18,20 +18,24 @@ class SuperAdminProgramSettingsRefreshCommand extends Command if ($this->option('clear-only')) { $overlay->flushCache(); - $this->line('cleared SuperAdmin program-settings cache'); + $this->line('cleared SuperAdmin program-settings request memo'); return self::SUCCESS; } $result = $overlay->refreshFromSuperAdmin(); if (! ($result['ok'] ?? false)) { - $this->error('fetch from SuperAdmin failed — cache cleared; next HTTP request will retry'); + $this->error('fetch from SuperAdmin failed; next HTTP request will retry'); return self::FAILURE; } $items = is_array($result['items'] ?? null) ? $result['items'] : []; + $meta = is_array($result['meta'] ?? null) ? $result['meta'] : []; $this->info('fetched '.(int) ($result['count'] ?? 0).' setting(s) from SuperAdmin'); + if ($meta !== []) { + $this->line('meta: '.json_encode($meta, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); + } foreach ($items as $key => $value) { $this->line(" {$key}={$value}"); } diff --git a/app/Http/Controllers/Admin/SettingsController.php b/app/Http/Controllers/Admin/SettingsController.php index 0f96372..11a6e34 100644 --- a/app/Http/Controllers/Admin/SettingsController.php +++ b/app/Http/Controllers/Admin/SettingsController.php @@ -94,10 +94,11 @@ class SettingsController extends Controller if (! ($result['ok'] ?? false)) { return response()->json([ - 'message' => 'ارتباط با سوپرادمین برقرار نشد؛ کش پاک شد و در درخواست بعدی دوباره تلاش می‌شود', + 'message' => 'ارتباط با سوپرادمین برقرار نشد؛ در درخواست بعدی دوباره تلاش می‌شود', 'ok' => false, 'count' => 0, 'items' => [], + 'meta' => $result['meta'] ?? [], ], 502); } @@ -109,6 +110,7 @@ class SettingsController extends Controller 'count' => $count, 'items' => $result['items'] ?? [], 'source' => $result['source'] ?? 'superadmin', + 'meta' => $result['meta'] ?? [], ]); } diff --git a/app/Services/SuperAdminProgramSettingsClient.php b/app/Services/SuperAdminProgramSettingsClient.php index 61ed3a3..7f3d956 100644 --- a/app/Services/SuperAdminProgramSettingsClient.php +++ b/app/Services/SuperAdminProgramSettingsClient.php @@ -22,8 +22,21 @@ class SuperAdminProgramSettingsClient public const RETRY_ROUNDS = 3; + /** @var array{channel?: string, origin?: string, domain?: string}|null */ + private ?array $lastFetchMeta = null; + + /** + * @return array{channel?: string, origin?: string, domain?: string} + */ + public function lastFetchMeta(): array + { + return $this->lastFetchMeta ?? []; + } + public function fetchItems(): ?array { + $this->lastFetchMeta = null; + $public = $this->fetchPublic(); if ($public !== null) { return $public; @@ -68,7 +81,12 @@ class SuperAdminProgramSettingsClient } $items = $this->extractItems($response->json()); - if ($items !== null) { + if ($this->hasSettingRows($items)) { + $this->lastFetchMeta = [ + 'channel' => 'remote', + 'origin' => $origin, + ]; + return $items; } } catch (\Throwable $e) { @@ -96,16 +114,29 @@ class SuperAdminProgramSettingsClient foreach ($this->attemptOrigins() as $origin) { try { $response = Http::acceptJson() - ->withHeaders(['Cache-Control' => 'no-store']) + ->withHeaders([ + 'Cache-Control' => 'no-store', + 'Pragma' => 'no-cache', + ]) + ->withOptions(['http_errors' => false]) ->timeout(6) - ->get($origin.self::PUBLIC_PATH, ['domain' => $domain]); + ->get($origin.self::PUBLIC_PATH, [ + 'domain' => $domain, + '_ts' => (string) time(), + ]); if (! $response->successful() || ! $this->isJsonResponse($response)) { continue; } $items = $this->extractItems($response->json()); - if ($items !== null) { + if ($this->hasSettingRows($items)) { + $this->lastFetchMeta = [ + 'channel' => 'public', + 'origin' => $origin, + 'domain' => $domain, + ]; + return $items; } } catch (\Throwable $e) { @@ -137,6 +168,9 @@ class SuperAdminProgramSettingsClient } /** + * اول montzar (منبع اصلی)، بعد origin/SMS صریح، بعد failover. + * قبلاً SMS اول بود و اگر رپلیکای کهنه جواب ۲۰۰ می‌داد، montzar هیچ‌وقت خوانده نمی‌شد. + * * @return list */ private function origins(): array @@ -162,7 +196,16 @@ class SuperAdminProgramSettingsClient } } - return array_values(array_unique(array_merge($extra, $defaults))); + // montzar اول؛ extra فقط اگر هنوز در لیست نیست به انتها اضافه می‌شود + return array_values(array_unique(array_merge($defaults, $extra))); + } + + /** + * @param array>|null $items + */ + private function hasSettingRows(?array $items): bool + { + return is_array($items) && $items !== []; } private function isJsonResponse($response): bool diff --git a/app/Services/SuperAdminProgramSettingsOverlay.php b/app/Services/SuperAdminProgramSettingsOverlay.php index 969144f..dff6848 100644 --- a/app/Services/SuperAdminProgramSettingsOverlay.php +++ b/app/Services/SuperAdminProgramSettingsOverlay.php @@ -2,22 +2,12 @@ namespace App\Services; -use Illuminate\Support\Facades\Cache; - /** * تنظیمات پیشرفته سوپرادمین برای ConfigService و get_settings. - * در هر درخواست یک بار fetch می‌شود؛ آخرین پاسخ سالم برای قطعی لحظه‌ای کش می‌ماند. + * هر درخواست HTTP یک بار از سوپرادمین می‌خواند (memo فقط داخل همان درخواست) — بدون کش Redis. */ class SuperAdminProgramSettingsOverlay { - /** - * آخرین پاسخ سالم سوپرادمین — فقط کلیدهای دسترسی (SUPERADMIN_OWNED_KEYS). - * پاسخ remote ممکن است کلیدهای دیگری هم داشته باشد؛ آن‌ها ذخیره و اعمال نمی‌شوند. - */ - private const LAST_GOOD_CACHE_KEY = 'superadmin:program_settings:last_good'; - - private const LAST_GOOD_CACHE_MINUTES = 1440; - /** * فقط این ۱۴ دسترسی از سوپرادمین خوانده می‌شود. * هر کلید دیگری (قیمت، ساعت کاری، برندینگ، …) مال پنل خانگی است. @@ -73,26 +63,22 @@ class SuperAdminProgramSettingsOverlay private static bool $loaded = false; - /** پاک کردن کش last-good و memo درخواست — برای دکمهٔ همگام‌سازی تنظیمات پیشرفته */ + /** پاک کردن memo درخواست — برای دکمهٔ همگام‌سازی / بروزرسانی */ public function flushCache(): void { self::$memo = null; self::$loaded = false; - try { - Cache::forget(self::LAST_GOOD_CACHE_KEY); - } catch (\Throwable $e) { - // کش در دسترس نیست - } } /** - * کش را پاک می‌کند و دوباره از سوپرادمین می‌خواند. + * دوباره از سوپرادمین می‌خواند (بدون کش پایدار). * - * @return array{ok: bool, count: int, items: array, source: string} + * @return array{ok: bool, count: int, items: array, source: string, meta?: array} */ public function refreshFromSuperAdmin(): array { $this->flushCache(); + $client = app(SuperAdminProgramSettingsClient::class); $map = $this->catalogMap(); if ($map === null) { return [ @@ -100,6 +86,7 @@ class SuperAdminProgramSettingsOverlay 'count' => 0, 'items' => [], 'source' => 'unavailable', + 'meta' => $client->lastFetchMeta(), ]; } @@ -120,6 +107,7 @@ class SuperAdminProgramSettingsOverlay 'count' => count($items), 'items' => $items, 'source' => 'superadmin', + 'meta' => $client->lastFetchMeta(), ]; } @@ -170,17 +158,6 @@ class SuperAdminProgramSettingsOverlay $items = app(SuperAdminProgramSettingsClient::class)->fetchItems(); - if ($items !== null) { - $this->rememberLastGood($items); - } else { - $items = $this->lastGoodItems(); - if ($items !== null) { - \Illuminate\Support\Facades\Log::warning('SuperAdmin program-settings using last-good cache', [ - 'count' => count($items), - ]); - } - } - if ($items === null) { self::$memo = null; @@ -216,39 +193,6 @@ class SuperAdminProgramSettingsOverlay return $map; } - /** @param array> $items */ - private function rememberLastGood(array $items): void - { - $owned = array_values(self::ownedItemsMap($items)); - if ($owned === []) { - return; - } - - try { - Cache::put( - self::LAST_GOOD_CACHE_KEY, - $owned, - now()->addMinutes(self::LAST_GOOD_CACHE_MINUTES) - ); - } catch (\Throwable $e) { - // کش در دسترس نیست — همان رفتار قبلی - } - } - - /** - * @return array>|null - */ - private function lastGoodItems(): ?array - { - try { - $cached = Cache::get(self::LAST_GOOD_CACHE_KEY); - } catch (\Throwable $e) { - return null; - } - - return is_array($cached) && $cached !== [] ? $cached : null; - } - /** * @return array> */