1.修复关闭自动创建下一局后还自动创建下一期

This commit is contained in:
2026-05-26 16:10:06 +08:00
parent 8c3f3c4e2c
commit 66c002f522
3 changed files with 171 additions and 50 deletions

View File

@@ -244,7 +244,7 @@ final class GameLiveService
'payout_until' => null,
'update_time' => $now,
]);
GameRecordService::createNextRecordAfterDraw();
$newPeriodNo = GameRecordService::createNextRecordAfterDraw();
Db::commit();
} catch (Throwable $e) {
Db::rollback();
@@ -259,6 +259,11 @@ final class GameLiveService
GameRecordStatService::refreshForRecordId($recordId);
} catch (Throwable) {
}
if (!GameRecordService::isLiveRuntimeEnabled()) {
self::voidRemainingOpenRoundsOnMaintenanceAfterFinalize();
} elseif (is_string($newPeriodNo ?? null) && $newPeriodNo !== '') {
self::publishImmediateBettingTickAfterFinalize();
}
self::publishSnapshot(null);
}
@@ -689,6 +694,7 @@ final class GameLiveService
if ($resultNumber === false || $resultNumber < 1) {
$resultNumber = null;
}
$newPeriodNo = null;
Db::startTrans();
try {
Db::name('game_record')->where('id', $id)->update([
@@ -696,9 +702,7 @@ final class GameLiveService
'payout_until' => null,
'update_time' => time(),
]);
if (GameRecordService::isLiveRuntimeEnabled()) {
GameRecordService::createNextRecordRowIfNoActive();
}
$newPeriodNo = GameRecordService::createNextRecordAfterDraw();
Db::commit();
} catch (Throwable $e) {
Db::rollback();
@@ -710,7 +714,11 @@ final class GameLiveService
GameRecordStatService::refreshForRecordId($id);
self::publishPublicPeriodFinished($id, $periodNo, $resultNumber);
self::publishSnapshot(null);
self::publishImmediateBettingTickAfterFinalize();
if (!GameRecordService::isLiveRuntimeEnabled()) {
self::voidRemainingOpenRoundsOnMaintenanceAfterFinalize();
} elseif (is_string($newPeriodNo ?? null) && $newPeriodNo !== '') {
self::publishImmediateBettingTickAfterFinalize();
}
} finally {
GameHotDataLock::release(GameHotDataLock::TYPE_GAME_RECORD, (string) $id, $lock['token'], $lock['redis_lock']);
}
@@ -737,6 +745,126 @@ final class GameLiveService
self::drawResult((int) $record['id'], null);
}
/**
* 关闭「自动创建下一局」时:作废除当前 draining 局外的其它下注/封盘期(历史重复开局遗留),避免被 tickAutoDraw 当成新一局继续跑。
*/
public static function voidOrphanActiveRoundsOnRuntimeDisabled(): void
{
if (GameRecordService::isLiveRuntimeEnabled()) {
return;
}
$canonical = GameHotDataRedis::gameRecordActive();
if (!$canonical) {
return;
}
$keepId = filter_var($canonical['id'] ?? 0, FILTER_VALIDATE_INT);
if ($keepId === false || $keepId <= 0) {
return;
}
$reason = (string) __('Orphan period closed: auto-create next round is disabled');
$rows = Db::name('game_record')
->whereIn('status', [0, 1])
->where('id', '<>', $keepId)
->order('id', 'asc')
->select()
->toArray();
foreach ($rows as $row) {
$rid = filter_var($row['id'] ?? 0, FILTER_VALIDATE_INT);
if ($rid === false || $rid <= 0) {
continue;
}
self::voidOpenPeriodInternal($rid, $reason);
}
GameHotDataRedis::gameRecordRefreshAggregateCaches();
self::publishSnapshot(null);
}
/**
* 维护模式:本期派彩结单后,清理仍停留在下注/封盘的遗留对局(不插入新期)。
*/
public static function voidRemainingOpenRoundsOnMaintenanceAfterFinalize(): void
{
if (GameRecordService::isLiveRuntimeEnabled()) {
return;
}
$reason = (string) __('Open period closed after payout: game is in maintenance');
$rows = Db::name('game_record')
->whereIn('status', [0, 1])
->order('id', 'asc')
->select()
->toArray();
foreach ($rows as $row) {
$rid = filter_var($row['id'] ?? 0, FILTER_VALIDATE_INT);
if ($rid === false || $rid <= 0) {
continue;
}
self::voidOpenPeriodInternal($rid, $reason);
}
GameHotDataRedis::gameRecordRefreshAggregateCaches();
}
/**
* 作废单期(仅 status 0/1不修改自动开局开关。
*
* @return array{ok: bool, msg?: string}
*/
private static function voidOpenPeriodInternal(int $recordId, string $voidReason): array
{
if ($recordId <= 0) {
return ['ok' => false, 'msg' => __('Parameter error')];
}
$reason = trim($voidReason);
if ($reason === '') {
return ['ok' => false, 'msg' => __('Void reason is required')];
}
$record = Db::name('game_record')->where('id', $recordId)->find();
if (!$record) {
return ['ok' => false, 'msg' => __('No active game in progress')];
}
$st = (int) ($record['status'] ?? -1);
if (!in_array($st, [0, 1], true)) {
return ['ok' => false, 'msg' => __('Current period cannot be voided')];
}
$lock = GameHotDataLock::tryAcquireWithWait(GameHotDataLock::TYPE_GAME_RECORD, (string) $recordId, 3000);
if (!$lock['acquired']) {
return ['ok' => false, 'msg' => __('Another operation is in progress for this period; please try again later')];
}
$refundedUserIds = [];
try {
$now = time();
Db::startTrans();
try {
$refund = self::refundPendingBetsSummaryForPeriodLocked($recordId, $now);
$refundedUserIds = $refund['user_ids'];
Db::name('game_record')->where('id', $recordId)->update([
'status' => 5,
'void_reason' => $reason,
'pending_draw_number' => null,
'payout_until' => null,
'ai_locked_number' => null,
'update_time' => $now,
]);
Db::commit();
} catch (Throwable $e) {
Db::rollback();
return ['ok' => false, 'msg' => __('Void failed') . ': ' . $e->getMessage()];
}
GameHotDataCoordinator::afterGameRecordCommitted($recordId);
foreach ($refundedUserIds as $uid) {
if ($uid > 0) {
GameHotDataCoordinator::afterUserCommitted($uid);
}
}
return [
'ok' => true,
'msg' => __('Period voided'),
'refund' => $refund,
];
} finally {
GameHotDataLock::release(GameHotDataLock::TYPE_GAME_RECORD, (string) $recordId, $lock['token'], $lock['redis_lock']);
}
}
/**
* 作废当前期(仅 status 为下注/封盘):待开奖注单退款,本期置为已作废,并关闭运行开关。
*
@@ -768,50 +896,22 @@ final class GameLiveService
return ['ok' => false, 'msg' => __('Current period cannot be voided')];
}
$rid = (int) $record['id'];
$lock = GameHotDataLock::tryAcquireWithWait(GameHotDataLock::TYPE_GAME_RECORD, (string) $rid, 3000);
if (!$lock['acquired']) {
return ['ok' => false, 'msg' => __('Another operation is in progress for this period; please try again later')];
$internal = self::voidOpenPeriodInternal($rid, $reason);
if (!($internal['ok'] ?? false)) {
$errMsg = $internal['msg'] ?? null;
return ['ok' => false, 'msg' => is_string($errMsg) ? $errMsg : __('Void failed')];
}
$refundedUserIds = [];
try {
$now = time();
$refund = ['user_ids' => [], 'order_count' => 0, 'total_amount' => '0.00', 'order_ids' => []];
Db::startTrans();
try {
$refund = self::refundPendingBetsSummaryForPeriodLocked($rid, $now);
$refundedUserIds = $refund['user_ids'];
Db::name('game_record')->where('id', $rid)->update([
'status' => 5,
'void_reason' => $reason,
'pending_draw_number' => null,
'payout_until' => null,
'ai_locked_number' => null,
'update_time' => $now,
]);
Db::commit();
} catch (Throwable $e) {
Db::rollback();
return ['ok' => false, 'msg' => __('Void failed') . ': ' . $e->getMessage()];
}
GameRecordService::setAutoCreateEnabled(false);
GameHotDataCoordinator::afterGameRecordCommitted($rid);
GameHotDataCoordinator::afterGameConfigKeyCommitted(GameRecordService::KEY_AUTO_CREATE);
foreach ($refundedUserIds as $uid) {
if ($uid > 0) {
GameHotDataCoordinator::afterUserCommitted($uid);
}
}
self::publishSnapshot(null);
GameRecordService::setAutoCreateEnabled(false);
GameHotDataCoordinator::afterGameConfigKeyCommitted(GameRecordService::KEY_AUTO_CREATE);
self::publishSnapshot(null);
$refund = $internal['refund'] ?? null;
return [
'ok' => true,
'msg' => __('Period voided'),
'record' => self::reloadRecord($rid),
'refund' => $refund,
];
} finally {
GameHotDataLock::release(GameHotDataLock::TYPE_GAME_RECORD, (string) $rid, $lock['token'], $lock['redis_lock']);
}
return [
'ok' => true,
'msg' => __('Period voided'),
'record' => self::reloadRecord($rid),
'refund' => is_array($refund) ? $refund : null,
];
}
/**