Files
lotteryLaravel/app/Services/Draw/LotteryHallRealtimeBroadcaster.php
kang a9d0f39a9c feat: 增强开奖与设置控制器的币种支持功能
引入 CurrencyResolver,用于在 DrawCurrentController、DrawResultShowController 与 DrawResultsIndexController 中统一处理币种代码解析。
更新 DrawHallSnapshotBuilder 与 DrawResultViewService 的构建方法,新增币种代码参数支持,确保开奖相关功能中的币种处理一致性。
增强 SettingIndexController:新增允许访问的 KV 配置分组校验。
在 OddsStreamService、PlayConfigStreamService 与 RiskCapStreamService 中新增广播功能,用于在玩法目录变更时推送更新通知。
新增测试用例,验证风险限额发布的广播行为。
2026-05-27 09:57:39 +08:00

201 lines
5.9 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Services\Draw;
use App\Events\OddsUpdateBroadcast;
use App\Events\PlayCatalogUpdatedBroadcast;
use App\Events\PlayToggleBroadcast;
use App\Events\RiskSoldOutBroadcast;
use App\Events\RiskWarningBroadcast;
use App\Events\JackpotBurstBroadcast;
use App\Events\DrawCountdownBroadcast;
use App\Events\DrawStatusChangeBroadcast;
use App\Events\DrawResultPublishedBroadcast;
/**
* 对齐界面文档 §2.1:大厅公共频道广播(`lottery-hall`)。
* 包含draw.countdown、draw.status_change、result.published、
* risk.sold_out、risk.warning、play.toggle、odds.update、jackpot.burst
*/
final class LotteryHallRealtimeBroadcaster
{
public function __construct(
private readonly DrawHallSnapshotBuilder $snapshot,
) {}
/** 每秒调度:`draw.countdown` 推送大厅快照(与 GET draw/current 一致),避免仅本地倒计时无法切期。 */
public function countdownPulse(): void
{
if (! $this->driverSupportsRealtime()) {
return;
}
$ms = (int) floor(microtime(true) * 1000);
broadcast(new DrawCountdownBroadcast($this->snapshot->build(), $ms));
}
/**
* Tick 首尾对比:**数据库**当期指纹({@see DrawHallSnapshotBuilder::hallTargetFingerprint})变了再发,
* 载荷仍为 {@see DrawHallSnapshotBuilder::build()}(含未到 tick 时对 `open` 的展示规范化)。
*/
public function notifyStatusChangeIfHallDbChanged(?array $fpBefore, ?array $fpAfter, ?array $snapshotPayload): void
{
if (! $this->driverSupportsRealtime()) {
return;
}
if (($fpBefore['draw_no'] ?? null) === ($fpAfter['draw_no'] ?? null)
&& ($fpBefore['status'] ?? null) === ($fpAfter['status'] ?? null)) {
return;
}
$this->notifyStatusChange($snapshotPayload);
}
/** `draw.status_change`(管理端发布后等不与 tick 同路径时使用)。 */
public function notifyStatusChange(?array $data): void
{
if (! $this->driverSupportsRealtime()) {
return;
}
broadcast(new DrawStatusChangeBroadcast($data, (int) floor(microtime(true) * 1000)));
}
/** `result.published` */
public function notifyResultPublished(?array $data): void
{
if (! $this->driverSupportsRealtime()) {
return;
}
broadcast(new DrawResultPublishedBroadcast($data, (int) floor(microtime(true) * 1000)));
}
/** `risk.sold_out` —— 号码赔付池耗尽 */
public function notifyRiskSoldOut(int $drawId, string $drawNo, string $normalizedNumber): void
{
if (! $this->driverSupportsRealtime()) {
return;
}
broadcast(new RiskSoldOutBroadcast(
$drawId,
$drawNo,
$normalizedNumber,
(int) floor(microtime(true) * 1000),
));
}
/** `risk.warning` —— 号码赔付池占用超 80% 预警 */
public function notifyRiskWarning(int $drawId, string $drawNo, string $normalizedNumber, float $usageRatio): void
{
if (! $this->driverSupportsRealtime()) {
return;
}
broadcast(new RiskWarningBroadcast(
$drawId,
$drawNo,
$normalizedNumber,
$usageRatio,
(int) floor(microtime(true) * 1000),
));
}
/** `play.toggle` —— 玩法开关变更 */
public function notifyPlayToggle(string $playCode, bool $enabled, ?string $reason = null): void
{
if (! $this->driverSupportsRealtime()) {
return;
}
broadcast(new PlayToggleBroadcast(
$playCode,
$enabled,
$reason,
(int) floor(microtime(true) * 1000),
));
}
/**
* `play.catalog_updated` —— 玩法/赔率/封顶版本发布(全量目录变更)。
*
* @param string $module play_config|odds|risk_cap
*/
public function notifyPlayCatalogUpdated(
string $module,
int $versionId,
string $versionLabel,
?array $meta = null,
): void {
if (! $this->driverSupportsRealtime()) {
return;
}
broadcast(new PlayCatalogUpdatedBroadcast(
$module,
$versionId,
$versionLabel,
$meta,
(int) floor(microtime(true) * 1000),
));
}
/** `odds.update` —— 赔率变更 */
public function notifyOddsUpdate(int $versionId, string $versionName, ?array $diff = null): void
{
if (! $this->driverSupportsRealtime()) {
return;
}
broadcast(new OddsUpdateBroadcast(
$versionId,
$versionName,
$diff,
(int) floor(microtime(true) * 1000),
));
}
/** `jackpot.burst` —— Jackpot 爆池动画与浏览器通知 */
public function notifyJackpotBurst(
int $drawId,
string $drawNo,
string $firstPrizeNumber,
string $currencyCode,
int $totalPayoutAmount,
int $winnerCount,
string $triggerType,
int $poolAmountAfter,
): void {
if (! $this->driverSupportsRealtime()) {
return;
}
broadcast(new JackpotBurstBroadcast(
$drawId,
$drawNo,
$firstPrizeNumber,
strtoupper($currencyCode),
$totalPayoutAmount,
$winnerCount,
$triggerType,
$poolAmountAfter,
(int) floor(microtime(true) * 1000),
));
}
private function driverSupportsRealtime(): bool
{
$default = config('broadcasting.default');
if ($default === null || $default === 'null') {
return false;
}
$driver = config("broadcasting.connections.{$default}.driver") ?? $default;
return ! in_array($driver, ['null', 'log'], true);
}
}