Files
webman-buildadmin/app/common/service/GameRecordService.php

228 lines
7.4 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace app\common\service;
use app\common\library\game\GamePeriodNo;
use support\think\Db;
use Throwable;
final class GameRecordService
{
public const KEY_AUTO_CREATE = 'period_auto_create_enabled';
public const KEY_MANUAL_CREATE = 'period_manual_create_enabled';
private const ACTIVE_STATUSES = [0, 1, 2, 3];
private const AUTO_CREATE_LOCK_KEY = 'auto-create-next-record';
public static function getConfigBool(string $key): bool
{
$row = GameHotDataRedis::gameConfigRow($key);
if (!$row) {
return false;
}
$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;
}
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);
}
}