1.修复自动创建下一期bug
This commit is contained in:
@@ -52,6 +52,7 @@ class Live extends Backend
|
|||||||
$topics = [
|
$topics = [
|
||||||
'admin.live.snapshot',
|
'admin.live.snapshot',
|
||||||
'admin.live.opened',
|
'admin.live.opened',
|
||||||
|
'admin.live.finalized',
|
||||||
'jackpot.hit',
|
'jackpot.hit',
|
||||||
'period.tick',
|
'period.tick',
|
||||||
'period.locked',
|
'period.locked',
|
||||||
|
|||||||
@@ -252,12 +252,16 @@ final class GameLiveService
|
|||||||
$now = time();
|
$now = time();
|
||||||
Db::startTrans();
|
Db::startTrans();
|
||||||
try {
|
try {
|
||||||
Db::name('game_record')->where('id', $recordId)->where('status', 3)->update([
|
$affected = Db::name('game_record')->where('id', $recordId)->where('status', 3)->update([
|
||||||
'status' => 4,
|
'status' => 4,
|
||||||
'payout_until' => null,
|
'payout_until' => null,
|
||||||
'update_time' => $now,
|
'update_time' => $now,
|
||||||
]);
|
]);
|
||||||
$newPeriodNo = GameRecordService::createNextRecordAfterDraw();
|
if ($affected < 1) {
|
||||||
|
Db::rollback();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
Db::commit();
|
Db::commit();
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
Db::rollback();
|
Db::rollback();
|
||||||
@@ -267,6 +271,17 @@ final class GameLiveService
|
|||||||
]);
|
]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
$newPeriodNo = null;
|
||||||
|
if (GameRecordService::isAutoCreateEnabled()) {
|
||||||
|
try {
|
||||||
|
$newPeriodNo = GameRecordService::createNextRecordAfterDraw();
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
Log::warning('game live startup create next record failed', [
|
||||||
|
'record_id' => $recordId,
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
GameHotDataCoordinator::afterGameRecordCommitted($recordId);
|
GameHotDataCoordinator::afterGameRecordCommitted($recordId);
|
||||||
try {
|
try {
|
||||||
GameRecordStatService::refreshForRecordId($recordId);
|
GameRecordStatService::refreshForRecordId($recordId);
|
||||||
@@ -283,6 +298,9 @@ final class GameLiveService
|
|||||||
|
|
||||||
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);
|
||||||
@@ -717,6 +735,8 @@ final class GameLiveService
|
|||||||
$id = (int) $row['id'];
|
$id = (int) $row['id'];
|
||||||
$lock = GameHotDataLock::tryAcquireWithWait(GameHotDataLock::TYPE_GAME_RECORD, (string) $id, 2000);
|
$lock = GameHotDataLock::tryAcquireWithWait(GameHotDataLock::TYPE_GAME_RECORD, (string) $id, 2000);
|
||||||
if (!$lock['acquired']) {
|
if (!$lock['acquired']) {
|
||||||
|
Log::warning('finalizePayoutGrace: lock not acquired', ['record_id' => $id]);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -726,14 +746,22 @@ final class GameLiveService
|
|||||||
$resultNumber = null;
|
$resultNumber = null;
|
||||||
}
|
}
|
||||||
$newPeriodNo = null;
|
$newPeriodNo = null;
|
||||||
|
$now = time();
|
||||||
Db::startTrans();
|
Db::startTrans();
|
||||||
try {
|
try {
|
||||||
Db::name('game_record')->where('id', $id)->update([
|
$affected = Db::name('game_record')
|
||||||
|
->where('id', $id)
|
||||||
|
->where('status', 3)
|
||||||
|
->update([
|
||||||
'status' => 4,
|
'status' => 4,
|
||||||
'payout_until' => null,
|
'payout_until' => null,
|
||||||
'update_time' => time(),
|
'update_time' => $now,
|
||||||
]);
|
]);
|
||||||
$newPeriodNo = GameRecordService::createNextRecordAfterDraw();
|
if ($affected < 1) {
|
||||||
|
Db::rollback();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
Db::commit();
|
Db::commit();
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
Db::rollback();
|
Db::rollback();
|
||||||
@@ -741,13 +769,33 @@ final class GameLiveService
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (GameRecordService::isAutoCreateEnabled()) {
|
||||||
|
try {
|
||||||
|
$newPeriodNo = GameRecordService::createNextRecordAfterDraw();
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
Log::warning('finalizePayoutGrace: create next record failed', [
|
||||||
|
'record_id' => $id,
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
GameHotDataCoordinator::afterGameRecordCommitted($id);
|
GameHotDataCoordinator::afterGameRecordCommitted($id);
|
||||||
GameRecordStatService::refreshForRecordId($id);
|
GameRecordStatService::refreshForRecordId($id);
|
||||||
self::publishPublicPeriodFinished($id, $periodNo, $resultNumber);
|
self::publishPublicPeriodFinished($id, $periodNo, $resultNumber);
|
||||||
|
GameWebSocketEventBus::publish('admin.live.finalized', [
|
||||||
|
'period_id' => $id,
|
||||||
|
'period_no' => $periodNo,
|
||||||
|
'result_number' => $resultNumber,
|
||||||
|
'runtime_enabled' => GameRecordService::isLiveRuntimeEnabled(),
|
||||||
|
'maintenance_ui' => !GameRecordService::isLiveRuntimeEnabled()
|
||||||
|
&& !GameRecordService::hasActiveRecord(),
|
||||||
|
'server_time' => $now,
|
||||||
|
]);
|
||||||
self::publishSnapshot(null);
|
self::publishSnapshot(null);
|
||||||
if (!GameRecordService::isLiveRuntimeEnabled()) {
|
if (!GameRecordService::isLiveRuntimeEnabled()) {
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -280,6 +280,7 @@ let clockTimer: number | null = null
|
|||||||
let payoutStuckRefreshTimer: number | null = null
|
let payoutStuckRefreshTimer: number | null = null
|
||||||
let drawStuckRefreshTimer: number | null = null
|
let drawStuckRefreshTimer: number | null = null
|
||||||
let drawStuckSeconds = 0
|
let drawStuckSeconds = 0
|
||||||
|
let payoutPhaseStuckSeconds = 0
|
||||||
let fallbackPollTimer: number | null = null
|
let fallbackPollTimer: number | null = null
|
||||||
let betStreamRefreshTimer: number | null = null
|
let betStreamRefreshTimer: number | null = null
|
||||||
/** 合并并发 snapshot 请求,避免 axios 重复请求取消导致控制台报错 */
|
/** 合并并发 snapshot 请求,避免 axios 重复请求取消导致控制台报错 */
|
||||||
@@ -352,6 +353,25 @@ function handleWsPayload(raw: unknown): void {
|
|||||||
void loadSnapshot({ force: true })
|
void loadSnapshot({ force: true })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (event === 'admin.live.finalized' && parsed.data && typeof parsed.data === 'object') {
|
||||||
|
const fin = parsed.data as anyObj
|
||||||
|
if (toBool(fin.maintenance_ui) === true) {
|
||||||
|
snapshot.is_payout_phase = false
|
||||||
|
snapshot.payout_remaining_seconds = 0
|
||||||
|
}
|
||||||
|
void loadSnapshot({ force: true })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (event === 'period.payout' && parsed.data && typeof parsed.data === 'object') {
|
||||||
|
mergePeriodPayoutTick(parsed.data as anyObj)
|
||||||
|
const payoutData = parsed.data as anyObj
|
||||||
|
if (typeof payoutData.result_number === 'number') {
|
||||||
|
snapshot.result_number = payoutData.result_number
|
||||||
|
calcResultNumber.value = payoutData.result_number
|
||||||
|
}
|
||||||
|
snapshot.is_payout_phase = true
|
||||||
|
return
|
||||||
|
}
|
||||||
if (event === 'bet.win' && parsed.data && typeof parsed.data === 'object') {
|
if (event === 'bet.win' && parsed.data && typeof parsed.data === 'object') {
|
||||||
const winData = parsed.data as anyObj
|
const winData = parsed.data as anyObj
|
||||||
if (winData.is_jackpot === true) {
|
if (winData.is_jackpot === true) {
|
||||||
@@ -419,7 +439,11 @@ function mergePeriodPayoutTick(data: anyObj): void {
|
|||||||
const remain = numberValue(data.payout_remaining_seconds)
|
const remain = numberValue(data.payout_remaining_seconds)
|
||||||
if (remain !== null) {
|
if (remain !== null) {
|
||||||
snapshot.payout_remaining_seconds = Math.max(0, remain)
|
snapshot.payout_remaining_seconds = Math.max(0, remain)
|
||||||
snapshot.is_payout_phase = remain > 0 || snapshot.is_payout_phase === true
|
snapshot.is_payout_phase = remain > 0
|
||||||
|
}
|
||||||
|
const until = readPayoutUntilUnix(data)
|
||||||
|
if (until !== null && snapshot.record && typeof snapshot.record === 'object') {
|
||||||
|
snapshot.record.payout_until = until
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -437,10 +461,10 @@ function handlePeriodTickEvent(periodData: anyObj): void {
|
|||||||
void loadSnapshot({ force: true })
|
void loadSnapshot({ force: true })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (status === 'finished' && snapshot.is_payout_phase) {
|
if (status === 'finished') {
|
||||||
if (!wsConnected.value) {
|
snapshot.is_payout_phase = false
|
||||||
void loadSnapshot()
|
snapshot.payout_remaining_seconds = 0
|
||||||
}
|
void loadSnapshot({ force: true })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (currentNo === '' && periodNo !== '' && !runtimeOff) {
|
if (currentNo === '' && periodNo !== '' && !runtimeOff) {
|
||||||
@@ -709,6 +733,11 @@ function mergeLiveSnapshot(data: anyObj): void {
|
|||||||
const serverMaintenance = data.maintenance_ui === true
|
const serverMaintenance = data.maintenance_ui === true
|
||||||
if (runtimeOff && serverMaintenance) {
|
if (runtimeOff && serverMaintenance) {
|
||||||
snapshot.record = null
|
snapshot.record = null
|
||||||
|
snapshot.is_payout_phase = false
|
||||||
|
snapshot.payout_remaining_seconds = 0
|
||||||
|
snapshot.can_calculate = false
|
||||||
|
snapshot.can_draw = false
|
||||||
|
snapshot.can_schedule_draw = false
|
||||||
} else {
|
} else {
|
||||||
snapshot.record = data.record
|
snapshot.record = data.record
|
||||||
}
|
}
|
||||||
@@ -1023,6 +1052,24 @@ function isPrePayoutDrawStuck(): boolean {
|
|||||||
return st === 0 || st === 1
|
return st === 0 || st === 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 派彩倒计时已为 0 但 is_payout_phase 仍为 true(关服排水时常见) */
|
||||||
|
function tickPayoutPhaseStuckRecovery(): void {
|
||||||
|
if (!snapshot.is_payout_phase) {
|
||||||
|
payoutPhaseStuckSeconds = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const remain = payoutRemainingLive.value
|
||||||
|
if (remain === null || remain > 0) {
|
||||||
|
payoutPhaseStuckSeconds = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
payoutPhaseStuckSeconds++
|
||||||
|
if (payoutPhaseStuckSeconds >= 4) {
|
||||||
|
payoutPhaseStuckSeconds = 0
|
||||||
|
void loadSnapshot({ force: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function tickPrePayoutDrawStuckRecovery(): void {
|
function tickPrePayoutDrawStuckRecovery(): void {
|
||||||
if (!isPrePayoutDrawStuck()) {
|
if (!isPrePayoutDrawStuck()) {
|
||||||
drawStuckSeconds = 0
|
drawStuckSeconds = 0
|
||||||
@@ -1051,6 +1098,7 @@ onMounted(async () => {
|
|||||||
clockTimer = window.setInterval(() => {
|
clockTimer = window.setInterval(() => {
|
||||||
clockTick.value++
|
clockTick.value++
|
||||||
tickPrePayoutDrawStuckRecovery()
|
tickPrePayoutDrawStuckRecovery()
|
||||||
|
tickPayoutPhaseStuckRecovery()
|
||||||
}, 1000)
|
}, 1000)
|
||||||
fallbackPollTimer = window.setInterval(() => {
|
fallbackPollTimer = window.setInterval(() => {
|
||||||
if (!wsConnected.value) {
|
if (!wsConnected.value) {
|
||||||
|
|||||||
Reference in New Issue
Block a user