18 KiB
30 — نقل تطبيقات سيرو (GetX) لتحل محل واجهات Tripz + دراسة دوكرة PHP سيرو
قرار المالك 2026-07-21. الباك إند NestJS مجمّد كما هو — كل التكيّف يحدث في طبقة استقبال البيانات داخل Flutter. المنهج: توثيق المجموعات هنا أولاً، ثم تنفيذ مجموعة واحدة في كل مرة مع برهان على جهاز حقيقي.
0) القرار وتقييمه الصادق
القرار: نسخ siro_rider وsiro_driver (GetX، مجرّبان ميدانياً 90–95%) إلى apps/rider وapps/driver ليحلا محل إعادة البناء Cubit/Bloc، مع تحويل طبقة البيانات فقط إلى عقد باك إند Tripz.
ملاحظة صدق تقني (حتى لا نبني القرار على سبب خاطئ): البطء الملحوظ على الجهاز عبر كابل USB سببه الغالب وضع debug نفسه (JIT + asserts + logging + الكابل)، وليس Bloc/Cubit كإطار — في build release الفرق بين الإطارين هامشي. القرار سليم لكن لسببه الحقيقي:
| الحجة الحقيقية | الدليل المتحقق |
|---|---|
| تطبيقات سيرو مكتملة ومجرّبة على مستخدمين حقيقيين | rider = 234 ملف dart، driver = 281 ملف |
| إعادة البناء ما زال أمامها ذيل UX طويل (مرحلة Q كلها) | rider الجديد = 77 ملف، driver = 53 ملف |
| المالك يعرف كود سيرو شبراً شبراً → سرعة تطوير أعلى | خبرة مباشرة |
| طبقة الشبكة في سيرو مركزية فعلاً → التحويل ممكن بدون مسّ الواجهات | crud.dart (625 سطر) يستورده 46 ملفاً في rider و71 في driver؛ الروابط كلها في links.dart (547 سطر) |
ما لا يتغير: الباك إند (A–K + ما بعدها) · قرار الخرائط المباشرة (docs/25 + ذاكرة maps-direct) · قرار الهويتين المنفصلتين راكب/سائق (commit 9f7bc31) · نموذج المال (محفظتان) · مبدأ Redis-first.
1) الحقائق المتحقق منها (فُحصت من الكود 2026-07-21)
تطبيقا سيرو
- الحالة: GetX +
get_storage، شبكةhttp+dio، خرائطintaleq_maps ^2.2.1(نفس ما يستخدمه Tripz — صفر عمل هجرة خرائط). crud.dartيحوي أصلاً: فحص صلاحية JWT محلياً + تجديد تلقائي + SSL pinning + NetGuard + إبلاغ أخطاء مركزي. البنية الذهنية مطابقة لما يحتاجه عقد Tripz — التغيير في التفاصيل لا في الفلسفة.links.dartيوزّع النداءات على أربع عائلات سيرفرات لكل دولة (API رئيسي · paymentServer · location API · locationServerSide سوكيت · routesOsm). في Tripz تنهار كلها إلى اثنين:tripz-api.intaleqapp.com+map-saas.intaleqapp.comمباشرة.- سوكيت:
socket_io_client 1.0.2— بروتوكول Socket.IO v2 قديم. - دفع: التطبيق ينادي PayMob مباشرة من Flutter (
controller/payment/paymob*.dart). - هويات سيرو:
com.siro.rider/com.siro.siro_driver+ shorebird خاصة بسيرو (44245793…,f8e9c087…) — لا تُستخدم في Tripz. - حزم محلية: rider يحمل fork
get/get_storageداخله (./packages/) — مكتفٍ ذاتياً. driver يشير خارج المستودع (../../Intaleq/packages/get) — يجب ضمّها داخله عند النسخ.
باك إند Tripz (الطرف الثابت)
- 38 controller تغطي كل الدورة (auth·trips·dispatch·wallet·credit·payments·payouts·ratings·chat·notifications·documents·vehicles·rewards·geofence·transit…).
- realtime = NestJS
socket.io v4+ redis-adapter، بلاallowEIO3. - المرجع الحي لعقد النداءات:
backend/scripts/e2e-test.mjs— يمرّ على الدورة كاملة (دخول→سائق→رحلة→حالات→WS→دفع→عمولة→تقييم→payout) بالأشكال الفعلية للطلب والرد. هذا هو "قاموس الترجمة" عند تحويل كل وحدة.
2) فجوات العقد الست (كل واحدة مصيدة معروفة سلفاً)
- المغلف والترويسات: ردود PHP بأشكالها الحرة ↔ ردود NestJS DTO. الترويسات الجديدة إلزامية:
AuthorizationJWT +x-tenant-id(slug) +x-app-role(قرار الهويتين) + لاحقاًx-device-idوتوقيع الطلبات. المعرّفات UUID لا int، والمال عشريnum(1.10 JOD تكسر أيint). - انهيار عائلات السيرفرات: كل
switch (currentCountry)فيlinks.dartيُستبدل: الباك إند واحد متعدد المستأجرين (الدولة تُحل منtenant.countryPackعلى السيرفر)، والخرائط/المسارات مباشرة لـ map-saas بترويسةx-api-key(العقد المثبت: reverse يرجع مصفوفة، route في حقلpoints، لاlang). - السوكيت غير متوافق أصلاً: عميل v2 لا يتصل بخادم v4 — ترقية
socket_io_clientإلى ^2/^3 إلزامية + إعادة رسم أسماء الأحداث + قناة رفع مواقع السائق تتحول منdriver_socket.php(بورت 2020) إلى realtime gateway الموحّد. - الدفع يعود للسيرفر: نداءات PayMob المباشرة من Flutter تُعاد عبر
payments.controller(بوابة + سجل محولات) — المفاتيح لا تسكن التطبيق. - الهويات التجارية: تُستبدل هويات سيرو بهويات Tripz المنشورة (rider
com.mobileapp.store.ride/ shorebird496cb3ac…). ⚠️ هوية driver متعارضة بين المنصتين (com.sefer_driverأندرويد /com.sefer.driveriOS) — قرار المالك مطلوب قبل أي إصدار driver. Firebase وshorebird والإضافات الأصلية تؤخذ من مستودع Tripz الحالي، لا من سيرو. - حزم fork المحلية: تُضم
get/get_storageداخلapps/driver/packages/أسوة بـ rider، حتى يبقى المستودع مكتفياً ذاتياً.
3) خطة التنفيذ — وحدة وحدة (G0…G7)
المنهج الثابت لكل مجموعة:
(أ) التقاط نداء سيرو الحالي (link + payload + شكل الرد) → (ب) مطابقته بعقد Tripz من e2e-test.mjs وswagger → (ج) التعديل في طبقة البيانات فقط (crud/links/models) وترك الواجهات ومنطق الـ controllers كما هو ما أمكن → (د) برهان على جهاز حقيقي قبل الانتقال للتالية. البناء والاختبار على السيرفر لا على الماك (القاعدة الثابتة).
| # | المجموعة | المحتوى | البرهان |
|---|---|---|---|
| G0 | الاستيراد والترجمة | أرشفة الكود الحالي بفرع archive/cubit-rebuild (لا حذف — فيه إصلاحات تُحمل، §4) · نسخ تطبيقي سيرو إلى apps/ · تبديل الهويات (bundle/firebase/shorebird من Tripz) · ضم fork الحزم · ترقية socket_io_client · حذف Twilio وبقايا لا تلزم |
flutter analyze صفر أخطاء + الإقلاع لشاشة الدخول على الجهاز |
| G1 | المصادقة | قلب crud.dart + links.dart للعقد الجديد (المغلف، الترويسات الأربع، تجديد refresh token) · دخول هاتف+OTP |
دخول حقيقي بـ OTP على الجهاز، والتوكن يتجدد |
| G2 | الهوم والخريطة | map-saas مباشر (العقد المثبت §2.2) · السائقون القريبون من الباك إند | خريطة + عنوان عكسي + سيارات قريبة تظهر |
| G3 | دورة الرحلة (راكب) | تسعيرة → طلب → مطابقة → حالات الرحلة → أحداث WS v4 | رحلة كاملة على جهازين حتى completed (ملاحظة: R1 sweeper ناقص على الباك — حالة البحث قد لا تنتهي من السيرفر؛ يُختبر بوجود سائق) |
| G4 | تطبيق السائق | دخول بـ x-app-role: driver · online/offline · رفع المواقع إلى gateway الموحّد · استقبال العروض · حالات الرحلة |
نفس رحلة G3 من طرف السائق، والموقع يظهر عند الراكب |
| G5 | المال | محفظة الراكب · رصيد السائق والعمولة · شحن عبر الباك إند (PayMob) · payout بـ OTP | شحن تجريبي + خصم عمولة رحلة + طلب payout |
| G6 | الإشعارات والتقييم والدردشة | FCM (⚠️ العقد الصحيح POST /notifications/token مع platform — مسار /devices/fcm-token كان 404 صامتاً) · التقييم الإجباري GET /trips/rating/pending · الدردشة |
إشعار يصل والتطبيق مغلق + تقييم إجباري يظهر |
| G7 | التصليب والتنظيف | SSL pinning على شهادة tripz-api · ثم تفعيل PAYMENTS_REQUIRE_SIGNATURE وAUTH_REQUIRE_DEVICE_BINDING على السيرفر · حذف الكود الميت (PHP-era) · مواءمة مع خطة الفئات lite/pro/max (docs/22 §1.5) |
e2e يمر والفلاغان مفعّلان |
4) ما يُحمل من إعادة البناء الحالية قبل الأرشفة (لا يعاد اكتشافه)
- عقد map-saas المثبت بالتجربة:
x-api-keyترويسة (query مرفوض 400) · reverse مصفوفة مرتبة بالأقرب (name/name_ar/address) · route فيpoints— موجود فيapps/rider/lib/core/api/antlaq_api.dartوfeatures/home/data/maps_repository.dart. - درس FCM: القناة كانت ميتة بسبب اختلاف المسار — العقد الصحيح موثق في G6.
- قرار الهويتين ومفتاح
MAP_API_KEYعبر--dart-define(المفتاح الحالي مفتاح سيرو؛ مفتاح Tripz مقيّد بالبصمة يصدر لاحقاً).
5) دراسة دوكرة سيرو PHP (مسار موازٍ مستقل عن النقل)
5.1 الجرد (فُحص 2026-07-21 — لا يوجد أي Docker حالياً في مستودع سيرو)
backend/ (API الرئيسي) · payment_server/v2 · loction_server/ (Workerman + PHPSocketIO — driver_socket.php بورت 2020 WS + 2021 HTTP داخلي، Redis-only مبطّن كل 500ms) · passenger_server/ · ride_server/ · siro_admin/ · siromove.com/ + MySQL + Redis.
5.2 الشكل المقترح — compose واحدة لكل نسخة
nginx (موجّه لكل الخدمات) ─┬─ php-fpm ← backend + passenger + ride + payment + admin (vhosts)
├─ php-cli ← driver_socket.php (Workerman يدير عمّاله بنفسه، restart: always)
├─ mysql (volume دائم + نسخ احتياطي)
└─ redis
.env واحدة لكل عميل: الدومين · مفاتيح OTP/دفع · كلمات سر القواعد · مفتاح map-saas
- استنساخ عميل جديد =
git clone+.env+docker compose up -d+ استيراد السكيمات الثلاث (schema_primary/ride/tracking+WalletDB+locationDB) — هدف واقعي: نسخة عاملة في يوم، وتسليم عميل في أسبوع (الوقت الباقي للعلامة والتطبيقات والمتاجر). - كل عميل على سيرفره الخاص = عزل كامل بالتعريف (لا حاجة لهندسة multi-tenant في PHP).
5.3 السعة: هدف 10,000 رحلة في الذروة
حساب مبدئي: 10k رحلة/ساعة ≈ 2.8 رحلة/ثانية ≈ 60–120 rps على API (بمعدل 20–40 نداء للرحلة) + سوكيت المواقع (Redis-only ومبطّن أصلاً — ليس عنق الزجاجة). عنق الزجاجة المتوقع: MySQL وحوض php-fpm. سيرفر 4–8 vCPU يفترض أن يكفي.
لكن القاعدة قاعدة: برهان لا تقدير. مجلد stress_test/ موجود في مستودع سيرو — يُشغَّل على نسخة docker مستنسخة نظيفة، ويُلتزم فقط بالرقم الذي يثبته الاختبار. (للمقارنة: باك إند Tripz أثبت ~1.15M رحلة/يوم على السيرفر المشترك.)
5.4 🔴 شرط غير قابل للتفاوض قبل أول عميل: طقم إصلاح ما قبل الاستنساخ
استنساخ الكود = استنساخ الثغرات. ثغرة payout في نسخة عميل = سرقة مال حقيقية باسمنا. من docs/21-siro-audit.md (17 عيباً)، عيوب P0 التي تُصلح مرة واحدة في الأصل قبل أي نسخة:
- IDOR في
request_payout.php(driverId من الطلب لا من JWT) — سحب رصيد الغير. - لا حجز رصيد عند طلب payout — طلبات متزامنة = صرف مزدوج.
- تناقض رسم 3500 (يُشترط جمعاً ويُخصم طرحاً، مكتوب مرتين) — تسريب مال.
finalizePayoutخمس كتابات بلا معاملة — فشل منتصفي يترك payout نصف مصفّى.driverWallet.amountنوعvarchar(10)— مال كنص وSUM()على نصوص.
5.5 قرارات الأداء المحسومة (سؤال المالك 2026-07-21)
- حاويات مقابل PHP عارٍ: على لينكس الحاوية = عمليات عادية بنفس النواة (namespaces/cgroups، لا محاكاة) — حمل CPU/ذاكرة ≈ صفر، والفرق الكلي 0–3% إذا وُضعت بيانات MySQL على volume مسمّى (يتجاوز overlayfs) وفُعّل opcache. القرار: دوكرة، بلا تردد — الأداء متكافئ وفائدة الاستنساخ حاسمة.
- حاوية واحدة مقابل تقسيم: الأداء متطابق (نفس العمليات بالحالتين) — الفرق تشغيلي بحت لصالح التقسيم (إعادة تشغيل خدمة وحدها، حدود موارد، لوغات منفصلة). حاوية-واحدة-فيها-كل-شيء anti-pattern. القرار: التقسيم بالدور — 6 حاويات: nginx / php-fpm واحدة لكل الخدمات الطلبية / socket_driver (2020) / socket_passenger (3030) / mysql / redis.
- JS مقابل PHP: لكل اتصال، Node أخف (event loop دائم بلا إقلاع لكل طلب) من نموذج fpm (عملية لكل طلب). لكن عند 10–20 ألف رحلة/ساعة كلاهما بعيد عن الحد — عنق الزجاجة MySQL وضبط fpm لا اللغة. ومسار سيرو الساخن (المواقع) أصلاً على Workerman = نفس نموذج Node بالضبط. مرجع: باك إند Tripz (نموذج JS) قاس ~1.15M رحلة/يوم على الصندوق المشترك؛ رقم PHP يقيسه الاختبار.
5.6 حالة التنفيذ (2026-07-21)
بُني كل شيء في مستودع سيرو، جاهز للتشغيل على السيرفر (الماك للكتابة فقط):
Siro/docker/— compose (6 خدمات بحدود ذاكرة لصندوق 6 أنوية/12GB) + صورتا PHP (fpm + cli مع ext-event للسوكيتات) + nginx نقطة-واحدة (/backend+/v2/mainبنفس أشكال مسارات اللايف — التطبيق يبدّل الدومين فقط) + قوالب env + runbook كامل بالأوامر فيdocker/README.md.Siro/stress_test/rate_test.js— جديد: معدل مستمر (رحلات/ساعة) بدل الدفعة، JWT داخل Node (أزال اختناق execSync للـ PHP لكل رحلة)، p50/p95/p99 لكل نقطة + زمن العرض + الرحلة كاملة، مع ramp وحكم نجاح/فشل (<1% فشل و p95<500ms).- قاعدة الصدق في القياس: سيناريو الاختبار أخف من الرحلة الحقيقية 3–5× → لتبنّي "10k حقيقية" يجب عبور 30k سيناريو. الفحص خارج الذروة، بجولات 5–10 دقائق، مرتين. المُلتزم به للعملاء = الرقم العابر فقط.
- المؤجل بقرار المالك: كل مسار الإيجار/الاستئجار على باك إند Tripz — التركيز الحالي على دوكرة PHP وتجربتها بتطبيق سيرو الحالي (تغيير base URLs فقط عبر
.envالتطبيق وإعادة بناء واحدة).
5.7 التموضع الاستراتيجي (حتى لا نشغّل منتجين للأبد)
- نسخ PHP للعملاء = المنتج النقدي المرحلي: سريع، مجرّب، سيرو نفسها هي الدعاية. تجميد وظيفي عليه — إصلاحات أمان فقط، لا ميزات جديدة على PHP.
- Tripz NestJS = المنصة النهائية: كل نسخة عميل مرشحة للهجرة لاحقاً كمستأجر (نفس فكرة "سيرو أول مستأجر"). بهذا لا نصير أسرى صيانة أسطول PHP متباعد النسخ.
- سيرو نفسها أول من يُعاد تشغيله بالـ compose (سيرفر واحد يتحمل الذروة) — فتكون هي البرهان الحي أمام العملاء.
6) أسئلة مفتوحة للمالك
- هوية driver المنشورة —
com.sefer_driverأمcom.sefer.driver؟ يلزم قبل G0 للسائق (الراكب لا ينتظرها). - أي المسارين أولاً — النقل (G0) أم compose سيرو (5.2)؟ يمكن التوازي: النقل عمل Flutter محلي والدوكرة عمل سيرفر.
- تسمية فرع الأرشفة
archive/cubit-rebuild— موافق؟