- 扩大玩家ID随机数位宽降低碰撞概率 - 授信额度变更加 lockForUpdate 防并发下调 - 结算冲正与入账利用唯一索引实现幂等闸门 - 失败登录计数加锁防丢失 - 代理子树查询改用子查询替代全表 pluck - 修复返点默认值百分比单位转换 - 测试中全量 fake 广播事件防 CI 500
This commit is contained in:
@@ -6,6 +6,7 @@ use App\Models\Player;
|
||||
use App\Models\TicketItem;
|
||||
use App\Services\Player\PlayerCreditService;
|
||||
use App\Support\PlayerFundingMode;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
final class GameSettlementReversalService
|
||||
@@ -24,21 +25,32 @@ final class GameSettlementReversalService
|
||||
$settledAt = now();
|
||||
|
||||
DB::transaction(function () use ($item, $ledger, $settledAt): void {
|
||||
DB::table('share_ledger')->insert([
|
||||
'ticket_item_id' => $item->id,
|
||||
'player_id' => $ledger->player_id,
|
||||
'agent_node_id' => $ledger->agent_node_id,
|
||||
'agent_path' => $ledger->agent_path,
|
||||
'share_snapshot' => $ledger->share_snapshot,
|
||||
'game_win_loss' => -1 * (int) $ledger->game_win_loss,
|
||||
'basic_rebate' => -1 * (int) $ledger->basic_rebate,
|
||||
'shared_net_win_loss' => -1 * (int) $ledger->shared_net_win_loss,
|
||||
'allocations_json' => $ledger->allocations_json,
|
||||
'reversal_of_id' => $ledger->id,
|
||||
'settled_at' => $settledAt,
|
||||
'created_at' => $settledAt,
|
||||
'updated_at' => $settledAt,
|
||||
]);
|
||||
// 幂等闸门:share_ledger.reversal_of_id 上有 partial unique 索引,
|
||||
// 同一原账不允许插入多条反转记录。重复调用时唯一约束冲突即视为已反转、跳过。
|
||||
// 一次只让 reversal_of_id 出现一次写入,确保所有后续副作用(rebate、credit 冲正)
|
||||
// 只在唯一反转成功路径上执行。
|
||||
try {
|
||||
DB::table('share_ledger')->insert([
|
||||
'ticket_item_id' => $item->id,
|
||||
'player_id' => $ledger->player_id,
|
||||
'agent_node_id' => $ledger->agent_node_id,
|
||||
'agent_path' => $ledger->agent_path,
|
||||
'share_snapshot' => $ledger->share_snapshot,
|
||||
'game_win_loss' => -1 * (int) $ledger->game_win_loss,
|
||||
'basic_rebate' => -1 * (int) $ledger->basic_rebate,
|
||||
'shared_net_win_loss' => -1 * (int) $ledger->shared_net_win_loss,
|
||||
'allocations_json' => $ledger->allocations_json,
|
||||
'reversal_of_id' => $ledger->id,
|
||||
'settled_at' => $settledAt,
|
||||
'created_at' => $settledAt,
|
||||
'updated_at' => $settledAt,
|
||||
]);
|
||||
} catch (QueryException $e) {
|
||||
if ($this->isUniqueViolation($e)) {
|
||||
return;
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$rebates = DB::table('rebate_records')
|
||||
->where('ticket_item_id', $item->id)
|
||||
@@ -72,4 +84,31 @@ final class GameSettlementReversalService
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 识别数据库唯一约束冲突(PostgreSQL SQLSTATE 23505;SQLite SQLSTATE 23000/error code 19)。
|
||||
* 兜底扫描消息中 unique 字样,兼容驱动返回的 SQLSTATE 缺失场景。
|
||||
*/
|
||||
private function isUniqueViolation(QueryException $e): bool
|
||||
{
|
||||
$sqlState = (string) $e->getCode();
|
||||
$errorInfo = $e->errorInfo ?? null;
|
||||
$driverCode = is_array($errorInfo) ? (int) ($errorInfo[1] ?? 0) : 0;
|
||||
|
||||
if ($sqlState === '23505') {
|
||||
return true;
|
||||
}
|
||||
if ($sqlState === '23000' || $sqlState === '19') {
|
||||
return true;
|
||||
}
|
||||
if ($driverCode === 19 || $driverCode === 1062) {
|
||||
return true;
|
||||
}
|
||||
$message = $e->getMessage();
|
||||
if (stripos($message, 'unique') !== false || stripos($message, 'UNIQUE constraint') !== false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user