$ticket */ public function isPermanentlyStopped($ticket): bool { return (int) ($this->readField($ticket, 'sync_auto_sync_stopped') ?? 0) === 1; } /** * 是否处于失败重试等待/重试周期内(未永久终止) * * @param Ticket|array $ticket */ public function isInRetryMode($ticket): bool { if ($this->isPermanentlyStopped($ticket)) { return false; } return (int) ($this->readField($ticket, 'sync_fail_pause_at') ?? 0) > 0; } /** * 首次达失败阈值时进入重试态(工单保持 normal,不关单) * * @return array 需合并进 save 的字段;无变更时返回空数组 */ public function enterRetryIfNeeded(Ticket $ticket, int $failCount, int $threshold): array { if ($failCount < $threshold || !SplitSyncConfigService::hasFailRetrySchedule()) { return []; } if ($this->isInRetryMode($ticket)) { return []; } return [ 'sync_fail_pause_at' => time(), 'sync_fail_retry_index' => 0, 'sync_auto_sync_stopped' => 0, ]; } /** * 计划内重试失败后递进索引;用尽全部时间点后标记永久终止 * * @return array */ public function onRetryFailure(Ticket $ticket): array { $schedule = SplitSyncConfigService::getFailRetryMinutes(); $index = (int) ($ticket['sync_fail_retry_index'] ?? 0) + 1; $update = ['sync_fail_retry_index' => $index]; if ($index >= count($schedule)) { $update['sync_auto_sync_stopped'] = 1; } return $update; } /** * 同步成功时清零失败计数与重试相关字段 * * @return array */ public function clearOnSuccessFields(): array { return [ 'sync_fail_count' => 0, 'sync_fail_pause_at' => null, 'sync_fail_retry_index' => 0, 'sync_auto_sync_stopped' => 0, ]; } /** * Cron 自动重试是否已到点;未到点返回 skip 原因,到点返回 null(允许执行) * * @param Ticket|array $ticket */ public function shouldAllowAutoRetry($ticket, ?int $now = null): ?string { if (!$this->isInRetryMode($ticket)) { return null; } if ($this->isPermanentlyStopped($ticket)) { return '自动同步已终止(重试已用尽)'; } $nextAt = $this->getNextRetryAt($ticket); if ($nextAt === null) { return '自动同步已终止(重试已用尽)'; } $now = $now ?? time(); if ($now < $nextAt) { $attempt = (int) ($this->readField($ticket, 'sync_fail_retry_index') ?? 0) + 1; return sprintf( '失败重试等待中,将于 %s 进行第 %d 次重试', date('Y-m-d H:i:s', $nextAt), $attempt ); } return null; } /** * 下一次计划重试的 Unix 时间戳;无待重试时返回 null * * @param Ticket|array $ticket */ public function getNextRetryAt($ticket): ?int { if (!$this->isInRetryMode($ticket) || $this->isPermanentlyStopped($ticket)) { return null; } $schedule = SplitSyncConfigService::getFailRetryMinutes(); if ($schedule === []) { return null; } $index = (int) ($this->readField($ticket, 'sync_fail_retry_index') ?? 0); if ($index >= count($schedule)) { return null; } $pauseAt = (int) ($this->readField($ticket, 'sync_fail_pause_at') ?? 0); if ($pauseAt <= 0) { return null; } return $pauseAt + $schedule[$index] * 60; } /** * @param Ticket|array $ticket */ private function readField($ticket, string $field) { if ($ticket instanceof Ticket) { return $ticket->getData($field) ?? ($ticket[$field] ?? null); } return $ticket[$field] ?? null; } }