Laravel CRM: Obogatite potencijalne klijente sažecima tehnološkog stoga web-stranica
Web-mjesto potencijalnog klijenta često otkriva više od slobodnog polja „industrija”. Sažet pregled tehnološkog stoga poput „WordPress 6.5, WooCommerce, Cloudflare” može pomoći agenciji da usmjeri prilike, pripremi uvodne pozive i prepozna vjerojatne potrebe održavanja prije nego što itko otvori razvojne alate.
Korisna verzija ove značajke nije sinkroni API poziv skriven unutar zahtjeva za stranicu. Analiza web-mjesta može biti spora, ograničena stopom zahtjeva ili privremeno nedostupna. Produkcijski CRM trebao bi staviti posao u red čekanja, validirati nesiguran vanjski odgovor na jednoj granici, sačuvati korisne dokaze i prikazati čitljiv rezultat bez narušavanja stabilnosti zaslona potencijalnog klijenta.
Ovaj vodič izrađuje taj potpuni tijek u Laravelu i PHP-u 8.3 ili novijem.
Preduvjeti i dovršena arhitektura
Započnite s postojećim Laravel CRM-om koji sadrži model Lead i stupac website_url. Također su vam potrebni PHP 8.3 ili noviji, konfigurirana baza podataka, funkcionalan Laravel red čekanja i dijeljena predmemorija ako aplikacija radi na više poslužitelja.
Integracija ima pet malih odgovornosti:
- Kontroler autorizira skeniranje i šalje pozadinski zadatak.
- Zadatak upravlja stanjem životnog ciklusa i ponašanjem ponovnih pokušaja reda čekanja.
- Namjenska HTTP usluga poziva detektor s ograničenim vremenskim ograničenjima.
- Domenski objekt validira detekcije, pouzdanost, dokaze, verzije i informacije o preusmjeravanju.
- Potencijalni klijent pohranjuje i čitljiv sažetak i strukturirane dokaze za kasniji pregled.
Ovo je namjerno skromna arhitektura. Vanjska granica zaslužuje izolaciju, ali malom CRM-u ne treba sabirnica događaja ni zasebna mikroservisna usluga za jednu operaciju obogaćivanja.
Pribavite pristup prije pisanja integracijskog koda
- Izradite račun na https://ai.mihajlo.mk/register ili se prijavite na https://ai.mihajlo.mk/login.
- 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 ploču Service token i kopirajte token ograničen na uslugu.
Ova usluga zahtijeva autentikaciju. Prihvaća Bearer token, zaglavlje X-API-Token ili parametar upita token. Dajte prednost zaglavlju Bearer: parametri upita vjerojatnije će se pojaviti u zapisnicima proxyja, preglednika i pristupa.
Ponovno generiranje tokena usluge opoziva prethodno aktivan token. Rotaciju tretirajte kao promjenu pri implementaciji: ažurirajte tajnu aplikacije, ponovno pokrenite workere, provjerite zahtjev i tek tada smatrajte rotaciju dovršenom.
Potvrdite točan endpoint
API poziv je POST https://ai.mihajlo.mk/api/website-technology-detector/v1/detect-technologies. Prihvaća JSON objekt koji sadrži url. Napravite jedan minimalan zahtjev prije integracije Laravela:
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"}'
Ne predajte token u repozitorij. Stavite ga u okruženje implementacije ili u nepraćenu datoteku .env projekta:
WEBSITE_TECH_TOKEN=YOUR_SERVICE_TOKEN
WEBSITE_TECH_ENDPOINT=https://ai.mihajlo.mk/api/website-technology-detector/v1/detect-technologies
QUEUE_CONNECTION=database
Izložite te vrijednosti kroz config/services.php. Aplikacijski kod treba čitati konfiguraciju, a nikada izravno pozivati env():
<?php
return [
// Existing services...
'website_technology_detector' => [
'token' => env('WEBSITE_TECH_TOKEN'),
'endpoint' => env(
'WEBSITE_TECH_ENDPOINT',
'https://ai.mihajlo.mk/api/website-technology-detector/v1/detect-technologies'
),
],
];
Dodajte stanje obogaćivanja potencijalnim klijentima
Izradite integracijske klase i migraciju:
php artisan make:migration add_technology_enrichment_to_leads_table
php artisan make:job EnrichLeadTechnology
php artisan make:controller LeadTechnologyController
php artisan make:test LeadTechnologyEnrichmentTest
Migracija drži sažetak namijenjen korisnicima odvojenim od strukturiranih podataka detektora. Stupci statusa i pogreške čine asinkrone neuspjehe vidljivima bez preopterećivanja zapisnika aplikacije.
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::table('leads', function (Blueprint $table): void {
$table->string('technology_status', 24)->default('not_scanned');
$table->text('technology_summary')->nullable();
$table->json('technology_report')->nullable();
$table->text('technology_error')->nullable();
$table->timestamp('technology_checked_at')->nullable();
$table->index('technology_status');
});
}
public function down(): void
{
Schema::table('leads', function (Blueprint $table): void {
$table->dropIndex(['technology_status']);
$table->dropColumn([
'technology_status',
'technology_summary',
'technology_report',
'technology_error',
'technology_checked_at',
]);
});
}
};
Dodajte nove atribute postojećoj politici masovnog dodjeljivanja i pretvorbama modela:
protected $fillable = [
// Existing lead fields...
'technology_status',
'technology_summary',
'technology_report',
'technology_error',
'technology_checked_at',
];
protected function casts(): array
{
return [
'technology_report' => 'array',
'technology_checked_at' => 'immutable_datetime',
];
}
Validirajte odgovor na granici
Vanjski JSON je nepouzdan ulaz čak i kada je usluga pouzdana. Detektor vraća tehnologije s ocjenom pouzdanosti, zajedno s dokazima, verzijama i informacijama o preusmjeravanju, ali aplikacija i dalje mora odbaciti neispravne oblike te tolerirati neupotrebljive pojedinačne unose.
Izradite app/Domain/Technology/DetectionReport.php:
<?php
namespace App\Domain\Technology;
use UnexpectedValueException;
final readonly class DetectionReport
{
public function __construct(
public array $detections,
public array $redirect,
) {}
public static function fromApi(array $payload): self
{
if (!isset($payload['detections']) || !is_array($payload['detections'])) {
throw new UnexpectedValueException('Missing detections array.');
}
$detections = [];
foreach ($payload['detections'] as $item) {
if (!is_array($item)) {
continue;
}
$name = $item['name'] ?? null;
$confidence = $item['confidence'] ?? null;
if (!is_string($name) || $name === '' || !is_numeric($confidence)) {
continue;
}
$versions = $item['versions'] ?? [];
$evidence = $item['evidence'] ?? [];
$detections[] = [
'name' => $name,
'confidence' => (float) $confidence,
'versions' => is_array($versions)
? array_values(array_filter($versions, 'is_string'))
: [],
'evidence' => is_array($evidence) ? $evidence : [],
];
}
$redirect = $payload['redirect'] ?? [];
return new self(
detections: $detections,
redirect: is_array($redirect) ? $redirect : [],
);
}
public function summary(): string
{
if ($this->detections === []) {
return 'No technologies identified.';
}
$items = $this->detections;
usort(
$items,
fn (array $a, array $b): int =>
$b['confidence'] <=> $a['confidence']
);
return implode(', ', array_map(function (array $item): string {
$version = $item['versions'][0] ?? null;
return $version ? "{$item['name']} {$version}" : $item['name'];
}, $items));
}
public function toArray(): array
{
return [
'detections' => $this->detections,
'redirect' => $this->redirect,
];
}
}
Aplikacija ne nameće prag pouzdanosti jer dostavljeni ugovor ne definira koriste li se ocjene na ljestvici od nula do jedan ili u postocima. Sortira ih numerički, zadržava ocjenu i politiku praga ostavlja za dokumentiranu poslovnu odluku.
Izradite ograničeni Laravel HTTP klijent
Izradite app/Services/TechnologyDetector.php. Usluga dopušta jedan neposredan ponovni pokušaj za neuspjele veze i pogreške poslužitelja. Ne pokušava ponovno autentikaciju, validaciju ni odgovore ograničenja stope unutar istog pokušaja workera.
<?php
namespace App\Services;
use App\Domain\Technology\DetectionReport;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\Facades\Http;
use Throwable;
use UnexpectedValueException;
final class TechnologyDetectorException extends \RuntimeException
{
public function __construct(
string $message,
public readonly bool $retryable,
?Throwable $previous = null,
) {
parent::__construct($message, 0, $previous);
}
}
final class TechnologyDetector
{
public function detect(string $url): DetectionReport
{
$parts = parse_url($url);
$scheme = strtolower((string) ($parts['scheme'] ?? ''));
if (
!filter_var($url, FILTER_VALIDATE_URL)
|| !in_array($scheme, ['http', 'https'], true)
|| empty($parts['host'])
|| isset($parts['user'])
|| isset($parts['pass'])
) {
throw new TechnologyDetectorException(
'The lead website URL is invalid.',
false
);
}
$token = (string) config('services.website_technology_detector.token');
$endpoint = (string) config('services.website_technology_detector.endpoint');
if ($token === '' || $endpoint === '') {
throw new TechnologyDetectorException(
'Detector configuration is missing.',
false
);
}
for ($attempt = 1; $attempt <= 2; $attempt++) {
try {
$response = Http::acceptJson()
->asJson()
->withToken($token)
->connectTimeout(3)
->timeout(12)
->post($endpoint, ['url' => $url]);
} catch (ConnectionException $exception) {
if ($attempt === 1) {
usleep(250_000);
continue;
}
throw new TechnologyDetectorException(
'Detector connection failed.',
true,
$exception
);
}
if ($response->successful()) {
$payload = $response->json();
if (!is_array($payload)) {
throw new TechnologyDetectorException(
'Detector returned invalid JSON.',
false
);
}
try {
return DetectionReport::fromApi($payload);
} catch (UnexpectedValueException $exception) {
throw new TechnologyDetectorException(
'Detector response did not match its contract.',
false,
$exception
);
}
}
if ($response->status() === 429) {
throw new TechnologyDetectorException(
'Detector rate limit reached.',
true
);
}
if ($response->serverError()) {
if ($attempt === 1) {
usleep(250_000);
continue;
}
throw new TechnologyDetectorException(
'Detector server error.',
true
);
}
throw new TechnologyDetectorException(
"Detector rejected the request with HTTP {$response->status()}.",
false
);
}
throw new TechnologyDetectorException('Detector request failed.', true);
}
}
Tijelo odgovora i vjerodajnica nikada ne ulaze u iznimke ni zapisnike. Pogreška 401, 403 ili druga klijentska pogreška trajna je za taj pokušaj zadatka; slijepo ponovno pokušavanje troši kvotu i prikriva konfiguracijske pogreške.
Pokrenite obogaćivanje izvan putanje zahtjeva
Izradite app/Jobs/EnrichLeadTechnology.php:
<?php
namespace App\Jobs;
use App\Models\Lead;
use App\Services\TechnologyDetector;
use App\Services\TechnologyDetectorException;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\Log;
use Throwable;
final class EnrichLeadTechnology implements ShouldQueue, ShouldBeUnique
{
use Queueable;
public int $tries = 3;
public int $timeout = 30;
public int $uniqueFor = 600;
public array $backoff = [60, 300];
public function __construct(public readonly int $leadId) {}
public function uniqueId(): string
{
return (string) $this->leadId;
}
public function handle(TechnologyDetector $detector): void
{
$lead = Lead::query()->findOrFail($this->leadId);
$lead->update([
'technology_status' => 'scanning',
'technology_error' => null,
]);
try {
$report = $detector->detect($lead->website_url);
} catch (TechnologyDetectorException $exception) {
if ($exception->retryable) {
Log::warning('Lead technology enrichment will retry.', [
'lead_id' => $lead->id,
'attempt' => $this->attempts(),
]);
throw $exception;
}
$lead->update([
'technology_status' => 'failed',
'technology_error' => $exception->getMessage(),
]);
return;
}
$lead->update([
'technology_status' => 'complete',
'technology_summary' => $report->summary(),
'technology_report' => $report->toArray(),
'technology_error' => null,
'technology_checked_at' => now(),
]);
}
public function failed(?Throwable $exception): void
{
Lead::query()->whereKey($this->leadId)->update([
'technology_status' => 'failed',
'technology_error' => 'Technology detection is temporarily unavailable.',
]);
}
}
Neposredni HTTP ponovni pokušaji i ponovni pokušaji reda čekanja služe različitim neuspjesima. Kratak drugi pokušaj ublažava prekinutu vezu; odgođeni pokušaji reda čekanja rješavaju dulje prekide rada i ograničenja stope. Gornja ograničenja ograničavaju trajnu pogrešku poslužitelja na šest HTTP poziva, a trajni 429 na tri.
Autorizirajte i pošaljite skeniranja
Izradite app/Http/Controllers/LeadTechnologyController.php:
<?php
namespace App\Http\Controllers;
use App\Jobs\EnrichLeadTechnology;
use App\Models\Lead;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Gate;
final class LeadTechnologyController extends Controller
{
public function store(Lead $lead): RedirectResponse
{
Gate::authorize('update', $lead);
$lead->update([
'technology_status' => 'queued',
'technology_error' => null,
]);
EnrichLeadTechnology::dispatch($lead->id)
->onQueue('integrations')
->afterCommit();
return back()->with('status', 'Technology scan queued.');
}
}
Registrirajte zaštićenu rutu u routes/web.php:
use App\Http\Controllers\LeadTechnologyController;
use Illuminate\Support\Facades\Route;
Route::post(
'/leads/{lead}/technology-scan',
[LeadTechnologyController::class, 'store']
)->middleware(['auth', 'throttle:20,1'])
->name('leads.technology.scan');
Uobičajeni međusoftver web pruža CSRF zaštitu. Autorizacija sprječava da jedan račun skenira potencijalne klijente drugog računa, dok ograničavanje sprječava slučajno opetovano klikanje gumba. Jedinstveno zaključavanje zadatka dodaje još jednu zaštitu; upotrijebite dijeljeni upravljački program predmemorije kada više aplikacijskih čvorova obrađuje red čekanja.
Testirajte uspjeh i trajni neuspjeh
Laravelov Http::fake() održava testove determinističkima i dokazuje da nisu potrebni stvarna vjerodajnica ni mrežna veza. Dodajte ove slučajeve u tests/Feature/LeadTechnologyEnrichmentTest.php:
<?php
namespace Tests\Feature;
use App\Jobs\EnrichLeadTechnology;
use App\Models\Lead;
use App\Services\TechnologyDetector;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;
final class LeadTechnologyEnrichmentTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
config()->set(
'services.website_technology_detector.token',
'test-token'
);
config()->set(
'services.website_technology_detector.endpoint',
'https://ai.mihajlo.mk/api/website-technology-detector/v1/detect-technologies'
);
}
public function test_it_saves_a_readable_summary_and_evidence(): void
{
Http::fake([
'https://ai.mihajlo.mk/*' => Http::response([
'detections' => [
[
'name' => 'WordPress',
'confidence' => 0.98,
'versions' => ['6.5'],
'evidence' => ['generator metadata'],
],
[
'name' => 'Cloudflare',
'confidence' => 0.91,
'versions' => [],
'evidence' => ['response headers'],
],
],
'redirect' => [],
], 200),
]);
$lead = Lead::factory()->create([
'website_url' => 'https://example.com',
]);
(new EnrichLeadTechnology($lead->id))
->handle(app(TechnologyDetector::class));
$lead->refresh();
$this->assertSame('complete', $lead->technology_status);
$this->assertSame(
'WordPress 6.5, Cloudflare',
$lead->technology_summary
);
$this->assertCount(
2,
$lead->technology_report['detections']
);
Http::assertSent(fn ($request): bool =>
$request->hasHeader('Authorization', 'Bearer test-token')
&& $request['url'] === 'https://example.com'
);
}
public function test_it_does_not_retry_an_authentication_failure(): void
{
Http::fake([
'https://ai.mihajlo.mk/*' => Http::response([], 401),
]);
$lead = Lead::factory()->create([
'website_url' => 'https://example.com',
]);
(new EnrichLeadTechnology($lead->id))
->handle(app(TechnologyDetector::class));
$this->assertSame(
'failed',
$lead->fresh()->technology_status
);
Http::assertSentCount(1);
}
}
Implementirajte, pratite i otklonite poteškoće
Pokrenite migraciju i testove, predmemorirajte produkcijsku konfiguraciju te pokrenite nadzirani worker:
php artisan migrate --force
php artisan test
php artisan config:cache
php artisan queue:work --queue=integrations --sleep=3 --tries=3 --timeout=40 --max-time=3600
Upotrijebite systemd, Supervisor ili upravitelj procesa koji pruža hosting platforma za ponovno pokretanje workera nakon neuspjeha i tijekom implementacije. Workeri reda čekanja dugotrajni su: nakon promjene tokena ili konfiguracije ponovno izgradite predmemoriju konfiguracije i ponovno ih pokrenite s php artisan queue:restart.
Pratite broj i starost potencijalnih klijenata sa statusima queued, scanning, complete i failed. Postavite upozorenje za rastući red integracija ili trajan porast neuspjeha. Zapisnici trebaju uključivati ID-ove potencijalnih klijenata, pokušaje, kategorije HTTP statusa i trajanja, ali nikada autorizacijska zaglavlja, potpuna tijela odgovora ni tokene usluge.
Uobičajeni obrasci neuspjeha
- Svaki zahtjev vraća 401 ili 403: potvrdite aktivaciju plana, token ograničen na uslugu i Bearer zaglavlje. Ponovno generirani token odmah poništava prethodno aktivan token.
- Zahtjevi ostaju u redu čekanja: provjerite obrađuje li worker red
integrationsi upotrebljava li istu konfiguraciju reda čekanja kao web proces. - Promjene konfiguracije nemaju učinka: ponovno izgradite Laravelovu predmemoriju konfiguracije i ponovno pokrenite dugotrajne workere.
- Odgovori ne prolaze mapiranje: usporedite payload sa službenom dokumentacijom. Ažurirajte samo mapper na granici; kontroleri, zadaci i pohranjeni domenski podaci trebaju ostati stabilni.
- Ograničenja stope se ponavljaju: smanjite učestalost skeniranja, izbjegavajte automatska ponovna skeniranja pri svakoj izmjeni potencijalnog klijenta i pregledajte aktivni plan umjesto povećavanja neposrednih ponovnih pokušaja.
- Duplicirani zadaci pojavljuju se na više poslužitelja: konfigurirajte upravljački program predmemorije koji dijele svi čvorovi kako bi
ShouldBeUniqueupotrebljavao jedno spremište zaključavanja.
Završni kontrolni popis za provjeru
- Token postoji samo u konfiguraciji podržanoj okruženjem i pohrani tajni.
- Aplikacija šalje zahtjeve
POSTna točan endpoint detektora s JSON-omurl. - Samo ovlašteni korisnici CRM-a mogu staviti skeniranja u red čekanja.
- Vremenska ograničenja HTTP veze i odgovora su ograničena.
- Neuspjesi autentikacije i validacije ne pokušavaju se slijepo ponovno.
- Ograničenja stope i privremeni neuspjesi poslužitelja prolaze kroz ograničene ponovne pokušaje reda čekanja.
- Detekcije, pouzdanost, verzije, dokazi i informacije o preusmjeravanju validiraju se prije pohrane.
- Zaslon potencijalnog klijenta može prikazati
technology_summarybez razumijevanja vanjskog payloada. - Workeri se ponovno pokreću nakon implementacija i rotacije tajni.
- Testovi koriste
Http::fake()i ne sadrže produkcijsku vjerodajnicu.
Trajna pouka veća je od detekcije tehnologije: obogaćivanje treba unaprijediti CRM bez toga da postane nova točka neuspjeha. Zadržite odgovor dobavljača na granici, životni ciklus reda čekanja eksplicitnim, a rezultat za potencijalnog klijenta ugodno jednostavnim. Tada „WordPress 6.5, Cloudflare” postaje koristan operativni kontekst, a ne još jedno nepouzdano polje oblikovano kao API.