Files
chatroom/app/Http/Controllers/HorseRaceController.php
lkddi f45483bcba 功能更新与UI优化:游戏图标移除、用户名片修复、婚礼红包界面重设计
- 移除聊天室右下角浮动游戏图标(占卜、百家乐、赛马、老虎机)
- 用户名片按钮区:修复已婚/已好友时按钮换行问题,统一单行显示
- 婚礼红包弹窗:重设计为喜庆鲜红背景,领取按钮改为圆形米黄样式
- 新增婚礼红包恢复接口(/wedding/pending-envelopes),刷新后自动恢复领取按钮
- 修复 Alpine :style 字符串覆盖静态 style 导致圆形按钮失效的问题
- 撤职后用户等级改为根据经验值重新计算,不再无条件重置为1
- 管理员修改用户经验值后自动重算等级,有职务用户等级锁定
- 娱乐大厅钓鱼游戏按钮直接调用 startFishing() 简化操作流程
- 新增赛马、占卜、百家乐游戏及相关后端逻辑
2026-03-03 23:19:59 +08:00

225 lines
7.5 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
/**
* 文件功能:赛马竞猜前台控制器
*
* 提供用户在聊天室内参与赛马的 API 接口:
* - 查询当前场次信息(马匹、注池、赔率)
* - 提交下注(扣除金币 + 写入下注记录)
* - 查询本人下注状态
* - 查询最近历史记录
*
* @author ChatRoom Laravel
*
* @version 1.0.0
*/
namespace App\Http\Controllers;
use App\Enums\CurrencySource;
use App\Models\GameConfig;
use App\Models\HorseBet;
use App\Models\HorseRace;
use App\Services\UserCurrencyService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class HorseRaceController extends Controller
{
public function __construct(
private readonly UserCurrencyService $currency,
) {}
/**
* 获取当前进行中的场次信息(前端轮询或事件触发后调用)。
*/
public function currentRace(Request $request): JsonResponse
{
$race = HorseRace::currentRace();
if (! $race) {
return response()->json(['race' => null]);
}
$user = $request->user();
$myBet = HorseBet::query()
->where('race_id', $race->id)
->where('user_id', $user->id)
->first();
// 计算各马匹当前注额
$config = GameConfig::forGame('horse_racing')?->params ?? [];
$houseTake = (int) ($config['house_take_percent'] ?? 5);
$horsePools = HorseBet::query()
->where('race_id', $race->id)
->groupBy('horse_id')
->selectRaw('horse_id, SUM(amount) as pool')
->pluck('pool', 'horse_id')
->toArray();
// 计算实时赔率
$horses = $race->horses ?? [];
$horsesWithBets = array_map(function ($horse) use ($horsePools, $houseTake) {
$horsePool = (int) ($horsePools[$horse['id']] ?? 0);
$totalPool = array_sum(array_values($horsePools));
$netPool = $totalPool * (1 - $houseTake / 100);
$odds = $horsePool > 0 ? round($netPool / $horsePool, 2) : null;
return [
'id' => $horse['id'],
'name' => $horse['name'],
'emoji' => $horse['emoji'],
'pool' => $horsePool,
'odds' => $odds,
];
}, $horses);
return response()->json([
'race' => [
'id' => $race->id,
'status' => $race->status,
'bet_closes_at' => $race->bet_closes_at?->toIso8601String(),
'seconds_left' => $race->status === 'betting'
? max(0, (int) now()->diffInSeconds($race->bet_closes_at, false))
: 0,
'horses' => $horsesWithBets,
'total_pool' => $race->total_pool + array_sum(array_values($horsePools)),
'my_bet' => $myBet ? [
'horse_id' => $myBet->horse_id,
'amount' => $myBet->amount,
] : null,
],
]);
}
/**
* 用户提交下注。
*
* 同一场每人限下一注,下注成功后立即扣除金币。
*/
public function bet(Request $request): JsonResponse
{
if (! GameConfig::isEnabled('horse_racing')) {
return response()->json(['ok' => false, 'message' => '赛马竞猜当前未开启。']);
}
$data = $request->validate([
'race_id' => 'required|integer|exists:horse_races,id',
'horse_id' => 'required|integer|min:1',
'amount' => 'required|integer|min:1',
]);
$config = GameConfig::forGame('horse_racing')?->params ?? [];
$minBet = (int) ($config['min_bet'] ?? 100);
$maxBet = (int) ($config['max_bet'] ?? 100000);
if ($data['amount'] < $minBet || $data['amount'] > $maxBet) {
return response()->json(['ok' => false, 'message' => "押注金额须在 {$minBet}~{$maxBet} 金币之间。"]);
}
$race = HorseRace::find($data['race_id']);
if (! $race || ! $race->isBettingOpen()) {
return response()->json(['ok' => false, 'message' => '当前不在下注时间内。']);
}
// 验证马匹 ID 是否有效
$horses = $race->horses ?? [];
$validIds = array_column($horses, 'id');
if (! in_array($data['horse_id'], $validIds, true)) {
return response()->json(['ok' => false, 'message' => '无效的马匹编号。']);
}
$user = $request->user();
$currency = $this->currency;
// 校验余额
if (($user->jjb ?? 0) < $data['amount']) {
return response()->json(['ok' => false, 'message' => '金币不足,无法下注。']);
}
return DB::transaction(function () use ($user, $race, $data, $currency, $horses): JsonResponse {
// 幂等:同一场只能下一注
$existing = HorseBet::query()
->where('race_id', $race->id)
->where('user_id', $user->id)
->lockForUpdate()
->exists();
if ($existing) {
return response()->json(['ok' => false, 'message' => '本场您已下注,请等待开奖。']);
}
// 找出马匹名称
$horseName = '';
foreach ($horses as $horse) {
if ((int) $horse['id'] === (int) $data['horse_id']) {
$horseName = ($horse['emoji'] ?? '').($horse['name'] ?? '');
break;
}
}
// 扣除金币
$currency->change(
$user,
'gold',
-$data['amount'],
CurrencySource::HORSE_BET,
"赛马 #{$race->id} 押注 {$horseName}",
);
// 写入下注记录
HorseBet::create([
'race_id' => $race->id,
'user_id' => $user->id,
'horse_id' => $data['horse_id'],
'amount' => $data['amount'],
'status' => 'pending',
]);
return response()->json([
'ok' => true,
'message' => "✅ 已押注「{$horseName}{$data['amount']} 金币,等待开跑!",
'amount' => $data['amount'],
'horse_id' => $data['horse_id'],
]);
});
}
/**
* 查询最近10场历史记录前端展示胜负趋势
*/
public function history(): JsonResponse
{
$races = HorseRace::query()
->where('status', 'settled')
->orderByDesc('id')
->limit(10)
->get(['id', 'horses', 'winner_horse_id', 'total_pool', 'total_bets', 'settled_at']);
// 转换获胜马匹名称
$history = $races->map(function ($race) {
$winnerName = '未知';
foreach (($race->horses ?? []) as $horse) {
if (($horse['id'] ?? 0) === (int) $race->winner_horse_id) {
$winnerName = ($horse['emoji'] ?? '').($horse['name'] ?? '');
break;
}
}
return [
'id' => $race->id,
'winner_id' => $race->winner_horse_id,
'winner_name' => $winnerName,
'total_pool' => $race->total_pool,
'total_bets' => $race->total_bets,
'settled_at' => $race->settled_at?->toDateTimeString(),
];
});
return response()->json(['history' => $history]);
}
}