Laravel: Автоматизирајте понуди за редизајн на веб-страници со откривање на технолошките стекови на клиентите
Понудата за редизајн може да тргне наопаку уште пред некој да разговара за типографија или распоред на страници. Веб-страница што изгледа едноставно може да крие хостирана платформа за е-трговија, неколку аналитички производи, frontend со многу JavaScript, застарени приклучоци и пренасочувања оставени од претходни миграции. Ако откривањето се потпира на брза визуелна проверка, тие детали подоцна се појавуваат како промени во опфатот.
Овој туторијал гради Laravel претпроверка ориентирана кон продукција, која прифаќа URL од клиент, го повикува API-то Website Technology Detector и враќа извештај за технолошкиот стек поткрепен со докази за лицето што ја подготвува понудата. Не тврди дека откривањето технологии може да пресмета конечна цена. Ја автоматизира механичката истрага, така што одлуките за цени почнуваат со подобри информации.
Добијте пристап и копирајте го сервисниот токен
Завршете го поставувањето на пристапот пред да пишувате код за интеграција:
- Создадете сметка на https://ai.mihajlo.mk/register, или користете https://ai.mihajlo.mk/login ако веќе имате.
- Отворете ја страницата на услугата Website Technology Detector.
- Изберете го достапниот Free, Plus или Pro план и завршете ја неговата активација.
- Отворете ја официјалната документација за услугата.
- Најдете го панелот Service token и копирајте го токенот ограничен на услугата.
Оваа услуга бара автентикација. Прифаќа Bearer токен, заглавие X-API-Token или параметар за пребарување token. Имплементацијата подолу користи Bearer токен бидејќи параметрите за пребарување често се појавуваат во дневниците за пристап и системите за мониторинг.
Повторното генерирање на сервисниот токен го поништува претходно активниот токен. Третирајте го повторното генерирање како ротација на акредитиви: заменете ја тајната во секоја распоредена околина, повторно изградете ја кешираната Laravel конфигурација, потврдете ја интеграцијата и дури тогаш сметајте го пуштањето во употреба за завршено.
Потврдете ја точната крајна точка
Потребниот повик е POST https://ai.mihajlo.mk/api/website-technology-detector/v1/detect-technologies. Тој прима JSON што содржи url. Тестирајте го еднаш од безбеден терминал, користејќи заменско место тука наместо да внесувате вистински акредитив:
curl --request POST \
'https://ai.mihajlo.mk/api/website-technology-detector/v1/detect-technologies' \
--header 'Authorization: Bearer YOUR_SERVICE_TOKEN' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data '{"url":"https://example.com"}'
Проверете го документираниот одговор заедно со овој тест. Интеграцијата мора да ги зачува детекциите оценети со доверба, доказите, верзиите и информациите за пренасочување. Овие вредности се корисни при откривањето, но остануваат набљудувања за јавна веб-страница, а не гаранции за нејзиниот изворен код или хостинг-сметка.
Чувајте го акредитивот во Laravel конфигурацијата
Примерот претпоставува PHP 8.3 или понова верзија, постоечка Laravel апликација со автентикација и тест-база на податоци што ја поддржува нејзината фабрика за корисници. Не е потребен HTTP пакет од трета страна; вградениот Laravel клиент е доволен.
composer create-project laravel/laravel redesign-preflight
cd redesign-preflight
# .env
WEBSITE_TECH_DETECTOR_TOKEN=YOUR_SERVICE_TOKEN
Додајте посебен запис во config/services.php. Чувањето на крајната точка во конфигурацијата исто така ги прави тестовите детерминистички, без расфрлање URL-адреси низ апликацијата.
// config/services.php
'website_technology_detector' => [
'endpoint' => env(
'WEBSITE_TECH_DETECTOR_ENDPOINT',
'https://ai.mihajlo.mk/api/website-technology-detector/v1/detect-technologies',
),
'token' => env('WEBSITE_TECH_DETECTOR_TOKEN'),
],
Не повикувајте env() од класите на апликацијата. Laravel кешот на конфигурацијата е дизајниран околу вредностите што се читаат преку config().
Одржувајте ја API границата мала
Апликацијата има четири релевантни делови:
WebsiteTechnologyDetectorуправува со автентикацијата, временските ограничувања, повторните обиди и обработката на HTTP статусите.TechnologyReportмапира недоверлив JSON во стабилен доменски објект.QuotePreflightControllerја проверува поднесената цел и ги изложува наодите релевантни за понудата.- Заштитена рута со ограничување на стапката спречува анонимни повикувачи да ја трошат квотата на планот.
Овој синхрон дизајн е соодветен за лице што бара еден извештај додека подготвува понуда. Редицата би вовела постојано складирање, работници, статус на задачи и грижи за дуплирано поднесување, без да го подобри овој работен тек. Ако извештаите подоцна станат сериски увози, истата услуга за детектор може да се повикува од идемпотентна задача во редица.
Мапирајте го одговорот дефанзивно
Оддалечениот JSON не е доверлив доменски објект. Маперот подолу прифаќа само низи, ги игнорира невалидно форматираните редови на детекција, ги зачувува структурираните докази и никогаш не претпоставува дека довербата е процент. Колекциите на детекции и пренасочувања од договорот за одговор остануваат изолирани тука, така што документирана ревизија на шемата има една точка за приспособување.
<?php
// app/Domain/Quotes/TechnologyReport.php
namespace App\Domain\Quotes;
use UnexpectedValueException;
final readonly class TechnologyReport
{
public function __construct(
public array $detections,
public array $redirects,
) {}
public static function fromApi(array $body): self
{
$payload = isset($body['data']) && is_array($body['data'])
? $body['data']
: $body;
$rows = $payload['technologies'] ?? $payload['detections'] ?? null;
if (! is_array($rows)) {
throw new UnexpectedValueException(
'Detector response has no detection collection.'
);
}
$detections = [];
foreach ($rows as $row) {
if (! is_array($row) || ! is_string($row['name'] ?? null)) {
continue;
}
$confidence = is_numeric($row['confidence'] ?? null)
? (float) $row['confidence']
: null;
$detections[] = [
'name' => $row['name'],
'confidence' => $confidence,
'versions' => is_array($row['versions'] ?? null)
? array_values($row['versions'])
: [],
'evidence' => is_array($row['evidence'] ?? null)
? $row['evidence']
: [],
];
}
$redirects = is_array($payload['redirects'] ?? null)
? array_values($payload['redirects'])
: [];
return new self($detections, $redirects);
}
}
Не отфрлајте празна листа на детекции: валидна јавна страница може да изложува малку препознатливи докази. Тоа се разликува од невалиден одговор, кој предизвикува неуспех на протоколот.
Додајте ограничени повторни обиди и структурирани неуспеси
Неуспесите на врската, HTTP 429 одговорите и избрани привремени серверски грешки заслужуваат ограничени повторни обиди. Не и неуспесите при автентикација и валидација: повторувањето на истиот лош токен или URL само троши време и создава шум.
<?php
// app/Services/WebsiteTechnologyDetector.php
namespace App\Services;
use App\Domain\Quotes\TechnologyReport;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use RuntimeException;
use Throwable;
final class DetectorFailure extends RuntimeException
{
public function __construct(
public readonly string $kind,
public readonly ?int $upstreamStatus = null,
?Throwable $previous = null,
) {
parent::__construct("Technology detector failed: {$kind}", 0, $previous);
}
}
final class WebsiteTechnologyDetector
{
public function detect(string $url): TechnologyReport
{
$endpoint = config('services.website_technology_detector.endpoint');
$token = config('services.website_technology_detector.token');
if (! is_string($token) || $token === '') {
throw new DetectorFailure('configuration');
}
for ($attempt = 1; $attempt <= 3; $attempt++) {
try {
$response = Http::acceptJson()
->asJson()
->withToken($token)
->connectTimeout(3)
->timeout(15)
->post($endpoint, ['url' => $url]);
} catch (ConnectionException $exception) {
if ($attempt === 3) {
throw new DetectorFailure(
'connection',
previous: $exception,
);
}
$this->pause($attempt, null);
continue;
}
if ($response->successful()) {
try {
$json = $response->json();
if (! is_array($json)) {
throw new RuntimeException('Response was not JSON.');
}
return TechnologyReport::fromApi($json);
} catch (Throwable $exception) {
throw new DetectorFailure(
'protocol',
$response->status(),
$exception,
);
}
}
$status = $response->status();
if (in_array($status, [401, 403], true)) {
throw new DetectorFailure('authentication', $status);
}
if (in_array($status, [400, 422], true)) {
throw new DetectorFailure('rejected_url', $status);
}
$retryable = $status === 429
|| in_array($status, [500, 502, 503, 504], true);
if (! $retryable || $attempt === 3) {
$kind = $status === 429 ? 'rate_limited' : 'upstream';
throw new DetectorFailure($kind, $status);
}
Log::notice('technology_detector_retry', [
'attempt' => $attempt,
'status' => $status,
]);
$this->pause($attempt, $response->header('Retry-After'));
}
throw new DetectorFailure('upstream');
}
private function pause(int $attempt, ?string $retryAfter): void
{
$seconds = ctype_digit((string) $retryAfter)
? min(5, max(1, (int) $retryAfter))
: min(2, $attempt);
usleep($seconds * 1_000_000);
}
}
И двете временски ограничувања и границата од три обиди се намерни. Ограниченото ракување со Retry-After почитува нумерички навестување од серверот, без да дозволи едно барање да задржи PHP работник неограничено. Услугата не запишува токени, тела на одговори, докази или целосни URL-адреси на клиенти во дневник.
Изложете заштитена претпроверка за понуда
Контролерот прифаќа само HTTP или HTTPS URL-адреси. Бидејќи Laravel апликацијата никогаш не ја презема целта директно, таа не дејствува како прокси за општа намена. Сепак, отфрлањето акредитиви, localhost имиња и IP литерали што не се јавни фаќа очигледни грешки и злоупотреба. Автентикацијата и ограничувањето на рутата додаваат поважна граница на квотата.
<?php
// app/Http/Controllers/QuotePreflightController.php
namespace App\Http\Controllers;
use App\Services\DetectorFailure;
use App\Services\WebsiteTechnologyDetector;
use Closure;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
final class QuotePreflightController extends Controller
{
public function __invoke(
Request $request,
WebsiteTechnologyDetector $detector,
): JsonResponse {
$validated = $request->validate([
'url' => [
'required',
'string',
'max:2048',
function (string $attribute, mixed $value, Closure $fail): void {
$parts = is_string($value) ? parse_url($value) : false;
$scheme = is_array($parts) ? ($parts['scheme'] ?? null) : null;
$host = is_array($parts) ? ($parts['host'] ?? null) : null;
if (
! in_array($scheme, ['http', 'https'], true)
|| ! is_string($host)
|| isset($parts['user'])
|| $host === 'localhost'
|| str_ends_with($host, '.local')
) {
$fail('The URL must identify a public HTTP website.');
return;
}
if (
filter_var($host, FILTER_VALIDATE_IP)
&& ! filter_var(
$host,
FILTER_VALIDATE_IP,
FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE,
)
) {
$fail('Private and reserved IP addresses are not allowed.');
}
},
],
]);
try {
$report = $detector->detect($validated['url']);
} catch (DetectorFailure $failure) {
Log::warning('technology_detector_terminal_failure', [
'kind' => $failure->kind,
'upstream_status' => $failure->upstreamStatus,
'target_host' => parse_url($validated['url'], PHP_URL_HOST),
]);
return response()->json([
'status' => 'temporarily_unavailable',
'failure' => $failure->kind,
], 503);
}
return response()->json([
'status' => 'ready',
'preflight' => [
'detected_count' => count($report->detections),
'technologies' => $report->detections,
'redirects' => $report->redirects,
'manual_evidence_review' => array_values(array_map(
fn (array $item): string => $item['name'],
array_filter(
$report->detections,
fn (array $item): bool => $item['evidence'] === [],
),
)),
],
]);
}
}
Регистрирајте ја крајната точка во routes/web.php:
use App\Http\Controllers\QuotePreflightController;
use Illuminate\Support\Facades\Route;
Route::post('/quote-preflight', QuotePreflightController::class)
->middleware(['auth', 'throttle:10,1'])
->name('quote-preflight');
Одговорот му ги дава на проценувачот откриените технологии, нивните вредности на доверба, верзии, поткрепувачки докази, патеката на пренасочување и експлицитна листа што бара рачна проверка на доказите. Правилата за цени треба да живеат на друго место и да останат деловна политика што може да се уредува. На пример, миграција на пренасочување или хостирана наплата може да влијае на опфатот, но ниту едното не треба тивко да додава пари без развивач да прегледа што всушност значат доказите.
Тестирајте ги патеките за успех и неуспех без мрежни повици
Http::fake() спречува тестовите да трошат квота и ги прави тврдењата за повторни обиди детерминистички. Подолниот фикстур ги проверува поддржаните облици на договорот на маперот; одржувајте ги продукциските фикстури санитизирани и без податоци за клиенти или акредитиви.
<?php
// tests/Feature/QuotePreflightTest.php
namespace Tests\Feature;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;
final class QuotePreflightTest extends TestCase
{
use RefreshDatabase;
private string $endpoint =
'https://ai.mihajlo.mk/api/website-technology-detector/v1/detect-technologies';
protected function setUp(): void
{
parent::setUp();
config([
'services.website_technology_detector.endpoint' => $this->endpoint,
'services.website_technology_detector.token' => 'test-token',
]);
}
public function test_authenticated_user_receives_stack_preflight(): void
{
Http::fake([
$this->endpoint => Http::response([
'data' => [
'technologies' => [[
'name' => 'Example CMS',
'confidence' => 0.94,
'versions' => ['1.2'],
'evidence' => ['generator' => 'Example CMS'],
]],
'redirects' => [
['from' => 'http://example.com', 'to' => 'https://example.com'],
],
],
], 200),
]);
$this->actingAs(User::factory()->create())
->postJson(route('quote-preflight'), [
'url' => 'https://example.com',
])
->assertOk()
->assertJsonPath('status', 'ready')
->assertJsonPath('preflight.detected_count', 1)
->assertJsonPath(
'preflight.technologies.0.name',
'Example CMS',
);
Http::assertSent(fn ($request): bool =>
$request->url() === $this->endpoint
&& $request['url'] === 'https://example.com'
&& $request->hasHeader(
'Authorization',
'Bearer test-token',
)
);
}
public function test_authentication_failure_is_not_retried(): void
{
Http::fake([
$this->endpoint => Http::response([], 401),
]);
$this->actingAs(User::factory()->create())
->postJson(route('quote-preflight'), [
'url' => 'https://example.com',
])
->assertStatus(503)
->assertJsonPath('failure', 'authentication');
Http::assertSentCount(1);
}
public function test_private_ip_is_rejected_before_api_call(): void
{
Http::fake();
$this->actingAs(User::factory()->create())
->postJson(route('quote-preflight'), [
'url' => 'http://127.0.0.1/admin',
])
->assertUnprocessable()
->assertJsonValidationErrors('url');
Http::assertNothingSent();
}
}
Извршете го пакетот тестови со php artisan test. Дополнителните тестови треба да покријат HTTP 429, исклучок при поврзување, невалиден JSON, празна листа на детекции, пренасочувања и анонимен пристап.
Управувајте со него безбедно во продукција
Чувајте го токенот во управувачот со тајни на платформата за распоредување, а не во комитирани .env датотеки. По промена на конфигурацијата, повторно изградете го кешот со php artisan config:cache. Распоредувањата треба рано да не успеат ако токенот недостига; мала проверка на конфигурацијата при подигање е попожелна од откривање на проблемот за време на состанок со клиент.
Следете структурирани броеви за успешни извештаи, видови на конечни неуспеси, статусни кодови од upstream, повторни обиди и латентност. Алармирајте при трајни неуспеси на автентикацијата бидејќи тие често укажуваат на поништен или неправилно распореден токен. Третирајте ги повторените одговори 429 како сигнал за капацитет или контрола на сообраќајот, а не како дозвола за додавање агресивни повторни обиди.
Вообичаените неуспеси имаат различни решенија:
- 401 или 403: проверете ги токенот ограничен на услугата, активацијата на планот, вбризгувањето тајна и кешот на конфигурацијата. Не обидувајте се повторно наслепо.
- 400 или 422: проверете ја валидацијата на URL и документираниот договор за барањето. Повторното праќање непроменет товар нема да помогне.
- 429: намалете го сообраќајот, задржете го ограничувањето на рутата, почитувајте го ограниченото доцнење за повторен обид и прегледајте го активниот план.
- Временски ограничувања или 5xx одговори: задржете ја политиката на ограничени повторни обиди и оставете проценувачот да се обиде подоцна, наместо да ги држи работниците неограничено.
- Невалиден успешен одговор: забележете неуспех на протоколот без да го запишете телото во дневник, споредете санитизиран одговор со официјалната документација и намерно ажурирајте го маперот на границата.
- Неочекувано ретки детекции: рачно прегледајте ги доставените докази и јавната страница. Отсуството на детекција не е доказ дека технологијата отсуствува.
Конечна листа за проверка
- Сметката и Free, Plus или Pro планот се активни.
- Токенот ограничен на услугата доаѓа од панелот Service token на страницата за документација.
- Ниту еден акредитив не се појавува во изворната контрола, фикстурите, дневниците, сликите од екранот или URL-адресите.
- Барањето ја користи точната POST крајна точка и испраќа JSON што содржи
url. - Неуспесите при автентикација, валидација, квота, поврзување, сервер и протокол се разликуваат.
- Временските ограничувања за поврзување и одговор се ограничени, а повторно се обидуваат само привремените неуспеси.
- Детекциите, довербата, доказите, верзиите и пренасочувањата се зачувуваат при доменското мапирање.
- Рутата бара автентициран корисник и има ограничување на ниво на апликација.
- Тестовите користат
Http::fake()и не прават вистински надворешни повици. - Продукциската конфигурација е кеширана и вистинска претпроверка е рачно прегледана.
Корисна понуда за редизајн не се создава со множење на бројот на технологии со часовна стапка. Таа произлегува од претворање на скриените детали за имплементацијата во експлицитни прашања: што мора да се мигрира, што може да се зачува, што треба да се потврди и каде доказите се слаби. Автоматизирањето на тој прв чекор им дава на развивачите повеќе време за тие проценки — и на клиентите им дава понуда заснована на веб-страницата што навистина ја имаат.