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

This commit is contained in:
2026-05-26 16:23:04 +08:00
parent 66c002f522
commit 53eefd901d
3 changed files with 62 additions and 14 deletions

View File

@@ -261,6 +261,7 @@ final class GameLiveService
} }
if (!GameRecordService::isLiveRuntimeEnabled()) { if (!GameRecordService::isLiveRuntimeEnabled()) {
self::voidRemainingOpenRoundsOnMaintenanceAfterFinalize(); self::voidRemainingOpenRoundsOnMaintenanceAfterFinalize();
GameHotDataRedis::gameRecordRefreshAggregateCaches();
} elseif (is_string($newPeriodNo ?? null) && $newPeriodNo !== '') { } elseif (is_string($newPeriodNo ?? null) && $newPeriodNo !== '') {
self::publishImmediateBettingTickAfterFinalize(); self::publishImmediateBettingTickAfterFinalize();
} }
@@ -716,6 +717,7 @@ final class GameLiveService
self::publishSnapshot(null); self::publishSnapshot(null);
if (!GameRecordService::isLiveRuntimeEnabled()) { if (!GameRecordService::isLiveRuntimeEnabled()) {
self::voidRemainingOpenRoundsOnMaintenanceAfterFinalize(); self::voidRemainingOpenRoundsOnMaintenanceAfterFinalize();
GameHotDataRedis::gameRecordRefreshAggregateCaches();
} elseif (is_string($newPeriodNo ?? null) && $newPeriodNo !== '') { } elseif (is_string($newPeriodNo ?? null) && $newPeriodNo !== '') {
self::publishImmediateBettingTickAfterFinalize(); self::publishImmediateBettingTickAfterFinalize();
} }
@@ -726,6 +728,12 @@ final class GameLiveService
public static function tickAutoDraw(): void public static function tickAutoDraw(): void
{ {
if (!GameRecordService::isAutoCreateEnabled()) {
$openCount = (int) Db::name('game_record')->whereIn('status', [0, 1])->count();
if ($openCount <= 0) {
return;
}
}
$record = self::resolveRecord(null); $record = self::resolveRecord(null);
if (!$record || !in_array((int) $record['status'], [0, 1], true)) { if (!$record || !in_array((int) $record['status'], [0, 1], true)) {
return; return;
@@ -801,6 +809,7 @@ final class GameLiveService
self::voidOpenPeriodInternal($rid, $reason); self::voidOpenPeriodInternal($rid, $reason);
} }
GameHotDataRedis::gameRecordRefreshAggregateCaches(); GameHotDataRedis::gameRecordRefreshAggregateCaches();
self::publishSnapshot(null);
} }
/** /**
@@ -1145,6 +1154,9 @@ final class GameLiveService
if (!self::shouldPublishPeriodTick($status, $periodNo)) { if (!self::shouldPublishPeriodTick($status, $periodNo)) {
return; return;
} }
if (empty($snapshot['runtime_enabled']) && in_array($status, ['betting', 'locked'], true)) {
return;
}
GameWebSocketEventBus::publish(self::EVT_PERIOD_TICK, $payload); GameWebSocketEventBus::publish(self::EVT_PERIOD_TICK, $payload);
} }

View File

@@ -23,6 +23,31 @@ final class GameRecordService
return false; return false;
} }
$v = $row['config_value'] ?? ''; $v = $row['config_value'] ?? '';
return self::truthyConfigValue($v);
}
/**
* 是否允许自动创建下一局(读库,避免 Redis 缓存仍为 1 时误开新期)。
*/
public static function isAutoCreateEnabled(): bool
{
return self::getConfigBoolFromDb(self::KEY_AUTO_CREATE);
}
private static function getConfigBoolFromDb(string $key): bool
{
if ($key === '') {
return false;
}
$row = Db::name('game_config')->where('config_key', $key)->find();
if (!$row) {
return false;
}
return self::truthyConfigValue($row['config_value'] ?? '');
}
private static function truthyConfigValue(mixed $v): bool
{
return $v === '1' || $v === 1; return $v === '1' || $v === 1;
} }
@@ -51,7 +76,7 @@ final class GameRecordService
public static function tickAutoCreate(): void public static function tickAutoCreate(): void
{ {
if (!self::getConfigBool(self::KEY_AUTO_CREATE)) { if (!self::isAutoCreateEnabled()) {
return; return;
} }
try { try {
@@ -78,10 +103,6 @@ final class GameRecordService
public static function createNextRecordAfterDraw(): ?string public static function createNextRecordAfterDraw(): ?string
{ {
// 派彩结束后是否自动开新局:由 period_auto_create_enabled 控制
if (!self::getConfigBool(self::KEY_AUTO_CREATE)) {
return null;
}
return self::createNextRecordRowIfNoActive(); return self::createNextRecordRowIfNoActive();
} }
@@ -90,7 +111,7 @@ final class GameRecordService
*/ */
public static function isLiveRuntimeEnabled(): bool public static function isLiveRuntimeEnabled(): bool
{ {
return self::getConfigBool(self::KEY_AUTO_CREATE); return self::isAutoCreateEnabled();
} }
public static function setLiveRuntimeEnabled(bool $enabled): void public static function setLiveRuntimeEnabled(bool $enabled): void
@@ -103,7 +124,7 @@ final class GameRecordService
*/ */
public static function bootstrapPeriodWhenRuntimeEnabled(): void public static function bootstrapPeriodWhenRuntimeEnabled(): void
{ {
if (!self::getConfigBool(self::KEY_AUTO_CREATE)) { if (!self::isAutoCreateEnabled()) {
return; return;
} }
try { try {
@@ -116,6 +137,7 @@ final class GameRecordService
{ {
$now = time(); $now = time();
$v = $enabled ? '1' : '0'; $v = $enabled ? '1' : '0';
GameHotDataRedis::gameConfigForget(self::KEY_AUTO_CREATE);
self::upsertConfig(self::KEY_AUTO_CREATE, $v, 'int', '是否允许自动创建下一局(全局仅一局)', $now); self::upsertConfig(self::KEY_AUTO_CREATE, $v, 'int', '是否允许自动创建下一局(全局仅一局)', $now);
} }
@@ -124,7 +146,7 @@ final class GameRecordService
*/ */
public static function createNextRecordRow(): string public static function createNextRecordRow(): string
{ {
$created = self::createNextRecordRowIfNoActive(); $created = self::createNextRecordRowIfNoActive(false);
if ($created === null) { if ($created === null) {
throw new \RuntimeException((string) __('There is an unfinished round; cannot create a new one')); throw new \RuntimeException((string) __('There is an unfinished round; cannot create a new one'));
} }
@@ -134,9 +156,14 @@ final class GameRecordService
/** /**
* 幂等插入下一期:有进行中局则不插入,返回 null。 * 幂等插入下一期:有进行中局则不插入,返回 null。
*
* @param bool $requireAutoCreate true=须开启自动开局(定时/派彩后false=手动创建下一期
*/ */
public static function createNextRecordRowIfNoActive(): ?string public static function createNextRecordRowIfNoActive(bool $requireAutoCreate = true): ?string
{ {
if ($requireAutoCreate && !self::isAutoCreateEnabled()) {
return null;
}
$lock = GameHotDataLock::tryAcquireWithWait(GameHotDataLock::TYPE_GAME_RECORD, self::AUTO_CREATE_LOCK_KEY, 1500); $lock = GameHotDataLock::tryAcquireWithWait(GameHotDataLock::TYPE_GAME_RECORD, self::AUTO_CREATE_LOCK_KEY, 1500);
if (!$lock['acquired']) { if (!$lock['acquired']) {
return null; return null;

View File

@@ -426,6 +426,11 @@ function handlePeriodTickEvent(periodData: anyObj): void {
const status = typeof periodData.status === 'string' ? periodData.status : '' const status = typeof periodData.status === 'string' ? periodData.status : ''
const periodNo = typeof periodData.period_no === 'string' ? periodData.period_no : '' const periodNo = typeof periodData.period_no === 'string' ? periodData.period_no : ''
const currentNo = typeof snapshot.record?.period_no === 'string' ? snapshot.record.period_no : '' const currentNo = typeof snapshot.record?.period_no === 'string' ? snapshot.record.period_no : ''
const runtimeOff =
toBool(snapshot.runtime_enabled) === false || toBool(periodData.runtime_enabled) === false
if (runtimeOff && (status === 'betting' || status === 'locked')) {
return
}
if (status === 'betting' && periodNo !== '' && periodNo !== currentNo) { if (status === 'betting' && periodNo !== '' && periodNo !== currentNo) {
void loadSnapshot({ force: true }) void loadSnapshot({ force: true })
return return
@@ -436,7 +441,7 @@ function handlePeriodTickEvent(periodData: anyObj): void {
} }
return return
} }
if (currentNo === '' && periodNo !== '') { if (currentNo === '' && periodNo !== '' && !runtimeOff) {
void loadSnapshot({ force: true }) void loadSnapshot({ force: true })
} }
} }
@@ -698,7 +703,13 @@ function mergeLiveSnapshot(data: anyObj): void {
if (data.record !== undefined) { if (data.record !== undefined) {
const nextId = data.record?.id != null ? Number(data.record.id) : null const nextId = data.record?.id != null ? Number(data.record.id) : null
periodChanged = prevPeriodId !== null && nextId !== null && prevPeriodId !== nextId periodChanged = prevPeriodId !== null && nextId !== null && prevPeriodId !== nextId
snapshot.record = data.record const runtimeOff = toBool(data.runtime_enabled) === false || toBool(snapshot.runtime_enabled) === false
const serverMaintenance = data.maintenance_ui === true
if (runtimeOff && serverMaintenance) {
snapshot.record = null
} else {
snapshot.record = data.record
}
} }
const incomingBets = Array.isArray(data.bets) ? data.bets : null const incomingBets = Array.isArray(data.bets) ? data.bets : null
@@ -835,9 +846,7 @@ async function loadSnapshot(options?: { force?: boolean }): Promise<void> {
async function onRuntimeSwitch(val: boolean | string | number): void { async function onRuntimeSwitch(val: boolean | string | number): void {
const on = toBool(val) === true const on = toBool(val) === true
// 防止某些场景下 model-value 变化触发重复 change 事件,造成 runtime 接口循环调用 if (runtimeSwitchLoading.value) {
const current = toBool(snapshot.runtime_enabled) === true
if (on === current) {
return return
} }
// el-switch 为受控组件model-value 来自 snapshot接口返回前先乐观更新避免点击后立刻回弹 // el-switch 为受控组件model-value 来自 snapshot接口返回前先乐观更新避免点击后立刻回弹