feat: 引入事务悲观锁(lockForUpdate)与余额二次校验,防范高并发资产越权与透支漏洞

This commit is contained in:
pllx
2026-06-30 11:32:52 +08:00
parent d1409d16bb
commit 3563b45038
4 changed files with 132 additions and 42 deletions
+22 -7
View File
@@ -19,6 +19,7 @@ use App\Enums\CurrencySource;
use App\Models\BaccaratBet;
use App\Models\BaccaratRound;
use App\Models\GameConfig;
use App\Models\User;
use App\Services\BaccaratLossCoverService;
use App\Services\GameBetBroadcastService;
use App\Services\GameRoomScopeService;
@@ -133,7 +134,7 @@ class BaccaratController extends Controller
$user = $request->user();
// 检查用户金币余额(金币字段为 jjb
// 快速过滤(非锁
if (($user->jjb ?? 0) < $data['amount']) {
return response()->json(['ok' => false, 'message' => '金币不足,无法下注。']);
}
@@ -142,10 +143,21 @@ class BaccaratController extends Controller
$lossCoverService = $this->lossCoverService;
return DB::transaction(function () use ($user, $round, $data, $currency, $lossCoverService): JsonResponse {
// 幂等:同一局只能下一注
// 1. 悲观锁锁定用户行
$lockedUser = User::query()
->whereKey($user->id)
->lockForUpdate()
->firstOrFail();
// 2. 锁保护下二次校验余额
if ((int) $lockedUser->jjb < $data['amount']) {
return response()->json(['ok' => false, 'message' => '金币不足,无法下注。']);
}
// 3. 幂等:同一局只能下一注
$existing = BaccaratBet::query()
->where('round_id', $round->id)
->where('user_id', $user->id)
->where('user_id', $lockedUser->id)
->lockForUpdate()
->exists();
@@ -153,9 +165,9 @@ class BaccaratController extends Controller
return response()->json(['ok' => false, 'message' => '本局您已下注,请等待开奖。']);
}
// 扣除金币
// 4. 扣除金币,传入锁定的用户实例
$currency->change(
$user,
$lockedUser,
'gold',
-$data['amount'],
CurrencySource::BACCARAT_BET,
@@ -167,16 +179,19 @@ class BaccaratController extends Controller
// 下注时间命中活动窗口时,将本次下注挂到对应的买单活动下。
$lossCoverEvent = $lossCoverService->findEventForBetTime(now());
// 写入下注记录
// 5. 写入下注记录
$bet = BaccaratBet::create([
'round_id' => $round->id,
'user_id' => $user->id,
'user_id' => $lockedUser->id,
'loss_cover_event_id' => $lossCoverEvent?->id,
'bet_type' => $data['bet_type'],
'amount' => $data['amount'],
'status' => 'pending',
]);
// 同步修改 Auth 内存实例的金币
$user->setAttribute('jjb', $lockedUser->jjb);
// 命中活动的下注要同步累计到用户活动记录中,便于后续前台查看。
$lossCoverService->registerBet($bet);