fix(lottery): 增加广播任务时效保护并强化命令异常处理
- 为多个广播事件添加 retryUntil 方法,设置派发后 30 秒内必须执行,否则丢弃 - 修改 LotteryDrawTickCommand handle,添加异常捕获及错误日志,保证失败时返回 FAILURE - 修改 LotteryHallCountdownCommand handle,添加异常捕获及错误日志,超时情况写入警告日志 - 针对 SettlementOrchestrator 和 DrawHallSnapshotBuilder 查询结果添加限制,防止数据量过大 - 调整计划任务 withoutOverlapping 调用,增加 expiresAt 参数避免任务锁死 - 更新 .env.example,完善本地开发与生产环境部署说明及日志配置说明 - 移除 e2e 相关测试代码、配置及依赖,精简项目体积
This commit is contained in:
@@ -13,20 +13,26 @@ final class LotteryDrawTickCommand extends Command
|
||||
|
||||
public function handle(DrawTickService $tickService): int
|
||||
{
|
||||
$report = $tickService->tick();
|
||||
try {
|
||||
$report = $tickService->tick();
|
||||
|
||||
$statusSum = array_sum($report['status_updates'] ?? []);
|
||||
$this->info(sprintf(
|
||||
'Status rows updated: %d | RNG runs: %d | Planned draws created: %d',
|
||||
$statusSum,
|
||||
$report['rng_rung'],
|
||||
$report['planned']['created'] ?? 0,
|
||||
));
|
||||
$statusSum = array_sum($report['status_updates'] ?? []);
|
||||
$this->info(sprintf(
|
||||
'Status rows updated: %d | RNG runs: %d | Planned draws created: %d',
|
||||
$statusSum,
|
||||
$report['rng_rung'],
|
||||
$report['planned']['created'] ?? 0,
|
||||
));
|
||||
|
||||
foreach ($report['rng_errors'] as $err) {
|
||||
$this->warn($err);
|
||||
foreach ($report['rng_errors'] as $err) {
|
||||
$this->warn($err);
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
} catch (\Throwable $e) {
|
||||
report($e);
|
||||
$this->error('lottery:draw-tick failed: '.$e->getMessage());
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,17 +14,22 @@ final class LotteryHallCountdownCommand extends Command
|
||||
|
||||
public function handle(LotteryHallRealtimeBroadcaster $broadcaster): int
|
||||
{
|
||||
$startedAt = hrtime(true);
|
||||
$broadcaster->countdownPulse();
|
||||
$elapsedMs = (int) round((hrtime(true) - $startedAt) / 1_000_000);
|
||||
try {
|
||||
$startedAt = hrtime(true);
|
||||
$broadcaster->countdownPulse();
|
||||
$elapsedMs = (int) round((hrtime(true) - $startedAt) / 1_000_000);
|
||||
|
||||
if ($elapsedMs >= (int) config('lottery.realtime_hall_countdown_warn_threshold_ms', 800)) {
|
||||
Log::warning('lottery:hall-countdown exceeded warn threshold', [
|
||||
'elapsed_ms' => $elapsedMs,
|
||||
'threshold_ms' => (int) config('lottery.realtime_hall_countdown_warn_threshold_ms', 800),
|
||||
]);
|
||||
if ($elapsedMs >= (int) config('lottery.realtime_hall_countdown_warn_threshold_ms', 800)) {
|
||||
Log::warning('lottery:hall-countdown exceeded warn threshold', [
|
||||
'elapsed_ms' => $elapsedMs,
|
||||
'threshold_ms' => (int) config('lottery.realtime_hall_countdown_warn_threshold_ms', 800),
|
||||
]);
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
} catch (\Throwable $e) {
|
||||
report($e);
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,12 @@ final class BalanceUpdateBroadcast implements ShouldBroadcast
|
||||
/** 单任务超时 10 秒 */
|
||||
public int $timeout = 10;
|
||||
|
||||
/** 广播时效性保护:派发后 30 秒内必须开始执行,否则丢弃 */
|
||||
public function retryUntil(): \DateTimeInterface
|
||||
{
|
||||
return now()->addSeconds(30);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $playerId 玩家 ID(用于频道隔离)
|
||||
* @param string $currencyCode 币种代码
|
||||
|
||||
@@ -22,6 +22,12 @@ final class DrawResultPublishedBroadcast implements ShouldBroadcast
|
||||
/** 单任务超时 10 秒 */
|
||||
public int $timeout = 10;
|
||||
|
||||
/** 广播时效性保护:派发后 30 秒内必须开始执行,否则丢弃 */
|
||||
public function retryUntil(): \DateTimeInterface
|
||||
{
|
||||
return now()->addSeconds(30);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed>|null $data 与 GET draw/current 的 data 相同(含 result_items)
|
||||
*/
|
||||
|
||||
@@ -22,6 +22,12 @@ final class DrawStatusChangeBroadcast implements ShouldBroadcast
|
||||
/** 单任务超时 10 秒 */
|
||||
public int $timeout = 10;
|
||||
|
||||
/** 广播时效性保护:派发后 30 秒内必须开始执行,否则丢弃 */
|
||||
public function retryUntil(): \DateTimeInterface
|
||||
{
|
||||
return now()->addSeconds(30);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed>|null $data 与 GET draw/current 的 data 相同
|
||||
*/
|
||||
|
||||
@@ -22,6 +22,12 @@ final class JackpotBurstBroadcast implements ShouldBroadcast
|
||||
/** 单任务超时 10 秒 */
|
||||
public int $timeout = 10;
|
||||
|
||||
/** 广播时效性保护:派发后 30 秒内必须开始执行,否则丢弃 */
|
||||
public function retryUntil(): \DateTimeInterface
|
||||
{
|
||||
return now()->addSeconds(30);
|
||||
}
|
||||
|
||||
public function __construct(
|
||||
public readonly int $drawId,
|
||||
public readonly string $drawNo,
|
||||
|
||||
@@ -27,6 +27,12 @@ final class OddsUpdateBroadcast implements ShouldBroadcast
|
||||
/** 单任务超时 10 秒 */
|
||||
public int $timeout = 10;
|
||||
|
||||
/** 广播时效性保护:派发后 30 秒内必须开始执行,否则丢弃 */
|
||||
public function retryUntil(): \DateTimeInterface
|
||||
{
|
||||
return now()->addSeconds(30);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $versionId 新版本 ID
|
||||
* @param string $versionName 版本名称/描述
|
||||
|
||||
@@ -27,6 +27,12 @@ final class PlayCatalogUpdatedBroadcast implements ShouldBroadcast
|
||||
/** 单任务超时 10 秒 */
|
||||
public int $timeout = 10;
|
||||
|
||||
/** 广播时效性保护:派发后 30 秒内必须开始执行,否则丢弃 */
|
||||
public function retryUntil(): \DateTimeInterface
|
||||
{
|
||||
return now()->addSeconds(30);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $module play_config|odds|risk_cap
|
||||
* @param array<string, mixed>|null $meta
|
||||
|
||||
@@ -27,6 +27,12 @@ final class PlayToggleBroadcast implements ShouldBroadcast
|
||||
/** 单任务超时 10 秒 */
|
||||
public int $timeout = 10;
|
||||
|
||||
/** 广播时效性保护:派发后 30 秒内必须开始执行,否则丢弃 */
|
||||
public function retryUntil(): \DateTimeInterface
|
||||
{
|
||||
return now()->addSeconds(30);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $playCode 玩法代码(如 straight_4d, box_2d 等)
|
||||
* @param bool $enabled 是否启用
|
||||
|
||||
@@ -27,6 +27,12 @@ final class RiskSoldOutBroadcast implements ShouldBroadcast
|
||||
/** 单任务超时 10 秒 */
|
||||
public int $timeout = 10;
|
||||
|
||||
/** 广播时效性保护:派发后 30 秒内必须开始执行,否则丢弃 */
|
||||
public function retryUntil(): \DateTimeInterface
|
||||
{
|
||||
return now()->addSeconds(30);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $drawId 期号 ID
|
||||
* @param string $drawNo 期号编号(如 20260101-001)
|
||||
|
||||
@@ -27,6 +27,12 @@ final class RiskWarningBroadcast implements ShouldBroadcast
|
||||
/** 单任务超时 10 秒 */
|
||||
public int $timeout = 10;
|
||||
|
||||
/** 广播时效性保护:派发后 30 秒内必须开始执行,否则丢弃 */
|
||||
public function retryUntil(): \DateTimeInterface
|
||||
{
|
||||
return now()->addSeconds(30);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $drawId 期号 ID
|
||||
* @param string $drawNo 期号编号
|
||||
|
||||
@@ -140,6 +140,7 @@ final class DrawHallSnapshotBuilder
|
||||
});
|
||||
})
|
||||
->orderBy('draw_time')
|
||||
->limit(50)
|
||||
->get();
|
||||
|
||||
foreach ($upcoming as $candidate) {
|
||||
|
||||
@@ -111,8 +111,17 @@ final class SettlementOrchestrator
|
||||
->where('status', 'pending_draw')
|
||||
->with(['combinations', 'order'])
|
||||
->orderBy('id')
|
||||
->limit(10_000)
|
||||
->get();
|
||||
|
||||
if ($ticketItems->count() >= 10_000) {
|
||||
\Illuminate\Support\Facades\Log::warning('SettlementOrchestrator: ticket_items hit safety cap', [
|
||||
'draw_id' => $locked->id,
|
||||
'draw_no' => $locked->draw_no,
|
||||
'loaded_count' => $ticketItems->count(),
|
||||
]);
|
||||
}
|
||||
|
||||
/** @var list<array{item: TicketItem, gross_win: int, matched_tier: ?string, net_win: int, match_detail: mixed}> $prepared */
|
||||
$prepared = [];
|
||||
foreach ($ticketItems as $item) {
|
||||
|
||||
Reference in New Issue
Block a user