1.修复自动创建下一期bug
This commit is contained in:
@@ -25,6 +25,8 @@ class Live extends Backend
|
|||||||
{
|
{
|
||||||
$recordIdRaw = $this->request ? $this->request->get('record_id') : null;
|
$recordIdRaw = $this->request ? $this->request->get('record_id') : null;
|
||||||
$recordId = is_numeric((string) $recordIdRaw) ? (int) $recordIdRaw : null;
|
$recordId = is_numeric((string) $recordIdRaw) ? (int) $recordIdRaw : null;
|
||||||
|
GameLiveService::recoverLiveRoundState();
|
||||||
|
|
||||||
return $this->success('', GameLiveService::buildSnapshot($recordId));
|
return $this->success('', GameLiveService::buildSnapshot($recordId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,6 +38,8 @@ class Live extends Backend
|
|||||||
}
|
}
|
||||||
$recordIdRaw = $request->get('record_id');
|
$recordIdRaw = $request->get('record_id');
|
||||||
$recordId = is_numeric((string) $recordIdRaw) ? (int) $recordIdRaw : null;
|
$recordId = is_numeric((string) $recordIdRaw) ? (int) $recordIdRaw : null;
|
||||||
|
GameLiveService::recoverLiveRoundState();
|
||||||
|
|
||||||
return $this->success('', GameLiveService::buildSnapshot($recordId));
|
return $this->success('', GameLiveService::buildSnapshot($recordId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,15 +177,12 @@ class Live extends Backend
|
|||||||
return $this->error(is_string($errMsg) ? $errMsg : __('Void failed'));
|
return $this->error(is_string($errMsg) ? $errMsg : __('Void failed'));
|
||||||
}
|
}
|
||||||
$okMsg = $res['msg'] ?? '';
|
$okMsg = $res['msg'] ?? '';
|
||||||
$snapshot = GameLiveService::buildSnapshot(null);
|
$snapshot = GameLiveService::buildSnapshotAfterVoid();
|
||||||
// 作废本局后:必须关闭自动创建下一局开关(允许管理员后续手动重新开启)
|
|
||||||
$snapshot['runtime_enabled'] = false;
|
|
||||||
// 作废后一般不存在进行中的局,直接进入维护态(用于前端展示“维护中”倒计时)
|
|
||||||
$snapshot['maintenance_ui'] = true;
|
|
||||||
$refund = $res['refund'] ?? null;
|
$refund = $res['refund'] ?? null;
|
||||||
if (is_array($refund)) {
|
if (is_array($refund)) {
|
||||||
$snapshot['void_refund'] = $refund;
|
$snapshot['void_refund'] = $refund;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->success(is_string($okMsg) ? $okMsg : '', $snapshot);
|
return $this->success(is_string($okMsg) ? $okMsg : '', $snapshot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -296,11 +296,17 @@ final class GameLiveService
|
|||||||
self::publishSnapshot(null);
|
self::publishSnapshot(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定时任务/WS 推送前:结单 + 超时自动开奖(勿在已持期号锁时调用)。
|
||||||
|
*/
|
||||||
|
public static function recoverLiveRoundState(): void
|
||||||
|
{
|
||||||
|
self::finalizePayoutGrace();
|
||||||
|
self::tickAutoDraw();
|
||||||
|
}
|
||||||
|
|
||||||
public static function buildSnapshot(?int $recordId = null): array
|
public static function buildSnapshot(?int $recordId = null): array
|
||||||
{
|
{
|
||||||
// HTTP/WS 拉快照时也尝试结单,避免仅 gameLiveTicker 未跑时派彩倒计时归零后长期卡住
|
|
||||||
self::finalizePayoutGrace();
|
|
||||||
|
|
||||||
$record = self::resolveRecord($recordId);
|
$record = self::resolveRecord($recordId);
|
||||||
if ($record) {
|
if ($record) {
|
||||||
$periodSeconds = self::getConfigInt(self::KEY_PERIOD_SECONDS, 30);
|
$periodSeconds = self::getConfigInt(self::KEY_PERIOD_SECONDS, 30);
|
||||||
@@ -639,9 +645,9 @@ final class GameLiveService
|
|||||||
if ($existingResult !== false && $existingResult >= 1 && $existingResult <= self::DRAW_NUMBER_MAX && $st >= 2) {
|
if ($existingResult !== false && $existingResult >= 1 && $existingResult <= self::DRAW_NUMBER_MAX && $st >= 2) {
|
||||||
Db::commit();
|
Db::commit();
|
||||||
$periodNo = is_string($record['period_no'] ?? null) ? (string) $record['period_no'] : '';
|
$periodNo = is_string($record['period_no'] ?? null) ? (string) $record['period_no'] : '';
|
||||||
GameHotDataCoordinator::afterGameRecordCommitted($rid);
|
|
||||||
$notifyAfterLock = static function () use ($rid): void {
|
$notifyAfterLock = static function () use ($rid): void {
|
||||||
self::publishSnapshot($rid);
|
GameHotDataCoordinator::afterGameRecordCommitted($rid);
|
||||||
|
self::publishSnapshot($rid, false);
|
||||||
};
|
};
|
||||||
$result = [
|
$result = [
|
||||||
'ok' => true,
|
'ok' => true,
|
||||||
@@ -685,20 +691,6 @@ final class GameLiveService
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($drawCommitted) {
|
if ($drawCommitted) {
|
||||||
GameBetSettleService::publishSettlementWinsAfterCommit(
|
|
||||||
$settleOut,
|
|
||||||
$rid,
|
|
||||||
$periodNo,
|
|
||||||
$finalNumber
|
|
||||||
);
|
|
||||||
|
|
||||||
GameHotDataCoordinator::afterGameRecordCommitted($rid);
|
|
||||||
|
|
||||||
try {
|
|
||||||
GameRecordStatService::refreshForRecordId($rid);
|
|
||||||
} catch (Throwable) {
|
|
||||||
}
|
|
||||||
|
|
||||||
$notifyAfterLock = static function () use (
|
$notifyAfterLock = static function () use (
|
||||||
$rid,
|
$rid,
|
||||||
$periodNo,
|
$periodNo,
|
||||||
@@ -708,6 +700,17 @@ final class GameLiveService
|
|||||||
$now,
|
$now,
|
||||||
$settleOut
|
$settleOut
|
||||||
): void {
|
): void {
|
||||||
|
GameBetSettleService::publishSettlementWinsAfterCommit(
|
||||||
|
$settleOut,
|
||||||
|
$rid,
|
||||||
|
$periodNo,
|
||||||
|
$finalNumber
|
||||||
|
);
|
||||||
|
GameHotDataCoordinator::afterGameRecordCommitted($rid);
|
||||||
|
try {
|
||||||
|
GameRecordStatService::refreshForRecordId($rid);
|
||||||
|
} catch (Throwable) {
|
||||||
|
}
|
||||||
self::publishPublicPeriodOpened($periodNo, $finalNumber, $drawMode, $now);
|
self::publishPublicPeriodOpened($periodNo, $finalNumber, $drawMode, $now);
|
||||||
self::publishPublicPeriodPayout($rid, $periodNo, $finalNumber, $payoutUntil, $now);
|
self::publishPublicPeriodPayout($rid, $periodNo, $finalNumber, $payoutUntil, $now);
|
||||||
$jackpotHits = is_array($settleOut['jackpot_hits'] ?? null) ? $settleOut['jackpot_hits'] : [];
|
$jackpotHits = is_array($settleOut['jackpot_hits'] ?? null) ? $settleOut['jackpot_hits'] : [];
|
||||||
@@ -720,7 +723,7 @@ final class GameLiveService
|
|||||||
'jackpot_hits' => $jackpotHits,
|
'jackpot_hits' => $jackpotHits,
|
||||||
'server_time' => $now,
|
'server_time' => $now,
|
||||||
]);
|
]);
|
||||||
self::publishSnapshot(null);
|
self::publishSnapshot(null, false);
|
||||||
};
|
};
|
||||||
|
|
||||||
$result = [
|
$result = [
|
||||||
@@ -833,14 +836,13 @@ final class GameLiveService
|
|||||||
'maintenance_ui' => !$runtimeEnabled && !GameRecordService::hasActiveRecord(),
|
'maintenance_ui' => !$runtimeEnabled && !GameRecordService::hasActiveRecord(),
|
||||||
'server_time' => $now,
|
'server_time' => $now,
|
||||||
]);
|
]);
|
||||||
self::publishSnapshot(null);
|
|
||||||
if (!$runtimeEnabled) {
|
if (!$runtimeEnabled) {
|
||||||
self::voidRemainingOpenRoundsOnMaintenanceAfterFinalize();
|
self::voidRemainingOpenRoundsOnMaintenanceAfterFinalize();
|
||||||
GameHotDataRedis::gameRecordRefreshAggregateCaches();
|
GameHotDataRedis::gameRecordRefreshAggregateCaches();
|
||||||
self::publishSnapshot(null);
|
|
||||||
} elseif (is_string($newPeriodNo ?? null) && $newPeriodNo !== '') {
|
} elseif (is_string($newPeriodNo ?? null) && $newPeriodNo !== '') {
|
||||||
self::publishImmediateBettingTickAfterFinalize();
|
self::publishImmediateBettingTickAfterFinalize();
|
||||||
}
|
}
|
||||||
|
self::publishSnapshot(null, false);
|
||||||
};
|
};
|
||||||
} finally {
|
} finally {
|
||||||
GameHotDataLock::release(GameHotDataLock::TYPE_GAME_RECORD, (string) $id, $lock['token'], $lock['redis_lock']);
|
GameHotDataLock::release(GameHotDataLock::TYPE_GAME_RECORD, (string) $id, $lock['token'], $lock['redis_lock']);
|
||||||
@@ -966,11 +968,12 @@ final class GameLiveService
|
|||||||
if (!in_array($st, [0, 1], true)) {
|
if (!in_array($st, [0, 1], true)) {
|
||||||
return ['ok' => false, 'msg' => __('Current period cannot be voided')];
|
return ['ok' => false, 'msg' => __('Current period cannot be voided')];
|
||||||
}
|
}
|
||||||
$lock = self::acquireRecordLockForAdminMutation((string) $recordId, 3000);
|
$lock = self::acquireRecordLockForAdminMutation((string) $recordId, 1500);
|
||||||
if (!$lock['acquired']) {
|
if (!$lock['acquired']) {
|
||||||
return ['ok' => false, 'msg' => __('Another operation is in progress for this period; please try again later')];
|
return ['ok' => false, 'msg' => __('Another operation is in progress for this period; please try again later')];
|
||||||
}
|
}
|
||||||
$refundedUserIds = [];
|
$refundedUserIds = [];
|
||||||
|
$refund = ['user_ids' => [], 'order_count' => 0, 'total_amount' => '0.00', 'order_ids' => []];
|
||||||
try {
|
try {
|
||||||
$now = time();
|
$now = time();
|
||||||
Db::startTrans();
|
Db::startTrans();
|
||||||
@@ -990,20 +993,21 @@ final class GameLiveService
|
|||||||
Db::rollback();
|
Db::rollback();
|
||||||
return ['ok' => false, 'msg' => __('Void failed') . ': ' . $e->getMessage()];
|
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 {
|
} finally {
|
||||||
GameHotDataLock::release(GameHotDataLock::TYPE_GAME_RECORD, (string) $recordId, $lock['token'], $lock['redis_lock']);
|
GameHotDataLock::release(GameHotDataLock::TYPE_GAME_RECORD, (string) $recordId, $lock['token'], $lock['redis_lock']);
|
||||||
}
|
}
|
||||||
|
GameHotDataCoordinator::afterGameRecordCommitted($recordId);
|
||||||
|
foreach ($refundedUserIds as $uid) {
|
||||||
|
if ($uid > 0) {
|
||||||
|
GameHotDataCoordinator::afterUserCommitted($uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'ok' => true,
|
||||||
|
'msg' => __('Period voided'),
|
||||||
|
'refund' => $refund,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1044,17 +1048,30 @@ final class GameLiveService
|
|||||||
}
|
}
|
||||||
GameRecordService::setAutoCreateEnabled(false);
|
GameRecordService::setAutoCreateEnabled(false);
|
||||||
GameHotDataCoordinator::afterGameConfigKeyCommitted(GameRecordService::KEY_AUTO_CREATE);
|
GameHotDataCoordinator::afterGameConfigKeyCommitted(GameRecordService::KEY_AUTO_CREATE);
|
||||||
self::publishSnapshot(null);
|
|
||||||
$refund = $internal['refund'] ?? null;
|
$refund = $internal['refund'] ?? null;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'ok' => true,
|
'ok' => true,
|
||||||
'msg' => __('Period voided'),
|
'msg' => __('Period voided'),
|
||||||
'record' => self::reloadRecord($rid),
|
|
||||||
'refund' => is_array($refund) ? $refund : null,
|
'refund' => is_array($refund) ? $refund : null,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 作废本局后返回给前端的轻量快照(不再触发 publishSnapshot,避免 HTTP 超时)。
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public static function buildSnapshotAfterVoid(): array
|
||||||
|
{
|
||||||
|
GameHotDataRedis::gameRecordRefreshAggregateCaches();
|
||||||
|
$snapshot = self::buildSnapshot(null);
|
||||||
|
$snapshot['runtime_enabled'] = false;
|
||||||
|
$snapshot['maintenance_ui'] = true;
|
||||||
|
|
||||||
|
return $snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return list<int>
|
* @return list<int>
|
||||||
*/
|
*/
|
||||||
@@ -1148,8 +1165,11 @@ final class GameLiveService
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function publishSnapshot(?int $recordId = null): void
|
public static function publishSnapshot(?int $recordId = null, bool $runRecovery = true): void
|
||||||
{
|
{
|
||||||
|
if ($runRecovery) {
|
||||||
|
self::recoverLiveRoundState();
|
||||||
|
}
|
||||||
$snapshot = self::buildSnapshot($recordId);
|
$snapshot = self::buildSnapshot($recordId);
|
||||||
self::publishPublicPeriodPayoutCountdown($snapshot);
|
self::publishPublicPeriodPayoutCountdown($snapshot);
|
||||||
self::publishPublicPeriodTick($snapshot);
|
self::publishPublicPeriodTick($snapshot);
|
||||||
@@ -1649,14 +1669,15 @@ final class GameLiveService
|
|||||||
if ($recordId === '') {
|
if ($recordId === '') {
|
||||||
return ['acquired' => false, 'token' => null, 'redis_lock' => false];
|
return ['acquired' => false, 'token' => null, 'redis_lock' => false];
|
||||||
}
|
}
|
||||||
|
GameHotDataLock::forceRelease(GameHotDataLock::TYPE_GAME_RECORD, $recordId);
|
||||||
$lock = GameHotDataLock::tryAcquireWithWait(GameHotDataLock::TYPE_GAME_RECORD, $recordId, $waitMs);
|
$lock = GameHotDataLock::tryAcquireWithWait(GameHotDataLock::TYPE_GAME_RECORD, $recordId, $waitMs);
|
||||||
if ($lock['acquired']) {
|
if ($lock['acquired']) {
|
||||||
return $lock;
|
return $lock;
|
||||||
}
|
}
|
||||||
GameHotDataLock::forceRelease(GameHotDataLock::TYPE_GAME_RECORD, $recordId);
|
GameHotDataLock::forceRelease(GameHotDataLock::TYPE_GAME_RECORD, $recordId);
|
||||||
Log::warning('admin record lock force-released before void', ['record_id' => $recordId]);
|
Log::warning('admin record lock force-released before void retry', ['record_id' => $recordId]);
|
||||||
|
|
||||||
return GameHotDataLock::tryAcquireWithWait(GameHotDataLock::TYPE_GAME_RECORD, $recordId, 800);
|
return GameHotDataLock::tryAcquire(GameHotDataLock::TYPE_GAME_RECORD, $recordId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ class GameLiveTicker
|
|||||||
GameLiveService::recoverAbnormalPeriodOnStartup();
|
GameLiveService::recoverAbnormalPeriodOnStartup();
|
||||||
|
|
||||||
Timer::add(1, static function (): void {
|
Timer::add(1, static function (): void {
|
||||||
GameLiveService::finalizePayoutGrace();
|
|
||||||
GameLiveService::tickAutoDraw();
|
|
||||||
GameLiveService::publishSnapshot(null);
|
GameLiveService::publishSnapshot(null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,9 +85,7 @@ class GameWebSocketServer
|
|||||||
if (!$hasAdminSubscriber) {
|
if (!$hasAdminSubscriber) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 与 GameLiveTicker 对齐:仅推快照时若 live ticker 未运行会导致倒计时归零但永不开奖
|
GameLiveService::recoverLiveRoundState();
|
||||||
GameLiveService::finalizePayoutGrace();
|
|
||||||
GameLiveService::tickAutoDraw();
|
|
||||||
$snapshot = GameLiveService::buildSnapshot(null);
|
$snapshot = GameLiveService::buildSnapshot(null);
|
||||||
$payload = json_encode([
|
$payload = json_encode([
|
||||||
'event' => 'admin.live.snapshot',
|
'event' => 'admin.live.snapshot',
|
||||||
|
|||||||
@@ -899,6 +899,7 @@ async function submitVoidPeriod(): Promise<void> {
|
|||||||
record_id: snapshot.record.id,
|
record_id: snapshot.record.id,
|
||||||
void_reason: reason,
|
void_reason: reason,
|
||||||
},
|
},
|
||||||
|
timeout: 60 * 1000,
|
||||||
showSuccessMessage: true,
|
showSuccessMessage: true,
|
||||||
})
|
})
|
||||||
if (res.code === 1 && res.data) {
|
if (res.code === 1 && res.data) {
|
||||||
|
|||||||
Reference in New Issue
Block a user