fix(sse): isolate price stream inbox per tenant
Shared Redis global inbox mixed other shops' product lists into this catalog and made the grid jump. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -52,7 +52,7 @@ class PriceBroadcastService
|
|||||||
}
|
}
|
||||||
|
|
||||||
$lastChanged = is_array($lastPayload) && $lastPayload !== []
|
$lastChanged = is_array($lastPayload) && $lastPayload !== []
|
||||||
&& $this->rememberHashIfChanged(self::LAST_HASH_KEY, self::lastPriceIdentity($lastPayload));
|
&& $this->rememberHashIfChanged($this->cacheKey(self::LAST_HASH_KEY), self::lastPriceIdentity($lastPayload));
|
||||||
|
|
||||||
if ($lastChanged && is_array($lastPayload)) {
|
if ($lastChanged && is_array($lastPayload)) {
|
||||||
$stream->publish('last', $lastPayload);
|
$stream->publish('last', $lastPayload);
|
||||||
@@ -106,7 +106,7 @@ class PriceBroadcastService
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $this->rememberHashIfChanged(self::LAST_HASH_KEY, self::lastPriceIdentity($lastPayload))) {
|
if (! $this->rememberHashIfChanged($this->cacheKey(self::LAST_HASH_KEY), self::lastPriceIdentity($lastPayload))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +117,7 @@ class PriceBroadcastService
|
|||||||
public function buildListPayload(bool $allowCached = false): ?array
|
public function buildListPayload(bool $allowCached = false): ?array
|
||||||
{
|
{
|
||||||
if ($allowCached) {
|
if ($allowCached) {
|
||||||
$cached = Cache::get(self::LIST_SNAPSHOT_KEY);
|
$cached = Cache::get($this->cacheKey(self::LIST_SNAPSHOT_KEY));
|
||||||
if (
|
if (
|
||||||
is_array($cached)
|
is_array($cached)
|
||||||
&& is_array($cached['body'] ?? null)
|
&& is_array($cached['body'] ?? null)
|
||||||
@@ -130,7 +130,7 @@ class PriceBroadcastService
|
|||||||
|
|
||||||
$body = $this->buildProductListPayload();
|
$body = $this->buildProductListPayload();
|
||||||
if ($body !== null && $body !== []) {
|
if ($body !== null && $body !== []) {
|
||||||
Cache::put(self::LIST_SNAPSHOT_KEY, [
|
Cache::put($this->cacheKey(self::LIST_SNAPSHOT_KEY), [
|
||||||
'body' => $body,
|
'body' => $body,
|
||||||
'stored_at' => now(),
|
'stored_at' => now(),
|
||||||
], now()->addSeconds(self::LIST_SNAPSHOT_SECONDS + 5));
|
], now()->addSeconds(self::LIST_SNAPSHOT_SECONDS + 5));
|
||||||
@@ -156,15 +156,15 @@ class PriceBroadcastService
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $force && ! $this->rememberHashIfChanged(self::LIST_HASH_KEY, self::listIdentity($listPayload))) {
|
if (! $force && ! $this->rememberHashIfChanged($this->cacheKey(self::LIST_HASH_KEY), self::listIdentity($listPayload))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($force) {
|
if ($force) {
|
||||||
$this->rememberHashIfChanged(self::LIST_HASH_KEY, self::listIdentity($listPayload));
|
$this->rememberHashIfChanged($this->cacheKey(self::LIST_HASH_KEY), self::listIdentity($listPayload));
|
||||||
}
|
}
|
||||||
|
|
||||||
Cache::forget(self::LIST_SNAPSHOT_KEY);
|
Cache::forget($this->cacheKey(self::LIST_SNAPSHOT_KEY));
|
||||||
app(PriceStreamService::class)->publish('list', $listPayload);
|
app(PriceStreamService::class)->publish('list', $listPayload);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -218,6 +218,11 @@ class PriceBroadcastService
|
|||||||
/**
|
/**
|
||||||
* @param array<string, mixed>|array<int, mixed> $payload
|
* @param array<string, mixed>|array<int, mixed> $payload
|
||||||
*/
|
*/
|
||||||
|
private function cacheKey(string $base): string
|
||||||
|
{
|
||||||
|
return $base.':'.app(PriceStreamService::class)->tenantScope();
|
||||||
|
}
|
||||||
|
|
||||||
private function rememberHashIfChanged(string $cacheKey, array $payload): bool
|
private function rememberHashIfChanged(string $cacheKey, array $payload): bool
|
||||||
{
|
{
|
||||||
$hash = md5(json_encode($payload, JSON_UNESCAPED_UNICODE));
|
$hash = md5(json_encode($payload, JSON_UNESCAPED_UNICODE));
|
||||||
|
|||||||
@@ -126,14 +126,37 @@ class PriceStreamService
|
|||||||
return $store;
|
return $store;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* چند مستأجر روی یک Redis با پیشوند یکسان inbox را قاطی میکردند
|
||||||
|
* (لیست شمش یک فروشگاه روی صفحهٔ فروشگاه دیگر).
|
||||||
|
*/
|
||||||
|
public function tenantScope(): string
|
||||||
|
{
|
||||||
|
$default = (string) config('database.default', 'mysql');
|
||||||
|
$db = (string) config("database.connections.{$default}.database");
|
||||||
|
$host = '';
|
||||||
|
try {
|
||||||
|
$host = app(ConfigService::class)->resolvePublicHost();
|
||||||
|
} catch (\Throwable) {
|
||||||
|
$host = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$parts = array_values(array_filter(
|
||||||
|
[$db, $host],
|
||||||
|
static fn (string $value): bool => $value !== ''
|
||||||
|
));
|
||||||
|
|
||||||
|
return $parts !== [] ? implode(':', $parts) : 'default';
|
||||||
|
}
|
||||||
|
|
||||||
private function inboxKey(): string
|
private function inboxKey(): string
|
||||||
{
|
{
|
||||||
return self::INBOX_PREFIX.'global';
|
return self::INBOX_PREFIX.$this->tenantScope();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function customerInboxKey(int $customerId): string
|
private function customerInboxKey(int $customerId): string
|
||||||
{
|
{
|
||||||
return self::CUSTOMER_INBOX_PREFIX.$customerId;
|
return self::CUSTOMER_INBOX_PREFIX.$this->tenantScope().':'.$customerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function currentInboxSeq(string $key): int
|
private function currentInboxSeq(string $key): int
|
||||||
|
|||||||
Reference in New Issue
Block a user