diff --git a/app/common/service/GameWebSocketDispatcher.php b/app/common/service/GameWebSocketDispatcher.php index 1aa7bfd..97da40e 100644 --- a/app/common/service/GameWebSocketDispatcher.php +++ b/app/common/service/GameWebSocketDispatcher.php @@ -94,7 +94,9 @@ final class GameWebSocketDispatcher } if ($userScoped && $payloadUserId > 0) { $boundUid = GameWebSocketSubscriptionRegistry::userIdOf($cid); - if ($boundUid !== $payloadUserId) { + // admin/ws 联调场景:admin 连接在握手时绑定 user_id=0,允许观测所有 user-scoped topic(例如 bet.win) + // mobile 用户连接则必须严格匹配 data.user_id,避免泄露其它用户事件 + if ($boundUid !== 0 && $boundUid !== $payloadUserId) { $skippedNotOwner++; continue; } diff --git a/app/common/service/GameWebSocketSubscriptionRegistry.php b/app/common/service/GameWebSocketSubscriptionRegistry.php index fadc0b3..abf7a73 100644 --- a/app/common/service/GameWebSocketSubscriptionRegistry.php +++ b/app/common/service/GameWebSocketSubscriptionRegistry.php @@ -20,20 +20,25 @@ final class GameWebSocketSubscriptionRegistry /** @var array> topic => { connection_id: true } */ private static array $topicIndex = []; - /** @var array, user_id: int, last_seen_at: int, remote_ip: string}> */ + /** @var array, user_id: int, mode: string, last_seen_at: int, remote_ip: string}> */ private static array $connectionMeta = []; /** * 注册新连接(onConnect 调用)。 */ - public static function registerConnection(int $connectionId, int $userId, string $remoteIp = ''): void + public static function registerConnection(int $connectionId, int $userId, string $mode = 'mobile', string $remoteIp = ''): void { if ($connectionId <= 0) { return; } + $mode = trim($mode); + if ($mode !== 'admin') { + $mode = 'mobile'; + } self::$connectionMeta[$connectionId] = [ 'topics' => [], 'user_id' => max(0, $userId), + 'mode' => $mode, 'last_seen_at' => time(), 'remote_ip' => $remoteIp, ]; @@ -139,6 +144,11 @@ final class GameWebSocketSubscriptionRegistry return self::$connectionMeta[$connectionId]['user_id'] ?? 0; } + public static function isAdmin(int $connectionId): bool + { + return (self::$connectionMeta[$connectionId]['mode'] ?? '') === 'admin'; + } + /** * 找出所有 last_seen_at 早于 $cutoff 的连接 id(用于服务端主动关闭僵尸连接)。 * diff --git a/app/process/GameWebSocketServer.php b/app/process/GameWebSocketServer.php index 832ef9e..1890a7a 100644 --- a/app/process/GameWebSocketServer.php +++ b/app/process/GameWebSocketServer.php @@ -114,7 +114,12 @@ class GameWebSocketServer $connection->wsAuth = $auth; self::$connectionAuth[$connection->id] = $auth; - GameWebSocketSubscriptionRegistry::registerConnection($connection->id, (int) $auth['user_id'], $remoteIp); + GameWebSocketSubscriptionRegistry::registerConnection( + $connection->id, + (int) $auth['user_id'], + is_string($auth['mode'] ?? null) ? (string) $auth['mode'] : 'mobile', + $remoteIp + ); Log::channel('ws')->info('handshake ok', [ 'connection_id' => $connection->id, diff --git a/scripts/republish_period_draw.php b/scripts/republish_period_draw.php index cc1c546..4d702f9 100644 --- a/scripts/republish_period_draw.php +++ b/scripts/republish_period_draw.php @@ -95,6 +95,14 @@ GameLiveService::ensurePeriodDrawNotifications( $payloads = GameBetSettleService::buildBetWinPayloadsFromSettledOrders($periodId, $resultNumber); if ($payloads !== []) { + // 同时触发 jackpot.hit(若 bet.win 判定为大奖则应广播);publishSettlementWinsAfterCommit 内含 jackpot.hit 独立去重与从 bet_wins 重建兜底 + GameBetSettleService::publishSettlementWinsAfterCommit([ + 'jackpot_hits' => [], + 'bet_wins' => $payloads, + 'user_streak_events' => [], + 'wallet_events' => [], + 'settled_order_count' => 0, + ], $periodId, $periodNo, $resultNumber); GameBetSettleService::publishBetWinsAfterCommit($payloads, $periodId); echo 'bet.win republished for user_ids: ' . implode(',', array_map(static fn (array $p): int => (int) ($p['user_id'] ?? 0), $payloads)) . PHP_EOL; } else {