111 lines
3.5 KiB
PHP
111 lines
3.5 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Wb;
|
|
|
|
use Illuminate\Support\Facades\Http;
|
|
use RuntimeException;
|
|
|
|
class WbApiClient
|
|
{
|
|
private string $baseUrl;
|
|
private string $apiKey;
|
|
private int $limit;
|
|
private int $timeout;
|
|
private int $requestSleepMs;
|
|
private int $tooManyAttemptsSleepSeconds;
|
|
private int $max429Retries;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->baseUrl = rtrim((string) config('wb_api.base_url'), '/');
|
|
$this->apiKey = (string) config('wb_api.key');
|
|
$this->limit = max(1, min((int) config('wb_api.limit', 100), 500));
|
|
$this->timeout = (int) config('wb_api.timeout', 30);
|
|
$this->requestSleepMs = (int) config('wb_api.request_sleep_ms', 1000);
|
|
$this->tooManyAttemptsSleepSeconds = (int) config('wb_api.too_many_attempts_sleep_seconds', 30);
|
|
$this->max429Retries = (int) config('wb_api.max_429_retries', 20);
|
|
|
|
if ($this->baseUrl === '') {
|
|
throw new RuntimeException('WB_API_BASE_URL is empty. Check your .env file.');
|
|
}
|
|
|
|
if ($this->apiKey === '') {
|
|
throw new RuntimeException('WB_API_KEY is empty. Check your .env file.');
|
|
}
|
|
}
|
|
|
|
public function limit(): int
|
|
{
|
|
return $this->limit;
|
|
}
|
|
|
|
public function fetchPage(string $endpoint, array $params, int $page): array
|
|
{
|
|
$url = $this->baseUrl . '/api/' . ltrim($endpoint, '/');
|
|
|
|
$query = array_filter(array_merge($params, [
|
|
'key' => $this->apiKey,
|
|
'limit' => $this->limit,
|
|
'page' => $page,
|
|
]), static fn ($value) => $value !== null && $value !== '');
|
|
|
|
$attempt429 = 0;
|
|
|
|
while (true) {
|
|
if ($this->requestSleepMs > 0) {
|
|
usleep($this->requestSleepMs * 1000);
|
|
}
|
|
|
|
$response = Http::timeout($this->timeout)
|
|
->acceptJson()
|
|
->get($url, $query);
|
|
|
|
if ($response->status() === 429) {
|
|
$attempt429++;
|
|
|
|
if ($attempt429 > $this->max429Retries) {
|
|
throw new RuntimeException("Too many 429 responses for {$endpoint} page {$page}");
|
|
}
|
|
|
|
$retryAfter = $response->header('Retry-After');
|
|
$sleepSeconds = is_numeric($retryAfter)
|
|
? max(1, (int) $retryAfter)
|
|
: $this->tooManyAttemptsSleepSeconds;
|
|
|
|
echo "{$endpoint} page={$page} got 429, sleeping {$sleepSeconds}s, retry {$attempt429}/{$this->max429Retries}\n";
|
|
|
|
sleep($sleepSeconds);
|
|
continue;
|
|
}
|
|
|
|
if (!$response->successful()) {
|
|
throw new RuntimeException(sprintf(
|
|
'API request failed: endpoint=%s page=%d http=%d body=%s',
|
|
$endpoint,
|
|
$page,
|
|
$response->status(),
|
|
mb_substr($response->body(), 0, 1000)
|
|
));
|
|
}
|
|
|
|
$json = $response->json();
|
|
|
|
if (!is_array($json)) {
|
|
throw new RuntimeException("Unexpected non-array API response for {$endpoint}, page {$page}");
|
|
}
|
|
|
|
$items = $json['data'] ?? $json;
|
|
|
|
if (!is_array($items)) {
|
|
throw new RuntimeException("Unexpected API response shape for {$endpoint}, page {$page}");
|
|
}
|
|
|
|
if ($items !== [] && array_keys($items) !== range(0, count($items) - 1)) {
|
|
$items = [$items];
|
|
}
|
|
|
|
return array_values(array_filter($items, 'is_array'));
|
|
}
|
|
}
|
|
}
|