feat: المجموعة H — المواقع في Redis، والقاعدة من الـworker فقط

كان كل نبضة موقع (كل 1-3 ثوانٍ لكل سائق متصل) تكلّف استعلاماً + كتابة صف
السائق في Postgres — أثقل حمل كان على القاعدة، وأثقل حتى من سيرو.

- H1/H2: نبضة الموقع = Redis فقط، بعتبات سيرو (10م / 1.0 سرعة / 5° اتجاه).
  سائق واقف = EXPIRE واحد: لا كتابة، لا نقطة مسار، ولا بثّ للراكب
- H3: لقطة آخر موقع على drivers (heading/speed/loc_status/loc_updated_at)
  يكتبها الـworker دورياً. لم نُنشئ جدولاً منفصلاً — drivers أصلاً صف واحد
  لكل سائق
- H4: driver_tracks — تتراكم في قائمة Redis ويُدرجها الـworker بعبارة واحدة
- H5: فهرسا available/busy — البحث يمسح المتاحين فقط. السائق يصير busy عند
  القبول ويعود available عند الإنهاء/الإلغاء
- H8: المطابقة والاحتيال و«الرحلات المتاحة» تقرأ الموقع الحيّ من Redis.
  كشف تزوير الوصول خاصةً يجب ألّا يحكم بلقطة دورية

الـworker صار حقيقياً (كان setInterval فارغاً): سياق Nest مستقل، تفريغ كل
5s، قفل يمنع تراكب الدورات، وتفريغة أخيرة عند SIGTERM.

مؤجَّل من H: H6 (سلوك السائق) و H7 (ساعات العمل) — تحليلات تُبنى من
driver_tracks لاحقاً.

هجرة: DriverLocations.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hamza-Ayed
2026-07-17 02:49:45 +03:00
co-authored by Claude Opus 4.8
parent a83dd7e5e5
commit 0ed3a3160a
19 changed files with 794 additions and 98 deletions
+18 -4
View File
@@ -57,20 +57,34 @@ export class RealtimeGateway implements OnGatewayConnection {
return { ok: true };
}
/** موقع السائق الحي: يحدّث Redis GEO ويبثّ لغرفة الرحلة. */
/**
* موقع السائق الحي: Redis فقط (لا قاعدة — docs/17 H1)، والبثّ لغرفة الرحلة
* **فقط عند حركة ذات دلالة**؛ سائق واقف يرسل كل ثانية لا يُغرق الراكب
* بتحديثات متطابقة.
*/
@SubscribeMessage('driver:location')
async onDriverLocation(
@ConnectedSocket() c: Socket,
@MessageBody() body: { lat: number; lng: number; tripId?: string },
@MessageBody()
body: { lat: number; lng: number; heading?: number; speed?: number; tripId?: string },
) {
const u = c.data.user;
if (!u || u.role !== 'driver' || body?.lat == null || body?.lng == null) return;
await this.drivers.updateLocation(u.tenantId, u.userId, body.lat, body.lng);
if (body.tripId) {
const { significant } = await this.drivers.updateLocation(
u.tenantId,
u.userId,
body.lat,
body.lng,
{ heading: body.heading, speed: body.speed },
);
if (body.tripId && significant) {
c.to(this.tripRoom(u.tenantId, body.tripId)).emit('driver:location', {
tripId: body.tripId,
lat: body.lat,
lng: body.lng,
heading: body.heading ?? 0,
speed: body.speed ?? 0,
});
}
return { ok: true };