1.增加互斥锁:保证缓存和数据库数据一致性
2.增加消费队列,保证mysql数据的正常保存
This commit is contained in:
@@ -8,6 +8,7 @@ use app\common\library\game\ZiHuaDictionary;
|
||||
use app\common\model\BetOrder;
|
||||
use app\common\model\GameRecord;
|
||||
use app\common\model\UserWalletRecord;
|
||||
use app\common\service\GameHotDataCoordinator;
|
||||
use app\common\service\GameHotDataRedis;
|
||||
use app\common\service\UserPushService;
|
||||
use support\think\Db;
|
||||
@@ -175,6 +176,12 @@ class Game extends MobileBase
|
||||
return $this->mobileError(3002, 'Betting is closed');
|
||||
}
|
||||
|
||||
$userIdRaw = $this->auth->id ?? null;
|
||||
$userId = filter_var($userIdRaw, FILTER_VALIDATE_INT);
|
||||
if ($userId === false || $userId <= 0) {
|
||||
return $this->mobileError(1001, 'Missing parameters');
|
||||
}
|
||||
|
||||
$user = $this->auth->getUser();
|
||||
if (bccomp((string) $user->coin, $totalAmount, 4) < 0) {
|
||||
return $this->mobileError(2001, 'Insufficient balance');
|
||||
@@ -185,64 +192,101 @@ class Game extends MobileBase
|
||||
return $this->mobileError(3003, 'Duplicate request');
|
||||
}
|
||||
|
||||
Db::startTrans();
|
||||
$lock = GameHotDataRedis::userAdminMutationLockTry($userId);
|
||||
if (!$lock['acquired']) {
|
||||
return $this->mobileError(5000, '该用户正在被其他管理员操作(钱包/并发保存),请稍后再试');
|
||||
}
|
||||
|
||||
try {
|
||||
$before = (string) $user->coin;
|
||||
$after = bcsub($before, $totalAmount, 4);
|
||||
UserWalletRecord::create([
|
||||
'user_id' => $user->id,
|
||||
'channel_id' => $user->channel_id,
|
||||
'biz_type' => 'bet',
|
||||
'direction' => 2,
|
||||
'amount' => $totalAmount,
|
||||
'balance_before' => $before,
|
||||
'balance_after' => $after,
|
||||
'ref_type' => 'bet_order',
|
||||
'remark' => '移动端下注',
|
||||
'create_time' => time(),
|
||||
]);
|
||||
Db::name('user')->where('id', $user->id)->update(['coin' => $after, 'update_time' => time()]);
|
||||
$orderNo = 'BO' . date('YmdHis') . substr(str_replace('.', '', uniqid('', true)), -6);
|
||||
BetOrder::create([
|
||||
'period_id' => $period->id,
|
||||
'period_no' => $period->period_no,
|
||||
'user_id' => $user->id,
|
||||
'channel_id' => $user->channel_id,
|
||||
'pick_numbers' => $numbers,
|
||||
'total_amount' => $totalAmount,
|
||||
'streak_at_bet' => $user->current_streak ?? 0,
|
||||
'is_auto' => 0,
|
||||
'status' => 1,
|
||||
'idempotency_key' => $idempotencyKey,
|
||||
'create_time' => time(),
|
||||
'update_time' => time(),
|
||||
]);
|
||||
Db::commit();
|
||||
$uid = filter_var($user->id, FILTER_VALIDATE_INT);
|
||||
if ($uid !== false) {
|
||||
GameHotDataRedis::userForget($uid);
|
||||
$coinRow = Db::name('user')->where('id', $userId)->field(['coin', 'channel_id', 'current_streak'])->find();
|
||||
if (!$coinRow) {
|
||||
return $this->mobileError(5000, 'System is busy, please try again later');
|
||||
}
|
||||
UserPushService::publish((int) $user->id, UserPushService::EVT_BET_ACCEPTED, [
|
||||
$before = (string) ($coinRow['coin'] ?? '0');
|
||||
if (bccomp($before, $totalAmount, 4) < 0) {
|
||||
return $this->mobileError(2001, 'Insufficient balance');
|
||||
}
|
||||
|
||||
$existsLocked = BetOrder::where('idempotency_key', $idempotencyKey)->find();
|
||||
if ($existsLocked) {
|
||||
return $this->mobileError(3003, 'Duplicate request');
|
||||
}
|
||||
|
||||
$channelIdRaw = $coinRow['channel_id'] ?? null;
|
||||
$channelId = filter_var($channelIdRaw, FILTER_VALIDATE_INT);
|
||||
if ($channelId === false) {
|
||||
$channelId = null;
|
||||
}
|
||||
|
||||
$now = time();
|
||||
$after = bcsub($before, $totalAmount, 4);
|
||||
$orderNo = 'BO' . date('YmdHis') . substr(str_replace('.', '', uniqid('', true)), -6);
|
||||
$streakAtBet = (int) ($coinRow['current_streak'] ?? 0);
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
$affected = Db::name('user')->where('id', $userId)->where('coin', $before)->update([
|
||||
'coin' => $after,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
if ($affected !== 1) {
|
||||
Db::rollback();
|
||||
return $this->mobileError(5000, '扣款失败:该用户余额已被其他请求修改(如下注、派彩或其他管理员已保存),请刷新后重试');
|
||||
}
|
||||
|
||||
UserWalletRecord::create([
|
||||
'user_id' => $userId,
|
||||
'channel_id' => $channelId,
|
||||
'biz_type' => 'bet',
|
||||
'direction' => 2,
|
||||
'amount' => $totalAmount,
|
||||
'balance_before' => $before,
|
||||
'balance_after' => $after,
|
||||
'ref_type' => 'bet_order',
|
||||
'remark' => '移动端下注',
|
||||
'create_time' => $now,
|
||||
]);
|
||||
BetOrder::create([
|
||||
'period_id' => $period->id,
|
||||
'period_no' => $period->period_no,
|
||||
'user_id' => $userId,
|
||||
'channel_id' => $channelId,
|
||||
'pick_numbers' => $numbers,
|
||||
'total_amount' => $totalAmount,
|
||||
'streak_at_bet' => $streakAtBet,
|
||||
'is_auto' => 0,
|
||||
'status' => 1,
|
||||
'idempotency_key' => $idempotencyKey,
|
||||
'create_time' => $now,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
Db::commit();
|
||||
} catch (\Throwable $e) {
|
||||
Db::rollback();
|
||||
return $this->mobileError(5000, 'System is busy, please try again later', ['detail' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
GameHotDataCoordinator::afterUserCommitted($userId);
|
||||
UserPushService::publish($userId, UserPushService::EVT_BET_ACCEPTED, [
|
||||
'order_no' => $orderNo,
|
||||
'period_no' => (string) $period->period_no,
|
||||
'status' => 'accepted',
|
||||
'balance_after' => $after,
|
||||
'total_amount' => $totalAmount,
|
||||
'current_streak' => (int) ($user->current_streak ?? 0),
|
||||
'current_streak' => $streakAtBet,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
Db::rollback();
|
||||
return $this->mobileError(5000, 'System is busy, please try again later', ['detail' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
return $this->mobileSuccess([
|
||||
'order_no' => $orderNo,
|
||||
'period_no' => $period->period_no,
|
||||
'status' => 'accepted',
|
||||
'locked_balance' => '0.0000',
|
||||
'balance_after' => $after,
|
||||
'current_streak' => $user->current_streak ?? 0,
|
||||
]);
|
||||
return $this->mobileSuccess([
|
||||
'order_no' => $orderNo,
|
||||
'period_no' => $period->period_no,
|
||||
'status' => 'accepted',
|
||||
'locked_balance' => '0.0000',
|
||||
'balance_after' => $after,
|
||||
'current_streak' => $streakAtBet,
|
||||
]);
|
||||
} finally {
|
||||
GameHotDataRedis::userAdminMutationLockRelease($userId, $lock['token'], $lock['redis_lock']);
|
||||
}
|
||||
}
|
||||
|
||||
public function betMyOrders(Request $request): Response
|
||||
|
||||
Reference in New Issue
Block a user