diff --git a/app/admin/controller/game/PlayRecord.php b/app/admin/controller/game/PlayRecord.php index a8cc162..09dfb7c 100644 --- a/app/admin/controller/game/PlayRecord.php +++ b/app/admin/controller/game/PlayRecord.php @@ -96,6 +96,7 @@ class PlayRecord extends Backend 'channel.name as channel__name', 'game_record.period_no as gameRecord__period_no', 'game_record.status as gameRecord__status', + 'game_record.result_number as gameRecord__result_number', ]) ->order($order) ->paginate($limit); @@ -120,6 +121,9 @@ class PlayRecord extends Backend 'status' => isset($row['gameRecord__status']) && is_numeric((string) $row['gameRecord__status']) ? (int) $row['gameRecord__status'] : null, + 'result_number' => isset($row['gameRecord__result_number']) && $row['gameRecord__result_number'] !== '' && $row['gameRecord__result_number'] !== null + ? (string) $row['gameRecord__result_number'] + : null, ]; unset( $row['user__username'], @@ -127,6 +131,7 @@ class PlayRecord extends Backend $row['channel__name'], $row['gameRecord__period_no'], $row['gameRecord__status'], + $row['gameRecord__result_number'], ); $list[$idx] = $row; } diff --git a/app/api/controller/Game.php b/app/api/controller/Game.php index f5dd94c..734004e 100644 --- a/app/api/controller/Game.php +++ b/app/api/controller/Game.php @@ -401,8 +401,40 @@ class Game extends MobileBase 'list_rows' => $pageSize, ]); + $periodIds = []; + foreach ($paginate->items() as $item) { + $periodIdValue = filter_var($item->period_id ?? null, FILTER_VALIDATE_INT); + if ($periodIdValue !== false && $periodIdValue > 0) { + $periodIds[] = $periodIdValue; + } + } + $periodIds = array_values(array_unique($periodIds)); + $resultNumberByPeriodId = []; + if ($periodIds !== []) { + $periodRows = Db::name('game_record') + ->whereIn('id', $periodIds) + ->field(['id', 'result_number']) + ->select() + ->toArray(); + foreach ($periodRows as $row) { + if (!is_array($row)) { + continue; + } + $pid = filter_var($row['id'] ?? null, FILTER_VALIDATE_INT); + if ($pid === false || $pid <= 0) { + continue; + } + $rn = filter_var($row['result_number'] ?? null, FILTER_VALIDATE_INT); + $resultNumberByPeriodId[$pid] = ($rn === false ? null : $rn); + } + } + $rows = []; foreach ($paginate->items() as $item) { + $periodIdValue = filter_var($item->period_id ?? null, FILTER_VALIDATE_INT); + if ($periodIdValue === false) { + $periodIdValue = 0; + } $rows[] = [ 'order_no' => (string) $item->id, 'period_no' => $item->period_no, @@ -410,7 +442,7 @@ class Game extends MobileBase // 整笔压注金额(本笔总扣款) 'bet_amount' => $item->total_amount, 'total_amount' => $item->total_amount, - 'result_number' => null, + 'result_number' => $periodIdValue > 0 ? ($resultNumberByPeriodId[$periodIdValue] ?? null) : null, 'win_amount' => $item->win_amount, 'status' => (string) $item->status, 'create_time' => $item->create_time, diff --git a/app/common/service/GameWebSocketSubscriptionRegistry.php b/app/common/service/GameWebSocketSubscriptionRegistry.php index abf7a73..e175095 100644 --- a/app/common/service/GameWebSocketSubscriptionRegistry.php +++ b/app/common/service/GameWebSocketSubscriptionRegistry.php @@ -106,6 +106,61 @@ final class GameWebSocketSubscriptionRegistry return $finalTopics; } + /** + * 增量订阅:将 topics 合并到现有订阅集合(不会移除旧 topic)。 + * + * 兼容部分客户端“多次 subscribe 但只携带增量 topic”的行为,避免后续误覆盖导致 bet.win/jackpot.hit 丢订阅。 + * + * @param list $topics + * @return list 合并后的订阅列表(去重排序) + */ + public static function mergeSubscriptions(int $connectionId, array $topics): array + { + if ($connectionId <= 0 || !isset(self::$connectionMeta[$connectionId])) { + return []; + } + + $clean = []; + foreach ($topics as $t) { + if (!is_string($t)) { + continue; + } + $v = trim($t); + if ($v === '' || strlen($v) > 64) { + continue; + } + $clean[$v] = true; + } + if ($clean === []) { + return self::$connectionMeta[$connectionId]['topics']; + } + + $existing = self::$connectionMeta[$connectionId]['topics']; + $mergedMap = []; + foreach ($existing as $t) { + $mergedMap[$t] = true; + } + foreach (array_keys($clean) as $t) { + $mergedMap[$t] = true; + } + $finalTopics = array_keys($mergedMap); + sort($finalTopics); + + // 只需要把新增 topic 写入 topicIndex(旧 topic 已存在索引) + $existingMap = []; + foreach ($existing as $t) { + $existingMap[$t] = true; + } + foreach ($finalTopics as $topic) { + if (!isset($existingMap[$topic])) { + self::$topicIndex[$topic][$connectionId] = true; + } + } + self::$connectionMeta[$connectionId]['topics'] = $finalTopics; + + return $finalTopics; + } + /** * 获取订阅了指定 topic 的所有 connection_id。 * diff --git a/app/process/GameWebSocketServer.php b/app/process/GameWebSocketServer.php index 79c5d90..26bcde6 100644 --- a/app/process/GameWebSocketServer.php +++ b/app/process/GameWebSocketServer.php @@ -170,10 +170,24 @@ class GameWebSocketServer if ($action === 'subscribe') { $rawTopics = $decoded['topics'] ?? []; $rawList = is_array($rawTopics) ? $rawTopics : []; - $finalTopics = GameWebSocketSubscriptionRegistry::replaceSubscriptions($connection->id, $rawList); + $replace = false; + if (!empty($decoded['replace'])) { + $replace = true; + } + if (isset($decoded['mode']) && is_string($decoded['mode']) && trim($decoded['mode']) === 'replace') { + $replace = true; + } + $before = GameWebSocketSubscriptionRegistry::meta($connection->id); + $beforeTopics = is_array($before) ? ($before['topics'] ?? []) : []; + + $finalTopics = $replace + ? GameWebSocketSubscriptionRegistry::replaceSubscriptions($connection->id, $rawList) + : GameWebSocketSubscriptionRegistry::mergeSubscriptions($connection->id, $rawList); Log::channel('ws')->info('subscribe', [ 'connection_id' => $connection->id, 'user_id' => GameWebSocketSubscriptionRegistry::userIdOf($connection->id), + 'replace' => $replace, + 'before_topics' => $beforeTopics, 'topics' => $finalTopics, 'requested_count' => is_array($rawTopics) ? count($rawTopics) : 0, ]); diff --git a/web/src/lang/backend/en/game/playRecord.ts b/web/src/lang/backend/en/game/playRecord.ts index e7c0f55..4a53a1f 100644 --- a/web/src/lang/backend/en/game/playRecord.ts +++ b/web/src/lang/backend/en/game/playRecord.ts @@ -34,6 +34,7 @@ export default { update_time: 'Updated', gameRecord_period_no: 'Round (relation)', gameRecord_status: 'Round status', + result_number: 'Result number', 'gameRecord_status 0': 'Open for betting', 'gameRecord_status 1': 'Closed', 'gameRecord_status 2': 'Settling tickets', diff --git a/web/src/lang/backend/zh-cn/game/playRecord.ts b/web/src/lang/backend/zh-cn/game/playRecord.ts index 13bf1d5..447a5fb 100644 --- a/web/src/lang/backend/zh-cn/game/playRecord.ts +++ b/web/src/lang/backend/zh-cn/game/playRecord.ts @@ -34,6 +34,7 @@ export default { update_time: '更新时间', gameRecord_period_no: '对局期号', gameRecord_status: '期状态', + result_number: '中奖号码', 'gameRecord_status 0': '下注开放', 'gameRecord_status 1': '已封盘', 'gameRecord_status 2': '算票中', diff --git a/web/src/views/backend/game/playRecord/index.vue b/web/src/views/backend/game/playRecord/index.vue index 9ced1ad..15ced63 100644 --- a/web/src/views/backend/game/playRecord/index.vue +++ b/web/src/views/backend/game/playRecord/index.vue @@ -147,6 +147,14 @@ const baTable = new baTableClass( '5': t('game.playRecord.gameRecord_status 5'), }, }, + { + label: t('game.playRecord.result_number'), + prop: 'gameRecord.result_number', + align: 'center', + width: 110, + operator: 'eq', + showOverflowTooltip: true, + }, { label: t('game.playRecord.user_id'), prop: 'user_id', align: 'center', show: false, width: 90, operator: 'RANGE' }, { label: t('game.playRecord.user_username'),