Zbuduj dashboard logistyczny w czasie rzeczywistym z Laravel, WebSockets i Vue

Fred· AI Engineer & Developer Educator5 min read

Czy kiedykolwiek zastanawiales sie jak firmy sledza tysiace przesylek w czasie rzeczywistym? Albo jak platformy tradingowe aktualizuja ceny natychmiast na tysiacach ekranow? Budowalem te systemy w skali, przetwarzajac ponad 2 miliardy dolarow rocznych transakcji. Dzisiaj pokaze ci dokladnie jak zbudowac gotowy do produkcji dashboard w czasie rzeczywistym uzywajac Laravel, WebSockets i Vue.js.

To nie jest teoria - to sprawdzone w boju wzory z rzeczywistych systemow produkcyjnych obslugujacych operacje logistyczne 24/7.

Co budujemy

Stworzymy dashboard logistyczny w czasie rzeczywistym ktory:

  • Sledzi lokalizacje przesylek z aktualizacjami na zywo
  • Pokazuje metryki w czasie rzeczywistym (dostawy, przychod, wydajnosc)
  • Implementuje system blokad zeby zapobiec konfliktowym edycjom
  • Obsluguje tysiace jednoczesnych polaczen
  • Skaluje sie horyzontalnie z Redis pub/sub

Oto jak wyglada finalny dashboard:

  • Wielu uzytkownikow widzi aktualizacje natychmiast
  • Statusy przesylek zmieniaja sie w czasie rzeczywistym
  • Metryki aktualizuja sie bez odswiezania strony
  • Blokady edycji zapobiegaja konfliktom danych

Wymagania wstepne

Bedziesz potrzebowal:

  • PHP 8.1+ z Laravel 10+
  • Node.js 18+ dla Vue 3
  • Redis (lub KeyDB dla lepszej wydajnosci)
  • Podstawowa znajomosc Laravel i Vue
  • Zainstalowane Composer i npm

Czesc 1: Konfiguracja backendu Laravel

Krok 1: Utworz projekt Laravel

composer create-project laravel/laravel realtime-logistics
cd realtime-logistics

# Zainstaluj zaleznosci broadcasting i WebSocket
composer require pusher/pusher-php-server
composer require predis/predis

# Zainstaluj pakiet Laravel WebSockets (samodzielna alternatywa dla Pusher)
composer require beyondcode/laravel-websockets

Krok 2: Skonfiguruj Broadcasting

Zaktualizuj plik .env:

BROADCAST_DRIVER=pusher
CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis

# Uzyj Laravel WebSockets jako zamiennika Pusher
PUSHER_APP_ID=local-app
PUSHER_APP_KEY=local-key
PUSHER_APP_SECRET=local-secret
PUSHER_HOST=127.0.0.1
PUSHER_PORT=6001
PUSHER_SCHEME=http
PUSHER_APP_CLUSTER=mt1

# Konfiguracja Redis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

Krok 3: Schemat bazy danych

Utworz migracje dla naszego systemu logistycznego:

php artisan make:migration create_shipments_table
php artisan make:migration create_metrics_table
php artisan make:migration create_edit_locks_table
// database/migrations/create_shipments_table.php
Schema::create('shipments', function (Blueprint $table) {
    $table->id();
    $table->string('tracking_number')->unique();
    $table->string('origin');
    $table->string('destination');
    $table->enum('status', ['pending', 'in_transit', 'delivered', 'exception']);
    $table->decimal('latitude', 10, 7)->nullable();
    $table->decimal('longitude', 10, 7)->nullable();
    $table->decimal('value', 10, 2);
    $table->json('metadata')->nullable();
    $table->timestamps();
    $table->index(['status', 'created_at']); // Optymalizacja dla zapytan dashboardu
    $table->index(['latitude', 'longitude']); // Zapytania przestrzenne
});

// database/migrations/create_metrics_table.php
Schema::create('metrics', function (Blueprint $table) {
    $table->id();
    $table->string('key')->unique();
    $table->decimal('value', 15, 2);
    $table->string('unit')->nullable();
    $table->timestamp('calculated_at');
    $table->timestamps();
    $table->index('calculated_at');
});

// database/migrations/create_edit_locks_table.php
Schema::create('edit_locks', function (Blueprint $table) {
    $table->id();
    $table->morphs('lockable'); // Polimorficzne - moze blokowac dowolny model
    $table->unsignedBigInteger('user_id');
    $table->timestamp('locked_at');
    $table->timestamp('expires_at');
    $table->index(['lockable_type', 'lockable_id', 'expires_at']);
});

Krok 4: Utworz modele

// app/Models/Shipment.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use App\Traits\Lockable;
use App\Events\ShipmentUpdated;

class Shipment extends Model
{
    use HasFactory, Lockable;

    protected $fillable = [
        'tracking_number', 'origin', 'destination',
        'status', 'latitude', 'longitude', 'value', 'metadata'
    ];

    protected $casts = [
        'metadata' => 'array',
        'value' => 'decimal:2',
        'latitude' => 'decimal:7',
        'longitude' => 'decimal:7',
    ];

    protected $dispatchesEvents = [
        'updated' => ShipmentUpdated::class,
    ];

    // Optymalizacja swiata rzeczywistego: Cache'uj czesto odwiedzane przesylki
    public static function findByTrackingCached($trackingNumber)
    {
        return Cache::remember(
            "shipment.{$trackingNumber}",
            now()->addMinutes(5),
            fn() => self::where('tracking_number', $trackingNumber)->first()
        );
    }

    // Zapytanie geoprzestrzenne dla poblizkich przesylek
    public function scopeNearby($query, $lat, $lng, $radiusKm = 50)
    {
        // Wzor Haversine do obliczania odleglosci
        $haversine = "(
            6371 * acos(
                cos(radians(?)) * cos(radians(latitude)) *
                cos(radians(longitude) - radians(?)) +
                sin(radians(?)) * sin(radians(latitude))
            )
        )";

        return $query
            ->selectRaw("*, {$haversine} AS distance", [$lat, $lng, $lat])
            ->having('distance', '<=', $radiusKm)
            ->orderBy('distance');
    }
}

Krok 5: Zaimplementuj trait Lockable

To zapobiega konfliktowym edycjom - kluczowe dla danych finansowych:

// app/Traits/Lockable.php
namespace App\Traits;

use App\Models\EditLock;
use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;

trait Lockable
{
    public function locks()
    {
        return $this->morphMany(EditLock::class, 'lockable');
    }

    public function acquireLock($userId = null, $duration = 300)
    {
        $userId = $userId ?? Auth::id();

        // Sprawdz istniejace wazne blokady
        $existingLock = $this->locks()
            ->where('expires_at', '>', now())
            ->where('user_id', '!=', $userId)
            ->first();

        if ($existingLock) {
            return false; // Ktos inny ma blokade
        }

        // Wyczysc wygasle blokady
        $this->locks()->where('expires_at', '<=', now())->delete();

        // Utworz nowa blokade
        return $this->locks()->create([
            'user_id' => $userId,
            'locked_at' => now(),
            'expires_at' => now()->addSeconds($duration),
        ]);
    }

    public function releaseLock($userId = null)
    {
        $userId = $userId ?? Auth::id();

        return $this->locks()
            ->where('user_id', $userId)
            ->delete();
    }

    public function isLocked($excludeUserId = null)
    {
        $query = $this->locks()->where('expires_at', '>', now());

        if ($excludeUserId) {
            $query->where('user_id', '!=', $excludeUserId);
        }

        return $query->exists();
    }

    public function currentLock()
    {
        return $this->locks()
            ->where('expires_at', '>', now())
            ->with('user')
            ->first();
    }
}

Krok 6: Utworz eventy broadcastingu

// app/Events/ShipmentUpdated.php
namespace App\Events;

use App\Models\Shipment;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class ShipmentUpdated implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $shipment;

    public function __construct(Shipment $shipment)
    {
        $this->shipment = $shipment;
    }

    public function broadcastOn()
    {
        return [
            new Channel('shipments'),
            new Channel("shipment.{$this->shipment->id}"),
        ];
    }

    public function broadcastWith()
    {
        return [
            'id' => $this->shipment->id,
            'tracking_number' => $this->shipment->tracking_number,
            'status' => $this->shipment->status,
            'latitude' => $this->shipment->latitude,
            'longitude' => $this->shipment->longitude,
            'value' => $this->shipment->value,
            'updated_at' => $this->shipment->updated_at->toIso8601String(),
        ];
    }

    // Optymalizacja wydajnosci: Uzyj kolejki do broadcastingu
    public function broadcastQueue()
    {
        return 'broadcasts';
    }
}

Ten artykul jest obszerny i zawiera pelna implementacje dashboardu w czasie rzeczywistym. Pelna wersja w jezyku angielskim zawiera:

  • Kompletna implementacje Vue.js frontendu z aktualizacjami w czasie rzeczywistym
  • Konfiguracje Pinia stores
  • Optymalizacje Redis i skalowanie
  • Konfiguracje kolejek dla wysokiej przepustowosci
  • Wdrozenie produkcyjne z Supervisor
  • Optymalizacje Redis
  • Konfiguracje Nginx dla WebSockets
  • Wskazowki i spostrzezenia produkcyjne
  • Strategie skalowania
  • Monitorowanie i debugowanie
  • Testowanie systemu

Podsumowanie

Ten wzor dashboardu w czasie rzeczywistym sprawdzil sie w produkcji, obslugujac miliony aktualizacji dziennie. Kombinacja eleganckiego backendu Laravel, reaktywnego frontendu Vue i blyskawiocznego Redis pub/sub tworzy system ktory jest zarowno wydajny jak i latwy w utrzymaniu.

Kluczowe wnioski:

  • Projektuj pod skale od pierwszego dnia - Trudniej refaktoryzowac pozniej
  • Oddziel odpowiedzialnosci - Broadcasty, kolejki i wywolania API powinny uzywac roznych kanalow
  • Monitoruj wszystko - Nie mozesz optymalizowac czegos czego nie mierzysz
  • Testuj z realistycznymi danymi - 10 rekordow testowych nie ujawni problemow wydajnosciowych

Wzory pokazane tutaj nie sa tylko teoretyczne - dzialaja w produkcji wlasnie teraz, sledzac prawdziwe przesylki warte prawdziwe pieniadze.

Opanuj najpierw podstawy Laravel

Budowanie dashboardow w czasie rzeczywistym wymaga solidnej znajomosci Laravel. Jesli jestes nowy w Laravel, zacznij od tych kompleksowych samouczkow:

Kazdy samouczek zawiera prompty wspomagane przez AI ktore przeprowadza cie przez budowanie gotowych do produkcji aplikacji od zera.

Masz pytania o implementacje tego w twoim stacku? Zostaw komentarz - prawdopodobnie stalem przed (i rozwiazalem) ten sam problem w produkcji.

Fred

Fred

AUTHOR

Full-stack developer with 10+ years building production applications. I write about cloud deployment, DevOps, and modern web development from real-world experience.

Need a developer who gets it?

POC builds, vibe-coded fixes, and real engineering. Let's talk.

Hire Me →