1.优化中奖后只推送一帧中奖记录消息
This commit is contained in:
@@ -25,7 +25,7 @@ final class GameBetSettleService
|
||||
/**
|
||||
* 对指定期次按开奖号码结算所有「待开奖」注单;同一注单幂等(仅 status=1 会更新)。
|
||||
*
|
||||
* @return array{jackpot_hits: list<array{user_id: int, 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}>}
|
||||
*
|
||||
* @throws Throwable
|
||||
*/
|
||||
@@ -147,6 +147,7 @@ final class GameBetSettleService
|
||||
}
|
||||
|
||||
$jackpotHits = [];
|
||||
$hitUserIds = [];
|
||||
foreach ($jackpotNotify as $uid => $_) {
|
||||
if (!isset($aggregateByUser[$uid])) {
|
||||
continue;
|
||||
@@ -155,8 +156,14 @@ final class GameBetSettleService
|
||||
if (bccomp($agg['total_win'], '0', 2) <= 0) {
|
||||
continue;
|
||||
}
|
||||
$hitUserIds[] = (int) $uid;
|
||||
}
|
||||
$userNameMap = self::loadUserDisplayNames($hitUserIds);
|
||||
foreach ($hitUserIds as $uid) {
|
||||
$agg = $aggregateByUser[$uid];
|
||||
$jackpotHits[] = [
|
||||
'user_id' => (int) $uid,
|
||||
'user_id' => $uid,
|
||||
'nickname' => $userNameMap[$uid] ?? ('用户' . $uid),
|
||||
'period_no' => (string) ($agg['period_no'] ?? ''),
|
||||
'total_win' => (string) $agg['total_win'],
|
||||
'result_number' => $resultNumber,
|
||||
@@ -166,6 +173,41 @@ final class GameBetSettleService
|
||||
return ['jackpot_hits' => $jackpotHits];
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量读取用户展示名:nickname 优先;空则 fallback 到 username;仍空则返回空串(调用方自行兜底)。
|
||||
*
|
||||
* @param list<int> $userIds
|
||||
* @return array<int, string>
|
||||
*/
|
||||
private static function loadUserDisplayNames(array $userIds): array
|
||||
{
|
||||
$userIds = array_values(array_unique(array_filter($userIds, static fn ($v): bool => $v > 0)));
|
||||
if ($userIds === []) {
|
||||
return [];
|
||||
}
|
||||
$rows = Db::name('user')
|
||||
->whereIn('id', $userIds)
|
||||
->field(['id', 'nickname', 'username'])
|
||||
->select()
|
||||
->toArray();
|
||||
$out = [];
|
||||
foreach ($rows as $row) {
|
||||
$uid = isset($row['id']) && is_numeric($row['id']) ? (int) $row['id'] : 0;
|
||||
if ($uid <= 0) {
|
||||
continue;
|
||||
}
|
||||
$nickname = isset($row['nickname']) && is_string($row['nickname']) ? trim($row['nickname']) : '';
|
||||
if ($nickname !== '') {
|
||||
$out[$uid] = $nickname;
|
||||
continue;
|
||||
}
|
||||
$username = isset($row['username']) && is_string($row['username']) ? trim($row['username']) : '';
|
||||
$out[$uid] = $username;
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 大奖审核通过后派彩(幂等):仅当 play_record.status=待审核 且 win_amount>=阈值时执行。
|
||||
*
|
||||
|
||||
@@ -9,6 +9,7 @@ use app\common\model\UserWalletRecord;
|
||||
use app\common\service\GameHotDataCoordinator;
|
||||
use app\common\service\GameHotDataLock;
|
||||
use support\Log;
|
||||
use support\Redis;
|
||||
use support\think\Db;
|
||||
use Throwable;
|
||||
|
||||
@@ -23,6 +24,10 @@ final class GameLiveService
|
||||
private const EVT_PERIOD_LOCKED = 'period.locked';
|
||||
private const EVT_PERIOD_OPENED = 'period.opened';
|
||||
private const EVT_PERIOD_PAYOUT = 'period.payout';
|
||||
|
||||
/** period.tick 边界帧去重(finished / void 每期只推一次),TTL 兼顾跨进程与跨期重启 */
|
||||
private const TICK_BOUNDARY_DEDUP_KEY_PREFIX = 'dfw:v1:ws:tick:boundary:';
|
||||
private const TICK_BOUNDARY_DEDUP_TTL_SECONDS = 300;
|
||||
private const KEY_PERIOD_SECONDS = 'period_seconds';
|
||||
private const KEY_BET_SECONDS = 'bet_seconds';
|
||||
private const KEY_PAYOUT_SECONDS = 'payout_seconds';
|
||||
@@ -923,9 +928,53 @@ final class GameLiveService
|
||||
'server_time' => time(),
|
||||
];
|
||||
|
||||
if (!self::shouldPublishPeriodTick($status, $periodNo)) {
|
||||
return;
|
||||
}
|
||||
|
||||
GameWebSocketEventBus::publish(self::EVT_PERIOD_TICK, $payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* period.tick 状态过滤(与《36字花-移动端接口设计草案》7.1.3 推送规则对齐):
|
||||
* - betting / locked:保持每秒推送
|
||||
* - payouting:完全静默(中奖信息已通过 period.opened / period.payout / jackpot.hit / wallet.changed 通知)
|
||||
* - finished / void:每个期号只推一次边界帧,告知前端本期收尾,随后静默直到下一期 betting
|
||||
*/
|
||||
private static function shouldPublishPeriodTick(string $status, string $periodNo): bool
|
||||
{
|
||||
if ($status === 'payouting') {
|
||||
return false;
|
||||
}
|
||||
if ($status === 'finished' || $status === 'void') {
|
||||
if ($periodNo === '') {
|
||||
return true;
|
||||
}
|
||||
return self::markBoundaryFrameOnce($periodNo, $status);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 边界帧去重:SET NX EX,占位成功(首次)返回 true;已存在返回 false。Redis 异常时降级为放行。
|
||||
*/
|
||||
private static function markBoundaryFrameOnce(string $periodNo, string $status): bool
|
||||
{
|
||||
$key = self::TICK_BOUNDARY_DEDUP_KEY_PREFIX . $periodNo . ':' . $status;
|
||||
try {
|
||||
$client = Redis::connection()->client();
|
||||
if (!is_object($client) || !method_exists($client, 'set')) {
|
||||
return true;
|
||||
}
|
||||
$ok = $client->set($key, '1', ['nx', 'ex' => self::TICK_BOUNDARY_DEDUP_TTL_SECONDS]);
|
||||
|
||||
return $ok === true;
|
||||
} catch (Throwable) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $record game_record 行
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user