diffInSeconds($cached['stored_at']) > self::TTL_SECONDS) { return null; } return $cached['body']; } public function put(array $body): array { $cached = Cache::get(self::FULL_RESPONSE_KEY); $cachedBody = is_array($cached['body'] ?? null) ? $cached['body'] : null; $config = app(ConfigService::class); // no_quote صفر را روی مظنهٔ معتبر قبلی ننویس — قطع فید لحظه‌ای نباید UI را خالی کند $incomingEmpty = ! $config->payloadHasDisplayablePrice($body) && (string) ($body['status'] ?? '') === 'no_quote'; $existingGood = is_array($cachedBody) && $config->payloadHasDisplayablePrice($cachedBody); if ($incomingEmpty && $existingGood) { Cache::put(self::FULL_RESPONSE_KEY, [ 'body' => $cachedBody, 'stored_at' => now(), ], now()->addSeconds(self::TTL_SECONDS + 5)); return $cachedBody; } if ($cachedBody !== null && PriceBroadcastService::lastPriceIdentity($cachedBody) === PriceBroadcastService::lastPriceIdentity($body)) { Cache::put(self::FULL_RESPONSE_KEY, [ 'body' => $cachedBody, 'stored_at' => now(), ], now()->addSeconds(self::TTL_SECONDS + 5)); return $cachedBody; } $this->storeBody($body); app(PriceBroadcastService::class)->broadcastLastIfChanged($body); app(PriceChannelBroadcastService::class)->scheduleBroadcastIfEligible(); return $body; } /** * بازنویسی کش last بدون گیت هویت — برای اعمال فوری ساعت کاری. * * @param array $body * @return array */ public function replaceBody(array $body): array { $this->storeBody($body); return $body; } /** * @param array $body */ private function storeBody(array $body): void { Cache::put(self::FULL_RESPONSE_KEY, [ 'body' => $body, 'stored_at' => now(), ], now()->addSeconds(self::TTL_SECONDS + 5)); app(ProductDisplayPriceService::class)->cacheLivePricePayload($body); } public function markTick(): void { Cache::put(self::LAST_TICK_KEY, now(), now()->addMinutes(10)); } public function hasRecentTick(): bool { $lastTick = Cache::get(self::LAST_TICK_KEY); if ($lastTick === null) { return false; } return now()->diffInSeconds($lastTick) <= self::TICK_MAX_AGE_SECONDS; } /** پاسخ سریع بدون API خارجی — از montzar_live_price / آخرین ردیف prices */ public function buildFromDatabase(): array { $config = app(ConfigService::class); $display = app(ProductDisplayPriceService::class); $last = Price::orderByDesc('id')->first(); $base = $last ? (float) $last->price : 0.0; $provider = $config->priceProvider(); $montzarSource = 0; $zaarboxSource = 0; $mainProductSource = 0; $priceSource = 'database_last'; if ($provider === 'montzar') { $liveMontzar = (float) $config->get('montzar_live_price', 0); if ($liveMontzar > 0 && $config->isPlausibleQuotePrice($liveMontzar)) { $base = $liveMontzar; $montzarSource = (int) $liveMontzar; $priceSource = 'montzar'; } } elseif ($provider === 'zaarbox' && $base > 0) { $zaarboxSource = (int) $base; $priceSource = 'zaarbox'; } elseif ($provider === 'main_product' && $base > 0) { $mainProductSource = (int) $base; $priceSource = 'main_product'; } // قبلاً فقط به جدول prices نگاه می‌کرد؛ اگر خالی بود SSE همان لحظه no_quote صفر // می‌فرستاد و UI مظنهٔ معتبر HTTP را پاک می‌کرد — در حالی که montzar_live_price پر بود. if ($base <= 0) { $payload = [ 'sell' => 0, 'buy' => 0, 'price_last' => 0, 'price' => 0, 'main_product' => 0, 'montzar' => 0, 'zaarbox' => 0, 'closed' => false, 'status' => 'no_quote', 'price_source' => '', 'time' => now(), ]; } else { $trade = $config->buildTradeDisplayPrices($base, $priceSource); $payload = [ 'sell' => $trade['sell'], 'buy' => $trade['buy'], 'price_last' => $trade['priceLast'], 'price' => (int) $base, 'main_product' => $mainProductSource, 'montzar' => $montzarSource, 'zaarbox' => $zaarboxSource, 'closed' => false, 'status' => 'open', 'price_source' => $priceSource, 'time' => now(), ]; } $payload['last_historical'] = $display->resolveHistoricalBasePrice(); $payload = $config->applyGlobalTradingHoursToPayload($payload); $extremes = $config->resolvedDailyBaseExtremes(); $payload['up'] = ($payload['status'] ?? 'open') !== 'open' ? ['sell' => 0, 'buy' => 0] : ($extremes['up'] > 0 ? [ 'sell' => max(0, (int) ceil($extremes['down'] - $config->displaySellPriceDiffRial())), 'buy' => (int) ceil($extremes['up'] + $config->displayBuyPriceDiffRial()), ] : ['sell' => $payload['sell'] ?? 0, 'buy' => $payload['buy'] ?? 0]); return app(ZaarboxPriceService::class)->attachGoldOunceToPayload($payload); } }