Files
chatroom/app/Jobs/CloseBaccaratRoundJob.php
lkddi 040dbdef3c 优化:全站金币图标由 🪙(银灰色)统一替换为 💰(金黄色)
🪙 在多数平台/字体上渲染为银灰色,与「金币」语义不符;
💰 各平台均渲染为金黄色,更直观传达金币概念。

涉及文件(43处):
- app/Jobs:百家乐、赛马结算广播
- app/Http/Controllers:管理员命令、红包、老虎机、神秘箱子
- app/Listeners
- resources/views:聊天室各游戏面板、商店、toolbar、后台页面等
2026-03-04 15:00:02 +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);
}
}