Files
Siro/knowledge/CODE_REVIEW_REPORT_AR.md
2026-06-15 19:39:21 +03:00

276 lines
22 KiB
Markdown

# تقرير المراجعة البرمجية الشاملة لنظام Siro (طلب السيارات)
---
## مقدمة
هذا التقرير يقدم مراجعة برمجية شاملة ومحاكاة تفصيلية لمسار الرحلة الكامل في تطبيق طلب السيارات **Siro**. تم تحليل الكود المصدري للمشروع بما يشمل الواجهة الخلفية (PHP)، سيرفرات الـ WebSockets (PHP + Socket.IO)، والواجهة الأمامية (Flutter/Dart). يغطي التقرير أربعة محاور رئيسية: معالجة الرحلة في الخلفية، الاتصالات اللحظية عبر WebSockets، أداء الواجهات الأمامية، وتقرير المشاكل والحلول المقترحة.
---
## المحور الأول: الواجهة الخلفية (Backend)
### 1.1 إنشاء الرحلة — `add.php` (rides)
**الملف:** `backend/ride/rides/add.php`
**تحليل سير العمل:**
1. **استلام البيانات:** يستقبل الملف بيانات الرحلة (موقع البداية، الوجهة، التاريخ، الوقت، السعر، معرف الراكب، معرف السائق، الحالة، نوع السيارة، إلخ).
2. **معالجة التواريخ:** يتم تحويل التواريخ الخام باستخدام `strtotime()` و `date()` لتتوافق مع تنسيق MySQL.
3. **إدراج مزدوج (Dual Insertion):**
- إدراج السجل في قاعدة البيانات المحلية (`intaleqDB1`).
- إدراج نسخة مطابقة في قاعدة بيانات التتبع عن بعد (`intaleq-ridesDB`) باستخدام نفس `insertedId`.
**نقاط القوة:**
- ✅ استخدام الـ Prepared Statements يمنع ثغرات SQL Injection.
- ✅ تسجيل الأخطاء المفصل عبر `error_log()` يساعد في تتبع المشاكل.
- ✅ النسخ الاحتياطي عبر قاعدة بيانات التتبع يوفر تكرارية (Redundancy).
**نقاط الضعف والمخاطر:**
-**لا يوجد Transaction (معاملة ذرية):** إذا نجح الإدراج في قاعدة البيانات المحلية وفشل في قاعدة التتبع، يصبح النظام في حالة عدم تناسق (Inconsistent State). الحل: لفّ العمليتين داخل `$con->beginTransaction()` مع `$con->rollBack()` عند فشل أي منهما.
-**لا يوجد قفل (Lock) لمنع التكرار:** يمكن إرسال طلب الرحلة مرتين في نفس الوقت (Race Condition)، مما قد يؤدي إلى إنشاء رحلتين مكررتين. الحل: إضافة `INSERT ... ON DUPLICATE KEY UPDATE` أو استخدام `SELECT ... FOR UPDATE` قبل الإدراج.
-**السعر يُرسل من العميل (Client-Side):** قيمة `price` تُستلم مباشرة من الطلب دون أي تحقق أو إعادة حساب على الخادم. هذا يشكل خطراً أمنياً كبيراً حيث يمكن للعميل التلاعب بالسعر. الحل: إعادة حساب السعر على الخادم بناءً على المسافة ونوع السيارة وتسعيرة الدولة الحالية.
### 1.2 حساب التكلفة — `pricing/get.php`
**المسار:** `backend/ride/pricing/`
**تحليل معمق:**
- يتم حساب السعر عن طريق إرسال المسافة والمدة المتوقعة من العميل إلى الخادم.
- الخادم يستعلم جدول `kazan` (نسبة العمولة) وجدول `pricing` (تسعيرة الدولة).
- يتم تطبيق الخصومات والعروض الترويجية (Promo Codes) عبر استعلام SQL.
**نقاط الضعف:**
-**إرسال البيانات من العميل (`distance`, `durationToRide`):** يمكن التلاعب بالمسافة والمدة مما يؤثر على السعر.
-**لا يوجد تحقق من توافق السعر المُرسل مع السعر المُحتسب:** التطبيق يرسل السعر النهائي ويتم إدراجه مباشرة دون مقارنته بالسعر الذي حسبه الخادم.
**الحل المقترح:**
- إعادة حساب السعر بالكامل على الخادم باستخدام إحداثيات البداية والنهاية (`start_location`, `end_location`).
- استخدام Map SaaS أو OSRM لحساب المسافة والمدة على الخادم وليس على العميل.
- رفض الطلب إذا كان السعر المُرسل من العميل لا يتطابق مع السعر المُحتسب.
### 1.3 عملية الخصم المالي (Server-to-Server / Wallet)
**المسار:** `walletintaleq.intaleq.xyz/v2/main/`
**سير العمل الحالي:**
- يتم الاتصال بسيرفر المحفظة المالية عبر `CRUD().postWallet()` الذي يرسل HMAC + JWT للمصادقة.
- العملية تتم بعد انتهاء الرحلة عبر `payment_method.page.dart`.
**تحليل الأمان:**
- ✅ استخدام HMAC للمصادقة بين الخادمين (S2S) يوفر طبقة أمان جيدة.
- ✅ JWT منفصل للمحفظة يوفر فصل الصلاحيات (Wallet JWT).
**المخاطر:**
-**لا توجد معاملة ذرية (Atomic Transaction) بين إنهاء الرحلة والخصم المالي:** إذا تم إنهاء الرحلة وفشل الخصم المالي (مثلاً بسبب انقطاع الشبكة)، تبقى الرحلة منتهية بدون دفع.
- **الحل:** استخدام نمط Saga (عكس المعاملة): إذا فشل الخصم، إعادة الرحلة إلى الحالة "غير منتهية" عبر `COMPENSATING TRANSACTION`.
-**تكرار طلب الدفع:** إذا أرسل العميل طلب الدفع مرتين (Double Payment)، قد يتم الخصم مرتين.
- **الحل:** إضافة `idempotency_key` لكل معاملة دفع، والتأكد من أن الخادم لا يعالج نفس المفتاح مرتين.
---
## المحور الثاني: سيرفرات الويب سوكيت (WebSockets)
### 2.1 سيرفر السائقين — `driver_socket.php` (بورت 2020)
**الملف:** `socket_intaleq/driver_socket.php`
**تحليل المعمارية:**
**المستوى المتقدم (Level 2 Architecture):**
1. **Event Buffering (تجميع الأحداث):** بدلاً من إرسال كل تحديث موقع إلى Redis بشكل منفصل، يتم تجميع التحديثات في `$eventBuffer` وإرسالها كل 500ms عبر Redis Pipeline.
2. **تقليل عمليات Redis:** يتم تجاهل التحديثات إذا لم يتغير الموقع بأكثر من 10 أمتار، أو لم تتغير السرعة بأكثر من 1 م/ث، أو لم يتغير الاتجاه بأكثر من 5 درجات.
3. **Forward غير متزامن (Async):** يتم إرسال موقع السائق إلى سيرفر الراكب عبر HTTP غير متزامن مع Throttle (كل 3 ثوانٍ و 15 متراً كحد أدنى).
**نقاط القوة:**
-**Redis Pipeline:** يقلل عدد اتصالات Redis من مئات إلى اتصال واحد كل نصف ثانية.
-**Async HTTP Forward:** لا يحجب سير العمل الرئيسي عن إرسال التحديثات.
-**Throttle ذكي:** يمنع إغراق سيرفر الراكب بالتحديثات المتكررة.
**نقاط الضعف:**
-**فقدان الأحداث عند انهيار السيرفر:** `$eventBuffer` مخزّن في الذاكرة (RAM)، فإذا انهار السيرفر، تُفقد جميع الأحداث المجمّعة قبل كتابتها في Redis.
- **الحل:** استخدام Redis Queue (قائمة انتظار) بدلاً منBuffer الذاكرة، أو إضافة Write-Ahead Log (WAL).
-**عدم وجود Watchdog للسائق:** إذا انقطع اتصال السائق (مثلاً فقدان الإنترنت)، لا توجد آلية لكشف ذلك وتحديث حالته إلى `offline` بشكل فوري.
- **الحل:** إضافة `Heartbeat Timeout` في السيرفر: إذا لم يستقبل نبضاً من السائق لمدة 30 ثانية، يُعتبر مفصولاً.
### 2.2 سيرفر الركاب — `passenger_socket.php` (بورت 3030)
**الملف:** `socket_intaleq/passenger_socket.php`
**تحليل سير العمل:**
- السيرفر يستمع على بورت 3030 لاتصالات WebSocket من الركاب.
- بورت 3031 هو HTTP Internal Server لتلقي الأحداث من سيرفر السائقين.
- عند استقبال حدث `update_driver_location` من سيرفر السائقين، يُبث فوراً للراكب المعني عبر `$io->to('passenger_' . $passengerId)->emit(...)`.
**نقاط القوة:**
-**فصل القنوات:** كل راكب لديه قناة خاصة (`passenger_ID`)، مما يضمن الخصوصية.
-**مصادقة داخلية:** جميع الطلبات الداخلية تتطلب `x-internal-key` لمنع الوصول غير المصرح به.
**نقاط الضعف:**
-**تسجيل مفرط في الملفات:** كل حدث يُسجل في `socket_debug.log`، مما قد يؤدي إلى امتلاء القرص الصلب بسرعة في الإنتاج.
- **الحل:** استخدام تدوير السجلات (Log Rotation) وتقليل مستوى التسجيل إلى `ERROR` فقط في الإنتاج، أو للاحداث الهامة فقط كفشل الاتصال.
-**لا يوجد Polling Fallback من جهة السيرفر:** إذا فشل WebSocket مع الراكب، السيرفر لا يقوم بإعادة الإرسال عبر HTTP.
- **الحل:** إضافة آلية `Message Acknowledgment`: الراكب يرسل `ack` لاستلام الموقع، وإذا لم يستلم، يُعاد الإرسال عبر FCM/Push.
### 2.3 اتصال WebSocket من جهة العميل (الراكب)
**الملف:** `siro_rider/lib/controller/home/map/map_socket_controller.dart`
**تحليل:**
1. **الاتصال:** يتم تهيئة WebSocket عند بدء البحث عن سائق.
2. **إعادة الاتصال:** 20 محاولة مع تأخير تصاعدي (2-10 ثوانٍ).
3. **الاشتراك:** بعد الاتصال، يُرسل `subscribe_driver_location` لربط الرحلة.
4. **النّبضات الحية (Heartbeat):** تُرسل كل 15 ثانية للحفاظ على الاتصال.
5. **مراقب الصحة:** `isSocketHealthy()` تتحقق من آخر تحديث (أقل من 20 ثانية).
**نقاط القوة:**
-**آلية إعادة اتصال قوية** مع تأخير تصاعدي.
-**التبديل التلقائي للاقتراع (Polling Fallback):** عند فقدان الاتصال، يتم تفعيل الاقتراع كل 4 ثوانٍ.
-**الكشف عن استقرار WebSocket:** بعد 3 تحديثات موثوقة عبر Socket، يتم إيقاف الاقتراع.
**مشاكل محتملة:**
-**مقاطعة Stream:** إذا تم إغلاق الـ Stream (`_timerStreamController`) بشكل غير متوقع، يتوقف المؤقت عن العمل.
- **الحل:** التحقق من `!streamController.isClosed` قبل كل إضافة.
-**تسريب الذاكرة:** إذا لم يتم استدعاء `disposeRideSocket()` عند تدمير الـ Widget، يبقى الاتصال مفتوحاً.
- **الحل:** استخدام `onClose()` في GetX Controller والتحقق من إغلاق جميع الموارد.
---
## المحور الثالث: الواجهة الأمامية (Frontend Clients)
### 3.1 متحكم دورة حياة الرحلة — `RideLifecycleController`
**الملف:** `siro_rider/lib/controller/home/map/ride_lifecycle_controller.dart` (4359 سطر)
**تحليل الأداء:**
**نقاط القوة:**
-**آلة حالة متكاملة (State Machine):** `RideState` يغطي جميع حالات الرحلة (`noRide``searching``driverApplied``driverArrived``inProgress``finished`).
-**حرّاس التكرار (Race Condition Guards):**
- `_isFinishProcessed` لمنع تكرار معالجة إنهاء الرحلة.
- `_isReviewProcessed` لمنع فتح شاشة التقييم مرتين.
- `_isAcceptanceProcessed` و `_isRideStartedProcessed` لنفس الغرض.
-**مؤقت رئيسي (Master Timer):** يتحكم في دورة حياة الرحلة بالكامل مع فترات استقصاء متغيرة حسب الحالة.
-**مراقب الانحراف (Deviation Detection):** يكتشف إذا انحرف السائق عن المسار بأكثر من 30 متراً ويعيد حساب المسار تلقائياً.
-**الصوت والتحذيرات:** تحذير عند السرعة الزائدة (100 كم/س) مع زر مشاركة الرحلة.
**المشاكل الهيكلية:**
-**حجم الملف كبير جداً (~4359 سطر):** هذا يعتبر Anti-Pattern. يصعب صيانته واختباره.
- **الحلول المقترحة:**
1. تقسيم الـ Controller إلى Services متخصصة:
- `RideStateMachine` — إدارة الحالات والانتقالات
- `RideRouteService` — حساب المسار والانحرافات وإعادة الرسم
- `RideTimerService` — إدارة المؤقتات
- `RideUIService` — تحديث واجهة المستخدم والعناصر المنبثقة
- `RidePaymentService` — معالجة الدفع والخصم
2. استخدام `mixins` لتوزيع الوظائف عبر ملفات متعددة.
### 3.2 رسم المسار والملاحة
**تحليل سير العمل:**
1. **حساب المسار الأولي:** عند قبول السائق، يتم الاتصال بـ `routec.intaleq.xyz` لحساب المسار.
2. **رسم المسار على الخريطة:** يتم فك تشفير الـ Polyline وعرضه بلون أصفر (قدوم السائق) أو أزرق (الرحلة الحالية).
3. **تحديث المسار المتبقي:** `updateRemainingRoute()` تحسب أقرب نقطة للسائق على المسار وتقص النقاط السابقة.
4. **إعادة الرسم عند الانحراف:** إذا انحرف السائق بأكثر من 30 متراً، يُعاد حساب المسار بالكامل.
**نقاط القوة:**
-**فك التشفير في Isolate:** استخدام `compute(decodePolylineIsolate, ...)` يمنع تجميد واجهة المستخدم.
-**التحريك الانسيابي (Smooth Animation):** دالة `smoothlyUpdateMarker()` تنقل أيقونة السياره بسلاسة بين النقاط.
**المشاكل:**
-**إعادة حساب المسار بشكل متكرر:** إذا كان السائق في منطقة ذات إشارات GPS ضعيفة، قد يتأرجح الموقع مسبباً إعادة حساب مستمرة.
- **الحل:** إضافة `Cooldown 10 ثوانٍ` بين عمليات إعادة الحساب، واستخدام **ترشيح كالمان (Kalman Filter)** لتنعيم إحداثيات GPS.
-**تخزين المسار في ذاكرة GETX:** `_currentDriverRoutePoints` يمكن أن تصبح كبيرة جداً في الرحلات الطويلة (آلاف النقاط).
- **الحل:** ضغط النقاط باستخدام خوارزمية **Douglas-Peucker** لتقليل عدد نقاط المسار مع الحفاظ على الدقة.
### 3.3 التوجيه الصوتي (TTS)
**تحليل:**
- يتم استخدام `audio_record1.dart` و `NotificationController` للتوجيه الصوتي والتنبيهات.
- لا يوجد نظام TTS (Text-to-Speech) منفصل للتوجيه الصوتي خطوة بخطوة.
**نقطة الضعف:**
-**عدم وجود TTS متكامل مع المسار:** التطبيق لا يقرأ التعليمات الصوتية تلقائياً (مثل "انعطف يميناً بعد 200 متر").
- **الحل المقترح:** دمج **Google TTS** مع المسار من OSRM حيث يوفر تعليمات صوتية نصية يمكن تحويلها إلى صوت عبر `flutter_tts` package.
---
## المحور الرابع: تقرير المشاكل والأخطاء (Error Report)
### 4.1 ثغرات Race Conditions
| المشكلة | الموقع | الوصف | الحل المقترح |
|---------|--------|-------|--------------|
| **تكرار تقييم الرحلة** | `addRateToDriver.php` | يمكن للراكب إرسال تقييمين في نفس الوقت (Double Rating) | إضافة UNIQUE KEY على `(ride_id, passenger_id)` في جدول `ratingDriver` |
| **تكرار إنشاء الرحلة** | `rides/add.php` | إرسال طلب إنشاء رحلة مرتين يؤدي إلى رحلتين مكررتين | إضافة `idempotency_key` في الطلب والتأكد من عدم معالجة المفتاح مراراً |
| **تكرار الخصم المالي** | `payment_method.page.dart` | الضغط على زر الدفع مرتين قد يؤدي إلى خصمين | تعطيل الزر فور الضغط الأولى مع `loading state` |
| **معالجة الإنهاء المزدوج** | `ride_lifecycle_controller.dart` | وصول حدث `finished` من السيرفر ومن المقبس في نفس الوقت | ✅ تم حلها باستخدام `_isFinishProcessed` |
### 4.2 حالات التعارض في WebSockets
| المشكلة | الوصف | الحل |
|---------|-------|------|
| **فقدان `ride_taken`** | إذا تم قبول الرحلة من سائقين في نفس الوقت (نادر)، قد يتم إرسال طلبين | استخدام `zrem` في Redis كعملية ذرية مع التحقق من الحذف |
| **تأخير Forward** | إذا كان الضغط على السيرفر عالياً، قد يتأخر forward موقع السائق إلى الراكب | ترقية إلى WebSocket مباشر بين السائق والراكب بدلاً من HTTP Forward |
| **فقدان النبضات** | إذا انقطع الإنترنت فجأة، قد يظل السائق متصلاً في قاعدة البيانات | إضافة `Heartbeat Timeout` في السيرفر (30 ثانية بدون نبض = فصل تلقائي) |
### 4.3 مشاكل الأداء
| المشكلة | الوصف | الحل |
|---------|-------|------|
| **حجم ملف التحكم** | `ride_lifecycle_controller.dart` (4359 سطر) يؤثر على وقت الترجمة والذاكرة | تقسيمه إلى موديولات أصغر |
| **استعلامات متكررة** | `getDriverCarsLocationToPassengerAfterApplied` يتم استدعاؤه كل 4-6 ثوانٍ حتى عبر polling | تقليل التردد إلى كل 15 ثانية أو الاعتماد الكامل على WebSocket |
| **ذاكرة التخزين المؤقت** | بيانات المسار الكامل مخزنة في الذاكرة قد تسبب OutOfMemory في الرحلات الطويلة | استخدام Douglas-Peucker لتقليل عدد النقاط |
| **Logging غير محكوم** | `socket_debug.log` و `errors.log` قد تملأ القرص | تفعيل Log Rotation وتحديد مستوى logs الإنتاجي |
### 4.4 مشاكل أمنية خطيرة
| المشكلة | الخطر | الحل |
|---------|-------|------|
| **سعر الرحلة من العميل** | يمكن للمخترق تعديل سعر الرحلة عبر وسائل الطرف الثالث (مان-إن-ذا-ميدل) أو تعديل الطلب | إعادة حساب السعر بالكامل على الخادم |
| **لا يوجد تحقق من المسافة** | يمكن تقليل المسافة المُرسلة للحصول على سعر أقل | حساب المسافة على الخادم من الإحداثيات |
| **تسريب Internal Key** | مفتاح `x-internal-key` مخزّن في ملف نصي على السيرفر | استخدام متغيرات البيئة فقط مع Hashicorp Vault |
---
## خطة التحسينات المقترحة (Roadmap)
### المرحلة الأولى — فورية (عالية الأولوية)
- [ ] إعادة حساب السعر على الخادم بدلاً من العميل
- [ ] إضافة `UNIQUE KEY (ride_id, passenger_id)` على جدول `ratingDriver` لمنع التقييم المزدوج
- [ ] إضافة `idempotency_key` لطلبات إنشاء الرحلة والدفع
- [ ] إضافة Heartbeat Timeout (30 ثانية) في سيرفر السائقين
### المرحلة الثانية — قصيرة المدى
- [ ] تحويل `add.php` (rides) إلى استخدام Atomic Transactions مع Rollback
- [ ] إضافة Kalman Filter لتنعيم إحداثيات GPS
- [ ] تقليل حجم ملف `ride_lifecycle_controller.dart` عبر التقسيم
- [ ] إضافة Log Rotation وتحديد مستوى التسجيل
### المرحلة الثالثة — طويلة المدى
- [ ] استبدال HTTP Forward في WebSocket باتصال مباشر (Peer-to-Peer)
- [ ] دمج نظام TTS للتوجيه الصوتي
- [ ] ترحيل Event Buffer من الذاكرة إلى Redis Queue
- [ ] استخدام Douglas-Peucker لتقليل نقاط المسار
---
## الخلاصة والتقييم العام
| المحور | التقييم | الدرجة |
|--------|---------|--------|
| Backend Architecture | جيد مع وجود مخاطر أمنية | 7/10 |
| WebSocket Architecture | ممتاز مع نظام تجميع الأحداث (Level 2) | 9/10 |
| Frontend Performance | متكامل لكن يحتاج إعادة هيكلة | 7.5/10 |
| Race Condition Handling | جيد جداً مع وجود حرّاس مناسبة | 8/10 |
| Security | يوجد ثغرات خطيرة (سعر الرحلة من العميل) | 5/10 |
| Code Maintainability | مدمج في ملف واحد كبير جداً | 4/10 |
**التقييم العام: 6.8/10**
النظام قوي من ناحية البنية التحتية للـ WebSocket مع نظام التجميع الذكي، لكنه يعاني من مشاكل أمنية حرجة تتعلق بتسعير الرحلة من جهة العميل، وهيكلة الكود الموحّدة في ملف ضخم يصعب صيانته. الأولوية القصوى يجب أن تكون لإعادة حساب السعر على الخادم وتقسيم الـ Controller الضخم.
---
*إعداد: تحليل برمجي تلقائي - يونيو 2026*