feat: 添加 Laravel Reverb 支持,更新 .env.example 文件以配置 WebSocket,增强彩票调度功能,更新 API 路由以支持期号管理与结果发布

This commit is contained in:
2026-05-09 17:40:49 +08:00
parent 781cf10928
commit aeaf124096
42 changed files with 3886 additions and 5 deletions

View File

@@ -0,0 +1,169 @@
<?php
namespace App\Services\Draw;
use App\Lottery\DrawResultBatchStatus;
use App\Lottery\DrawStatus;
use App\Models\Draw;
use App\Models\DrawResultBatch;
use App\Models\DrawResultItem;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
/**
* 将已发布的 {@see DrawResultItem} 聚合成前端/文档约定结构。
*/
final class DrawResultViewService
{
/**
* `docs/01-产品文档` GET /api/v1/results 示例键名对齐1st/2nd/3rd/starter/consolation
*
* @return array{
* 1st: string,
* 2nd: string,
* 3rd: string,
* starter: array<int, string>,
* consolation: array<int, string>
* }
*/
public function numbersFromItems(Collection $items): array
{
$byType = [
'first' => [],
'second' => [],
'third' => [],
'starter' => [],
'consolation' => [],
];
foreach ($items->sortBy(['prize_type', 'prize_index']) as $row) {
/** @var DrawResultItem $row */
$t = (string) $row->prize_type;
if (! isset($byType[$t])) {
continue;
}
$byType[$t][] = (string) $row->number_4d;
}
return [
'1st' => $byType['first'][0] ?? '',
'2nd' => $byType['second'][0] ?? '',
'3rd' => $byType['third'][0] ?? '',
'starter' => array_values($byType['starter']),
'consolation' => array_values($byType['consolation']),
];
}
/**
* 返回 null 若该期尚未有可展示的开奖采纳版本。
*
* @return array<string, mixed>|null
*/
public function summarizeDraw(Draw $draw): ?array
{
$version = (int) $draw->current_result_version;
if ($version < 1) {
return null;
}
$batch = DrawResultBatch::query()
->where('draw_id', $draw->id)
->where('result_version', $version)
->where('status', DrawResultBatchStatus::Published->value)
->first();
if ($batch === null) {
return null;
}
$items = DrawResultItem::query()
->where('result_batch_id', $batch->id)
->orderBy('prize_type')
->orderBy('prize_index')
->get([
'prize_type', 'prize_index', 'number_4d',
'suffix_3d', 'suffix_2d', 'head_digit', 'tail_digit',
]);
if ($items->isEmpty()) {
return null;
}
$numbers = $this->numbersFromItems($items);
return [
'draw_id' => $draw->draw_no,
'draw_no' => $draw->draw_no,
'business_date' => $draw->business_date?->format('Y-m-d') ?? (string) $draw->business_date,
'draw_time' => $draw->draw_time?->format('Y-m-d H:i:s'),
'draw_time_iso' => $draw->draw_time?->toIso8601String(),
'result_version' => $version,
'result_source' => $draw->result_source,
'results' => $numbers,
'result_items' => $items->map(fn (DrawResultItem $r) => [
'prize_type' => $r->prize_type,
'prize_index' => (int) $r->prize_index,
'number_4d' => $r->number_4d,
'suffix_3d' => $r->suffix_3d,
'suffix_2d' => $r->suffix_2d,
'head_digit' => $r->head_digit !== null ? (int) $r->head_digit : null,
'tail_digit' => $r->tail_digit !== null ? (int) $r->tail_digit : null,
])->values()->all(),
];
}
/**
* @param LengthAwarePaginator<int, Draw> $paginator
*/
public function decoratePaginator(LengthAwarePaginator $paginator): LengthAwarePaginator
{
$collection = $paginator->getCollection()->map(function (Draw $draw): ?array {
return $this->summarizeDraw($draw);
})->filter();
$paginator->setCollection($collection->values());
return $paginator;
}
/** 已发布开奖结果的可查询状态(对外展示往期)。 */
public static function publishedDrawStatuses(): array
{
return [
DrawStatus::Cooldown->value,
DrawStatus::Settling->value,
DrawStatus::Settled->value,
];
}
public function neighborsIsoTime(Draw $draw): array
{
$statuses = self::publishedDrawStatuses();
$t = $draw->draw_time;
$prevNo = null;
$nextNo = null;
if ($t !== null) {
$prevNo = Draw::query()
->whereIn('status', $statuses)
->where('current_result_version', '>', 0)
->whereNotNull('draw_time')
->where('draw_time', '<', $t)
->orderByDesc('draw_time')
->value('draw_no');
$nextNo = Draw::query()
->whereIn('status', $statuses)
->where('current_result_version', '>', 0)
->whereNotNull('draw_time')
->where('draw_time', '>', $t)
->orderBy('draw_time')
->value('draw_no');
}
return [
'previous_draw_no' => $prevNo,
'next_draw_no' => $nextNo,
];
}
}