- 将玩法相关的显示名称字段统一为 `display_name`,移除多语言字段。 - 在 `PlayTypePatchController` 中新增即时切换玩法开关的功能,并推送大厅更新。 - 优化多个控制器和服务中的权限检查与数据处理逻辑,提升代码可读性与维护性。
126 lines
4.3 KiB
PHP
126 lines
4.3 KiB
PHP
<?php
|
||
|
||
namespace App\Services\Draw;
|
||
|
||
use Carbon\Carbon;
|
||
use App\Models\Draw;
|
||
use App\Lottery\DrawStatus;
|
||
use App\Models\DrawResultItem;
|
||
use App\Models\DrawResultBatch;
|
||
use Illuminate\Support\Facades\DB;
|
||
use App\Services\LotterySettings;
|
||
use App\Lottery\DrawResultSourceType;
|
||
use App\Lottery\DrawResultBatchStatus;
|
||
|
||
/**
|
||
* 按配置执行 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) LotterySettings::get(
|
||
'draw.require_manual_review',
|
||
(bool) config('lottery.draw.require_manual_review', false),
|
||
);
|
||
$seedHex = DrawRngSeedDerivation::generateSeedHex();
|
||
$rngSeedHash = DrawRngSeedDerivation::hashSeedHex($seedHex);
|
||
$rawSeedEncrypted = DrawRngSeedDerivation::encryptSeedHex($seedHex);
|
||
$derivedRows = DrawRngSeedDerivation::deriveAllSlotRows($seedHex, (int) $draw->id);
|
||
|
||
$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' => $rawSeedEncrypted,
|
||
'status' => $manualReview ? DrawResultBatchStatus::PendingReview->value : DrawResultBatchStatus::Published->value,
|
||
'created_by' => null,
|
||
'confirmed_by' => null,
|
||
'confirmed_at' => $manualReview ? null : now(),
|
||
]);
|
||
|
||
foreach ($derivedRows as $row) {
|
||
DrawResultItem::query()->create([
|
||
'draw_id' => $draw->id,
|
||
'result_batch_id' => $batch->id,
|
||
'prize_type' => $row['prize_type'],
|
||
'prize_index' => $row['prize_index'],
|
||
'number_4d' => $row['number_4d'],
|
||
'suffix_3d' => $row['suffix_3d'],
|
||
'suffix_2d' => $row['suffix_2d'],
|
||
'head_digit' => $row['head_digit'],
|
||
'tail_digit' => $row['tail_digit'],
|
||
]);
|
||
}
|
||
|
||
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)
|
||
->where('settle_version', 0)
|
||
->where(function ($q): void {
|
||
$q->where('is_reopened', true)
|
||
->orWhereDoesntHave('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 ((int) $locked->settle_version > 0) {
|
||
return;
|
||
}
|
||
if (! (bool) $locked->is_reopened && $locked->resultBatches()->exists()) {
|
||
return;
|
||
}
|
||
$this->executeLocked($locked);
|
||
$rung++;
|
||
});
|
||
} catch (\Throwable $e) {
|
||
$errors[] = (string) $drawId.': '.$e->getMessage();
|
||
}
|
||
}
|
||
|
||
return ['rung' => $rung, 'errors' => $errors];
|
||
}
|
||
}
|