Laravel: Automatizirajte ponude za redizajn web-stranica otkrivanjem tehnoloških skupova klijenata
Ponuda za redizajn može poći po zlu prije nego što itko raspravlja o tipografiji ili rasporedima stranica. Web-mjesto koje izgleda jednostavno može skrivati platformu za hostiranu e-trgovinu, nekoliko analitičkih proizvoda, frontend s mnogo JavaScripta, naslijeđene dodatke i preusmjeravanja koja su ostala nakon prethodnih migracija. Ako se otkrivanje oslanja na brz vizualni pregled, ti se detalji kasnije pojavljuju kao promjene opsega.
Ovaj vodič izrađuje Laravel preflight spreman za produkciju koji prihvaća URL klijenta, poziva API za otkrivanje tehnologija web-mjesta i vraća izvještaj o sklopu potkrijepljen dokazima za osobu koja priprema ponudu. Ne pretvara se da otkrivanje tehnologija može izračunati konačnu cijenu. Automatizira mehaničko istraživanje kako bi odluke o određivanju cijena započele s boljim informacijama.
Dobijte pristup i kopirajte servisni token
Dovršite postavljanje pristupa prije pisanja integracijskog koda:
- Izradite račun na https://ai.mihajlo.mk/register ili upotrijebite https://ai.mihajlo.mk/login ako ga već imate.
- Otvorite stranicu usluge Website Technology Detector.
- Odaberite dostupni paket Free, Plus ili Pro i dovršite njegovu aktivaciju.
- Otvorite službenu dokumentaciju usluge.
- Pronađite ploču Service token i kopirajte token ograničen na uslugu.
Ova usluga zahtijeva autentifikaciju. Prihvaća Bearer token, zaglavlje X-API-Token ili parametar upita token. Implementacija u nastavku koristi Bearer token jer se parametri upita često pojavljuju u zapisnicima pristupa i sustavima za nadzor.
Ponovno generiranje servisnog tokena opoziva prethodno aktivni token. Ponovno generiranje tretirajte kao rotaciju vjerodajnice: zamijenite tajnu u svakom implementiranom okruženju, ponovno izgradite predmemoriranu konfiguraciju Laravela, provjerite integraciju i tek tada smatrajte uvođenje dovršenim.
Potvrdite točan endpoint
Potrebni poziv je POST https://ai.mihajlo.mk/api/website-technology-detector/v1/detect-technologies. Prima JSON koji sadržava url. Jednom ga testirajte iz sigurnog terminala, koristeći ovdje rezervirano mjesto umjesto stvarne vjerodajnice:
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"}'
Pregledajte dokumentirani odgovor uz ovaj test. Integracija mora sačuvati detekcije s ocjenom pouzdanosti, dokaze, verzije i informacije o preusmjeravanju. Te su vrijednosti korisne tijekom otkrivanja, no ostaju opažanja o javnom web-mjestu, a ne jamstva o njegovu izvornom kodu ili računu za hosting.
Pohranite vjerodajnicu u konfiguraciju Laravela
Primjer pretpostavlja PHP 8.3 ili noviji, postojeću Laravel aplikaciju s autentifikacijom i testnu bazu podataka koja podržava njezinu tvorničku metodu za korisnike. Nije potreban HTTP paket treće strane; ugrađeni Laravelov klijent je dovoljan.
composer create-project laravel/laravel redesign-preflight
cd redesign-preflight
# .env
WEBSITE_TECH_DETECTOR_TOKEN=YOUR_SERVICE_TOKEN
Dodajte namjenski unos u config/services.php. Zadržavanje endpointa u konfiguraciji također čini testove determinističkima bez raspršivanja URL-ova kroz aplikaciju.
// 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'),
],
Nemojte pozivati env() iz klasa aplikacije. Laravelova predmemorija konfiguracije osmišljena je oko vrijednosti koje se čitaju kroz config().
Održite API granicu malom
Aplikacija ima četiri relevantna dijela:
WebsiteTechnologyDetectorupravlja autentifikacijom, vremenskim ograničenjima, ponovnim pokušajima i rukovanjem HTTP statusima.TechnologyReportpreslikava nepouzdani JSON u stabilan objekt domene.QuotePreflightControllerprovjerava poslani cilj i izlaže nalaze relevantne za ponudu.- Zaštićena ruta s ograničenjem broja zahtjeva sprječava anonimne pozivatelje da troše kvotu paketa.
Ovaj sinkroni dizajn prikladan je za osobu koja traži jedan izvještaj dok priprema ponudu. Red bi uveo postojanost, radnike, status posla i probleme s dupliciranim slanjem bez poboljšanja ovog tijeka rada. Ako izvještaji kasnije postanu skupni uvozi, ista usluga detektora može se pozivati iz idempotentnog posla u redu.
Defenzivno preslikajte odgovor
Udaljeni JSON nije pouzdan objekt domene. Donji mapper prihvaća samo polja, zanemaruje neispravne retke detekcije, čuva strukturirane dokaze i nikada ne pretpostavlja da je pouzdanost postotak. Zbirke detekcija i preusmjeravanja iz ugovora odgovora ostaju ovdje izolirane, pa dokumentirana revizija sheme ima jednu točku prilagodbe.
<?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);
}
}
Nemojte odbaciti prazan popis detekcija: valjana javna stranica može izložiti malo prepoznatljivih dokaza. To se razlikuje od neispravnog odgovora, koji izaziva neuspjeh protokola.
Dodajte ograničene ponovne pokušaje i strukturirane neuspjehe
Neuspjesi veze, HTTP 429 odgovori i odabrane privremene pogreške poslužitelja zaslužuju ograničene ponovne pokušaje. Neuspjesi autentifikacije i provjere valjanosti ne zaslužuju: ponavljanje istog lošeg tokena ili URL-a samo troši vrijeme i stvara šum.
<?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);
}
}
I vremenska ograničenja i granica od tri pokušaja namjerni su. Ograničeno rukovanje Retry-After poštuje numerički savjet poslužitelja bez dopuštanja da jedan zahtjev neograničeno zadržava PHP radnik. Usluga ne bilježi tokene, tijela odgovora, dokaze ni potpune URL-ove klijenata.
Izložite zaštićeni preflight za ponudu
Kontroler prihvaća samo HTTP ili HTTPS URL-ove. Budući da Laravel aplikacija nikada ne dohvaća cilj izravno, ne djeluje kao proxy opće namjene. Unatoč tome, odbacivanje vjerodajnica, naziva localhosta i nejavnih IP literala hvata očite pogreške i zlouporabu. Autentifikacija i ograničavanje rute dodaju važniju granicu kvote.
<?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'] === [],
),
)),
],
]);
}
}
Registrirajte endpoint u 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');
Odgovor procjenitelju daje otkrivene tehnologije, njihove vrijednosti pouzdanosti, verzije, potkrepljujuće dokaze, put preusmjeravanja i izričit popis koji zahtijeva ručni pregled dokaza. Pravila za određivanje cijena trebaju se nalaziti drugdje i ostati izmjenjiva poslovna politika. Primjerice, migracija preusmjeravanja ili hostirana naplata mogu utjecati na opseg, ali nijedno ne bi smjelo potajno dodati novac bez da programer pregleda što dokazi zapravo znače.
Testirajte putanje uspjeha i neuspjeha bez mrežnih poziva
Http::fake() sprječava testove da troše kvotu i čini tvrdnje o ponovnim pokušajima determinističkima. Donji fixture provjerava oblike ugovora koje mapper podržava; produkcijske fixturee držite sanitiziranima i bez podataka klijenata ili vjerodajnica.
<?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();
}
}
Pokrenite skup testova s php artisan test. Dodatni testovi trebali bi obuhvatiti HTTP 429, iznimku veze, neispravan JSON, prazan popis detekcija, preusmjeravanja i anonimni pristup.
Sigurno upravljajte time u produkciji
Pohranite token u upravitelj tajni platforme za implementaciju, a ne u predane .env datoteke. Nakon promjene konfiguracije ponovno izgradite predmemoriju naredbom php artisan config:cache. Implementacije bi trebale rano neuspješno završiti ako token nedostaje; mala provjera konfiguracije pri pokretanju bolja je od otkrivanja problema tijekom sastanka s klijentom.
Pratite strukturirane brojke za uspješne izvještaje, vrste konačnih neuspjeha, uzvodne statusne kodove, ponovne pokušaje i latenciju. Upozorite na kontinuirane neuspjehe autentifikacije jer često ukazuju na opozvani ili neispravno implementirani token. Ponavljane odgovore 429 tretirajte kao signal kapaciteta ili kontrole prometa, a ne kao dopuštenje za dodavanje agresivnih ponovnih pokušaja.
Uobičajeni neuspjesi imaju različita rješenja:
- 401 ili 403: provjerite token ograničen na uslugu, aktivaciju paketa, ubrizgavanje tajne i predmemoriju konfiguracije. Nemojte naslijepo ponavljati pokušaj.
- 400 ili 422: pregledajte provjeru valjanosti URL-a i dokumentirani ugovor zahtjeva. Ponovni pokušaj s nepromijenjenim sadržajem neće pomoći.
- 429: smanjite promet, zadržite ograničavanje rute, poštujte ograničeno kašnjenje ponovnog pokušaja i pregledajte aktivni paket.
- Vremenska ograničenja ili odgovori 5xx: zadržite politiku ograničenih ponovnih pokušaja i pustite procjenitelja da pokuša kasnije umjesto da neograničeno zauzima radnike.
- Neispravan uspješan odgovor: zabilježite neuspjeh protokola bez zapisivanja tijela, usporedite sanitizirani odgovor sa službenom dokumentacijom i namjerno ažurirajte granični mapper.
- Neočekivano oskudne detekcije: ručno pregledajte dostavljene dokaze i javnu stranicu. Odsutnost detekcije nije dokaz da tehnologija nije prisutna.
Završni kontrolni popis za provjeru
- Račun i paket Free, Plus ili Pro aktivni su.
- Token ograničen na uslugu dolazi s ploče Service token na stranici dokumentacije.
- Nijedna vjerodajnica ne pojavljuje se u kontroli izvornog koda, fixtureima, zapisnicima, snimkama zaslona ni URL-ovima.
- Zahtjev koristi točan POST endpoint i šalje JSON koji sadržava
url. - Neuspjesi autentifikacije, provjere valjanosti, kvote, veze, poslužitelja i protokola mogu se razlikovati.
- Vremenska ograničenja veze i odgovora ograničena su, a ponavljaju se samo privremeni neuspjesi.
- Detekcije, pouzdanost, dokazi, verzije i preusmjeravanja preživljavaju preslikavanje domene.
- Ruta zahtijeva autentificiranog korisnika i ima ograničavanje na razini aplikacije.
- Testovi koriste
Http::fake()i ne upućuju stvarne vanjske pozive. - Produkcijska konfiguracija je predmemorirana, a stvarni preflight ručno je pregledan.
Korisna ponuda za redizajn ne nastaje množenjem broja tehnologija sa satnicom. Nastaje pretvaranjem skrivenih pojedinosti implementacije u izričita pitanja: što se mora migrirati, što se može sačuvati, što treba provjeriti i gdje su dokazi slabi. Automatiziranje tog prvog prolaza daje programerima više vremena za te procjene — a klijentima ponudu utemeljenu na web-mjestu koje doista imaju.