127 lines
4.0 KiB
PHP
127 lines
4.0 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\Price;
|
|
use Illuminate\Support\Facades\Cache;
|
|
|
|
class LivePriceCacheService
|
|
{
|
|
public const FULL_RESPONSE_KEY = 'live_price_full_response';
|
|
|
|
public const LAST_TICK_KEY = 'live_price_last_tick_at';
|
|
|
|
/** همتراز با کران ۱۰ ثانیهای */
|
|
public const TTL_SECONDS = 10;
|
|
|
|
/** اگر tick اخیر نباشد، buildFromDatabase قیمت را قفل میکند */
|
|
public const TICK_MAX_AGE_SECONDS = 20;
|
|
|
|
public function get(): ?array
|
|
{
|
|
$cached = Cache::get(self::FULL_RESPONSE_KEY);
|
|
if (! is_array($cached) || ! isset($cached['body'], $cached['stored_at'])) {
|
|
return null;
|
|
}
|
|
|
|
if (now()->diffInSeconds($cached['stored_at']) > self::TTL_SECONDS) {
|
|
return null;
|
|
}
|
|
|
|
return $cached['body'];
|
|
}
|
|
|
|
public function put(array $body): array
|
|
{
|
|
Cache::put(self::FULL_RESPONSE_KEY, [
|
|
'body' => $body,
|
|
'stored_at' => now(),
|
|
], now()->addSeconds(self::TTL_SECONDS + 5));
|
|
|
|
app(ProductDisplayPriceService::class)->cacheLivePricePayload($body);
|
|
|
|
return $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 خارجی — فقط وقتی کران اخیراً tick زده (همان فرمول lastPrice) */
|
|
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();
|
|
|
|
if ($base <= 0) {
|
|
$payload = [
|
|
'sell' => 0,
|
|
'buy' => 0,
|
|
'price_last' => 0,
|
|
'price' => 0,
|
|
'main_product' => 0,
|
|
'montzar' => 0,
|
|
'zaarbox' => 0,
|
|
'closed' => true,
|
|
'time' => now(),
|
|
];
|
|
} else {
|
|
$montzarSource = 0;
|
|
$zaarboxSource = 0;
|
|
$mainProductSource = 0;
|
|
$priceSource = 'database_last';
|
|
|
|
if ($provider === 'montzar') {
|
|
$liveMontzar = (float) $config->get('montzar_live_price', 0);
|
|
if ($liveMontzar > 0) {
|
|
$base = $liveMontzar;
|
|
}
|
|
$montzarSource = (int) ($liveMontzar > 0 ? $liveMontzar : $base);
|
|
$priceSource = 'montzar';
|
|
} elseif ($provider === 'zaarbox') {
|
|
$zaarboxSource = (int) $base;
|
|
} elseif ($provider === 'main_product') {
|
|
$mainProductSource = (int) $base;
|
|
}
|
|
|
|
$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,
|
|
'time' => now(),
|
|
];
|
|
}
|
|
|
|
$payload['up'] = $config->resolvedDailyBaseExtremes()['up'] > 0
|
|
? [
|
|
'sell' => max(0, (int) ceil($config->resolvedDailyBaseExtremes()['down'] - $config->displaySellSpreadRial())),
|
|
'buy' => (int) ceil($config->resolvedDailyBaseExtremes()['up'] + $config->displayBuySpreadRial()),
|
|
]
|
|
: ['sell' => $payload['sell'] ?? 0, 'buy' => $payload['buy'] ?? 0];
|
|
$payload['last_historical'] = $display->resolveHistoricalBasePrice();
|
|
|
|
return $payload;
|
|
}
|
|
}
|