Реализация 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

Реализация Real-Time трекинга курьера/заказа на сайте

Пользователь оформил заказ и ждёт курьера. Страница «Мои заказы» с полем «статус: в пути» — это уже прошлый век. Современный стандарт — карта с живым маркером курьера и счётчиком «прибудет через N минут». Технически это связка трёх компонентов: мобильное приложение/устройство курьера, бэкенд-приложение, клиентский браузер.

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

[Устройство курьера]
    GPS → POST /api/courier/location каждые 3–5с
        ↓
[Backend]
    Сохранить в Redis (TTL 30s)
    Publish в 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. Для СНГ Яндекс предпочтительнее по качеству геокодирования и покрытию.

Плавное движение маркера

Резкие скачки маркера каждые 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));
}

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

Если устройство курьера теряет связь — на стороне мобильного приложения накапливается буфер координат, который отправляется пачкой при восстановлении соединения. Бэкенд принимает массив точек с временными метками и воспроизводит анимацию пути, а не перепрыгивает на финальную позицию.

Сроки

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