94 lines
4.3 KiB
PHP
94 lines
4.3 KiB
PHP
<?php
|
||||
|
|
|
|||
|
|
namespace App\Jobs;
|
|||
|
|
|
|||
|
|
use App\Enums\SerpCheckStatus;
|
|||
|
|
use App\Models\SerpCheck;
|
|||
|
|
use App\Serp\Data\SerpQuery;
|
|||
|
|
use App\Serp\Exceptions\SerpProviderException;
|
|||
|
|
use App\Serp\Operations\ActivationManager;
|
|||
|
|
use App\Serp\Operations\BudgetManager;
|
|||
|
|
use App\Serp\Operations\CircuitBreaker;
|
|||
|
|
use App\Serp\Operations\OperationAuditLogger;
|
|||
|
|
use App\Serp\SerpProviderRegistry;
|
|||
|
|
use App\Services\ProjectDomainMatcher;
|
|||
|
|
use App\Services\SerpResultPersister;
|
|||
|
|
use Illuminate\Bus\Queueable;
|
|||
|
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
|||
|
|
use Illuminate\Foundation\Bus\Dispatchable;
|
|||
|
|
use Illuminate\Queue\InteractsWithQueue;
|
|||
|
|
use Illuminate\Queue\SerializesModels;
|
|||
|
|
use Illuminate\Support\Facades\Cache;
|
|||
|
|
use Throwable;
|
|||
|
|
|
|||
|
|
class RunSerpCheck implements ShouldQueue
|
|||
|
|
{
|
|||
|
|
use Dispatchable,InteractsWithQueue,Queueable,SerializesModels;
|
|||
|
|
|
|||
|
|
public int $tries = 1;
|
|||
|
|
|
|||
|
|
public int $timeout = 60;
|
|||
|
|
|
|||
|
|
public array $backoff = [10, 60, 300];
|
|||
|
|
|
|||
|
|
public function __construct(public readonly string $checkUlid) {}
|
|||
|
|
|
|||
|
|
public function handle(SerpProviderRegistry $registry, SerpResultPersister|ProjectDomainMatcher $writer, ?ActivationManager $activation = null, ?BudgetManager $budget = null, ?CircuitBreaker $circuit = null, ?OperationAuditLogger $audit = null): void
|
|||
|
|
{
|
|||
|
|
$persister = $writer instanceof SerpResultPersister ? $writer : new SerpResultPersister($writer);
|
|||
|
|
$activation ??= app(ActivationManager::class);
|
|||
|
|
$budget ??= app(BudgetManager::class);
|
|||
|
|
$circuit ??= app(CircuitBreaker::class);
|
|||
|
|
$audit ??= app(OperationAuditLogger::class);
|
|||
|
|
Cache::lock('serp-job:'.$this->checkUlid, 75)->block(1, function () use ($registry, $persister, $activation, $budget, $circuit, $audit) {
|
|||
|
|
$c = SerpCheck::where('ulid', $this->checkUlid)->firstOrFail();
|
|||
|
|
if (in_array($c->status, [SerpCheckStatus::Completed, SerpCheckStatus::Failed], true)) {
|
|||
|
|
return;
|
|||
|
|
}if ($c->execution_mode !== 'live') {
|
|||
|
|
SubmitSerpCheck::dispatch($c->ulid)->onQueue(config('serp.queue'));
|
|||
|
|
|
|||
|
|
return;
|
|||
|
|
}if ($c->status === SerpCheckStatus::Pending && ! $c->begin()) {
|
|||
|
|
return;
|
|||
|
|
}$started = microtime(true);
|
|||
|
|
try {
|
|||
|
|
if ($c->provider === 'dataforseo') {
|
|||
|
|
$activation->assertPaid();
|
|||
|
|
$circuit->assertClosed();
|
|||
|
|
$budget->reserve('check:'.$c->ulid, 'live', $c->id);
|
|||
|
|
}$audit->record('live_request_started', ['check_ulid' => $c->ulid, 'provider' => $c->provider, 'mode' => 'live']);
|
|||
|
|
$response = $registry->resolve($c->provider)->search(new SerpQuery($c->phrase, $c->search_engine->value, $c->country_code, $c->language_code, $c->device->value, 10));
|
|||
|
|
if ($c->provider === 'dataforseo') {
|
|||
|
|
$budget->reconcile('check:'.$c->ulid, $response->cost, $response->referenceId);
|
|||
|
|
$circuit->success();
|
|||
|
|
}$persister->complete($c, $response, ['provider_cost' => $response->cost, 'provider_cost_currency' => $response->currency, 'provider_status_code' => $response->statusCode, 'duration_ms' => (int) ((microtime(true) - $started) * 1000)]);
|
|||
|
|
$audit->record('live_request_completed', ['check_ulid' => $c->ulid, 'provider' => $c->provider, 'mode' => 'live', 'duration_ms' => (int) ((microtime(true) - $started) * 1000), 'result_count' => count($response->results)]);
|
|||
|
|
} catch (SerpProviderException $e) {
|
|||
|
|
if ($c->provider === 'dataforseo') {
|
|||
|
|
$circuit->failure($e);
|
|||
|
|
}$audit->record('live_request_failed', ['check_ulid' => $c->ulid, 'error_category' => $e->safeCode()]);
|
|||
|
|
if ($e->retryable()) {
|
|||
|
|
throw $e;
|
|||
|
|
}$c->fail($e->safeCode(), $e->safeMessage());
|
|||
|
|
} catch (Throwable) {
|
|||
|
|
$c->fail('unexpected_error', 'SERP kontrolü tamamlanamadı.');
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public function failed(?Throwable $e): void
|
|||
|
|
{
|
|||
|
|
$c = SerpCheck::where('ulid', $this->checkUlid)->first();
|
|||
|
|
if ($c && $e instanceof SerpProviderException) {
|
|||
|
|
$c->fail($e->safeCode(), $e->safeMessage());
|
|||
|
|
} elseif ($c) {
|
|||
|
|
$c->fail('job_failed', 'SERP kontrolü tamamlanamadı.');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public function tags(): array
|
|||
|
|
{
|
|||
|
|
return ['serp-check:'.$this->checkUlid, 'serp-mode:live'];
|
|||
|
|
}
|
|||
|
|
}
|