1.对局新增查看异常订单

This commit is contained in:
2026-04-24 17:22:38 +08:00
parent d9b574676b
commit 5ab9172b31
6 changed files with 325 additions and 3 deletions

View File

@@ -8,6 +8,7 @@ use app\common\library\game\StreakWinReward;
use app\common\model\UserWalletRecord;
use app\common\service\GameHotDataCoordinator;
use app\common\service\GameHotDataLock;
use support\Log;
use support\think\Db;
use Throwable;
@@ -31,6 +32,114 @@ final class GameLiveService
/** 开奖后派彩展示宽限期(秒),之后再创建下一期 */
private const PAYOUT_GRACE_SECONDS = 3;
/** 启动自愈:判定“异常卡局”的最小超时冗余秒数 */
private const STARTUP_RECOVER_GRACE_SECONDS = 10;
/**
* 服务重启后自动巡检上一局:若长时间卡在进行中状态,则自动作废并退款待开奖注单。
*/
public static function recoverAbnormalPeriodOnStartup(): void
{
$row = Db::name('game_record')
->whereIn('status', [0, 1, 2, 3])
->order('id', 'desc')
->find();
if (!$row) {
return;
}
$recordId = (int) ($row['id'] ?? 0);
if ($recordId <= 0) {
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;
}
$status = (int) ($row['status'] ?? 0);
$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) {
return;
}
$freshStatus = (int) ($fresh['status'] ?? 0);
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) {
return;
}
$now = time();
$refund = ['user_ids' => [], 'order_count' => 0, 'total_amount' => '0.00'];
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,
$refundedUserCount,
$refundedOrderCount,
$refundedTotalAmount
);
Db::name('game_record')->where('id', $recordId)->update([
'status' => 5,
'void_reason' => $reason,
'pending_draw_number' => null,
'payout_until' => null,
'ai_locked_number' => null,
'update_time' => $now,
]);
Db::commit();
} catch (Throwable $e) {
Db::rollback();
Log::warning('game live startup recover failed', [
'record_id' => $recordId,
'error' => $e->getMessage(),
]);
return;
}
GameHotDataCoordinator::afterGameRecordCommitted($recordId);
foreach ($refund['user_ids'] as $uid) {
if ($uid > 0) {
GameHotDataCoordinator::afterUserCommitted($uid);
}
}
GameRecordService::bootstrapPeriodWhenRuntimeEnabled();
self::publishSnapshot(null);
Log::info('game live startup recovered abnormal period', [
'record_id' => $recordId,
'old_status' => $freshStatus,
'refunded_user_count' => count($refund['user_ids']),
'refunded_order_count' => (int) ($refund['order_count'] ?? 0),
'refunded_total_amount' => is_string($refund['total_amount'] ?? null) ? $refund['total_amount'] : '0.00',
]);
} finally {
GameHotDataLock::release(GameHotDataLock::TYPE_GAME_RECORD, (string) $recordId, $lock['token'], $lock['redis_lock']);
}
}
public static function buildSnapshot(?int $recordId = null): array
{
@@ -519,7 +628,8 @@ final class GameLiveService
$now = time();
Db::startTrans();
try {
$refundedUserIds = self::refundPendingBetsForPeriodLocked($rid, $now);
$refund = self::refundPendingBetsSummaryForPeriodLocked($rid, $now);
$refundedUserIds = $refund['user_ids'];
Db::name('game_record')->where('id', $rid)->update([
'status' => 5,
'void_reason' => $reason,
@@ -557,8 +667,19 @@ final class GameLiveService
* @return list<int>
*/
private static function refundPendingBetsForPeriodLocked(int $periodId, int $now): array
{
$summary = self::refundPendingBetsSummaryForPeriodLocked($periodId, $now);
return $summary['user_ids'];
}
/**
* @return array{user_ids:list<int>,order_count:int,total_amount:string}
*/
private static function refundPendingBetsSummaryForPeriodLocked(int $periodId, int $now): array
{
$userIdSet = [];
$orderCount = 0;
$totalAmount = '0.00';
$bets = Db::name('bet_order')
->where('period_id', $periodId)
->where('status', 1)
@@ -614,6 +735,8 @@ final class GameLiveService
'create_time' => $now,
]);
$userIdSet[$userId] = true;
$orderCount++;
$totalAmount = bcadd($totalAmount, $total, 2);
}
$out = [];
@@ -621,7 +744,11 @@ final class GameLiveService
$out[] = (int) $uid;
}
return $out;
return [
'user_ids' => $out,
'order_count' => $orderCount,
'total_amount' => $totalAmount,
];
}
public static function publishSnapshot(?int $recordId = null): void