优化游戏实时对局页面样式

This commit is contained in:
2026-04-28 14:54:59 +08:00
parent d7375222ce
commit aefb8b16c8
8 changed files with 381 additions and 153 deletions

View File

@@ -86,7 +86,6 @@ class Record extends Backend
$rows = Db::name('game_record')
->where('status', 5)
->whereLike('void_reason', 'system_recover:%')
->field(['id', 'period_no', 'void_reason', 'update_time'])
->order('id', 'desc')
->limit($limit)
@@ -96,6 +95,8 @@ class Record extends Backend
$list = [];
foreach ($rows as $row) {
$meta = $this->parseRecoverVoidReason(is_string($row['void_reason'] ?? null) ? $row['void_reason'] : '');
$reason = is_string($row['void_reason'] ?? null) ? $row['void_reason'] : '';
$isAutoRecover = $this->isSystemRecoverReason($reason);
$list[] = [
'id' => (int) ($row['id'] ?? 0),
'period_no' => (string) ($row['period_no'] ?? ''),
@@ -104,7 +105,8 @@ class Record extends Backend
'refunded_order_count' => $meta['orders'],
'refunded_total_amount' => $meta['amount'],
'recovered_at' => (int) ($row['update_time'] ?? 0),
'void_reason' => (string) ($row['void_reason'] ?? ''),
'void_reason' => $reason,
'is_auto_recover' => $isAutoRecover ? 1 : 0,
];
}
@@ -125,10 +127,13 @@ class Record extends Backend
'orders' => 0,
'amount' => '0.00',
];
if ($reason === '' || str_starts_with($reason, 'system_recover:') === false) {
if (!$this->isSystemRecoverReason($reason)) {
return $meta;
}
$payload = substr($reason, strlen('system_recover:'));
if (!str_contains($payload, '=')) {
return $meta;
}
$parts = explode('|', $payload);
foreach ($parts as $part) {
$item = trim($part);
@@ -156,4 +161,9 @@ class Record extends Backend
}
return $meta;
}
private function isSystemRecoverReason(string $reason): bool
{
return $reason !== '' && str_starts_with($reason, 'system_recover:');
}
}

View File

@@ -52,23 +52,102 @@ final class GameLiveService
if ($recordId <= 0) {
return;
}
$status = (int) ($row['status'] ?? 0);
$resultNumber = isset($row['result_number']) ? (int) $row['result_number'] : 0;
if ($resultNumber > 0 && in_array($status, [0, 1, 2, 3], true)) {
self::recoverPayoutForRecordOnStartup($recordId);
return;
}
$periodStartAt = (int) ($row['period_start_at'] ?? 0);
if ($periodStartAt <= 0) {
return;
}
$periodSeconds = self::getConfigInt(self::KEY_PERIOD_SECONDS, 30);
$timeoutAt = $periodStartAt + $periodSeconds + self::PAYOUT_GRACE_SECONDS + self::STARTUP_RECOVER_GRACE_SECONDS;
if (time() <= $timeoutAt) {
return;
}
self::markAbnormalAndRefundOnStartup($recordId, $status);
}
$status = (int) ($row['status'] ?? 0);
private static function recoverPayoutForRecordOnStartup(int $recordId): void
{
$lock = GameHotDataLock::tryAcquireWithWait(GameHotDataLock::TYPE_GAME_RECORD, (string) $recordId, 3000);
if (!$lock['acquired']) {
return;
}
try {
$row = Db::name('game_record')->where('id', $recordId)->find();
if (!$row) {
return;
}
$status = (int) ($row['status'] ?? 0);
if (!in_array($status, [0, 1, 2, 3], true)) {
return;
}
$resultNumber = isset($row['result_number']) ? (int) $row['result_number'] : 0;
if ($resultNumber <= 0) {
return;
}
$now = time();
$payoutUntil = isset($row['payout_until']) ? (int) $row['payout_until'] : 0;
Db::startTrans();
try {
GameBetSettleService::settleBetsForDraw($recordId, $resultNumber);
if ($status === 2) {
if ($payoutUntil <= 0) {
$payoutUntil = $now + self::PAYOUT_GRACE_SECONDS;
}
Db::name('game_record')->where('id', $recordId)->update([
'status' => 3,
'payout_until' => $payoutUntil,
'update_time' => $now,
]);
} elseif ($status === 3) {
if ($payoutUntil <= 0) {
$payoutUntil = $now;
Db::name('game_record')->where('id', $recordId)->update([
'payout_until' => $payoutUntil,
'update_time' => $now,
]);
}
} else {
$payoutUntil = $now;
Db::name('game_record')->where('id', $recordId)->update([
'status' => 3,
'payout_until' => $payoutUntil,
'update_time' => $now,
]);
}
Db::commit();
} catch (Throwable $e) {
Db::rollback();
Log::warning('game live startup payout recover failed', [
'record_id' => $recordId,
'error' => $e->getMessage(),
]);
return;
}
GameHotDataCoordinator::afterGameRecordCommitted($recordId);
self::publishSnapshot(null);
if ($payoutUntil <= $now) {
self::finalizePayoutForRecordLocked($recordId);
}
} finally {
GameHotDataLock::release(GameHotDataLock::TYPE_GAME_RECORD, (string) $recordId, $lock['token'], $lock['redis_lock']);
}
}
private static function markAbnormalAndRefundOnStartup(int $recordId, int $status): void
{
$lock = GameHotDataLock::tryAcquireWithWait(GameHotDataLock::TYPE_GAME_RECORD, (string) $recordId, 3000);
if (!$lock['acquired']) {
return;
}
try {
$fresh = Db::name('game_record')->where('id', $recordId)->find();
if (!$fresh) {
@@ -78,12 +157,8 @@ final class GameLiveService
if (!in_array($freshStatus, [0, 1, 2, 3], true)) {
return;
}
$freshPeriodStartAt = (int) ($fresh['period_start_at'] ?? 0);
if ($freshPeriodStartAt <= 0) {
return;
}
$freshTimeoutAt = $freshPeriodStartAt + $periodSeconds + self::PAYOUT_GRACE_SECONDS + self::STARTUP_RECOVER_GRACE_SECONDS;
if (time() <= $freshTimeoutAt) {
$freshResultNumber = isset($fresh['result_number']) ? (int) $fresh['result_number'] : 0;
if ($freshResultNumber > 0) {
return;
}
@@ -92,13 +167,12 @@ final class GameLiveService
Db::startTrans();
try {
$refund = self::refundPendingBetsSummaryForPeriodLocked($recordId, $now);
$oldStatus = $freshStatus;
$refundedUserCount = count($refund['user_ids']);
$refundedOrderCount = (int) ($refund['order_count'] ?? 0);
$refundedTotalAmount = is_string($refund['total_amount'] ?? null) ? $refund['total_amount'] : '0.00';
$reason = sprintf(
'system_recover:from=%d|users=%d|orders=%d|amount=%s',
$oldStatus,
$freshStatus,
$refundedUserCount,
$refundedOrderCount,
$refundedTotalAmount
@@ -114,8 +188,9 @@ final class GameLiveService
Db::commit();
} catch (Throwable $e) {
Db::rollback();
Log::warning('game live startup recover failed', [
Log::warning('game live startup abnormal recover failed', [
'record_id' => $recordId,
'status' => $status,
'error' => $e->getMessage(),
]);
return;
@@ -129,7 +204,7 @@ final class GameLiveService
}
GameRecordService::bootstrapPeriodWhenRuntimeEnabled();
self::publishSnapshot(null);
Log::info('game live startup recovered abnormal period', [
Log::info('game live startup marked abnormal and refunded', [
'record_id' => $recordId,
'old_status' => $freshStatus,
'refunded_user_count' => count($refund['user_ids']),
@@ -141,6 +216,34 @@ final class GameLiveService
}
}
private static function finalizePayoutForRecordLocked(int $recordId): void
{
$now = time();
Db::startTrans();
try {
Db::name('game_record')->where('id', $recordId)->where('status', 3)->update([
'status' => 4,
'payout_until' => null,
'update_time' => $now,
]);
GameRecordService::createNextRecordAfterDraw();
Db::commit();
} catch (Throwable $e) {
Db::rollback();
Log::warning('game live startup finalize payout failed', [
'record_id' => $recordId,
'error' => $e->getMessage(),
]);
return;
}
GameHotDataCoordinator::afterGameRecordCommitted($recordId);
try {
GameRecordStatService::refreshForRecordId($recordId);
} catch (Throwable) {
}
self::publishSnapshot(null);
}
public static function buildSnapshot(?int $recordId = null): array
{
$record = self::resolveRecord($recordId);
@@ -170,9 +273,12 @@ final class GameLiveService
}
$bets = Db::name('bet_order')
->where('period_id', $rid)
->order('id', 'desc')
->alias('bo')
->leftJoin('user gu', 'gu.id = bo.user_id')
->where('bo.period_id', $rid)
->order('bo.id', 'desc')
->limit(200)
->field('bo.id,bo.user_id,bo.period_no,bo.pick_numbers,bo.total_amount,bo.streak_at_bet,bo.create_time,gu.username as user_username')
->select()
->toArray();
@@ -218,6 +324,7 @@ final class GameLiveService
return [
'id' => (int) $row['id'],
'user_id' => (int) $row['user_id'],
'username' => isset($row['user_username']) && is_string($row['user_username']) ? $row['user_username'] : '',
'period_no' => (string) $row['period_no'],
'pick_numbers' => $row['pick_numbers'],
'total_amount' => (string) $row['total_amount'],