getExpireMinutes(); if ($expireMinutes <= 0) { return false; } if (! in_array($round->status, ['pending', 'active'], true)) { return false; } if (! $round->started_at) { return false; } return $round->started_at->copy()->addMinutes($expireMinutes)->lte(now()); } /** * 方法功能:结算并结束已过期的回合,必要时发送超时公告。 */ public function expireRound(IdiomGameRound $round, bool $announce = true): bool { if (! $this->isRoundExpired($round)) { return false; } $round->loadMissing('idiom'); // 已过期的回合统一落为 ended,防止继续答题或阻塞新开题。 $round->update([ 'status' => 'ended', 'ended_at' => $round->ended_at ?? now(), ]); if ($announce) { $this->pushExpiredRoundMessage($round); } return true; } /** * 方法功能:批量清理指定房间内已超时但仍处于进行中的回合。 */ public function expireActiveRoundsForRoom(int $roomId, bool $announce = true): int { $expiredCount = 0; IdiomGameRound::with('idiom') ->where('room_id', $roomId) ->whereIn('status', ['pending', 'active']) ->orderBy('id') ->get() ->each(function (IdiomGameRound $round) use ($announce, &$expiredCount): void { if ($this->expireRound($round, $announce)) { $expiredCount++; } }); return $expiredCount; } /** * 方法功能:向公屏推送猜成语超时公告。 */ public function pushExpiredRoundMessage(IdiomGameRound $round): void { $answer = $round->idiom?->answer ?? '未知答案'; $message = [ 'id' => $this->chatState->nextMessageId($round->room_id), 'room_id' => $round->room_id, 'from_user' => '星海小博士', 'to_user' => '大家', 'content' => "⌛ 本轮猜成语已超时结束,正确答案是「{$answer}」。", 'is_secret' => false, 'font_color' => '#f59e0b', 'action' => '', 'idiom_game_round_ended_id' => $round->id, 'sent_at' => now()->toDateTimeString(), ]; $this->chatState->pushMessage($round->room_id, $message); broadcast(new MessageSent($round->room_id, $message)); } }