1.优化websocket中的jackpot.hit

2.优化/api/game/betMyOrders接口,新增中奖号码字段result_number
This commit is contained in:
2026-05-28 17:13:59 +08:00
parent c3ac33ec3d
commit 99df6b378a
7 changed files with 118 additions and 2 deletions

View File

@@ -96,6 +96,7 @@ class PlayRecord extends Backend
'channel.name as channel__name', 'channel.name as channel__name',
'game_record.period_no as gameRecord__period_no', 'game_record.period_no as gameRecord__period_no',
'game_record.status as gameRecord__status', 'game_record.status as gameRecord__status',
'game_record.result_number as gameRecord__result_number',
]) ])
->order($order) ->order($order)
->paginate($limit); ->paginate($limit);
@@ -120,6 +121,9 @@ class PlayRecord extends Backend
'status' => isset($row['gameRecord__status']) && is_numeric((string) $row['gameRecord__status']) 'status' => isset($row['gameRecord__status']) && is_numeric((string) $row['gameRecord__status'])
? (int) $row['gameRecord__status'] ? (int) $row['gameRecord__status']
: null, : null,
'result_number' => isset($row['gameRecord__result_number']) && $row['gameRecord__result_number'] !== '' && $row['gameRecord__result_number'] !== null
? (string) $row['gameRecord__result_number']
: null,
]; ];
unset( unset(
$row['user__username'], $row['user__username'],
@@ -127,6 +131,7 @@ class PlayRecord extends Backend
$row['channel__name'], $row['channel__name'],
$row['gameRecord__period_no'], $row['gameRecord__period_no'],
$row['gameRecord__status'], $row['gameRecord__status'],
$row['gameRecord__result_number'],
); );
$list[$idx] = $row; $list[$idx] = $row;
} }

View File

@@ -401,8 +401,40 @@ class Game extends MobileBase
'list_rows' => $pageSize, '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 = []; $rows = [];
foreach ($paginate->items() as $item) { foreach ($paginate->items() as $item) {
$periodIdValue = filter_var($item->period_id ?? null, FILTER_VALIDATE_INT);
if ($periodIdValue === false) {
$periodIdValue = 0;
}
$rows[] = [ $rows[] = [
'order_no' => (string) $item->id, 'order_no' => (string) $item->id,
'period_no' => $item->period_no, 'period_no' => $item->period_no,
@@ -410,7 +442,7 @@ class Game extends MobileBase
// 整笔压注金额(本笔总扣款) // 整笔压注金额(本笔总扣款)
'bet_amount' => $item->total_amount, 'bet_amount' => $item->total_amount,
'total_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, 'win_amount' => $item->win_amount,
'status' => (string) $item->status, 'status' => (string) $item->status,
'create_time' => $item->create_time, 'create_time' => $item->create_time,

View File

@@ -106,6 +106,61 @@ final class GameWebSocketSubscriptionRegistry
return $finalTopics; return $finalTopics;
} }
/**
* 增量订阅:将 topics 合并到现有订阅集合(不会移除旧 topic
*
* 兼容部分客户端“多次 subscribe 但只携带增量 topic”的行为避免后续误覆盖导致 bet.win/jackpot.hit 丢订阅。
*
* @param list<string> $topics
* @return list<string> 合并后的订阅列表(去重排序)
*/
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。 * 获取订阅了指定 topic 的所有 connection_id。
* *

View File

@@ -170,10 +170,24 @@ class GameWebSocketServer
if ($action === 'subscribe') { if ($action === 'subscribe') {
$rawTopics = $decoded['topics'] ?? []; $rawTopics = $decoded['topics'] ?? [];
$rawList = is_array($rawTopics) ? $rawTopics : []; $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', [ Log::channel('ws')->info('subscribe', [
'connection_id' => $connection->id, 'connection_id' => $connection->id,
'user_id' => GameWebSocketSubscriptionRegistry::userIdOf($connection->id), 'user_id' => GameWebSocketSubscriptionRegistry::userIdOf($connection->id),
'replace' => $replace,
'before_topics' => $beforeTopics,
'topics' => $finalTopics, 'topics' => $finalTopics,
'requested_count' => is_array($rawTopics) ? count($rawTopics) : 0, 'requested_count' => is_array($rawTopics) ? count($rawTopics) : 0,
]); ]);

View File

@@ -34,6 +34,7 @@ export default {
update_time: 'Updated', update_time: 'Updated',
gameRecord_period_no: 'Round (relation)', gameRecord_period_no: 'Round (relation)',
gameRecord_status: 'Round status', gameRecord_status: 'Round status',
result_number: 'Result number',
'gameRecord_status 0': 'Open for betting', 'gameRecord_status 0': 'Open for betting',
'gameRecord_status 1': 'Closed', 'gameRecord_status 1': 'Closed',
'gameRecord_status 2': 'Settling tickets', 'gameRecord_status 2': 'Settling tickets',

View File

@@ -34,6 +34,7 @@ export default {
update_time: '更新时间', update_time: '更新时间',
gameRecord_period_no: '对局期号', gameRecord_period_no: '对局期号',
gameRecord_status: '期状态', gameRecord_status: '期状态',
result_number: '中奖号码',
'gameRecord_status 0': '下注开放', 'gameRecord_status 0': '下注开放',
'gameRecord_status 1': '已封盘', 'gameRecord_status 1': '已封盘',
'gameRecord_status 2': '算票中', 'gameRecord_status 2': '算票中',

View File

@@ -147,6 +147,14 @@ const baTable = new baTableClass(
'5': t('game.playRecord.gameRecord_status 5'), '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_id'), prop: 'user_id', align: 'center', show: false, width: 90, operator: 'RANGE' },
{ {
label: t('game.playRecord.user_username'), label: t('game.playRecord.user_username'),