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 <cursoragent@cursor.com>
This commit is contained in:
@@ -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}");
|
||||
}
|
||||
|
||||
@@ -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'] ?? [],
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<string>
|
||||
*/
|
||||
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<int, array<string, mixed>>|null $items
|
||||
*/
|
||||
private function hasSettingRows(?array $items): bool
|
||||
{
|
||||
return is_array($items) && $items !== [];
|
||||
}
|
||||
|
||||
private function isJsonResponse($response): bool
|
||||
|
||||
@@ -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<string, string>, source: string}
|
||||
* @return array{ok: bool, count: int, items: array<string, string>, source: string, meta?: array<string, mixed>}
|
||||
*/
|
||||
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<int, array<string, mixed>> $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<int, array<string, mixed>>|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<int, array<string, mixed>>
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user