fix price force refresh 2
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Services\LivePriceTickService;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* حلقه بینهایت — برای supervisor: php artisan prices:tick-daemon
|
||||
*/
|
||||
class PriceTickDaemonCommand extends Command
|
||||
{
|
||||
protected $signature = 'prices:tick-daemon {--interval=10}';
|
||||
|
||||
protected $description = 'Continuously refresh montzar/zaarbox/products every N seconds (supervisor)';
|
||||
|
||||
public function handle(LivePriceTickService $tickService): int
|
||||
{
|
||||
$interval = max(1, (int) $this->option('interval'));
|
||||
|
||||
$this->info("Price tick daemon started (every {$interval}s). Ctrl+C to stop.");
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
$tickService->tick();
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('prices:tick-daemon failed', [
|
||||
'message' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
|
||||
sleep($interval);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Services\LivePriceTickService;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
/**
|
||||
* هر دقیقه ۶ بار prices:tick — برای schedule:run (فاصله ۱۰ ثانیه).
|
||||
*/
|
||||
class PriceTickLoopCommand extends Command
|
||||
{
|
||||
protected $signature = 'prices:tick-loop {--iterations=6} {--interval=10}';
|
||||
|
||||
protected $description = 'Run prices:tick several times per minute (default: 6× every 10s)';
|
||||
|
||||
public function handle(LivePriceTickService $tickService): int
|
||||
{
|
||||
$iterations = max(1, (int) $this->option('iterations'));
|
||||
$interval = max(1, (int) $this->option('interval'));
|
||||
|
||||
for ($i = 0; $i < $iterations; $i++) {
|
||||
$tickService->tick();
|
||||
|
||||
if ($i < $iterations - 1) {
|
||||
sleep($interval);
|
||||
}
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -13,15 +13,12 @@ class Kernel extends ConsoleKernel
|
||||
|
||||
protected function schedule(Schedule $schedule)
|
||||
{
|
||||
// تیک قیمت — هر ۱۰ ثانیه (۶ بار در دقیقه)
|
||||
$schedule->call(function () {
|
||||
for ($i = 0; $i < 6; $i++) {
|
||||
\Illuminate\Support\Facades\Artisan::call('prices:tick');
|
||||
if ($i < 5) {
|
||||
sleep(10);
|
||||
}
|
||||
}
|
||||
})->everyMinute()->name('prices_tick_10s')->withoutOverlapping();
|
||||
// تیک قیمت — ۶ بار در دقیقه با فاصله ۱۰ ثانیه (montzar + zaarbox + محصولات)
|
||||
// نیاز: * * * * * php artisan schedule:run
|
||||
$schedule->command('prices:tick-loop --iterations=6 --interval=10')
|
||||
->everyMinute()
|
||||
->name('prices_tick_10s')
|
||||
->withoutOverlapping(58);
|
||||
|
||||
$schedule->command('prices:refresh-zaarbox')
|
||||
->everyThirtyMinutes()
|
||||
|
||||
@@ -122,13 +122,13 @@ class ReportController extends Controller
|
||||
];
|
||||
}
|
||||
|
||||
protected function appendUpPrices(array $payload): array
|
||||
protected function appendUpPrices(array $payload, bool $saveFullCache = false): array
|
||||
{
|
||||
$payload['up'] = $this->buildUpDownPrices();
|
||||
$payload['last_historical'] = app(ProductDisplayPriceService::class)->resolveHistoricalBasePrice();
|
||||
app(ProductDisplayPriceService::class)->cacheLivePricePayload($payload);
|
||||
|
||||
if (request()->boolean('force_refresh')) {
|
||||
if ($saveFullCache || request()->boolean('force_refresh')) {
|
||||
return app(LivePriceCacheService::class)->put($payload);
|
||||
}
|
||||
|
||||
@@ -139,8 +139,9 @@ class ReportController extends Controller
|
||||
{
|
||||
$config = app(ConfigService::class);
|
||||
$cache = app(LivePriceCacheService::class);
|
||||
$forceRefresh = $request->boolean('force_refresh');
|
||||
|
||||
if (! $request->boolean('force_refresh')) {
|
||||
if (! $forceRefresh) {
|
||||
$cached = $cache->get();
|
||||
if ($cached !== null) {
|
||||
return $cached;
|
||||
@@ -158,7 +159,7 @@ class ReportController extends Controller
|
||||
'time' => now(),
|
||||
'main_product' => 0,
|
||||
'zaarbox' => 0,
|
||||
], $this->storeLastPrice($config)));
|
||||
], $this->storeLastPrice($config)), $forceRefresh);
|
||||
}
|
||||
|
||||
$closed = false;
|
||||
@@ -167,9 +168,6 @@ class ReportController extends Controller
|
||||
$zaarbox_price = 0;
|
||||
$pr_price = 0;
|
||||
|
||||
$open_buy = true;
|
||||
$open_sell = true;
|
||||
|
||||
$montzar_open_buy = false;
|
||||
$montzar_open_sell = false;
|
||||
|
||||
@@ -180,7 +178,7 @@ class ReportController extends Controller
|
||||
return filter_var($value, FILTER_VALIDATE_BOOLEAN);
|
||||
};
|
||||
|
||||
$zeroResponse = function () {
|
||||
$zeroResponse = function () use ($forceRefresh) {
|
||||
return $this->appendUpPrices([
|
||||
'sell' => 0,
|
||||
'buy' => 0,
|
||||
@@ -191,7 +189,7 @@ class ReportController extends Controller
|
||||
'zaarbox' => 0,
|
||||
'closed' => true,
|
||||
'time' => now(),
|
||||
]);
|
||||
], $forceRefresh);
|
||||
};
|
||||
|
||||
$priceProvider = $config->priceProvider();
|
||||
@@ -231,20 +229,13 @@ class ReportController extends Controller
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
$settings = Setting::whereIn('key', [
|
||||
'fee_single',
|
||||
'main_product_price',
|
||||
'buy_fee',
|
||||
'sell_fee',
|
||||
])->get();
|
||||
|
||||
$settingsMap = $settings->pluck('value', 'key');
|
||||
$show_last_price = $config->getBool('show_last_price', false);
|
||||
$main_product_price = $settingsMap->get('main_product_price');
|
||||
|
||||
$difference = (float) $settingsMap->get('fee_single', 0);
|
||||
$diff_buy = (float) $settingsMap->get('buy_fee', $difference);
|
||||
$diff_sell = (float) $settingsMap->get('sell_fee', $difference);
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Montzar — فقط وقتی price_provider=montzar
|
||||
@@ -402,7 +393,7 @@ class ReportController extends Controller
|
||||
'zaarbox' => $zaarbox_price ?? 0,
|
||||
'closed' => true,
|
||||
'time' => now(),
|
||||
]);
|
||||
], $forceRefresh);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,43 +449,17 @@ class ReportController extends Controller
|
||||
| محاسبه sell / buy
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
if ($priceSource === 'montzar') {
|
||||
$montzar_profit = (float) $config->Config('montzar_diff');
|
||||
|
||||
$final_buy_diff = $diff_buy + $montzar_profit;
|
||||
$final_sell_diff = $diff_sell + $montzar_profit;
|
||||
|
||||
$sell = $montzar_open_sell
|
||||
? ceil($priceValue - $final_sell_diff)
|
||||
: 0;
|
||||
|
||||
$buy = $montzar_open_buy
|
||||
? ceil($priceValue + $final_buy_diff)
|
||||
: 0;
|
||||
|
||||
$priceLast = ceil($priceValue + $final_buy_diff);
|
||||
} else {
|
||||
$sell = $open_sell
|
||||
? ceil($priceValue - $difference)
|
||||
: 0;
|
||||
|
||||
$buy = $open_buy
|
||||
? ceil($priceValue + $difference)
|
||||
: 0;
|
||||
|
||||
$priceLast = ceil($priceValue + $difference);
|
||||
}
|
||||
|
||||
/*
|
||||
| main_product / zaarbox: buy/sell از همان قیمت زنده (نه رکورد قدیمی DB).
|
||||
*/
|
||||
if (in_array($priceProvider, ['main_product', 'zaarbox'], true) && !$closed) {
|
||||
$base = (float) $priceValue;
|
||||
|
||||
$sell = $open_sell ? (int) ceil($base - $diff_sell) : 0;
|
||||
$buy = $open_buy ? (int) ceil($base + $diff_buy) : 0;
|
||||
$priceLast = (int) ceil($base + $diff_buy);
|
||||
}
|
||||
$display = $config->buildTradeDisplayPrices(
|
||||
(float) $priceValue,
|
||||
$priceSource,
|
||||
[
|
||||
'montzar_open_buy' => $montzar_open_buy,
|
||||
'montzar_open_sell' => $montzar_open_sell,
|
||||
]
|
||||
);
|
||||
$sell = $display['sell'];
|
||||
$buy = $display['buy'];
|
||||
$priceLast = $display['priceLast'];
|
||||
|
||||
if (!$closed && $priceValue > 0) {
|
||||
$config->trackDailyPriceExtreme((float) $priceValue);
|
||||
@@ -510,7 +475,7 @@ class ReportController extends Controller
|
||||
'zaarbox' => $zaarbox_price ?? 0,
|
||||
'closed' => false,
|
||||
'time' => now(),
|
||||
]);
|
||||
], $forceRefresh);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -102,6 +102,54 @@ class ConfigService
|
||||
return $sellFee > 0 ? $sellFee : $feeSingle;
|
||||
}
|
||||
|
||||
/**
|
||||
* sell / buy / price_last — یک منبع حقیقت برای lastPrice و buildFromDatabase.
|
||||
*
|
||||
* @param float $priceValue قیمت پایه (ریال/گرم) — از API یا DB
|
||||
* @param string|null $priceSource montzar | zaarbox | main_product | database_last | null
|
||||
* @param array{montzar_open_buy?:bool,montzar_open_sell?:bool} $montzarFlags فقط وقتی API تازه خوانده شده
|
||||
* @return array{sell:int,buy:int,priceLast:int}
|
||||
*/
|
||||
public function buildTradeDisplayPrices(
|
||||
float $priceValue,
|
||||
?string $priceSource = null,
|
||||
array $montzarFlags = []
|
||||
): array {
|
||||
$diffBuy = (float) $this->displayBuySpreadRial();
|
||||
$diffSell = (float) $this->displaySellSpreadRial();
|
||||
$feeSingle = (float) $this->get('fee_single', 0);
|
||||
|
||||
$openBuy = true;
|
||||
$openSell = true;
|
||||
|
||||
if ($priceSource === 'montzar') {
|
||||
$montzarProfit = (float) $this->get('montzar_diff', 0);
|
||||
$finalBuyDiff = $diffBuy + $montzarProfit;
|
||||
$finalSellDiff = $diffSell + $montzarProfit;
|
||||
$openBuy = $montzarFlags['montzar_open_buy'] ?? $this->getBool('montzar_open_buy', true);
|
||||
$openSell = $montzarFlags['montzar_open_sell'] ?? $this->getBool('montzar_open_sell', true);
|
||||
|
||||
$sell = $openSell ? (int) ceil($priceValue - $finalSellDiff) : 0;
|
||||
$buy = $openBuy ? (int) ceil($priceValue + $finalBuyDiff) : 0;
|
||||
$priceLast = (int) ceil($priceValue + $finalBuyDiff);
|
||||
|
||||
return compact('sell', 'buy', 'priceLast');
|
||||
}
|
||||
|
||||
$sell = $openSell ? (int) ceil($priceValue - $feeSingle) : 0;
|
||||
$buy = $openBuy ? (int) ceil($priceValue + $feeSingle) : 0;
|
||||
$priceLast = (int) ceil($priceValue + $feeSingle);
|
||||
|
||||
$provider = $this->priceProvider();
|
||||
if (in_array($provider, ['main_product', 'zaarbox'], true)) {
|
||||
$sell = $openSell ? (int) ceil($priceValue - $diffSell) : 0;
|
||||
$buy = $openBuy ? (int) ceil($priceValue + $diffBuy) : 0;
|
||||
$priceLast = (int) ceil($priceValue + $diffBuy);
|
||||
}
|
||||
|
||||
return compact('sell', 'buy', 'priceLast');
|
||||
}
|
||||
|
||||
/**
|
||||
* تنها منبع فعال قیمت — montzar | main_product | zaarbox
|
||||
*
|
||||
|
||||
@@ -37,19 +37,15 @@ class LivePriceCacheService
|
||||
return $body;
|
||||
}
|
||||
|
||||
/** پاسخ سریع بدون API خارجی — از آخرین رکورد prices + تنظیمات */
|
||||
/** پاسخ سریع بدون API خارجی — از آخرین رکورد prices + تنظیمات (همان فرمول lastPrice) */
|
||||
public function buildFromDatabase(): array
|
||||
{
|
||||
$config = app(ConfigService::class);
|
||||
$display = app(ProductDisplayPriceService::class);
|
||||
$last = Price::orderByDesc('id')->first();
|
||||
$base = $last ? (int) $last->price : 0;
|
||||
$base = $last ? (float) $last->price : 0.0;
|
||||
$provider = $config->priceProvider();
|
||||
|
||||
$montzarPrice = 0;
|
||||
$zaarboxPrice = 0;
|
||||
$mainProductPrice = 0;
|
||||
|
||||
if ($base <= 0) {
|
||||
$payload = [
|
||||
'sell' => 0,
|
||||
@@ -63,35 +59,32 @@ class LivePriceCacheService
|
||||
'time' => now(),
|
||||
];
|
||||
} else {
|
||||
$diffBuy = (int) $config->displayBuySpreadRial();
|
||||
$diffSell = (int) $config->displaySellSpreadRial();
|
||||
$openBuy = true;
|
||||
$openSell = true;
|
||||
$montzarSource = 0;
|
||||
$zaarboxSource = 0;
|
||||
$mainProductSource = 0;
|
||||
$priceSource = 'database_last';
|
||||
|
||||
if ($provider === 'montzar') {
|
||||
$montzarPrice = $config->getInt('montzar_live_price', 0) ?: $base;
|
||||
$montzarDiff = (int) $config->getInt('montzar_diff', 0);
|
||||
$diffBuy += $montzarDiff;
|
||||
$diffSell += $montzarDiff;
|
||||
$openBuy = $config->getBool('montzar_open_buy', true);
|
||||
$openSell = $config->getBool('montzar_open_sell', true);
|
||||
$liveMontzar = (float) $config->get('montzar_live_price', 0);
|
||||
$montzarSource = (int) ($liveMontzar > 0 ? $liveMontzar : $base);
|
||||
// DB از آخرین tick montzar پر شده — همان فرمول نمایش معامله
|
||||
$priceSource = 'montzar';
|
||||
} elseif ($provider === 'zaarbox') {
|
||||
$zaarboxPrice = $base;
|
||||
$zaarboxSource = (int) $base;
|
||||
} elseif ($provider === 'main_product') {
|
||||
$mainProductPrice = $base;
|
||||
$mainProductSource = (int) $base;
|
||||
}
|
||||
|
||||
$buy = $openBuy ? (int) ceil($base + $diffBuy) : 0;
|
||||
$sell = $openSell ? max(0, (int) ceil($base - $diffSell)) : 0;
|
||||
$trade = $config->buildTradeDisplayPrices($base, $priceSource);
|
||||
|
||||
$payload = [
|
||||
'sell' => $sell,
|
||||
'buy' => $buy,
|
||||
'price_last' => $buy,
|
||||
'price' => $base,
|
||||
'main_product' => $mainProductPrice,
|
||||
'montzar' => $montzarPrice,
|
||||
'zaarbox' => $zaarboxPrice,
|
||||
'sell' => $trade['sell'],
|
||||
'buy' => $trade['buy'],
|
||||
'price_last' => $trade['priceLast'],
|
||||
'price' => (int) $base,
|
||||
'main_product' => $mainProductSource,
|
||||
'montzar' => $montzarSource,
|
||||
'zaarbox' => $zaarboxSource,
|
||||
'closed' => false,
|
||||
'time' => now(),
|
||||
];
|
||||
@@ -105,8 +98,6 @@ class LivePriceCacheService
|
||||
: ['sell' => $payload['sell'] ?? 0, 'buy' => $payload['buy'] ?? 0];
|
||||
$payload['last_historical'] = $display->resolveHistoricalBasePrice();
|
||||
|
||||
$display->cacheLivePricePayload($payload);
|
||||
|
||||
return $payload;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,44 +5,79 @@ namespace App\Services;
|
||||
use App\Http\Controllers\Site\ReportController;
|
||||
use App\Models\Product;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class LivePriceTickService
|
||||
{
|
||||
private const LOCK_SECONDS = 25;
|
||||
|
||||
public function tick(): void
|
||||
{
|
||||
$config = app(ConfigService::class);
|
||||
$sync = app(ProductPriceSyncService::class);
|
||||
|
||||
if ($config->isStore()) {
|
||||
$sync->syncStorePrices(true);
|
||||
$source = 'zaarbox';
|
||||
$sourceUrl = app(ZaarboxPriceService::class)->apiUrl();
|
||||
} else {
|
||||
$sync->syncAll(true);
|
||||
$source = $config->priceProvider() ?: 'legacy';
|
||||
$sourceUrl = $source === 'zaarbox'
|
||||
? app(ZaarboxPriceService::class)->apiUrl()
|
||||
: null;
|
||||
$lock = Cache::lock('prices:tick', self::LOCK_SECONDS);
|
||||
if (! $lock->get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$liquidGold = (int) $config->getInt('montzar_live_price', 0);
|
||||
if ($liquidGold <= 0) {
|
||||
$liquidGold = app(ProductDisplayPriceService::class)->resolveHistoricalBasePrice();
|
||||
}
|
||||
try {
|
||||
$config = app(ConfigService::class);
|
||||
$sync = app(ProductPriceSyncService::class);
|
||||
$zaarbox = app(ZaarboxPriceService::class);
|
||||
|
||||
$activeProducts = Product::query()
|
||||
->where('active', 1)
|
||||
->whereNotNull('reading_method')
|
||||
->count();
|
||||
/*
|
||||
| ۱. montzar / zaarbox زنده + کش ۱۵ثانیهای /api/price/last
|
||||
| (قبل از sync محصولات تا آبشدهٔ «قیمت اپ» تازه باشد)
|
||||
*/
|
||||
$request = Request::create('/api/price/last', 'GET', ['force_refresh' => '1']);
|
||||
$response = app(ReportController::class)->lastPrice($request);
|
||||
$payload = json_decode($response->getContent(), true);
|
||||
if (! is_array($payload)) {
|
||||
$payload = [];
|
||||
}
|
||||
|
||||
app(PriceSyncLogService::class)->recordTick($source, $sourceUrl, $liquidGold, $activeProducts);
|
||||
/*
|
||||
| ۲. فید zaarbox برای محصولات متغیر (reading_method)
|
||||
*/
|
||||
if ($zaarbox->isEnabled() || Product::query()->whereNotNull('reading_method')->exists()) {
|
||||
try {
|
||||
$zaarbox->cachedPriceList(true);
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('Zaarbox feed refresh in tick failed', ['message' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
$request = Request::create('/api/price/last', 'GET', ['force_refresh' => '1']);
|
||||
$response = app(ReportController::class)->lastPrice($request);
|
||||
$payload = json_decode($response->getContent(), true);
|
||||
/*
|
||||
| ۳. همگامسازی قیمت محصولات
|
||||
*/
|
||||
if ($config->isStore()) {
|
||||
$sync->syncStorePrices(true);
|
||||
$source = 'zaarbox';
|
||||
$sourceUrl = $zaarbox->apiUrl();
|
||||
} else {
|
||||
$sync->syncAll(true);
|
||||
$source = $config->priceProvider() ?: 'legacy';
|
||||
$sourceUrl = $source === 'zaarbox'
|
||||
? $zaarbox->apiUrl()
|
||||
: null;
|
||||
}
|
||||
|
||||
if (is_array($payload)) {
|
||||
app(PriceSnapshotService::class)->recordTick($payload);
|
||||
$liquidGold = (int) ($payload['price'] ?? $config->getInt('montzar_live_price', 0));
|
||||
if ($liquidGold <= 0) {
|
||||
$liquidGold = app(ProductDisplayPriceService::class)->resolveHistoricalBasePrice();
|
||||
}
|
||||
|
||||
$activeProducts = Product::query()
|
||||
->where('active', 1)
|
||||
->whereNotNull('reading_method')
|
||||
->count();
|
||||
|
||||
app(PriceSyncLogService::class)->recordTick($source, $sourceUrl, $liquidGold, $activeProducts);
|
||||
|
||||
if ($payload !== []) {
|
||||
app(PriceSnapshotService::class)->recordTick($payload);
|
||||
}
|
||||
} finally {
|
||||
$lock->release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/bin/sh
|
||||
# نصب کران قیمت — یکی از دو روش را انتخاب کنید.
|
||||
# مسیر را با مسیر واقعی سرور عوض کنید.
|
||||
APP_DIR="/opt/rahimigold/server"
|
||||
|
||||
# ─── روش ۱ (پیشنهادی): schedule:run هر دقیقه ───
|
||||
# prices:tick-loop داخل Kernel هر ۱۰ ثانیه ۶ بار اجرا میشود.
|
||||
# crontab -e:
|
||||
# * * * * * cd $APP_DIR && php artisan schedule:run >> /dev/null 2>&1
|
||||
|
||||
# ─── روش ۲: مستقیم هر ۱۰ ثانیه (بدون schedule:run) ───
|
||||
# * * * * * cd $APP_DIR && php artisan prices:tick >> /dev/null 2>&1
|
||||
# * * * * * sleep 10 && cd $APP_DIR && php artisan prices:tick >> /dev/null 2>&1
|
||||
# * * * * * sleep 20 && cd $APP_DIR && php artisan prices:tick >> /dev/null 2>&1
|
||||
# * * * * * sleep 30 && cd $APP_DIR && php artisan prices:tick >> /dev/null 2>&1
|
||||
# * * * * * sleep 40 && cd $APP_DIR && php artisan prices:tick >> /dev/null 2>&1
|
||||
# * * * * * sleep 50 && cd $APP_DIR && php artisan prices:tick >> /dev/null 2>&1
|
||||
|
||||
# ─── روش ۳: supervisor (حلقه دائمی) ───
|
||||
# php artisan prices:tick-daemon --interval=10
|
||||
|
||||
echo "See comments in this file for crontab lines."
|
||||
Reference in New Issue
Block a user