Реалізація Real-Time трекінгу кур'єра/замовлення на сайті

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація Real-Time трекінгу кур'єра/замовлення на сайті
Середня
~5 робочих днів
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Реалізація трекінгу кур'єра/замовлення в реальному часі на веб-сайті

Користувач оформив замовлення та чекає кур'єра. Сторінка «Мої замовлення» з полем «статус: у дорозі» — це вже застарілий метод. Сучасний стандарт — карта з живим маркером кур'єра та лічильником «прибуде через N хвилин». Технічно це поєднання трьох компонентів: мобільне пристрій/пристрій кур'єра, backend-приложення, клієнтський браузер.

Архітектура потоку даних

[Пристрій кур'єра]
    GPS → POST /api/courier/location кожні 3–5 сек
        ↓
[Backend]
    Зберегти в Redis (TTL 30s)
    Опублікувати в Redis Pub/Sub канал order:{id}
        ↓
[WebSocket сервер (Laravel Reverb / Pusher)]
    Broadcast event LocationUpdated
        ↓
[Браузер клієнта]
    Оновити маркер на карті

Геопозиції не зберігаються в PostgreSQL при кожному оновленні — це 720 записів на годину на одного кур'єра. Пишемо в базу лише при зміні статусу замовлення та кінцеву позицію при завершенні. Поточна позиція — у Redis з TTL.

Таблиця замовлень

CREATE TABLE delivery_orders (
    id             BIGSERIAL PRIMARY KEY,
    user_id        BIGINT NOT NULL REFERENCES users(id),
    courier_id     BIGINT REFERENCES couriers(id),
    status         VARCHAR(50) NOT NULL DEFAULT 'pending',
                   -- pending | assigned | picked_up | in_transit | delivered | failed
    address_lat    DECIMAL(10, 8),
    address_lng    DECIMAL(11, 8),
    address_text   VARCHAR(500),
    estimated_at   TIMESTAMP,
    delivered_at   TIMESTAMP,
    created_at     TIMESTAMP NOT NULL DEFAULT NOW()
);

CREATE TABLE delivery_status_log (
    id         BIGSERIAL PRIMARY KEY,
    order_id   BIGINT NOT NULL REFERENCES delivery_orders(id),
    status     VARCHAR(50) NOT NULL,
    lat        DECIMAL(10, 8),
    lng        DECIMAL(11, 8),
    note       TEXT,
    created_at TIMESTAMP NOT NULL DEFAULT NOW()
);

API кур'єра: оновлення позиції

Ендпоінт викликається з пристрою кур'єра кожні 3–5 секунд:

class CourierLocationController extends Controller
{
    public function update(Request $request, DeliveryOrder $order): JsonResponse
    {
        $data = $request->validate([
            'lat' => 'required|numeric|between:-90,90',
            'lng' => 'required|numeric|between:-180,180',
        ]);

        // Поточна позиція — тільки в Redis, з TTL 60 секунд
        $key = "courier_location:{$order->courier_id}";
        Redis::setex($key, 60, json_encode([
            'lat'      => $data['lat'],
            'lng'      => $data['lng'],
            'order_id' => $order->id,
            'ts'       => now()->timestamp,
        ]));

        // Broadcast клієнту замовлення
        broadcast(new CourierLocationUpdated(
            orderId: $order->id,
            lat:     $data['lat'],
            lng:     $data['lng'],
            eta:     $this->calculateEta($order, $data['lat'], $data['lng']),
        ));

        return response()->json(['ok' => true]);
    }

    private function calculateEta(DeliveryOrder $order, float $lat, float $lng): ?int
    {
        // Приблизний розрахунок по прямій — 30 км/год середня швидкість в місті
        $distanceKm = $this->haversineKm($lat, $lng, $order->address_lat, $order->address_lng);
        return (int) round($distanceKm / 30 * 60); // хвилини
    }
}

Laravel Event

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

    public function __construct(
        public readonly int    $orderId,
        public readonly float  $lat,
        public readonly float  $lng,
        public readonly ?int   $eta,
    ) {}

    public function broadcastOn(): Channel
    {
        return new PrivateChannel("order.{$this->orderId}");
    }

    public function broadcastWith(): array
    {
        return [
            'lat' => $this->lat,
            'lng' => $this->lng,
            'eta' => $this->eta,
        ];
    }
}

PrivateChannel — клієнт повинен бути авторизованим для підписки. Це виключає ситуацію, коли сторонній користувач може підписатися на канал чужого замовлення.

Авторизація каналу

// routes/channels.php
Broadcast::channel('order.{orderId}', function (User $user, int $orderId) {
    return $user->id === DeliveryOrder::find($orderId)?->user_id;
});

Клієнтська частина: карта

import mapboxgl from 'mapbox-gl';

mapboxgl.accessToken = 'pk.eyJ...';
const map = new mapboxgl.Map({
    container: 'map',
    style: 'mapbox://styles/mapbox/streets-v12',
    center: [orderLng, orderLat],
    zoom: 14,
});

// Маркер адреси доставки
new mapboxgl.Marker({ color: '#EF4444' })
    .setLngLat([orderLng, orderLat])
    .addTo(map);

// Маркер кур'єра
const courierMarker = new mapboxgl.Marker({ color: '#3B82F6' })
    .setLngLat([initialLng, initialLat])
    .addTo(map);

// WebSocket підписка
Echo.private(`order.${orderId}`)
    .listen('CourierLocationUpdated', ({ lat, lng, eta }) => {
        courierMarker.setLngLat([lng, lat]);
        if (eta !== null) {
            document.getElementById('eta').textContent =
                eta < 2 ? 'Кур\'єр уже рядом' : `Прибуде через ~${eta} хв`;
        }
    });

Альтернатива Mapbox — Yandex Maps API або Google Maps Platform. Для CIS країн Яндекс переважний за якістю геокодування та покриттям.

Плавне переміщення маркера

Різкі стрибки маркера кожні 3–5 секунд виглядають грубо. Рішення — анімація через requestAnimationFrame:

function animateMarker(marker, from, to, duration = 500) {
    const start = performance.now();
    function step(now) {
        const t = Math.min((now - start) / duration, 1);
        const ease = t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t; // easeInOut
        const lng = from[0] + (to[0] - from[0]) * ease;
        const lat = from[1] + (to[1] - from[1]) * ease;
        marker.setLngLat([lng, lat]);
        if (t < 1) requestAnimationFrame(step);
    }
    requestAnimationFrame(step);
}

Push-сповіщення при зміні статусу

Крім живого трекінгу, клієнти потребують сповіщень про переходи: «кур'єр забрав замовлення», «кур'єр за 5 хвилин», «замовлення доставлено». Реалізується через Web Push API або SMS:

// У обробнику події OrderStatusChanged
if ($order->status === 'in_transit') {
    $order->user->notify(new CourierPickedUpNotification($order));
}

Офлайн-режим кур'єра

Якщо пристрій кур'єра втрачає з'єднання — на стороні мобільного приложення накопичується буфер координат, який відправляється пачкою при відновленні з'єднання. Backend приймає масив точок з часовими мітками та відтворює анімацію шляху, а не перепригує на кінцеву позицію.

Терміни

  • Базовий трекінг (Redis + broadcast + карта): 4–5 днів
  • Авторизація Private Channel + логіка доступу: 1 день
  • ETA-розрахунок по прямій: 0.5 дня
  • ETA через Routing API (OSRM / Google Directions): +1–2 дні
  • Анімація маркера + згладжування шляху: 1 день
  • Push-сповіщення при зміні статусу: 1–2 дні
  • Адміністративна панель диспетчера (усі кур'єри на карті): 3–4 дні