מעקב שליח/הזמנה בזמן אמת באתר שלך
משתמש ביצע הזמנה ומחכה לשליח. עמוד "ההזמנות שלי" עם שדה "סטטוס: בדרך" הוא מהמאה הקודמת. הסטנדרט המודרני הוא מפה עם סמן שליח חי וספירה לאחור "מגיע בעוד N דקות". טכנית, זהו שילוב של שלושה רכיבים: אפליקציית/מכשיר הנייד של השליח, אפליקציית הקצה האחורי, ודפדפן הלקוח. אנחנו בנינו מערכות כאלה עבור 30+ פרויקטי משלוחים — משירותים מקומיים ועד רשתות פדרליות. צור קשר לקבלת הערכת פרויקט.
למה Polling אינו יעיל: WebSocket לעומת Polling
בדיקת השרת כל 5 שניות יוצרת עומס מיותר ועיכובים. WebSocket יעיל פי 100 — אירועים מגיעים מיידית ללא תקורה של בדיקות. עם זאת, יישום WebSocket פשוט ללא הרשאה פותח גישה להזמנות של אחרים. אנו משתמשים ב-PrivateChannel מ-Laravel Broadcasting עם בדיקות הרשאות.
סקירת ארכיטקטורה
[Устройство курьера] GPS → POST /api/courier/location каждые 3–5с
↓
[Backend] Сохранить в Redis (TTL 60s) Publish в Redis Pub/Sub канал order:{id}
↓
[WebSocket сервер (Laravel Reverb / Pusher)] Broadcast event LocationUpdated
↓
[Браузер клиента] Обновить маркер на картеמיקומים גיאוגרפיים אינם נשמרים ב-PostgreSQL עם כל עדכון — זה יהיה 720 רשומות בשעה לכל שליח. אנו כותבים למסד הנתונים רק על שינויי סטטוס הזמנה ועל המיקום הסופי בסיום. המיקום הנוכחי נשאר ב-Redis עם TTL.
למה Redis עדיף על PostgreSQL לנתונים גיאוגרפיים
Redis מספק זמן השהיה של כתיבה/קריאה מתחת ל-1ms וניקוי אוטומטי דרך TTL. כתיבות קבועות ל-PostgreSQL היו עולות $500 לחודש יותר במשאבי שרת. עם Redis, אנו חוסכים עד 40% בעלויות תשתית. קבלת המיקום הנוכחי היא בקשה אחת בזיכרון.
דוגמה לתצורת שרת WebSocket עם Laravel Reverb
לייצור, אנו ממליצים להשתמש ב-Reverb עם קנה מידה אופקי דרך Redis. ההתקנה כוללת התקנת החבילה, פרסום התצורה, והפעלת ה-worker. פרטים נוספים במסמכי תיעוד Laravel Broadcasting.סכמת מסד נתונים
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 שליח ושידור
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',
]);
$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(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
{
$distanceKm = $this->haversineKm($lat, $lng, $order->address_lat, $order->address_lng);
return (int) round($distanceKm / 30 * 60);
}
}
// CourierLocationUpdated 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,
];
}
}הרשאת ערוץ ב-routes/channels.php:
Broadcast::channel('order.{orderId}', function (User $user, int $orderId) {
return $user->id === DeliveryOrder::find($orderId)?->user_id;
});ה-[Устройство курьера] GPS → POST /api/courier/location каждые 3–5с ↓ [Backend] Сохранить в Redis (TTL 60s) Publish в Redis Pub/Sub канал order:{id} ↓ [WebSocket сервер (Laravel Reverb / Pusher)] Broadcast event LocationUpdated ↓ [Браузер клиента] Обновить маркер на карте מבטיח שרק בעל ההזמנה מקבל את הקואורדינטות. זו דרישת אבטחה חובה.
צד לקוח: מפה ואנימציית סמן
אנו משתמשים ב-Mapbox להצגת המפה. חלופה היא Yandex.Maps, המועדפת למדינות CIS.
import mapboxgl from 'mapbox-gl'; // ... инициализация карты
Echo.private(`order.${orderId}`)
.listen('CourierLocationUpdated', ({ lat, lng, eta }) => {
animateMarker(courierMarker, courierMarker.getLngLat().toArray(), [lng, lat]);
if (eta !== null) {
document.getElementById('eta').textContent = eta < 2 ? 'Курьер уже рядом' : `Прибудет через ~${eta} мин`;
}
});
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;
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);
} טבלאות השוואה
| שיטה | דיוק | זמן חישוב מסלול | תלות |
|---|---|---|---|
| קו ישר (Haversine) | נמוך (מתעלם מכבישים) | מיידי | אין |
| Routing API (OSRM) | גבוה (מתחשב בכבישים) | 50–200 ms | שירות חיצוני |
| Google Directions API | גבוה מאוד (תנועה) | 200–500 ms | מפתח API |
| גישה | השהיית עדכון | עומס שרת | מורכבות יישום |
|---|---|---|---|
| Polling (HTTP כל 5 שניות) | 5 שניות בממוצע | גבוה | נמוך |
| WebSocket (חיבור קבוע) | מיידי | נמוך | בינוני |
| Server-Sent Events | מיידי | נמוך | בינוני |
הגדרת ערוץ פרטי ב-5 שלבים
- התקן את Laravel Reverb או Pusher.
- הגדר שידור ב-config/broadcasting.php.
- צור אירוע המיישם ShouldBroadcast.
- הגדר PrivateChannel ב-routes/channels.php.
- הירשם לערוץ בצד הלקוח באמצעות Echo.
מה כלול
- ניתוח: אינטגרציה עם אפליקציית הנייד של השליח, הסכם פרוטוקול.
- עיצוב: סכמת נתונים, ארכיטקטורת WebSocket, הרשאות.
- יישום: קצה אחורי על Laravel עם Redis, שידור, מפת צד לקוח.
- בדיקות: בדיקות עומס של WebSocket (1k+ חיבורים במקביל).
- תיעוד: תיאור API, הוראות פריסה.
- תמיכה: אחריות קוד, הדרכת צוות.
לוחות זמנים משוערים
- מעקב בסיסי (Redis + שידור + מפה): 4 עד 5 ימים.
- הרשאת ערוץ פרטי + לוגיקת גישה: יום אחד.
- חישוב ETA דרך קו ישר (Haversine): 0.5 יום.
- ETA דרך Routing API: 1 עד 2 ימים.
- אנימציית סמן + החלקת נתיב: יום אחד.
- התראות Push על שינוי סטטוס: 1 עד 2 ימים.
- פאנל מנהל שיגור: 3 עד 4 ימים.
למה להזמין מאיתנו
יישמנו מעקב בזמן אמת עבור 30+ פרויקטים, כולל שירותי משלוחים גדולים. אנו מבטיחים חיבורי WebSocket יציבים עם אלפי שליחים, פרטיות ערוצים, ואנימציה חלקה ללא קפיצות. בקש הערכת פרויקט — נציע את הפתרון האופטימלי.







