1.修复自动创建下一期bug
This commit is contained in:
@@ -11,6 +11,9 @@ final class WebSocketConfigHelper
|
||||
public static function wsUrl(?Request $request = null): string
|
||||
{
|
||||
$url = trim((string) env('H5_WEBSOCKET_URL', ''));
|
||||
if ($url !== '' && $request !== null && self::isLoopbackWsUrl($url) && !self::isLoopbackRequestHost($request)) {
|
||||
$url = '';
|
||||
}
|
||||
if ($url !== '') {
|
||||
return $url;
|
||||
}
|
||||
@@ -36,5 +39,30 @@ final class WebSocketConfigHelper
|
||||
|
||||
return 'ws://127.0.0.1:3131/ws/';
|
||||
}
|
||||
|
||||
private static function isLoopbackWsUrl(string $url): bool
|
||||
{
|
||||
$host = parse_url($url, PHP_URL_HOST);
|
||||
if (!is_string($host) || $host === '') {
|
||||
return false;
|
||||
}
|
||||
$host = strtolower($host);
|
||||
|
||||
return in_array($host, ['127.0.0.1', 'localhost', '::1'], true);
|
||||
}
|
||||
|
||||
private static function isLoopbackRequestHost(Request $request): bool
|
||||
{
|
||||
$host = strtolower(trim((string) $request->host(true)));
|
||||
if ($host === '') {
|
||||
$host = strtolower(trim((string) $request->header('host', '')));
|
||||
}
|
||||
if ($host === '') {
|
||||
return false;
|
||||
}
|
||||
$hostOnly = preg_split('/:/', $host)[0] ?? $host;
|
||||
|
||||
return in_array($hostOnly, ['127.0.0.1', 'localhost', '::1'], true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -641,60 +641,103 @@ final class GameLiveService
|
||||
$now = time();
|
||||
$drawCommitted = false;
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
$record = self::loadRecordRowFromDb($rid, true);
|
||||
if (!$record) {
|
||||
Db::rollback();
|
||||
$result = ['ok' => false, 'msg' => __('No active game in progress')];
|
||||
} else {
|
||||
$txResult = self::withShortInnodbLockWait(3, static function () use (
|
||||
$rid,
|
||||
$betSeconds,
|
||||
$periodSeconds,
|
||||
$manualNumber,
|
||||
$now
|
||||
): array {
|
||||
Db::startTrans();
|
||||
try {
|
||||
$record = self::loadRecordRowFromDb($rid, true);
|
||||
if (!$record) {
|
||||
Db::rollback();
|
||||
|
||||
return ['ok' => false, 'draw_committed' => false, 'result' => ['ok' => false, 'msg' => __('No active game in progress')]];
|
||||
}
|
||||
$st = (int) ($record['status'] ?? -1);
|
||||
$existingResult = filter_var($record['result_number'] ?? 0, FILTER_VALIDATE_INT);
|
||||
if ($existingResult !== false && $existingResult >= 1 && $existingResult <= self::DRAW_NUMBER_MAX && $st >= 2) {
|
||||
Db::commit();
|
||||
$periodNo = is_string($record['period_no'] ?? null) ? (string) $record['period_no'] : '';
|
||||
$postLockWork = static function () use ($rid): void {
|
||||
GameHotDataCoordinator::afterGameRecordCommitted($rid);
|
||||
self::publishSnapshot($rid, false);
|
||||
};
|
||||
$result = [
|
||||
'ok' => true,
|
||||
'msg' => __('Draw completed; paying out'),
|
||||
'result_number' => $existingResult,
|
||||
'estimated_loss' => '0.00',
|
||||
'payout_until' => (int) ($record['payout_until'] ?? 0),
|
||||
];
|
||||
} elseif (!in_array($st, [0, 1], true)) {
|
||||
Db::rollback();
|
||||
$result = ['ok' => false, 'msg' => __('Current game status does not allow drawing')];
|
||||
} else {
|
||||
$elapsedLocked = max(0, $now - (int) ($record['period_start_at'] ?? $now));
|
||||
if ($elapsedLocked < $betSeconds || $elapsedLocked < $periodSeconds) {
|
||||
Db::rollback();
|
||||
$result = ['ok' => false, 'msg' => __('Period countdown has not ended; cannot draw yet')];
|
||||
} else {
|
||||
[$finalNumber, $drawMode] = self::resolveFinalDrawNumber($record, $manualNumber);
|
||||
$bets = Db::name('bet_order')->where('period_id', $rid)->select()->toArray();
|
||||
$finalLoss = self::estimateLossForNumber($bets, $finalNumber);
|
||||
$payoutUntil = $now + self::getPayoutGraceSeconds();
|
||||
$periodNo = is_string($record['period_no'] ?? null) ? (string) $record['period_no'] : '';
|
||||
|
||||
Db::name('game_record')->where('id', $rid)->update([
|
||||
'status' => 3,
|
||||
'result_number' => $finalNumber,
|
||||
'draw_mode' => $drawMode,
|
||||
'pending_draw_number' => null,
|
||||
'payout_until' => $payoutUntil,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
Db::commit();
|
||||
$drawCommitted = true;
|
||||
}
|
||||
return [
|
||||
'ok' => true,
|
||||
'draw_committed' => false,
|
||||
'existing_result' => $existingResult,
|
||||
'record' => $record,
|
||||
];
|
||||
}
|
||||
if (!in_array($st, [0, 1], true)) {
|
||||
Db::rollback();
|
||||
|
||||
return ['ok' => false, 'draw_committed' => false, 'result' => ['ok' => false, 'msg' => __('Current game status does not allow drawing')]];
|
||||
}
|
||||
$elapsedLocked = max(0, $now - (int) ($record['period_start_at'] ?? $now));
|
||||
if ($elapsedLocked < $betSeconds || $elapsedLocked < $periodSeconds) {
|
||||
Db::rollback();
|
||||
|
||||
return ['ok' => false, 'draw_committed' => false, 'result' => ['ok' => false, 'msg' => __('Period countdown has not ended; cannot draw yet')]];
|
||||
}
|
||||
[$finalNumber, $drawMode] = self::resolveFinalDrawNumber($record, $manualNumber);
|
||||
$bets = Db::name('bet_order')->where('period_id', $rid)->select()->toArray();
|
||||
$finalLoss = self::estimateLossForNumber($bets, $finalNumber);
|
||||
$payoutUntil = $now + self::getPayoutGraceSeconds();
|
||||
$periodNo = is_string($record['period_no'] ?? null) ? (string) $record['period_no'] : '';
|
||||
|
||||
Db::name('game_record')->where('id', $rid)->update([
|
||||
'status' => 3,
|
||||
'result_number' => $finalNumber,
|
||||
'draw_mode' => $drawMode,
|
||||
'pending_draw_number' => null,
|
||||
'payout_until' => $payoutUntil,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
Db::commit();
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'draw_committed' => true,
|
||||
'final_number' => $finalNumber,
|
||||
'draw_mode' => $drawMode,
|
||||
'final_loss' => $finalLoss,
|
||||
'payout_until' => $payoutUntil,
|
||||
'period_no' => $periodNo,
|
||||
];
|
||||
} catch (Throwable $e) {
|
||||
Db::rollback();
|
||||
|
||||
return [
|
||||
'ok' => false,
|
||||
'draw_committed' => false,
|
||||
'result' => ['ok' => false, 'msg' => __('Game live: settlement error') . ': ' . $e->getMessage()],
|
||||
];
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
Db::rollback();
|
||||
$result = ['ok' => false, 'msg' => __('Game live: settlement error') . ': ' . $e->getMessage()];
|
||||
});
|
||||
|
||||
if (!($txResult['ok'] ?? false)) {
|
||||
$result = is_array($txResult['result'] ?? null) ? $txResult['result'] : ['ok' => false, 'msg' => __('Game live: settlement error')];
|
||||
} elseif (!empty($txResult['draw_committed'])) {
|
||||
$drawCommitted = true;
|
||||
$finalNumber = (int) ($txResult['final_number'] ?? 0);
|
||||
$drawMode = (int) ($txResult['draw_mode'] ?? 0);
|
||||
$finalLoss = is_string($txResult['final_loss'] ?? null) ? $txResult['final_loss'] : '0.00';
|
||||
$payoutUntil = (int) ($txResult['payout_until'] ?? 0);
|
||||
$periodNo = is_string($txResult['period_no'] ?? null) ? (string) $txResult['period_no'] : '';
|
||||
} else {
|
||||
$existingResult = (int) ($txResult['existing_result'] ?? 0);
|
||||
$periodNo = is_string($txResult['record']['period_no'] ?? null) ? (string) $txResult['record']['period_no'] : '';
|
||||
$postLockWork = static function () use ($rid): void {
|
||||
GameHotDataCoordinator::afterGameRecordCommitted($rid);
|
||||
self::publishSnapshot($rid, false);
|
||||
};
|
||||
$result = [
|
||||
'ok' => true,
|
||||
'msg' => __('Draw completed; paying out'),
|
||||
'result_number' => $existingResult,
|
||||
'estimated_loss' => '0.00',
|
||||
'payout_until' => (int) ($txResult['record']['payout_until'] ?? 0),
|
||||
];
|
||||
}
|
||||
|
||||
if ($drawCommitted) {
|
||||
@@ -882,34 +925,33 @@ final class GameLiveService
|
||||
return;
|
||||
}
|
||||
$rid = (int) $record['id'];
|
||||
if (GameHotDataRedis::isStaleOpenPeriodRecord($record, $periodSeconds)) {
|
||||
GameHotDataLock::forceRelease(GameHotDataLock::TYPE_GAME_RECORD, (string) $rid);
|
||||
$st = (int) ($record['status'] ?? 0);
|
||||
$abnormalAfter = $periodSeconds + self::getPayoutGraceSeconds() + self::STARTUP_RECOVER_GRACE_SECONDS;
|
||||
if ($elapsed > $abnormalAfter) {
|
||||
$resultNumber = isset($record['result_number']) ? (int) $record['result_number'] : 0;
|
||||
if ($resultNumber <= 0) {
|
||||
GameHotDataLock::forceRelease(GameHotDataLock::TYPE_GAME_RECORD, (string) $rid);
|
||||
self::markAbnormalAndRefundOnStartup($rid, $st);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$out = self::drawResult($rid, null);
|
||||
if ($out['ok'] ?? false) {
|
||||
return;
|
||||
}
|
||||
$msg = is_string($out['msg'] ?? null) ? (string) $out['msg'] : '';
|
||||
if (!str_contains($msg, 'Another operation is in progress')) {
|
||||
if (
|
||||
!str_contains($msg, 'Another operation is in progress')
|
||||
&& !str_contains($msg, 'Lock wait timeout')
|
||||
&& !str_contains($msg, '1205')
|
||||
) {
|
||||
Log::warning('tickAutoDraw: drawResult failed', [
|
||||
'record_id' => $rid,
|
||||
'period_no' => $record['period_no'] ?? '',
|
||||
'msg' => $msg,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
if (!GameHotDataRedis::isStaleOpenPeriodRecord($record, $periodSeconds)) {
|
||||
return;
|
||||
}
|
||||
GameHotDataLock::forceRelease(GameHotDataLock::TYPE_GAME_RECORD, (string) $rid);
|
||||
$retry = self::drawResult($rid, null);
|
||||
if (!($retry['ok'] ?? false)) {
|
||||
Log::warning('tickAutoDraw: drawResult failed after lock force-release', [
|
||||
'record_id' => $rid,
|
||||
'period_no' => $record['period_no'] ?? '',
|
||||
'msg' => is_string($retry['msg'] ?? null) ? $retry['msg'] : '',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1871,4 +1913,28 @@ final class GameLiveService
|
||||
}
|
||||
return $parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 开奖事务使用较短行锁等待,避免 HTTP/定时任务被 InnoDB 默认 50s 锁等待拖死。
|
||||
*
|
||||
* @template T
|
||||
* @param callable(): T $fn
|
||||
* @return T
|
||||
*/
|
||||
private static function withShortInnodbLockWait(int $seconds, callable $fn): mixed
|
||||
{
|
||||
$seconds = max(1, min(50, $seconds));
|
||||
try {
|
||||
Db::execute('SET SESSION innodb_lock_wait_timeout = ' . $seconds);
|
||||
} catch (Throwable) {
|
||||
}
|
||||
try {
|
||||
return $fn();
|
||||
} finally {
|
||||
try {
|
||||
Db::execute('SET SESSION innodb_lock_wait_timeout = 50');
|
||||
} catch (Throwable) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user