fix channel price bug 2

This commit is contained in:
2026-07-07 19:24:46 +03:30
parent fcc2181679
commit a04dc6e57d
+72 -45
View File
@@ -22,6 +22,8 @@ class PriceChannelBroadcastService
private const SIGNATURE_CACHE_KEY = 'price_channel:last_signature'; private const SIGNATURE_CACHE_KEY = 'price_channel:last_signature';
private const SEND_LOCK_KEY = 'price_channel:send_lock';
private const SUPPORTED_MESSENGERS = ['bale', 'telegram', 'rubika']; private const SUPPORTED_MESSENGERS = ['bale', 'telegram', 'rubika'];
/** وزن هر مثقال به گرم — ضریب تبدیل قیمت هر گرم به هر مثقال (مثقال = گرم × این عدد) */ /** وزن هر مثقال به گرم — ضریب تبدیل قیمت هر گرم به هر مثقال (مثقال = گرم × این عدد) */
@@ -48,60 +50,70 @@ class PriceChannelBroadcastService
return; return;
} }
// شرط ۱ (ارزان): از آخرین «ارسال واقعی» حداقل price_channel_min_seconds گذشته باشد. // قفل — دو فراخوان هم‌زمان (مثلاً دو force_refresh پشت‌سرهم) را سریالی می‌کند تا
// چون فقط روی ارسال واقعی markSent می‌کنیم، یک بررسیِ بی‌ارسال تایمر را ریست نمی‌کند // هر دو قبل از ذخیرهٔ markSent/امضا، cooldown را «گذشته» و امضا را «قدیمی» نبینند و
// و ارسالِ بعدی را عقب نمی‌اندازد. // پیام تکراری نفرستند. اگر قفل دست کسی است، همین حالا بی‌ارسال برمی‌گردیم.
if (! $this->cooldownElapsed()) { $lock = Cache::lock(self::SEND_LOCK_KEY, 30);
if (! $lock->get()) {
return; return;
} }
$products = $this->eligibleProducts(); try {
if ($products === []) { // شرط ۱: از آخرین «ارسال واقعی» حداقل price_channel_min_seconds گذشته باشد.
Log::debug('PriceChannel: محصول واجد شرایطی نیست (active/قیمت/ساعت کاری)'); // چون فقط روی ارسال واقعی markSent می‌کنیم، یک بررسیِ بی‌ارسال تایمر را ریست نمی‌کند.
return; if (! $this->cooldownElapsed()) {
return;
}
$products = $this->eligibleProducts();
if ($products === []) {
Log::debug('PriceChannel: محصول واجد شرایطی نیست (active/قیمت/ساعت کاری)');
return;
}
// شرط ۲: قیمتِ نمایشی نسبت به آخرین ارسال واقعاً تغییر کرده باشد؛ وگرنه چیزی نفرست
// و تایمر را هم دست نزن (تا سرِ اولین تغییرِ بعدی بلافاصله بفرستد).
$signature = $this->priceSignature($products);
if ($signature === Cache::get(self::SIGNATURE_CACHE_KEY)) {
return;
}
$message = $this->buildMessage($products);
if ($message === '') {
return;
}
$results = [];
foreach ($targets as $messenger => $target) {
$results[$messenger] = $this->dispatchToMessenger($messenger, $target['token'], $target['chat_ids'], $message);
}
// فقط بعد از ارسال واقعی: تایمر cooldown و امضای قیمت به‌روزرسانی می‌شوند
$this->markSent();
Cache::put(self::SIGNATURE_CACHE_KEY, $signature, now()->addDay());
Log::info('PriceChannel: قیمت به کانال ارسال شد', [
'targets' => array_keys($targets),
'products' => count($products),
'results' => $results,
]);
} finally {
$lock->release();
} }
// شرط ۲: قیمت نسبت به آخرین ارسال واقعاً تغییر کرده باشد؛ وگرنه چیزی نفرست
// و تایمر را هم دست نزن (تا سرِ اولین تغییرِ بعدی بلافاصله بفرستد).
$signature = $this->priceSignature($products);
if ($signature === Cache::get(self::SIGNATURE_CACHE_KEY)) {
return;
}
$message = $this->buildMessage($products);
if ($message === '') {
return;
}
$results = [];
foreach ($targets as $messenger => $target) {
$results[$messenger] = $this->dispatchToMessenger($messenger, $target['token'], $target['chat_ids'], $message);
}
// فقط بعد از ارسال واقعی: تایمر cooldown و امضای قیمت به‌روزرسانی می‌شوند
$this->markSent();
Cache::put(self::SIGNATURE_CACHE_KEY, $signature, now()->addDay());
Log::info('PriceChannel: قیمت به کانال ارسال شد', [
'targets' => array_keys($targets),
'products' => count($products),
'results' => $results,
]);
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::warning('PriceChannelBroadcast failed', ['message' => $e->getMessage()]); Log::warning('PriceChannelBroadcast failed', ['message' => $e->getMessage()]);
} }
} }
/** امضای قیمت محصولات واجد شرایط — برای تشخیص تغییر قیمت */ /**
* امضای قیمت — روی «بدنهٔ نمایشیِ» پیام (بدون هدر/ساعت) گرفته می‌شود تا دقیقاً همان چیزی
* که کاربر می‌بیند مبنای تشخیص تغییر باشد؛ اختلاف چند ریالیِ زیرِ آستانهٔ نمایش پیام تکراری نمی‌سازد.
*
* @param array<int, Product> $products
*/
private function priceSignature(array $products): string private function priceSignature(array $products): string
{ {
$parts = []; return md5($this->buildPriceBody($products));
foreach ($products as $product) {
$parts[] = $product->id . ':' . (int) ($product->price ?? 0)
. ':' . ((int) ($product->open_buy)) . ((int) ($product->open_sell));
}
return md5(implode('|', $parts));
} }
/** /**
@@ -232,6 +244,23 @@ class PriceChannelBroadcastService
public function buildMessage(?array $products = null): string public function buildMessage(?array $products = null): string
{ {
$products = $products ?? $this->eligibleProducts(); $products = $products ?? $this->eligibleProducts();
$body = $this->buildPriceBody($products);
if ($body === '') {
return '';
}
return trim($this->header() . "\n\n" . $body);
}
/**
* بدنهٔ قیمت (بدون هدر/ساعت) — همان چیزی که «تغییر قیمت» را می‌سازد.
* امضای تشخیص تغییر روی همین بدنه‌ی نمایشیِ رُندشده گرفته می‌شود تا اختلاف چند ریالی
* که به یک عدد نمایشیِ یکسان می‌رسد، پیام تکراری نفرستد.
*
* @param array<int, Product> $products
*/
private function buildPriceBody(array $products): string
{
if ($products === []) { if ($products === []) {
return ''; return '';
} }
@@ -242,8 +271,6 @@ class PriceChannelBroadcastService
$mesghal = $this->changeMesghal(); $mesghal = $this->changeMesghal();
$lines = []; $lines = [];
$lines[] = $this->header();
$lines[] = '';
foreach ($products as $product) { foreach ($products as $product) {
$base = (float) ($product->price ?? 0); $base = (float) ($product->price ?? 0);