240 lines
8.0 KiB
PHP
240 lines
8.0 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace app\common\service;
|
||
|
||
use app\common\library\game\StreakWinReward;
|
||
use support\think\Db;
|
||
|
||
/**
|
||
* 移动端 WebSocket:仅推送当前玩家本局适用赔率(非 streak_win_reward 全表)。
|
||
*/
|
||
final class GameWebSocketPayloadHelper
|
||
{
|
||
public const TOPIC_USER_STREAK = 'user.streak';
|
||
|
||
/** @var list<string> */
|
||
public const ODDS_PUSH_TOPICS = [
|
||
'user.streak',
|
||
'wallet.changed',
|
||
'bet.accepted',
|
||
];
|
||
|
||
/**
|
||
* @return array{user_id: int, current_streak: int, streak_level: int, odds_factor: int, is_jackpot: bool}
|
||
*/
|
||
public static function userStreakData(int $userId, ?int $currentStreak = null): array
|
||
{
|
||
if ($userId <= 0) {
|
||
return [
|
||
'user_id' => 0,
|
||
'current_streak' => 0,
|
||
'streak_level' => 1,
|
||
'odds_factor' => 1,
|
||
'is_jackpot' => false,
|
||
];
|
||
}
|
||
if ($currentStreak === null) {
|
||
$row = GameHotDataRedis::userRow($userId);
|
||
$raw = $row['current_streak'] ?? 0;
|
||
$parsed = filter_var($raw, FILTER_VALIDATE_INT);
|
||
$currentStreak = $parsed === false ? 0 : $parsed;
|
||
}
|
||
|
||
$odds = StreakWinReward::playerBetOddsForCurrentStreak($currentStreak);
|
||
|
||
return [
|
||
'user_id' => $userId,
|
||
'current_streak' => $odds['current_streak'],
|
||
'streak_level' => $odds['streak_level'],
|
||
'odds_factor' => $odds['odds_factor'],
|
||
'is_jackpot' => $odds['is_jackpot'],
|
||
];
|
||
}
|
||
|
||
/**
|
||
* @param array<string, mixed> $payload
|
||
* @return array<string, mixed>
|
||
*/
|
||
public static function mergeUserStreakInto(array $payload, int $userId, ?int $currentStreak = null): array
|
||
{
|
||
if ($userId <= 0) {
|
||
return $payload;
|
||
}
|
||
|
||
return array_merge($payload, self::userStreakData($userId, $currentStreak));
|
||
}
|
||
|
||
/**
|
||
* @param array<string, mixed> $extra
|
||
*/
|
||
public static function publishUserStreak(int $userId, ?int $currentStreak = null, array $extra = []): void
|
||
{
|
||
if ($userId <= 0) {
|
||
return;
|
||
}
|
||
$payload = self::userStreakData($userId, $currentStreak);
|
||
if ($extra !== []) {
|
||
$payload = array_merge($payload, $extra);
|
||
}
|
||
GameWebSocketEventBus::publish(self::TOPIC_USER_STREAK, $payload);
|
||
}
|
||
|
||
/**
|
||
* 后台 WebSocket 联调:从库内选取样例玩家(优先 current_streak 最高)。
|
||
*
|
||
* @return array<string, mixed>|null
|
||
*/
|
||
public static function pickAdminTestUserRow(): ?array
|
||
{
|
||
$fields = ['id', 'username', 'uuid', 'phone', 'current_streak', 'coin'];
|
||
$row = Db::name('user')
|
||
->where('status', 1)
|
||
->order('current_streak', 'desc')
|
||
->order('id', 'asc')
|
||
->field($fields)
|
||
->find();
|
||
if (is_array($row) && !empty($row['id'])) {
|
||
return $row;
|
||
}
|
||
$fallback = Db::name('user')
|
||
->order('id', 'asc')
|
||
->field($fields)
|
||
->find();
|
||
|
||
return is_array($fallback) && !empty($fallback['id']) ? $fallback : null;
|
||
}
|
||
|
||
/**
|
||
* wsConfig 与订阅后演示推送共用的玩家赔率快照。
|
||
*
|
||
* @return array<string, mixed>
|
||
*/
|
||
public static function adminTestPlayerOddsSnapshot(): array
|
||
{
|
||
$row = self::pickAdminTestUserRow();
|
||
if ($row === null) {
|
||
$demoStreak = 3;
|
||
$odds = StreakWinReward::playerBetOddsForCurrentStreak($demoStreak);
|
||
|
||
return array_merge([
|
||
'is_test' => true,
|
||
'preview' => true,
|
||
'user_id' => 0,
|
||
'username' => '演示玩家(库内无用户)',
|
||
'uuid' => '',
|
||
'phone' => '',
|
||
'coin' => '0.00',
|
||
'source' => 'synthetic',
|
||
], $odds);
|
||
}
|
||
|
||
$userIdRaw = $row['id'] ?? 0;
|
||
$userIdParsed = filter_var($userIdRaw, FILTER_VALIDATE_INT);
|
||
$userId = $userIdParsed === false ? 0 : $userIdParsed;
|
||
$streakRaw = $row['current_streak'] ?? 0;
|
||
$streakParsed = filter_var($streakRaw, FILTER_VALIDATE_INT);
|
||
$currentStreak = $streakParsed === false ? 0 : $streakParsed;
|
||
$odds = StreakWinReward::playerBetOddsForCurrentStreak($currentStreak);
|
||
|
||
return array_merge([
|
||
'is_test' => true,
|
||
'preview' => true,
|
||
'user_id' => $userId,
|
||
'username' => (string) ($row['username'] ?? ''),
|
||
'uuid' => (string) ($row['uuid'] ?? ''),
|
||
'phone' => (string) ($row['phone'] ?? ''),
|
||
'coin' => (string) ($row['coin'] ?? '0.00'),
|
||
'source' => 'db_user',
|
||
], $odds);
|
||
}
|
||
|
||
/**
|
||
* 后台测试连接订阅赔率主题后,推送演示帧(非真实业务事件)。
|
||
*
|
||
* @param list<string> $subscribedTopics
|
||
* @return list<array{topic: string, event: string, data: array<string, mixed>}>
|
||
*/
|
||
public static function adminTestPushFrames(array $subscribedTopics): array
|
||
{
|
||
$snapshot = self::adminTestPlayerOddsSnapshot();
|
||
$userIdRaw = $snapshot['user_id'] ?? 0;
|
||
$userIdParsed = filter_var($userIdRaw, FILTER_VALIDATE_INT);
|
||
$userId = $userIdParsed === false ? 0 : $userIdParsed;
|
||
$streakRaw = $snapshot['current_streak'] ?? 0;
|
||
$streakParsed = filter_var($streakRaw, FILTER_VALIDATE_INT);
|
||
$currentStreak = $streakParsed === false ? 0 : $streakParsed;
|
||
|
||
$topicSet = [];
|
||
foreach ($subscribedTopics as $topic) {
|
||
if (!is_string($topic)) {
|
||
continue;
|
||
}
|
||
$value = trim($topic);
|
||
if ($value !== '') {
|
||
$topicSet[$value] = true;
|
||
}
|
||
}
|
||
|
||
$frames = [];
|
||
if (isset($topicSet[self::TOPIC_USER_STREAK])) {
|
||
$frames[] = [
|
||
'topic' => self::TOPIC_USER_STREAK,
|
||
'event' => self::TOPIC_USER_STREAK,
|
||
'data' => $snapshot,
|
||
];
|
||
}
|
||
if (isset($topicSet['wallet.changed'])) {
|
||
$frames[] = [
|
||
'topic' => 'wallet.changed',
|
||
'event' => 'wallet.changed',
|
||
'data' => self::mergeOddsFieldsFromSnapshot([
|
||
'is_test' => true,
|
||
'preview' => true,
|
||
'user_id' => $userId,
|
||
'balance_after' => (string) ($snapshot['coin'] ?? '0.00'),
|
||
'biz_type' => 'admin_test_preview',
|
||
'changed_at' => time(),
|
||
], $snapshot),
|
||
];
|
||
}
|
||
if (isset($topicSet['bet.accepted'])) {
|
||
$frames[] = [
|
||
'topic' => 'bet.accepted',
|
||
'event' => 'bet.accepted',
|
||
'data' => self::mergeOddsFieldsFromSnapshot([
|
||
'is_test' => true,
|
||
'preview' => true,
|
||
'user_id' => $userId,
|
||
'period_no' => 'ADMIN-TEST-PREVIEW',
|
||
'numbers' => [1, 2, 3],
|
||
'bet_id' => 1,
|
||
'single_bet_amount' => '1.00',
|
||
'numbers_count' => 3,
|
||
'total_amount' => '3.00',
|
||
'balance_after' => (string) ($snapshot['coin'] ?? '0.00'),
|
||
'accepted_at' => time(),
|
||
], $snapshot),
|
||
];
|
||
}
|
||
|
||
return $frames;
|
||
}
|
||
|
||
/**
|
||
* @param array<string, mixed> $payload
|
||
* @param array<string, mixed> $snapshot
|
||
* @return array<string, mixed>
|
||
*/
|
||
private static function mergeOddsFieldsFromSnapshot(array $payload, array $snapshot): array
|
||
{
|
||
return array_merge($payload, [
|
||
'current_streak' => $snapshot['current_streak'] ?? 0,
|
||
'streak_level' => $snapshot['streak_level'] ?? 1,
|
||
'odds_factor' => $snapshot['odds_factor'] ?? 1,
|
||
'is_jackpot' => ($snapshot['is_jackpot'] ?? false) === true,
|
||
]);
|
||
}
|
||
}
|