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; } public static function getRecordSettings(): array { return [ 'period_auto_create_enabled' => self::getConfigBool(self::KEY_AUTO_CREATE) ? 1 : 0, 'period_manual_create_enabled' => self::getConfigBool(self::KEY_MANUAL_CREATE) ? 1 : 0, ]; } public static function saveRecordSettings(array $data): void { $now = time(); $auto = self::truthyConfigInput($data['period_auto_create_enabled'] ?? null) ? '1' : '0'; $manual = self::truthyConfigInput($data['period_manual_create_enabled'] ?? null) ? '1' : '0'; self::upsertConfig(self::KEY_AUTO_CREATE, $auto, 'int', '是否允许定时任务自动创建下一局(全局仅一局)', $now); self::upsertConfig(self::KEY_MANUAL_CREATE, $manual, 'int', '是否允许后台手动创建下一局', $now); } public static function hasActiveRecord(): bool { $count = Db::name('game_record')->whereIn('status', self::ACTIVE_STATUSES)->count(); return $count > 0; } public static function tickAutoCreate(): void { if (!self::isAutoCreateEnabled()) { return; } try { self::createNextRecordRowIfNoActive(); } catch (Throwable) { } } public static function createNextRecordForManual(): array { if (!self::getConfigBool(self::KEY_MANUAL_CREATE)) { return ['ok' => false, 'msg' => __('Manual create next round is disabled')]; } if (self::hasActiveRecord()) { return ['ok' => false, 'msg' => __('There is an unfinished round; cannot create a new one')]; } try { $periodNo = self::createNextRecordRow(); return ['ok' => true, 'msg' => __('New round created'), 'period_no' => $periodNo]; } catch (Throwable $e) { return ['ok' => false, 'msg' => $e->getMessage()]; } } public static function createNextRecordAfterDraw(): ?string { return self::createNextRecordRowIfNoActive(); } /** * 实时对局页「自动创建下一局」开关(兼容旧命名 runtime)。 */ public static function isLiveRuntimeEnabled(): bool { return self::isAutoCreateEnabled(); } public static function setLiveRuntimeEnabled(bool $enabled): void { self::setAutoCreateEnabled($enabled); } /** * 重新开启游戏时:若无进行中/未结清对局,则立即创建新一期(与定时任务「无局时自动创建」语义一致,供开关打开时立刻开局)。 */ public static function bootstrapPeriodWhenRuntimeEnabled(): void { if (!self::isAutoCreateEnabled()) { return; } try { self::createNextRecordRowIfNoActive(); } catch (Throwable) { } } public static function setAutoCreateEnabled(bool $enabled): void { $now = time(); $v = $enabled ? '1' : '0'; GameHotDataRedis::gameConfigForget(self::KEY_AUTO_CREATE); self::upsertConfig(self::KEY_AUTO_CREATE, $v, 'int', '是否允许自动创建下一局(全局仅一局)', $now); } /** * 派彩结单后插入新一期(调用方须已保证无其它进行中局)。 */ public static function createNextRecordRow(): string { $created = self::createNextRecordRowIfNoActive(false); if ($created === null) { throw new \RuntimeException((string) __('There is an unfinished round; cannot create a new one')); } return $created; } /** * 幂等插入下一期:有进行中局则不插入,返回 null。 * * @param bool $requireAutoCreate true=须开启自动开局(定时/派彩后);false=手动创建下一期 */ 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); if (!$lock['acquired']) { return null; } try { if (self::hasActiveRecord()) { return null; } $periodNo = self::generatePeriodNo(); $now = time(); Db::name('game_record')->insert([ 'period_no' => $periodNo, 'period_start_at' => $now, 'status' => 0, 'draw_mode' => 0, 'void_reason' => '', 'create_time' => $now, 'update_time' => $now, ]); GameHotDataCoordinator::afterGameRecordCommitted(null); return $periodNo; } finally { GameHotDataLock::release(GameHotDataLock::TYPE_GAME_RECORD, self::AUTO_CREATE_LOCK_KEY, $lock['token'], $lock['redis_lock']); } } private static function generatePeriodNo(): string { return GamePeriodNo::generateNext(); } private static function truthyConfigInput(mixed $v): bool { return $v === 1 || $v === '1' || $v === true; } private static function upsertConfig(string $key, string $value, string $valueType, string $remark, int $now): void { $exists = Db::name('game_config')->where('config_key', $key)->find(); if ($exists) { Db::name('game_config')->where('config_key', $key)->update([ 'config_value' => $value, 'value_type' => $valueType, 'remark' => $remark, 'update_time' => $now, ]); GameHotDataCoordinator::afterGameConfigKeyCommitted($key); return; } Db::name('game_config')->insert([ 'config_key' => $key, 'config_value' => $value, 'value_type' => $valueType, 'remark' => $remark, 'create_time' => $now, 'update_time' => $now, ]); GameHotDataCoordinator::afterGameConfigKeyCommitted($key); } }