feat: 彩票业务迁移并补全后台权限与代理结算体系

This commit is contained in:
2026-06-10 10:29:43 +08:00
parent bbdb69dabb
commit 1948b10fe6
108 changed files with 7083 additions and 5033 deletions

View File

@@ -9,6 +9,7 @@ use App\Lottery\DrawStatus;
use App\Models\DrawResultItem;
use App\Models\DrawResultBatch;
use App\Lottery\DrawResultBatchStatus;
use Illuminate\Support\Facades\Cache;
use App\Services\Jackpot\JackpotSummaryService;
use App\Services\LotterySettings;
@@ -19,6 +20,12 @@ use App\Services\LotterySettings;
*/
final class DrawHallSnapshotBuilder
{
private const JACKPOT_CACHE_TTL_SECONDS = 5;
private const RISK_ALERTS_CACHE_TTL_SECONDS = 2;
private const RESULT_ITEMS_CACHE_TTL_SECONDS = 5;
public function __construct(
private readonly JackpotSummaryService $jackpotSummary,
) {}
@@ -108,6 +115,20 @@ final class DrawHallSnapshotBuilder
DrawStatus::Settled->value,
DrawStatus::Cancelled->value,
])
->where(function ($q) use ($nowUtc): void {
$q->where('status', '!=', DrawStatus::Pending->value)
->orWhere(function ($q2) use ($nowUtc): void {
$q2->where('status', DrawStatus::Pending->value)
->where(function ($q3) use ($nowUtc): void {
$q3->whereNull('close_time')
->orWhere('close_time', '>', $nowUtc);
})
->where(function ($q3) use ($nowUtc): void {
$q3->whereNull('draw_time')
->orWhere('draw_time', '>', $nowUtc);
});
});
})
->where(function ($q) use ($nowUtc): void {
$q->where(function ($q2) use ($nowUtc): void {
$q2->whereNotNull('close_time')
@@ -258,56 +279,19 @@ final class DrawHallSnapshotBuilder
'cooling_end_time' => $target->cooling_end_time?->toIso8601String(),
'seconds_remaining_in_cooldown' => $coolingRemain,
'jackpot_currency_code' => $currencyCode,
'jackpot' => $this->jackpotSummary->summary($currencyCode),
'jackpot' => $this->cachedJackpotSummary($currencyCode),
];
$riskAlerts = RiskPool::query()
->where('draw_id', $target->id)
->where(function ($q): void {
$q->where('sold_out_status', 1)
->orWhereRaw('(locked_amount * 1.0 / NULLIF(total_cap_amount, 0)) >= 0.8');
})
->orderByRaw('(locked_amount * 1.0 / NULLIF(total_cap_amount, 0)) DESC')
->orderByDesc('locked_amount')
->orderBy('normalized_number')
->limit(500)
->get(['normalized_number', 'sold_out_status'])
->map(fn ($row) => [
'normalized_number' => (string) $row->normalized_number,
'status' => (int) $row->sold_out_status === 1 ? 'sold_out' : 'warning',
])
->values()
->all();
$payload['risk_pool_alerts'] = $riskAlerts;
$payload['risk_pool_alerts'] = $this->cachedRiskAlerts((int) $target->id);
if ($this->showsPublishedResults((string) $target->status)) {
$batchId = DrawResultBatch::query()
->where('draw_id', $target->id)
->where('result_version', (int) $target->current_result_version)
->where('status', DrawResultBatchStatus::Published->value)
->value('id');
$resultItems = $this->cachedPublishedResultItems(
(int) $target->id,
(int) $target->current_result_version,
);
if ($batchId !== null) {
$payload['result_items'] = DrawResultItem::query()
->where('result_batch_id', $batchId)
->orderBy('prize_type')
->orderBy('prize_index')
->get([
'prize_type', 'prize_index',
'number_4d', 'suffix_3d', 'suffix_2d', 'head_digit', 'tail_digit',
])
->map(fn ($row) => [
'prize_type' => $row->prize_type,
'prize_index' => (int) $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,
])
->values()
->all();
if ($resultItems !== null) {
$payload['result_items'] = $resultItems;
}
$payload['result_version'] = (int) $target->current_result_version;
@@ -327,4 +311,89 @@ final class DrawHallSnapshotBuilder
return LotterySettings::defaultCurrency();
}
/**
* @return array<string, mixed>
*/
private function cachedJackpotSummary(string $currencyCode): array
{
$cacheKey = sprintf('hall_snapshot:jackpot:%s', strtoupper($currencyCode));
/** @var array<string, mixed> */
return Cache::remember($cacheKey, self::JACKPOT_CACHE_TTL_SECONDS, fn (): array => $this->jackpotSummary->summary($currencyCode));
}
/**
* @return list<array{normalized_number: string, status: string}>
*/
private function cachedRiskAlerts(int $drawId): array
{
$cacheKey = sprintf('hall_snapshot:risk_alerts:%d', $drawId);
/** @var list<array{normalized_number: string, status: string}> */
return Cache::remember($cacheKey, self::RISK_ALERTS_CACHE_TTL_SECONDS, function () use ($drawId): array {
return RiskPool::query()
->where('draw_id', $drawId)
->where(function ($q): void {
$q->where('sold_out_status', 1)
->orWhereRaw('(locked_amount * 1.0 / NULLIF(total_cap_amount, 0)) >= 0.8');
})
->orderByRaw('(locked_amount * 1.0 / NULLIF(total_cap_amount, 0)) DESC')
->orderByDesc('locked_amount')
->orderBy('normalized_number')
->limit(500)
->get(['normalized_number', 'sold_out_status'])
->map(fn ($row) => [
'normalized_number' => (string) $row->normalized_number,
'status' => (int) $row->sold_out_status === 1 ? 'sold_out' : 'warning',
])
->values()
->all();
});
}
/**
* @return list<array<string, mixed>>|null
*/
private function cachedPublishedResultItems(int $drawId, int $resultVersion): ?array
{
if ($resultVersion <= 0) {
return null;
}
$cacheKey = sprintf('hall_snapshot:result_items:%d:%d', $drawId, $resultVersion);
/** @var list<array<string, mixed>>|null */
return Cache::remember($cacheKey, self::RESULT_ITEMS_CACHE_TTL_SECONDS, function () use ($drawId, $resultVersion): ?array {
$batchId = DrawResultBatch::query()
->where('draw_id', $drawId)
->where('result_version', $resultVersion)
->where('status', DrawResultBatchStatus::Published->value)
->value('id');
if ($batchId === null) {
return null;
}
return DrawResultItem::query()
->where('result_batch_id', $batchId)
->orderBy('prize_type')
->orderBy('prize_index')
->get([
'prize_type', 'prize_index',
'number_4d', 'suffix_3d', 'suffix_2d', 'head_digit', 'tail_digit',
])
->map(fn ($row) => [
'prize_type' => $row->prize_type,
'prize_index' => (int) $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,
])
->values()
->all();
});
}
}