Files
chatroom/app/Jobs/CloseBaccaratRoundJob.php
lkddi 1c53acbd1b 优化:百家乐结算公告新增各用户输赢明细
- 结算时同步收集 winners(中奖用户+金额)和 losers(未中用户-金额)
- 公屏广播消息末尾附加:
  🏆 中奖:甲+2,000、乙+1,000 🪙
  😔 未中:丙-500、丁-1,000
- 单方向最多显示 10 人,防止消息过长
- 顺手修正豹子结果文本中 dice1 重复的 bug(dice2、dice3 显示错误)
2026-03-04 14:41:07 +08:00

203 lines
7.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* 文件功能:百家乐结算队列任务
*
* 在押注截止时间到达后自动触发:
* ① 摇三颗骰子 ② 判断大/小/豹子/庄家收割
* ③ 遍历下注记录逐一发放赔付 ④ 广播结果 ⑤ 公屏公告
*
* @author ChatRoom Laravel
*
* @version 1.0.0
*/
namespace App\Jobs;
use App\Enums\CurrencySource;
use App\Events\BaccaratRoundSettled;
use App\Events\MessageSent;
use App\Models\BaccaratBet;
use App\Models\BaccaratRound;
use App\Models\GameConfig;
use App\Services\ChatStateService;
use App\Services\UserCurrencyService;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\DB;
class CloseBaccaratRoundJob implements ShouldQueue
{
use Queueable;
/**
* 最大重试次数。
*/
public int $tries = 3;
/**
* @param BaccaratRound $round 要结算的局次
*/
public function __construct(
public readonly BaccaratRound $round,
) {}
/**
* 执行结算逻辑。
*/
public function handle(
UserCurrencyService $currency,
ChatStateService $chatState,
): void {
$round = $this->round->fresh();
// 防止重复结算
if (! $round || $round->status !== 'betting') {
return;
}
$config = GameConfig::forGame('baccarat')?->params ?? [];
// 乐观锁CAS 先把状态改为 settling
$updated = BaccaratRound::query()
->where('id', $round->id)
->where('status', 'betting')
->update(['status' => 'settling']);
if (! $updated) {
return; // 已被其他进程处理
}
// ── 摇骰子 ──────────────────────────────────────────────────
$dice = [random_int(1, 6), random_int(1, 6), random_int(1, 6)];
$total = array_sum($dice);
// ── 判断结果 ────────────────────────────────────────────────
$killPoints = $config['kill_points'] ?? [3, 18];
if (! is_array($killPoints)) {
$killPoints = explode(',', (string) $killPoints);
}
$killPoints = array_map('intval', $killPoints);
$result = match (true) {
$dice[0] === $dice[1] && $dice[1] === $dice[2] => 'triple', // 豹子(优先判断)
in_array($total, $killPoints, true) => 'kill', // 庄家收割
$total >= 11 && $total <= 17 => 'big', // 大
default => 'small', // 小
};
// ── 结算下注记录 ─────────────────────────────────────────────
$bets = BaccaratBet::query()->where('round_id', $round->id)->where('status', 'pending')->with('user')->get();
$totalPayout = 0;
// 收集各用户输赢结果,用于公屏展示
$winners = [];
$losers = [];
DB::transaction(function () use ($bets, $result, $config, $currency, &$totalPayout, &$winners, &$losers) {
foreach ($bets as $bet) {
$username = $bet->user->username ?? '匿名';
if ($result === 'kill') {
// 庄家收割:全灭无退款
$bet->update(['status' => 'lost', 'payout' => 0]);
$losers[] = "{$username}-{$bet->amount}";
continue;
}
if ($bet->bet_type === $result) {
// 中奖:计算赔付(含本金返还)
$payout = BaccaratRound::calcPayout($bet->bet_type, $bet->amount, $config);
$bet->update(['status' => 'won', 'payout' => $payout]);
// 金币入账
$currency->change(
$bet->user,
'gold',
$payout,
CurrencySource::BACCARAT_WIN,
"百家乐 #{$this->round->id}{$bet->betTypeLabel()} 中奖",
);
$totalPayout += $payout;
$winners[] = "{$username}+".number_format($payout);
} else {
$bet->update(['status' => 'lost', 'payout' => 0]);
$losers[] = "{$username}-".number_format($bet->amount);
}
}
});
// ── 更新局次记录 ─────────────────────────────────────────────
$round->update([
'dice1' => $dice[0],
'dice2' => $dice[1],
'dice3' => $dice[2],
'total_points' => $total,
'result' => $result,
'status' => 'settled',
'settled_at' => now(),
'total_payout' => $totalPayout,
]);
$round->refresh();
// ── 广播结算事件 ─────────────────────────────────────────────
broadcast(new BaccaratRoundSettled($round));
// ── 公屏公告 ─────────────────────────────────────────────────
$this->pushResultMessage($round, $chatState, $winners, $losers);
}
/**
* 向公屏发送开奖结果系统消息(含各用户输赢情况)。
*
* @param array<string> $winners 中奖用户列表,格式 "用户名+金额"
* @param array<string> $losers 未中奖用户列表,格式 "用户名-金额"
*/
private function pushResultMessage(BaccaratRound $round, ChatStateService $chatState, array $winners = [], array $losers = []): void
{
$diceStr = "{$round->dice1}】【{$round->dice2}】【{$round->dice3}";
$resultText = match ($round->result) {
'big' => "🔵 大({$round->total_points} 点)",
'small' => "🟡 小({$round->total_points} 点)",
'triple' => "💥 豹子!({$round->dice1}{$round->dice2}{$round->dice3}",
'kill' => "☠️ 庄家收割!({$round->total_points} 点)全灭",
default => '',
};
$payoutText = $round->total_payout > 0
? '共派发 🪙'.number_format($round->total_payout).' 金币'
: '本局无人获奖';
// 拼接用户输赢明细(最多显示 10 人,防止消息过长)
$detailParts = [];
if ($winners) {
$detailParts[] = '🏆 中奖:'.implode('、', array_slice($winners, 0, 10)).' 🪙';
}
if ($losers) {
$detailParts[] = '😔 未中:'.implode('、', array_slice($losers, 0, 10));
}
$detail = $detailParts ? ' '.implode(' ', $detailParts) : '';
$content = "🎲 【百家乐】第 #{$round->id} 局开奖!{$diceStr} 总点 {$round->total_points}{$resultText}{$payoutText}{$detail}";
$msg = [
'id' => $chatState->nextMessageId(1),
'room_id' => 1,
'from_user' => '系统传音',
'to_user' => '大家',
'content' => $content,
'is_secret' => false,
'font_color' => '#8b5cf6',
'action' => '大声宣告',
'sent_at' => now()->toDateTimeString(),
];
$chatState->pushMessage(1, $msg);
broadcast(new MessageSent(1, $msg));
SaveMessageJob::dispatch($msg);
}
}