Laravel: Automatizirajte revizije web-mjesta klijenata za ponude redizajna pomoću Tech Detector API-ja
Ponuda za redizajn postaje rizična kada se temelji samo na onome što preglednik otkriva. Uglađena početna stranica može prikrivati zastarjeli CMS, nekoliko analitičkih proizvoda, izlog trgovine koji se uvelike oslanja na JavaScript ili infrastrukturu koja će zakomplicirati migraciju. Otkrivanje tog skupa tehnologija prije procjene posla freelanceru ili malom razvojnom timu daje bolji temelj za opseg, pitanja i određivanje cijene.
Ovaj vodič izrađuje Laravel aplikaciju usmjerenu na produkciju koja šalje javni URL klijenta API-ju Website Technology Detector, pretvara odgovor u domenske objekte i vraća nalaze tehnologija potkrijepljene dokazima za procjenu redizajna. Integracija koristi Laravelov ugrađeni HTTP klijent, ograničena vremenska ograničenja, selektivne ponovne pokušaje, strukturirane pogreške, sigurno zapisivanje u logove i determinističke testove.
Dobijte pristup i kopirajte token usluge
Registrirajte se na https://ai.mihajlo.mk/register ili upotrijebite https://ai.mihajlo.mk/login ako već imate račun.
- Otvorite stranicu usluge Website Technology Detector.
- Odaberite dostupni Free, Plus ili Pro plan i dovršite njegovu aktivaciju.
- Otvorite službenu dokumentaciju usluge.
- Pronađite panel Service token i kopirajte token ograničen na uslugu.
Ova usluga zahtijeva autentikaciju. Podržava Bearer token, zaglavlje X-API-Token ili parametar upita token. Upotrijebit ćemo Bearer oblik jer je veća vjerojatnost da će se vjerodajnice u nizovima upita pojaviti u zapisima pristupa, povijesti preglednika i sustavima za nadzor.
Ponovno generiranje tokena usluge opoziva prethodno aktivan token. Generiranje tretirajte kao rotaciju vjerodajnica: odmah ažurirajte svako implementirano okruženje, očistite predmemoriranu Laravel konfiguraciju i provjerite integraciju prije uklanjanja bilo kakvog operativnog upozorenja.
Potvrdite točnu krajnju točku
Potreban zahtjev je POST https://ai.mihajlo.mk/api/website-technology-detector/v1/detect-technologies. Njegovo JSON tijelo sadrži jedan url. Izravno testirajte vjerodajnicu prije pisanja aplikacijskog koda:
curl --request POST \
--url 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"}'
Uspješan odgovor sadrži detekcije tehnologija s ocijenjenom pouzdanošću i dokazima, informacije o verziji kada su dostupne te informacije o preusmjeravanju. Vrijednosti pouzdanosti i dokazi trebaju informirati procjenu, a ne tretirati se kao nepogrešiv dokaz. Web-mjesto može sakriti komponente na strani poslužitelja, ukloniti zaglavlja za identifikaciju ili učitati tehnologije tek nakon određenih korisničkih interakcija.
Stvorite granicu Laravel projekta
Implementacija ostaje namjerno mala: kontroler validira javni URL, servis upravlja udaljenom HTTP razmjenom, a domenski objekti normaliziraju odgovor. Zahtjev ostaje sinkron jer korisniku koji provjerava jedno web-mjesto koristi trenutačan rezultat. Ako proizvod kasnije prihvaća popise web-mjesta, premjestite isti poziv servisa u poslove u redu čekanja umjesto da web-zahtjevi ostanu otvoreni.
Započnite s aktualnom Laravel aplikacijom koja koristi PHP 8.3 ili noviji:
composer create-project laravel/laravel redesign-auditor
cd redesign-auditor
php artisan make:controller WebsiteAuditController
php artisan make:test TechnologyDetectorTest
Relevantna struktura projekta bit će:
app/
Domain/WebsiteAudit/TechnologyAudit.php
Exceptions/TechnologyDetectorFailure.php
Http/Controllers/WebsiteAuditController.php
Services/TechnologyDetector.php
config/
services.php
routes/
api.php
tests/
Feature/TechnologyDetectorTest.php
Stavite vjerodajnicu u konfiguraciju podržanu okruženjem
Dodajte rezervirana mjesta u .env.example, a zatim stvarni token stavite samo u neverzionirani .env:
'technology_detector' => [
'url' => env(
'TECHNOLOGY_DETECTOR_URL',
'https://ai.mihajlo.mk/api/website-technology-detector'
),
'token' => env('TECHNOLOGY_DETECTOR_TOKEN'),
],
Aplikacijski kod mora čitati config(), a ne izravno pozivati env(). Ta je razlika važna nakon što php artisan config:cache kompajlira konfiguraciju za produkciju.
Preslikajte odgovor na granici aplikacije
Udaljeni JSON ne bi se trebao širiti kroz kontrolere i kod za izradu ponuda. Sljedeći domenski preslikivač prihvaća izravni payload ili uobičajenu ovojnicu data, ignorira neispravne unose popisa i dodjeljuje sigurne zadane vrijednosti neobaveznim poljima. Ne mijenja mjerilo pouzdanosti jer se ljestvica koju vraća usluga treba sačuvati točno takvom kakva jest.
<?php
namespace App\Domain\WebsiteAudit;
final readonly class TechnologyDetection
{
public function __construct(
public string $name,
public ?float $confidence,
public array $versions,
public array $evidence,
) {}
public static function fromApi(array $item): ?self
{
$name = $item['name'] ?? null;
if (! is_string($name) || trim($name) === '') {
return null;
}
$confidence = $item['confidence'] ?? null;
return new self(
name: trim($name),
confidence: is_numeric($confidence) ? (float) $confidence : null,
versions: self::stringList($item['versions'] ?? []),
evidence: is_array($item['evidence'] ?? null)
? $item['evidence']
: [],
);
}
private static function stringList(mixed $value): array
{
if (! is_array($value)) {
return [];
}
return array_values(array_filter(
$value,
static fn (mixed $item): bool => is_string($item)
&& trim($item) !== ''
));
}
}
final readonly class TechnologyAudit
{
public function __construct(
public array $technologies,
public array $redirects,
) {}
public static function fromApi(array $payload): self
{
$body = is_array($payload['data'] ?? null)
? $payload['data']
: $payload;
$items = is_array($body['technologies'] ?? null)
? $body['technologies']
: [];
$technologies = array_values(array_filter(array_map(
static fn (mixed $item): ?TechnologyDetection =>
is_array($item)
? TechnologyDetection::fromApi($item)
: null,
$items,
)));
return new self(
technologies: $technologies,
redirects: is_array($body['redirects'] ?? null)
? array_values($body['redirects'])
: [],
);
}
}
Ovaj preslikivač validira dokumentirana semantička polja umjesto da vjeruje njihovim PHP tipovima. Nedostajuće verzije nisu pogreške, a dokazi ostaju strukturirani jer bi njihovo izravnavanje odbacilo razlog iza detekcije. Ako službena dokumentacija promijeni svoju JSON ovojnicu, ovo je jedina klasa koja bi se trebala promijeniti.
Izgradite otporan servis detektora
Definirajte strukturiranu iznimku kako bi pozivatelji mogli razlikovati pogreške konfiguracije, autentikacije, kvote, validacije, mreže i uzvodne usluge:
<?php
namespace App\Exceptions;
use RuntimeException;
final class TechnologyDetectorFailure extends RuntimeException
{
public function __construct(
public readonly string $kind,
string $message,
public readonly ?int $upstreamStatus = null,
public readonly bool $retryable = false,
) {
parent::__construct($message);
}
}
Sada stvorite app/Services/TechnologyDetector.php. Servis postavlja zasebna ograničenja za povezivanje i ukupno vrijeme odgovora. Ponovno pokušava kod pogrešaka povezivanja, ograničenja broja zahtjeva i prolaznih odgovora poslužitelja s kratkim ograničenim odgodama. Pogreške autentikacije i validacije namjerno su isključene iz ponovnih pokušaja jer ponavljanje nepromijenjenog zahtjeva ne može ih otkloniti.
<?php
namespace App\Services;
use App\Domain\WebsiteAudit\TechnologyAudit;
use App\Exceptions\TechnologyDetectorFailure;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Throwable;
final class TechnologyDetector
{
public function detect(string $url): TechnologyAudit
{
$baseUrl = rtrim((string) config('services.technology_detector.url'), '/');
$token = (string) config('services.technology_detector.token');
if ($token === '') {
throw new TechnologyDetectorFailure(
'configuration',
'The technology detector token is not configured.'
);
}
$started = hrtime(true);
$host = parse_url($url, PHP_URL_HOST) ?: 'unknown';
try {
$response = Http::baseUrl($baseUrl)
->acceptJson()
->asJson()
->withToken($token)
->connectTimeout(3)
->timeout(15)
->retry(
[200, 500],
0,
static function (
Throwable $exception,
PendingRequest $request
): bool {
if ($exception instanceof ConnectionException) {
return true;
}
return $exception instanceof RequestException
&& in_array(
$exception->response->status(),
[429, 500, 502, 503, 504],
true
);
},
false
)
->post('/v1/detect-technologies', ['url' => $url]);
} catch (ConnectionException $exception) {
Log::warning('technology_detector.network_failure', [
'host' => $host,
'message' => $exception->getMessage(),
]);
throw new TechnologyDetectorFailure(
'network',
'The detector could not be reached.',
retryable: true
);
}
Log::info('technology_detector.completed', [
'host' => $host,
'status' => $response->status(),
'duration_ms' => (int) ((hrtime(true) - $started) / 1_000_000),
]);
if ($response->successful()) {
$payload = $response->json();
if (! is_array($payload)) {
throw new TechnologyDetectorFailure(
'invalid_response',
'The detector returned invalid JSON.',
$response->status(),
true
);
}
return TechnologyAudit::fromApi($payload);
}
$status = $response->status();
throw match (true) {
in_array($status, [401, 403], true) =>
new TechnologyDetectorFailure(
'authentication',
'The service token was rejected.',
$status
),
$status === 422 =>
new TechnologyDetectorFailure(
'validation',
'The detector rejected the submitted URL.',
$status
),
$status === 429 =>
new TechnologyDetectorFailure(
'rate_limit',
'The detector quota or rate limit was reached.',
$status,
true
),
$status >= 500 =>
new TechnologyDetectorFailure(
'upstream',
'The detector is temporarily unavailable.',
$status,
true
),
default =>
new TechnologyDetectorFailure(
'request',
'The detector request failed.',
$status
),
};
}
}
Log bilježi naziv hosta, status i trajanje, ali ne token, tijelo odgovora, dokaze ni puni URL. Puni URL-ovi mogu sadržavati identifikatore klijenta ili parametre upita, dok dokazi odgovora mogu otkriti pojedinosti implementacije koje ne pripadaju široko dostupnim logovima.
Izložite krajnju točku provjere
Stvorite app/Http/Controllers/WebsiteAuditController.php. Osim Laravelove URL validacije, kontroler odbacuje localhost te privatne ili rezervirane IP literale. To značajku usklađuje s njezinom svrhom: provjerom javnih web-mjesta.
<?php
namespace App\Http\Controllers;
use App\Exceptions\TechnologyDetectorFailure;
use App\Services\TechnologyDetector;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
final class WebsiteAuditController extends Controller
{
public function __invoke(
Request $request,
TechnologyDetector $detector
): JsonResponse {
$validated = $request->validate([
'url' => ['required', 'string', 'url:http,https', 'max:2048'],
]);
$host = parse_url($validated['url'], PHP_URL_HOST);
if (
! is_string($host)
|| strtolower($host) === 'localhost'
|| (
filter_var($host, FILTER_VALIDATE_IP)
&& ! filter_var(
$host,
FILTER_VALIDATE_IP,
FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
)
)
) {
throw ValidationException::withMessages([
'url' => 'Enter a publicly reachable website URL.',
]);
}
try {
$audit = $detector->detect($validated['url']);
} catch (TechnologyDetectorFailure $failure) {
$status = match ($failure->kind) {
'validation' => 422,
'rate_limit' => 429,
'configuration', 'authentication' => 503,
default => 502,
};
return response()->json([
'error' => [
'type' => $failure->kind,
'message' => $failure->getMessage(),
'retryable' => $failure->retryable,
],
], $status);
}
return response()->json([
'technologies' => array_map(
static fn ($technology): array => [
'name' => $technology->name,
'confidence' => $technology->confidence,
'versions' => $technology->versions,
'evidence' => $technology->evidence,
],
$audit->technologies
),
'redirects' => $audit->redirects,
]);
}
}
Registrirajte krajnju točku u routes/api.php. Ograničenje na razini rute štiti vaš plan od slučajnih petlji i neobaveznog zlostavljanja; autentikirane aplikacije mogu ga zamijeniti pravilom po korisniku.
<?php
use App\Http\Controllers\WebsiteAuditController;
use Illuminate\Support\Facades\Route;
Route::post('/website-audits', WebsiteAuditController::class)
->middleware('throttle:10,1');
Testirajte uspjeh i pogrešku bez ponovnog pokušaja
Laravelov Http::fake() održava testove determinističkima i osigurava da iz paketa ne pobjegne nijedan plaćeni zahtjev ili zahtjev ograničen kvotom. Test uspjeha dokazuje autentikaciju zahtjeva i preslikavanje; test autentikacije dokazuje da se odbijeni token ne pokušava ponovno.
<?php
namespace Tests\Feature;
use App\Exceptions\TechnologyDetectorFailure;
use App\Services\TechnologyDetector;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;
final class TechnologyDetectorTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
config()->set(
'services.technology_detector.url',
'https://ai.mihajlo.mk/api/website-technology-detector'
);
config()->set(
'services.technology_detector.token',
'test-service-token'
);
}
public function test_it_maps_a_technology_audit(): void
{
Http::fake([
'*/v1/detect-technologies' => Http::response([
'data' => [
'technologies' => [[
'name' => 'Example CMS',
'confidence' => 92,
'versions' => ['6.x'],
'evidence' => ['generator metadata'],
]],
'redirects' => [
['from' => 'http://example.com',
'to' => 'https://example.com'],
],
],
], 200),
]);
$audit = app(TechnologyDetector::class)
->detect('https://example.com');
$this->assertCount(1, $audit->technologies);
$this->assertSame(
'Example CMS',
$audit->technologies[0]->name
);
$this->assertSame(
92.0,
$audit->technologies[0]->confidence
);
Http::assertSent(fn ($request): bool =>
$request->url()
=== 'https://ai.mihajlo.mk/api/website-technology-detector/v1/detect-technologies'
&& $request->hasHeader(
'Authorization',
'Bearer test-service-token'
)
&& $request['url'] === 'https://example.com'
);
}
public function test_authentication_failure_is_not_retried(): void
{
Http::fake([
'*/v1/detect-technologies' =>
Http::response(['message' => 'Unauthorized'], 401),
]);
try {
app(TechnologyDetector::class)
->detect('https://example.com');
$this->fail('Expected detector failure was not thrown.');
} catch (TechnologyDetectorFailure $failure) {
$this->assertSame('authentication', $failure->kind);
$this->assertFalse($failure->retryable);
}
Http::assertSentCount(1);
}
}
Pokrenite paket testova i provjerite granicu aplikacije:
php artisan test
php artisan serve
curl --request POST \
--url http://127.0.0.1:8000/api/website-audits \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--data '{"url":"https://example.com"}'
Sigurnost, nadziranost i implementacija u produkciji
Zadržite krajnju točku iza autentikacije svoje aplikacije ako su rezultati provjere dio privatnog tijeka rada za izradu ponuda. Ograničavanje broja zahtjeva štiti API kvotu, ali nije autorizacija. Ne prikazujte vraćene dokaze kao pouzdani HTML; pohranite ih kao strukturirane podatke i escapeajte ih u svakom sučelju za ponude.
Pratite broj poziva i latenciju za uspješne pozive, mrežne pogreške, odgovore 429 i uzvodne odgovore 5xx. Trajni neuspjeh autentikacije obično znači da je token istekao, ponovno generiran ili nepravilno implementiran. Odgovor ograničenja broja zahtjeva trebao bi pauzirati skupnu obradu umjesto da pokrene agresivne trenutačne ponovne pokušaje.
Implementirajte tajnu aplikacije putem hosting platforme, zatim ponovno izgradite Laravelovu predmemoriranu konfiguraciju:
php artisan optimize:clear
php artisan config:cache
php artisan route:cache
php artisan test --testsuite=Feature
Prilikom rotacije tokena ažurirajte TECHNOLOGY_DETECTOR_TOKEN, ponovno pokrenite config:cache i izvedite jednu kontroliranu provjeru. Zapamtite da ponovno generiranje opoziva stari aktivni token, stoga će zastarjela instanca odmah početi primati pogreške autentikacije.
Uobičajene pogreške i njihovo značenje
- 401 ili 403: token nedostaje, opozvan je, nepravilno je kopiran ili nije dostupan predmemoriranoj konfiguraciji. Nemojte automatski ponovno pokušavati.
- 422: poslani URL je odbijen. Vratite validacijsku poruku usmjerenu na ispravak umjesto da to tretirate kao prekid rada.
- 429: dosegnuta je kvota plana ili ograničenje broja zahtjeva. Prikažite stanje koje dopušta ponovni pokušaj, poštujte smjernice usluge i odgodite skupni rad.
- Vremensko ograničenje povezivanja: DNS, mreža ili uzvodna usluga možda nisu dostupni. Ograničeni ponovni pokušaj rješava kratkotrajne pogreške bez neograničenog zauzimanja radnika.
- Prazne detekcije: ovo može biti valjan rezultat. Ne dokazuje da web-mjesto ne koristi tehnologiju; znači da javni dokazi nisu proizveli preslikane detekcije.
- Neočekivani JSON: tretirajte ga kao neuspjeh ugovora uzvodne usluge, zadržite sigurne metapodatke statusa i ažurirajte rubni preslikivač prema službenoj dokumentaciji.
Kontrolni popis za završnu provjeru
- Račun i Free, Plus ili Pro plan su aktivni.
- Token ograničen na uslugu pohranjen je izvan kontrole izvornog koda.
- Aplikacija poziva točnu krajnju točku detektora
POSTs JSON poljemurl. - Vremenska ograničenja povezivanja i ukupnog odgovora su ograničena.
- Ponovno se pokušavaju samo mrežni kvarovi, ograničenja broja zahtjeva i prolazni kvarovi poslužitelja.
- Detekcije, pouzdanost, verzije, dokazi i preusmjeravanja prolaze kroz obrambenu domensku granicu.
- Logovi izostavljaju tokene, tijela odgovora i pune URL-ove klijenta.
- Testovi
Http::fake()pokrivaju preslikavanje i neponovljivu pogrešku autentikacije. - Predmemorirana produkcijska konfiguracija sadrži trenutačni token.
Korisna provjera redizajna ne zamjenjuje tehničko otkrivanje; čini ga preciznijim. Otkriveni skup tehnologija govori vam koja pitanja o migraciji trebate postaviti, dokazi pokazuju zašto je svaka tehnologija prijavljena, a informacije o preusmjeravanju otkrivaju ponašanje usmjeravanja koje snimka zaslona početne stranice ne može. Uz usku Laravel granicu i disciplinirano rukovanje pogreškama, ta inteligencija postaje pouzdan ulaz za ponudu umjesto još jednog krhkog API poziva.