components->info('E 签宝定时轮询未启用。'); return self::SUCCESS; } if (! EsignClient::configured()) { $this->components->warn('E 签宝尚未正确配置,已跳过定时轮询。'); return self::SUCCESS; } $limit = min(max((int) ($this->option('limit') ?? config('services.esign.poll_batch_size', 25)), 1), 100); $minAge = max((int) ($this->option('min-age') ?? config('services.esign.poll_min_age_seconds', 120)), 0); $cutoff = now()->subSeconds($minAge); $candidates = $this->candidates($limit, $cutoff); $succeeded = 0; $failed = 0; foreach ($candidates as $candidate) { $model = $candidate['model']; try { if ($candidate['type'] === 'quote') { $quoteSignatures->synchronize($model, (int) ($model->updated_by ?: $model->created_by)); } else { $agreementSignatures->synchronize($model, (int) ($model->updated_by ?: $model->created_by)); } $succeeded++; } catch (Throwable $exception) { $failed++; Log::warning('E 签宝签署流程定时查询失败。', [ 'signature_type' => $candidate['type'], 'signature_id' => $model->getKey(), 'exception' => $exception::class, ]); } } $this->components->info(sprintf( 'E 签宝轮询完成:候选 %d,成功 %d,失败 %d。', $candidates->count(), $succeeded, $failed, )); return $failed > 0 ? self::FAILURE : self::SUCCESS; } /** @return Collection */ private function candidates(int $limit, \DateTimeInterface $cutoff): Collection { $environment = strtolower(trim((string) config('services.esign.environment', 'sandbox'))); $quotes = CourierQuoteSignature::allCompanies() ->where('environment', $environment) ->whereNotNull('esign_sign_flow_id') ->where('esign_sign_flow_id', '<>', '') ->whereIn('status', [ CourierQuoteSignature::STATUS_SIGNING, CourierQuoteSignature::STATUS_COMPLETED, ]) ->where(fn (Builder $query) => $query ->whereNull('last_synced_at') ->orWhere('last_synced_at', '<=', $cutoff)) ->orderByRaw('CASE WHEN last_synced_at IS NULL THEN 0 ELSE 1 END') ->orderBy('last_synced_at') ->orderBy('id') ->limit($limit) ->get() ->map(fn (CourierQuoteSignature $signature): array => [ 'type' => 'quote', 'model' => $signature, ]); $agreements = CourierAgreementSignature::allCompanies() ->disableCache() ->where('environment', $environment) ->whereNotNull('esign_sign_flow_id') ->where('esign_sign_flow_id', '<>', '') ->whereIn('status', [ CourierAgreementSignature::STATUS_SIGNING, CourierAgreementSignature::STATUS_UNKNOWN, CourierAgreementSignature::STATUS_COMPLETED, ]) ->where(fn (Builder $query) => $query ->whereNull('last_synced_at') ->orWhere('last_synced_at', '<=', $cutoff)) ->orderByRaw('CASE WHEN last_synced_at IS NULL THEN 0 ELSE 1 END') ->orderBy('last_synced_at') ->orderBy('id') ->limit($limit) ->get() ->map(fn (CourierAgreementSignature $signature): array => [ 'type' => 'agreement', 'model' => $signature, ]); return $quotes ->concat($agreements) ->sortBy(fn (array $candidate): string => sprintf( '%020d:%s:%020d', $candidate['model']->last_synced_at?->getTimestamp() ?? 0, $candidate['type'], $candidate['model']->getKey(), )) ->take($limit) ->values(); } }