1.优化ws游戏对局推送消息-小奖为bet.win
This commit is contained in:
@@ -26,13 +26,18 @@ class GameCurrentStatus extends Backend
|
|||||||
|
|
||||||
$subscribeTopics = [
|
$subscribeTopics = [
|
||||||
'period.tick',
|
'period.tick',
|
||||||
'user.streak',
|
|
||||||
'period.opened',
|
|
||||||
'period.locked',
|
'period.locked',
|
||||||
|
'period.opened',
|
||||||
'period.payout',
|
'period.payout',
|
||||||
'bet.accepted',
|
'period.payout.tick',
|
||||||
|
'bet.win',
|
||||||
|
'user.streak',
|
||||||
'wallet.changed',
|
'wallet.changed',
|
||||||
|
'bet.accepted',
|
||||||
|
'jackpot.hit',
|
||||||
'auto.spin.progress',
|
'auto.spin.progress',
|
||||||
|
'admin.live.snapshot',
|
||||||
|
'admin.live.opened',
|
||||||
];
|
];
|
||||||
|
|
||||||
$oddsPushTopics = GameWebSocketPayloadHelper::ODDS_PUSH_TOPICS;
|
$oddsPushTopics = GameWebSocketPayloadHelper::ODDS_PUSH_TOPICS;
|
||||||
|
|||||||
@@ -22,17 +22,23 @@ final class GameBetSettleService
|
|||||||
|
|
||||||
public const CONFIG_KEY_JACKPOT_MAX_AMOUNT = 'jackpot_max_amount';
|
public const CONFIG_KEY_JACKPOT_MAX_AMOUNT = 'jackpot_max_amount';
|
||||||
|
|
||||||
|
/** 小奖(非大奖档)中奖:移动端弹窗/通知专用 */
|
||||||
|
public const TOPIC_BET_WIN = 'bet.win';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 对指定期次按开奖号码结算所有「待开奖」注单;同一注单幂等(仅 status=1 会更新)。
|
* 对指定期次按开奖号码结算所有「待开奖」注单;同一注单幂等(仅 status=1 会更新)。
|
||||||
*
|
*
|
||||||
* @return array{jackpot_hits: list<array{user_id: int, nickname: string, period_no: string, total_win: string, result_number: int}>}
|
* @return array{
|
||||||
|
* jackpot_hits: list<array{user_id: int, nickname: string, period_no: string, total_win: string, result_number: int}>,
|
||||||
|
* bet_wins: list<array<string, mixed>>
|
||||||
|
* }
|
||||||
*
|
*
|
||||||
* @throws Throwable
|
* @throws Throwable
|
||||||
*/
|
*/
|
||||||
public static function settleBetsForDraw(int $recordId, int $resultNumber): array
|
public static function settleBetsForDraw(int $recordId, int $resultNumber): array
|
||||||
{
|
{
|
||||||
if ($recordId <= 0 || $resultNumber < 1) {
|
if ($recordId <= 0 || $resultNumber < 1) {
|
||||||
return ['jackpot_hits' => []];
|
return ['jackpot_hits' => [], 'bet_wins' => []];
|
||||||
}
|
}
|
||||||
|
|
||||||
$now = time();
|
$now = time();
|
||||||
@@ -53,6 +59,9 @@ final class GameBetSettleService
|
|||||||
/** @var array<int, true> */
|
/** @var array<int, true> */
|
||||||
$jackpotNotify = [];
|
$jackpotNotify = [];
|
||||||
|
|
||||||
|
/** @var array<int, array{user_id: int, period_id: int, period_no: string, result_number: int, total_win: string, balance_after: string, bets: list<array{bet_id: int, win_amount: string}>}> */
|
||||||
|
$smallWinByUser = [];
|
||||||
|
|
||||||
foreach ($bets as $bet) {
|
foreach ($bets as $bet) {
|
||||||
$betId = (int) ($bet['id'] ?? 0);
|
$betId = (int) ($bet['id'] ?? 0);
|
||||||
if ($betId <= 0) {
|
if ($betId <= 0) {
|
||||||
@@ -103,10 +112,30 @@ final class GameBetSettleService
|
|||||||
|
|
||||||
$balanceAfter = (string) (Db::name('user')->where('id', $userId)->value('coin') ?? '0');
|
$balanceAfter = (string) (Db::name('user')->where('id', $userId)->value('coin') ?? '0');
|
||||||
if (!$needReview && bccomp($win, '0', 2) > 0) {
|
if (!$needReview && bccomp($win, '0', 2) > 0) {
|
||||||
$paid = self::creditUserPayout($bet, $betId, $win, $now, null, '压注派彩');
|
$paid = self::creditUserPayout($bet, $betId, $win, $now, null, '压注派彩', $resultNumber);
|
||||||
if ($paid !== null) {
|
if ($paid !== null) {
|
||||||
$balanceAfter = $paid;
|
$balanceAfter = $paid;
|
||||||
}
|
}
|
||||||
|
$streakAtBet = (int) ($bet['streak_at_bet'] ?? 0);
|
||||||
|
if ($paid !== null && !StreakWinReward::isJackpotForStreakAtBet($streakAtBet)) {
|
||||||
|
if (!isset($smallWinByUser[$userId])) {
|
||||||
|
$smallWinByUser[$userId] = [
|
||||||
|
'user_id' => $userId,
|
||||||
|
'period_id' => $recordId,
|
||||||
|
'period_no' => (string) ($bet['period_no'] ?? ''),
|
||||||
|
'result_number' => $resultNumber,
|
||||||
|
'total_win' => '0.00',
|
||||||
|
'balance_after' => $balanceAfter,
|
||||||
|
'bets' => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$smallWinByUser[$userId]['total_win'] = bcadd($smallWinByUser[$userId]['total_win'], $win, 2);
|
||||||
|
$smallWinByUser[$userId]['balance_after'] = $balanceAfter;
|
||||||
|
$smallWinByUser[$userId]['bets'][] = [
|
||||||
|
'bet_id' => $betId,
|
||||||
|
'win_amount' => $win,
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$periodNo = (string) ($bet['period_no'] ?? '');
|
$periodNo = (string) ($bet['period_no'] ?? '');
|
||||||
@@ -170,7 +199,32 @@ final class GameBetSettleService
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return ['jackpot_hits' => $jackpotHits];
|
$betWins = array_values($smallWinByUser);
|
||||||
|
|
||||||
|
return ['jackpot_hits' => $jackpotHits, 'bet_wins' => $betWins];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事务提交后推送小奖中奖(bet.win)。
|
||||||
|
*
|
||||||
|
* @param list<array<string, mixed>> $betWins
|
||||||
|
*/
|
||||||
|
public static function publishBetWinsAfterCommit(array $betWins): void
|
||||||
|
{
|
||||||
|
foreach ($betWins as $payload) {
|
||||||
|
if (!is_array($payload) || empty($payload['user_id'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$userId = filter_var($payload['user_id'], FILTER_VALIDATE_INT);
|
||||||
|
if ($userId === false || $userId <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$data = array_merge($payload, [
|
||||||
|
'is_jackpot' => false,
|
||||||
|
'server_time' => time(),
|
||||||
|
]);
|
||||||
|
GameWebSocketEventBus::publish(self::TOPIC_BET_WIN, $data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -408,7 +462,7 @@ final class GameBetSettleService
|
|||||||
/**
|
/**
|
||||||
* @return string|null 派彩后余额;已幂等入账过时返回当前余额;失败或未执行派彩返回 null
|
* @return string|null 派彩后余额;已幂等入账过时返回当前余额;失败或未执行派彩返回 null
|
||||||
*/
|
*/
|
||||||
private static function creditUserPayout(array $bet, int $betId, string $winAmount, int $now, ?int $operatorAdminId, string $remark): ?string
|
private static function creditUserPayout(array $bet, int $betId, string $winAmount, int $now, ?int $operatorAdminId, string $remark, ?int $resultNumber = null): ?string
|
||||||
{
|
{
|
||||||
$userId = (int) ($bet['user_id'] ?? 0);
|
$userId = (int) ($bet['user_id'] ?? 0);
|
||||||
if ($userId <= 0) {
|
if ($userId <= 0) {
|
||||||
@@ -451,13 +505,20 @@ final class GameBetSettleService
|
|||||||
'update_time' => $now,
|
'update_time' => $now,
|
||||||
]);
|
]);
|
||||||
GameHotDataCoordinator::afterUserCommitted($userId);
|
GameHotDataCoordinator::afterUserCommitted($userId);
|
||||||
GameWebSocketEventBus::publish('wallet.changed', GameWebSocketPayloadHelper::mergeUserStreakInto([
|
$walletPayload = [
|
||||||
'user_id' => $userId,
|
'user_id' => $userId,
|
||||||
'balance_after' => $after,
|
'balance_after' => $after,
|
||||||
'biz_type' => 'payout',
|
'biz_type' => 'payout',
|
||||||
'ref_id' => $betId,
|
'ref_id' => $betId,
|
||||||
|
'amount' => $winAmount,
|
||||||
|
'period_no' => (string) ($bet['period_no'] ?? ''),
|
||||||
|
'period_id' => isset($bet['period_id']) && is_numeric($bet['period_id']) ? (int) $bet['period_id'] : 0,
|
||||||
'changed_at' => $now,
|
'changed_at' => $now,
|
||||||
], $userId));
|
];
|
||||||
|
if ($resultNumber !== null && $resultNumber > 0) {
|
||||||
|
$walletPayload['result_number'] = $resultNumber;
|
||||||
|
}
|
||||||
|
GameWebSocketEventBus::publish('wallet.changed', GameWebSocketPayloadHelper::mergeUserStreakInto($walletPayload, $userId));
|
||||||
|
|
||||||
return $after;
|
return $after;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,9 +101,10 @@ final class GameLiveService
|
|||||||
|
|
||||||
$now = time();
|
$now = time();
|
||||||
$payoutUntil = isset($row['payout_until']) ? (int) $row['payout_until'] : 0;
|
$payoutUntil = isset($row['payout_until']) ? (int) $row['payout_until'] : 0;
|
||||||
|
$settleOut = ['jackpot_hits' => [], 'bet_wins' => []];
|
||||||
Db::startTrans();
|
Db::startTrans();
|
||||||
try {
|
try {
|
||||||
GameBetSettleService::settleBetsForDraw($recordId, $resultNumber);
|
$settleOut = GameBetSettleService::settleBetsForDraw($recordId, $resultNumber);
|
||||||
if ($status === 2) {
|
if ($status === 2) {
|
||||||
if ($payoutUntil <= 0) {
|
if ($payoutUntil <= 0) {
|
||||||
$payoutUntil = $now + self::getPayoutGraceSeconds();
|
$payoutUntil = $now + self::getPayoutGraceSeconds();
|
||||||
@@ -139,6 +140,9 @@ final class GameLiveService
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$betWins = is_array($settleOut['bet_wins'] ?? null) ? $settleOut['bet_wins'] : [];
|
||||||
|
GameBetSettleService::publishBetWinsAfterCommit($betWins);
|
||||||
|
|
||||||
GameHotDataCoordinator::afterGameRecordCommitted($recordId);
|
GameHotDataCoordinator::afterGameRecordCommitted($recordId);
|
||||||
self::publishSnapshot(null);
|
self::publishSnapshot(null);
|
||||||
|
|
||||||
@@ -585,7 +589,7 @@ final class GameLiveService
|
|||||||
$now = time();
|
$now = time();
|
||||||
$payoutUntil = $now + self::getPayoutGraceSeconds();
|
$payoutUntil = $now + self::getPayoutGraceSeconds();
|
||||||
|
|
||||||
$settleOut = ['jackpot_hits' => []];
|
$settleOut = ['jackpot_hits' => [], 'bet_wins' => []];
|
||||||
Db::startTrans();
|
Db::startTrans();
|
||||||
try {
|
try {
|
||||||
Db::name('game_record')->where('id', (int) $record['id'])->update([
|
Db::name('game_record')->where('id', (int) $record['id'])->update([
|
||||||
@@ -603,6 +607,9 @@ final class GameLiveService
|
|||||||
return ['ok' => false, 'msg' => __('Game live: settlement error') . ': ' . $e->getMessage()];
|
return ['ok' => false, 'msg' => __('Game live: settlement error') . ': ' . $e->getMessage()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$betWins = is_array($settleOut['bet_wins'] ?? null) ? $settleOut['bet_wins'] : [];
|
||||||
|
GameBetSettleService::publishBetWinsAfterCommit($betWins);
|
||||||
|
|
||||||
GameHotDataCoordinator::afterGameRecordCommitted($rid);
|
GameHotDataCoordinator::afterGameRecordCommitted($rid);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -791,7 +791,7 @@
|
|||||||
- 订阅连胜/赔率(仅当前玩家):`{"action":"subscribe","topics":["user.streak","wallet.changed","bet.accepted"]}`
|
- 订阅连胜/赔率(仅当前玩家):`{"action":"subscribe","topics":["user.streak","wallet.changed","bet.accepted"]}`
|
||||||
- 订阅资金流:`{"action":"subscribe","topics":["bet.accepted","wallet.changed"]}`
|
- 订阅资金流:`{"action":"subscribe","topics":["bet.accepted","wallet.changed"]}`
|
||||||
- 订阅托管流:`{"action":"subscribe","topics":["auto.spin.progress","wallet.changed"]}`
|
- 订阅托管流:`{"action":"subscribe","topics":["auto.spin.progress","wallet.changed"]}`
|
||||||
- 移动端推荐合并订阅:`period.tick`、`user.streak`、`wallet.changed`、`bet.accepted`、`period.opened`
|
- 移动端推荐合并订阅:`period.tick`、`bet.win`、`user.streak`、`wallet.changed`、`bet.accepted`、`period.opened`、`period.payout`、`jackpot.hit`
|
||||||
|
|
||||||
#### 7.1.1 消息协议字段定义(联调口径)
|
#### 7.1.1 消息协议字段定义(联调口径)
|
||||||
|
|
||||||
@@ -833,7 +833,13 @@
|
|||||||
- `user.streak`:每期结算更新用户连胜后按用户推送(未中奖也会推送,`current_streak` 可能归零)。
|
- `user.streak`:每期结算更新用户连胜后按用户推送(未中奖也会推送,`current_streak` 可能归零)。
|
||||||
- `admin.live.snapshot`:**每秒一次**(后台实时对局页全量快照;不受派彩静默期影响)。
|
- `admin.live.snapshot`:**每秒一次**(后台实时对局页全量快照;不受派彩静默期影响)。
|
||||||
- `period.opened` / `period.payout` / `admin.live.opened`:按开奖流程阶段触发(事件触发型,非固定频率)。
|
- `period.opened` / `period.payout` / `admin.live.opened`:按开奖流程阶段触发(事件触发型,非固定频率)。
|
||||||
- `wallet.changed`:仅在余额发生变更时推送(如下注扣款、充值入账、派彩入账)。
|
- `wallet.changed`:仅在余额发生变更时推送(如下注扣款、充值入账、派彩入账)。派彩时 `biz_type=payout`,并带 `amount`(本次派彩金额)、`period_no`、`period_id`、`result_number`(若有)。
|
||||||
|
- **`bet.win`(小奖)**:本期中奖且**非大奖档**(`streak_win_reward` 中 `is_jackpot=false`)时,按用户聚合推送一帧;用于弹窗/横幅,**比 `wallet.changed` 更适合做「你中了小奖」展示**。
|
||||||
|
- `data.user_id` / `data.period_id` / `data.period_no` / `data.result_number`
|
||||||
|
- `data.total_win`:本期该用户小奖派彩合计
|
||||||
|
- `data.balance_after`:派彩后余额
|
||||||
|
- `data.bets[]`:`{ bet_id, win_amount }` 明细
|
||||||
|
- `data.is_jackpot`:固定 `false`
|
||||||
- `jackpot.hit`:**仅在本期存在中大奖命中用户时推送**;无命中不推送。
|
- `jackpot.hit`:**仅在本期存在中大奖命中用户时推送**;无命中不推送。
|
||||||
- **载荷字段**:`period_id` / `period_no` / `result_number` / `hits[]` / `server_time`。
|
- **载荷字段**:`period_id` / `period_no` / `result_number` / `hits[]` / `server_time`。
|
||||||
- `hits[]` 数组每项字段:
|
- `hits[]` 数组每项字段:
|
||||||
|
|||||||
@@ -98,13 +98,18 @@ const logs = ref<Array<{ t: number; event: string; payload: string }>>([])
|
|||||||
|
|
||||||
const defaultSubscribeTopics = [
|
const defaultSubscribeTopics = [
|
||||||
'period.tick',
|
'period.tick',
|
||||||
'user.streak',
|
|
||||||
'period.opened',
|
|
||||||
'period.locked',
|
'period.locked',
|
||||||
|
'period.opened',
|
||||||
'period.payout',
|
'period.payout',
|
||||||
'bet.accepted',
|
'period.payout.tick',
|
||||||
|
'bet.win',
|
||||||
|
'user.streak',
|
||||||
'wallet.changed',
|
'wallet.changed',
|
||||||
|
'bet.accepted',
|
||||||
|
'jackpot.hit',
|
||||||
'auto.spin.progress',
|
'auto.spin.progress',
|
||||||
|
'admin.live.snapshot',
|
||||||
|
'admin.live.opened',
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
const testPlayerOddsSourceLabel = computed(() => {
|
const testPlayerOddsSourceLabel = computed(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user