feat: 添加 Laravel Reverb 支持,更新 .env.example 文件以配置 WebSocket,增强彩票调度功能,更新 API 路由以支持期号管理与结果发布
This commit is contained in:
116
app/Services/Draw/DrawRngRunner.php
Normal file
116
app/Services/Draw/DrawRngRunner.php
Normal file
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Draw;
|
||||
|
||||
use App\Lottery\DrawResultBatchStatus;
|
||||
use App\Lottery\DrawResultSourceType;
|
||||
use App\Lottery\DrawStatus;
|
||||
use App\Models\Draw;
|
||||
use App\Models\DrawResultBatch;
|
||||
use App\Models\DrawResultItem;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* 按配置执行 RNG,写入 {@see DrawResultBatch} / {@see DrawResultItem}。
|
||||
*/
|
||||
final class DrawRngRunner
|
||||
{
|
||||
public function __construct(
|
||||
private readonly DrawPublishService $publisher,
|
||||
) {}
|
||||
|
||||
/** 已对单期加锁外层调用时使用 */
|
||||
public function executeLocked(Draw $draw): DrawResultBatch
|
||||
{
|
||||
$draw->forceFill([
|
||||
'status' => DrawStatus::Drawing->value,
|
||||
])->save();
|
||||
|
||||
$manualReview = (bool) config('lottery.draw.require_manual_review', false);
|
||||
$seedMaterial = bin2hex(random_bytes(32));
|
||||
$rngSeedHash = hash('sha256', $seedMaterial);
|
||||
|
||||
$nextVersion = max(1, (int) $draw->current_result_version + 1);
|
||||
|
||||
$batch = DrawResultBatch::query()->create([
|
||||
'draw_id' => $draw->id,
|
||||
'result_version' => $nextVersion,
|
||||
'source_type' => DrawResultSourceType::Rng->value,
|
||||
'rng_seed_hash' => $rngSeedHash,
|
||||
'raw_seed_encrypted' => null,
|
||||
'status' => $manualReview ? DrawResultBatchStatus::PendingReview->value : DrawResultBatchStatus::Published->value,
|
||||
'created_by' => null,
|
||||
'confirmed_by' => null,
|
||||
'confirmed_at' => $manualReview ? null : now(),
|
||||
]);
|
||||
|
||||
foreach (DrawPrizeLayout::slots() as $slot) {
|
||||
$num = str_pad((string) random_int(0, 9999), 4, '0', STR_PAD_LEFT);
|
||||
$suffix3 = substr($num, -3);
|
||||
$suffix2 = substr($num, -2);
|
||||
|
||||
DrawResultItem::query()->create([
|
||||
'draw_id' => $draw->id,
|
||||
'result_batch_id' => $batch->id,
|
||||
'prize_type' => $slot['prize_type'],
|
||||
'prize_index' => $slot['prize_index'],
|
||||
'number_4d' => $num,
|
||||
'suffix_3d' => $suffix3,
|
||||
'suffix_2d' => $suffix2,
|
||||
'head_digit' => $num !== '' ? (int) substr($num, 0, 1) : null,
|
||||
'tail_digit' => $num !== '' ? (int) substr($num, 3, 1) : null,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($manualReview) {
|
||||
$draw->forceFill([
|
||||
'status' => DrawStatus::Review->value,
|
||||
'result_source' => DrawResultSourceType::Rng->value,
|
||||
])->save();
|
||||
} else {
|
||||
$this->publisher->markPublishedInTransaction($draw->fresh(), $batch->fresh());
|
||||
}
|
||||
|
||||
return $batch->fresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{rung: int, errors: array<int, string>}
|
||||
*/
|
||||
public function runDue(?Carbon $now = null): array
|
||||
{
|
||||
$nowUtc = ($now ?? Carbon::now())->utc();
|
||||
$rung = 0;
|
||||
$errors = [];
|
||||
|
||||
$ids = Draw::query()
|
||||
->where('status', DrawStatus::Closed->value)
|
||||
->whereNotNull('draw_time')
|
||||
->where('draw_time', '<=', $nowUtc)
|
||||
->whereDoesntHave('resultBatches')
|
||||
->orderBy('draw_time')
|
||||
->pluck('id');
|
||||
|
||||
foreach ($ids as $drawId) {
|
||||
try {
|
||||
DB::transaction(function () use ($drawId, &$rung): void {
|
||||
/** @var Draw|null $locked */
|
||||
$locked = Draw::query()->whereKey($drawId)->lockForUpdate()->first();
|
||||
if ($locked === null || $locked->status !== DrawStatus::Closed->value) {
|
||||
return;
|
||||
}
|
||||
if ($locked->resultBatches()->exists()) {
|
||||
return;
|
||||
}
|
||||
$this->executeLocked($locked);
|
||||
$rung++;
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
$errors[] = (string) $drawId.': '.$e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
return ['rung' => $rung, 'errors' => $errors];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user