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::isLiveRuntimeEnabled()) { return; } if (!self::getConfigBool(self::KEY_AUTO_CREATE)) { return; } if (self::hasActiveRecord()) { return; } try { self::createNextRecordRow(); } 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 { if (!self::isLiveRuntimeEnabled()) { return null; } if (self::hasActiveRecord()) { return null; } return self::createNextRecordRow(); } /** * 未配置键时视为开启(兼容旧库未跑迁移)。 */ public static function isLiveRuntimeEnabled(): bool { $row = GameHotDataRedis::gameConfigRow(self::KEY_LIVE_RUNTIME); if ($row === null) { return true; } $v = $row['config_value'] ?? ''; return $v === '1' || $v === 1; } public static function setLiveRuntimeEnabled(bool $enabled): void { $now = time(); $v = $enabled ? '1' : '0'; self::upsertConfig( self::KEY_LIVE_RUNTIME, $v, 'int', '后台「游戏实时对局」运行开关:0=维护(禁止下注、结束后不自动开新期,当局仍自动开奖并结算);1=运行', $now ); } /** * 重新开启游戏时:若无进行中/未结清对局,则立即创建新一期(与定时任务「无局时自动创建」语义一致,供开关打开时立刻开局)。 */ public static function bootstrapPeriodWhenRuntimeEnabled(): void { if (self::hasActiveRecord()) { return; } try { self::createNextRecordRow(); } catch (Throwable) { } } private static function createNextRecordRow(): string { $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; } private static function generatePeriodNo(): string { return date('Ymd-His') . '-' . substr(bin2hex(random_bytes(4)), 0, 8); } 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); } }