whereKey($batch->id)->lockForUpdate()->firstOrFail(); if ($lockedBatch->status !== DrawResultBatchStatus::PendingReview->value) { throw new \RuntimeException('batch_not_pending_review'); } /** @var Draw $draw */ $draw = Draw::query()->whereKey($lockedBatch->draw_id)->lockForUpdate()->firstOrFail(); $lockedBatch->forceFill([ 'status' => DrawResultBatchStatus::Published->value, 'confirmed_by' => $admin->id, 'confirmed_at' => now(), ])->save(); return $this->applyPublishedToDraw($draw, $lockedBatch); }); $data = $this->snapshot->build(); $this->hallRealtime->notifyResultPublished($data); $this->hallRealtime->notifyStatusChange($data); return $draw; } /** RNG 自动生成且无需审核时在同一事务调用 */ public function markPublishedInTransaction(Draw $draw, DrawResultBatch $batch): Draw { $batch->forceFill([ 'status' => DrawResultBatchStatus::Published->value, 'confirmed_by' => null, 'confirmed_at' => now(), ])->save(); $draw = $this->applyPublishedToDraw($draw, $batch); DB::afterCommit(function (): void { $data = app(DrawHallSnapshotBuilder::class)->build(); app(LotteryHallRealtimeBroadcaster::class)->notifyResultPublished($data); }); return $draw; } private function applyPublishedToDraw(Draw $draw, DrawResultBatch $batch): Draw { $cooldownMinutes = max(0, (int) LotterySettings::get( 'draw.cooldown_minutes', (int) config('lottery.draw.cooldown_minutes', 15), )); if ($cooldownMinutes > 0) { $draw->forceFill([ 'status' => DrawStatus::Cooldown->value, 'current_result_version' => (int) $batch->result_version, 'result_source' => $batch->source_type, 'cooling_end_time' => now()->addMinutes($cooldownMinutes), ])->save(); } else { $draw->forceFill([ 'status' => DrawStatus::Settling->value, 'current_result_version' => (int) $batch->result_version, 'result_source' => $batch->source_type, 'cooling_end_time' => null, ])->save(); } return $draw->refresh(); } }