MongoDB: التحقق من صحة البيانات والبرمجيات الوسيطة في…

آخر تحديث: 2026-08-26

يُعد التحقق من صحة البيانات خط الدفاع الأول على مستوى طبقة التطبيق — حيث يمكن لأدوات التحقق من صحة البيانات في Mongoose حجب 80% من البيانات غير الصحيحة.

في هذه الدورة التدريبية، ستتعرف على أدوات التحقق المدمجة في Mongoose، وأدوات التحقق المخصصة، وvalidateBeforeSave، والبرمجيات الوسيطة من خلال تمارين عملية.

دور أدوات التحقق من الصحة في إطار عمل حماية البيانات: تتألف الحماية الشاملة للبيانات من هيكل ثلاثي المستويات — 1. طبقة قاعدة البيانات (التحقق من صحة مخطط MongoDB/الفهارس الفريدة): خط الدفاع الأخير، الذي يمنع إدخال البيانات غير الصحيحة إلى قاعدة البيانات بسبب الثغرات الأمنية في طبقة التطبيق؛ 2. طبقة التطبيق (أدوات التحقق من صحة البيانات في Mongoose + البرمجيات الوسيطة): خط الدفاع الأساسي، الذي يعترض معظم البيانات غير الصحيحة ويقدم رسائل خطأ سهلة الفهم للمستخدم؛ 3. طبقة الواجهة الأمامية (التحقق من صحة النماذج): طبقة تجربة المستخدم، التي تقدم ردود فعل فورية ولكنها غير آمنة (يمكن تجاوزها). العلاقة بين هذه الطبقات الثلاث: التحقق من صحة الواجهة الأمامية ≠ الأمان (يمكن تجاوزه)، التحقق من صحة Mongoose = خط الأساس للأمان (لا يمكن تجاوزه)، التحقق من صحة قاعدة البيانات = شبكة الأمان (يضمن عدم تخزين أي بيانات غير صالحة حتى في حالة فشل طبقة التطبيق). لا تعتمد أبدًا على التحقق من صحة الواجهة الأمامية وحده.

عندما يتم تشغيل أدوات التحقق في Mongoose: يتم تشغيل أدوات التحقق في Mongoose تلقائيًا عند استدعاء document.save() وdocument.validate() (مع تعيين validateBeforeSave إلى true افتراضيًّا). ملاحظة: 1. لا تؤدي عمليات التحديث مثل Model.updateOne() وupdateMany() إلى تشغيل أدوات التحقق من الصحة — فهي تعمل مباشرةً على قاعدة البيانات، متجاوزةً طبقة مستندات Mongoose؛ 2. للتحقق من الصحة أثناء عملية التحديث، استخدم Model.findOneAndUpdate مع الخيار runValidators: true؛ 3. تُنفَّذ أدوات التحقق من الصحة قبل البرمجيات الوسيطة pre('save') — إذا فشل التحقق من الصحة، فلن يتم تنفيذ البرمجيات الوسيطة pre('save') (لن يتم إجراء تجزئة كلمة المرور على البيانات غير الصحيحة). إن فهم ترتيب التنفيذ هذا أمر بالغ الأهمية لتصحيح أخطاء التحقق من الصحة.

1. ما ستتعلمه



2. أدوات التحقق المدمجة

شرح المفهوم: أدوات التحقق المدمجة في Mongoose هي قواعد التحقق التي يوفرها SchemaType، والتي تتيح لك تحديد القيود الشائعة دون الحاجة إلى كتابة دوال مخصصة. وتشمل هذه required (مطلوب)، وmin/max (النطاق العددي)، وminlength/maxlength (طول السلسلة)، وenum (قيم التعداد)، وmatch (مطابقة التعبير العادي)، وغيرها. يتم تطبيقها تلقائيًا على save() وvalidate()، وتشكل خط الدفاع الأول لضمان جودة البيانات على مستوى طبقة التطبيق.

كيفية العمل: قبل حفظ المستند (أو عند استدعاء validate() صراحةً)، يقوم Mongoose بالتكرار عبر SchemaType لجميع الحقول وتنفيذ أدوات التحقق المدمجة واحدة تلو الأخرى. يتلقى كل أداة تحقق قيمة الحقل، ثم إما أن تُرجع قيمة منطقية (boolean) أو تُطلق خطأً. إذا فشل التحقق من الصحة، يقوم Mongoose بجمع جميع الأخطاء في كائن ValidationError.errors، الذي يتضمن مسار الحقل ونوع الخطأ والرسالة المخصصة.

السلوك الضمني لأدوات التحقق المدمجة: تتميز أدوات التحقق المدمجة بعدة سلوكيات ضمنية يسهل إغفالها — 1. تتحقق أداة التحقق required من وجود الحقل ومن أنه ليس undefined، لكن السلسلة الفارغة '' تجتاز فحص required (يجب استخدام minlength: 1 لمنع السلاسل الفارغة)؛ 2. min وmax لا يعملان إلا مع النوع Number (بالنسبة لأنواع String، تتم مقارنة min وmax حسب الترتيب الأبجدي وليس القيمة العددية)؛ 3. enum يراعي تمييز الأحرف الكبيرة والصغيرة ('Admin' و'admin' قيمتان مختلفتان)؛ 4. match يتحقق من التنسيق فقط، وليس من المحتوى (على سبيل المثال، /\d+/ يطابق "abc123def"؛ استخدم /^d+$/ لمطابقة صارمة للأرقام فقط).

رسائل الخطأ المخصصة لأدوات التحقق: تدعم كل أداة تحقق مدمجة رسائل الخطأ المخصصة — required: [true, 'لا يمكن أن يكون اسم المستخدم فارغًا'], min: [0, 'لا يمكن أن يكون العمر سالبًا'], enum: {values: ['customer', 'admin'], message: '{VALUE} ليس دورًا صالحًا'}. تدعم قوالب الرسائل المتغيرات مثل {VALUE} (القيمة الحالية)، و{PATH} (اسم الحقل)، و{MIN}/{MAX} (قيم الحدود). يجب أن تسمح رسائل الخطأ الجيدة لمطوري الواجهة الأمامية بمعرفة كيفية إصلاح المشكلة دون الرجوع إلى المخطط — فعبارة «يجب أن يتكون اسم المستخدم من 3 إلى 30 حرفًا أبجديًا رقميًّا وشرطة سفلية» أكثر فائدة بكثير من «فشل أداة التحقق في الحقل username».

100%
graph TD
    A[doc.save] --> B[validate Phase]
    B --> C[Inspection required]
    B --> D[Inspection min/max]
    B --> E[Inspection enum]
    B --> F[Inspection match]
    B --> G[Inspection minlength/maxlength]
    
    C --> H{All passed?}
    D --> H
    E --> H
    F --> H
    G --> H
    
    H -->|Yes| I[pre save hooks]
    H -->|No| J[ValidationError<br/>Collect all errors]
    
    I --> K[MongoDB insertOne]
    
    style K fill:#d4edda
    style J fill:#f8d7da
أداة التحقق النوع المطبق شرط التشغيل نموذج رسالة الخطأ
required الكل عند عدم وجود حقل '{PATH} is required'
min/max الرقم، التاريخ القيمة خارج النطاق '{PATH} must be >= {MIN}'
minlength/maxlength سلسلة عندما يكون الطول غير صالح '{PATH} must be >= {MINLENGTH} chars'
enum سلسلة عندما لا تكون القيمة موجودة في قائمة التعداد '{VALUE} is not valid'
match سلسلة عندما لا يتطابق التعبير النمطي '{PATH} is invalid'
unique الكل عند حدوث تعارض في الفهرس E11000 duplicate key

(1) القائمة الكاملة

أولوية أدوات التحقق وترتيب التنفيذ: يقوم Mongoose بتنفيذ أدوات التحقق بترتيب ثابت — 1. تحويلات الأنواع المدمجة (String→Number، إلخ)؛ 2. الفحوصات الإلزامية؛ 3. فحوصات النطاق المدمجة (min/max/minlength/maxlength/enum/match)؛ 4. أدوات التحقق المخصصة المتزامنة؛ 5. أدوات التحقق المخصصة غير المتزامنة. وهذا يعني أنه حتى إذا نجحت أداة التحقق المخصصة، فقد تفشل أداة التحقق المدمجة. مبدأ التصميم: إعطاء الأولوية لأدوات التحقق المدمجة (أداء أفضل، رسائل خطأ موحدة)؛ واستخدام أدوات التحقق المخصصة فقط في الحالات التي لا تغطيها أدوات التحقق المدمجة.

تصميم رسائل الخطأ لأدوات التحقق المخصصة: يجب أن تتضمن رسالة الخطأ الجيدة ثلاثة عناصر—1. الحقل الذي يحتوي على خطأ (يقدم Mongoose المسار تلقائيًا)؛ 2. سبب حدوث الخطأ (على سبيل المثال، «يجب أن يبدأ اسم المستخدم بحرف» بدلاً من «فشل التحقق من الصحة»); 3. ما هو التنسيق المتوقع (على سبيل المثال، «التنسيق: يجب أن يبدأ بحرف، 3–30 حرفًا أبجديًا رقميًا أو علامات تحتية»). يدعم message متغيرات القوالب: {PATH} (اسم الحقل)، {VALUE} (القيمة الحالية)، {MINLENGTH} (الحد الأدنى للطول)، إلخ. يجب أن تسمح رسائل الخطأ في بيئة الإنتاج لمطوري الواجهة الأمامية بإصلاح المشكلات دون الرجوع إلى الوثائق — وهذا أكثر فائدة من الرسائل الموجزة ولكن الغامضة.

أداة التحقق النوع المطبق الوصف
required الكل الحقول الإلزامية
min/max الرقم، التاريخ النطاق العددي
minlength/maxlength سلسلة طول السلسلة
enum سلسلة القيم المُعدَّدة
match سلسلة مطابقة التعبير النمطي
unique الكل الفهرس الفريد (طبقة قاعدة البيانات)

▶ المثال 1: الاستخدام العملي لأداة التحقق المدمجة

تحليل استراتيجيات طبقة التحقق من صحة البيانات: يُعد تحديد الطبقة التي سيتم فيها إجراء التحقق من صحة البيانات قرارًا هندسيًّا بالغ الأهمية. يُعد التحقق من صحة البيانات على مستوى طبقة التطبيق (أدوات التحقق من صحة البيانات في Mongoose) مرنًا وقابلًا للتحكم — فهو يدعم المنطق المخصص، والتحقق غير المتزامن، ورسائل الخطأ سهلة الاستخدام — ولكنه لا يكون فعالًا إلا لعملاء Node.js. أما التحقق من صحة البيانات على مستوى طبقة قاعدة البيانات ($jsonSchema) فهو صارم وموثوق — حيث ينطبق على جميع العملاء — ولكنه لا يدعم سوى القواعد الثابتة. أفضل استراتيجية هي التحقق المزدوج: استخدام Mongoose للتحقق الأساسي (قواعد العمل، والمطالبات سهلة الاستخدام)، و$jsonSchema كخيار احتياطي (حماية هيكلية أساسية لمنع تجاوز طبقة التطبيق).

ترتيب تنفيذ عمليات التحقق من الصحة: تُنفَّذ عمليات التحقق من الصحة في Mongoose وفقًا لترتيب صارم: 1. عمليات التحقق من الصحة المدمجة في SchemaType (مطلوب → تحويل النوع → الحد الأدنى/الحد الأقصى/الحد الأدنى للطول/الحد الأقصى للطول/التعداد/المطابقة)؛ 2. أدوات التحقق المخصصة المتزامنة؛ 3. أدوات التحقق المخصصة غير المتزامنة؛ 4. pre-validate البرمجيات الوسيطة؛ 5. pre-save البرمجيات الوسيطة. سيؤدي الفشل في أي مرحلة إلى إيقاف عمليات التحقق اللاحقة وإصدار استثناء ValidationError.

الاختيار بين أدوات التحقق المتزامنة وغير المتزامنة: تتوفر أدوات التحقق في Mongoose بنوعين: المتزامنة وغير المتزامنة. تُرجع أدوات التحقق المتزامنة قيمة منطقية (على سبيل المثال، validator: v => v.length >= 3)، بينما تُرجع أدوات التحقق غير المتزامنة وعدًا (Promise) (على سبيل المثال، validator: async function(v) { const existing = await User.findOne({email: v}); return !existing; }). إرشادات الاختيار: 1. استخدم أدوات التحقق المتزامنة لعمليات التحقق التي لا تتطلب استعلامًا عن قاعدة البيانات (فحوصات التنسيق، وفحوصات النطاق، ومطابقة التعبيرات العادية)؛ 2. استخدم أدوات التحقق غير المتزامنة لعمليات التحقق التي تتطلب استعلامًا عن قاعدة البيانات (فحوصات التفرد، وفحوصات التكامل المرجعي). ملاحظة: أدوات التحقق غير المتزامنة أبطأ بـ 10 إلى 100 مرة من أدوات التحقق المتزامنة (حيث يتضمن كل عملية تحقق استعلامًا إلى قاعدة البيانات)، لذا يجب تقليل استخدامها إلى الحد الأدنى — يمكن استبدال عمليات التحقق من التفرد بفهارس فريدة (مضمونة على مستوى قاعدة البيانات، وهو أكثر موثوقية وكفاءة من الاستعلامات على مستوى التطبيق).

أنماط أدوات التحقق المجمعة: يمكن دمج عدة أدوات تحقق لتغطية سيناريوهات مختلفة — 1. «مطلوب» + «التنسيق»: required: [true, 'البريد الإلكتروني مطلوب'] + match: [/^.+@.+$/, 'تنسيق البريد الإلكتروني غير صالح'] (يتم أولاً التحقق من وجود العنصر، ثم التحقق من تنسيقه)؛ 2. النطاق + مخصص: min: [0, 'لا يمكن أن يكون رقمًا سالبًا'] + validator: v => v % 1 === 0 (يتم أولاً التحقق من النطاق، ثم التحقق مما إذا كان عددًا صحيحًا)؛ 3. قائمة + شرط: enum: ['draft', 'published'] + أداة تحقق مخصصة تتأكد من أنه «عند التغيير من 'draft' إلى 'published'، يجب ألا يكون حقل 'content' فارغًا». ترتيب أدوات التحقق مهم — ضع required أولاً (بحيث يتم تخطي القواعد اللاحقة إذا كان الحقل فارغًا)، وفحوصات التنسيق في المنتصف، وفحوصات منطق الأعمال في النهاية.

طبقة التحقق الموقع المزايا العيوب
مدمج في Mongoose طبقة التطبيق لا يحتاج إلى تهيئة، التنفيذ التلقائي يقتصر على القواعد الشائعة
Mongoose Custom طبقة التطبيق مرنة، غير متزامنة تزيد من حجم قاعدة الكود
$jsonSchema طبقة قاعدة البيانات ينطبق على جميع العملاء القواعد الثابتة فقط
JAVASCRIPT
const UserSchema = new mongoose.Schema({
  email: {
  username: {
    type: String,
    required: true,
    unique: true,
    minlength: [3, 'Username at least 3 chars'],
    maxlength: [30, 'Username at most 30 chars'],
    match: [/^[a-zA-Z0-9_]+$/, 'Only letters, numbers, underscores']
  },
  age: {
    type: Number,
    required: true,
    min: [0, 'Age cannot be negative'],
    max: [150, 'Age too large']
  },
  role: {
    type: String,
    enum: {
      values: ['customer', 'admin', 'moderator'],
      message: '{VALUE} is not a valid role'
    },
    default: 'customer'
  },
  passwordHash: {
    type: String,
    required: true,
    minlength: 60  // bcrypt Hash Length
  }
});

الإخراج:

TEXT 📖 للعرض فقط
تم تعريف مخطط المستخدم مع أدوات التحقق المدمجة: اسم المستخدم (3-30 حرفًا)، العمر (0-150)، الدور (قائمة تعداد).

100%
sequenceDiagram
    participant App as Application Code
    participant Schema as mongoose Schema
    participant DB as MongoDB

    App->>Schema: User.create({email, age})
    Schema->>Schema: Verification required
    Schema->>Schema: Verification match (emailFormat)
    Schema->>Schema: Verification min/max (age)
    alt Verification Passed
        Schema->>DB: insertOne()
        DB-->>Schema: Success
        Schema-->>App: Back User Object
    else Verification Failed
        Schema-->>App: ValidationError
    end

3. أداة التحقق المخصصة

شرح المفهوم: عندما لا تستطيع أدوات التحقق المدمجة تلبية قواعد العمل (مثل «لا يمكن أن يبدأ اسم المستخدم برقم» أو «القائمة السوداء لنطاقات البريد الإلكتروني»)، يتيح لك Mongoose كتابة دوال التحقق المخصصة في تعريفات الحقول. تنقسم أدوات التحقق المخصصة إلى نوعين: متزامنة وغير متزامنة؛ حيث تُرجع الدوال المتزامنة boolean، بينما تُرجع الدوال غير المتزامنة Promise<boolean>. ويتم تكوين كلا النوعين عبر الخيار validate.

كيفية العمل: يتم تشغيل أدوات التحقق المخصصة بعد عملية التحقق المدمجة. تتلقى أدوات التحقق المتزامنة قيم الحقول وتُرجع true في حالة النجاح أو false في حالة الفشل. أما أدوات التحقق غير المتزامنة، فتتلقى قيم الحقول وتُرجع وعدًا (Promise): resolve(true) في حالة النجاح وresolve(false) في حالة الفشل. في حالة الفشل، يتم إنشاء رسالة خطأ باستخدام القالب المحدد بواسطة الخيار message. ملاحظة: تزيد أدوات التحقق غير المتزامنة من وقت التأخير لكل عملية حفظ.

نمط تجميع أدوات التحقق من الصحة: يمكن تجميع عدة أدوات للتحقق من الصحة — حيث يقوم Mongoose بتنفيذها بالترتيب الذي تم إعلانها به، ولا يتم التحقق من الصحة بنجاح إلا إذا اجتازت جميعها الاختبار. التجميعات الشائعة: 1. required + match (مطلوب ومهيأ بشكل صحيح)؛ 2. minlength + التحقق المخصص من قوة كلمة المرور (يتحقق من الطول والتعقيد معًا)؛ 3. min + التحقق المخصص من النطاق (على سبيل المثال، لـ «معدل الخصم بين 0 و1»، استخدم min: 0 + max: 1؛ أما لـ «المبلغ لا يمكن أن يكون 0»، فيلزم أداة تحقق مخصصة: validator: v => v !== 0). عند الجمع بين أدوات التحقق من الصحة، انتبه إلى تمييز رسائل الخطأ — حيث يساعد قالب الرسالة المستقل لكل أداة تحقق الواجهة الأمامية على تحديد الحقل الذي به مشكلة بدقة.

100%
graph LR
    A[doc.save] --> B[Built-in validators<br/>required/min/max/enum...]
    B --> C{Built-in Pass?}
    C -->|No| D[ValidationError]
    C -->|Yes| E[Custom validators<br/>Synchronize/Asynchronous]
    E --> F{Custom Pass?}
    F -->|No| D
    F -->|Yes| G[pre save hooks]
    G --> H[MongoDB write]
    
    style D fill:#f8d7da
    style H fill:#d4edda
بعد المقارنة أداة التحقق المتزامنة أداة التحقق غير المتزامنة
طريقة التعريف validator: v => v >= 18 validator: async v => await check(v)
سرعة التنفيذ سريعة (بدون عمليات إدخال/إخراج) بطيئة (قد تتطلب استعلامًا عن قاعدة البيانات)
الاستخدامات الشائعة التحقق من صحة التنسيق، والتحقق من النطاق التحقق من التفرّد، والتحقق من القائمة السوداء
معالجة الأخطاء إرجاع false resolve(false)
توصيات الأداء استخدم كأولوية استخدم فقط عند الضرورة

(1) أداة التحقق المتزامنة

أنماط التصميم لآليات التحقق المتزامنة: تُعد آليات التحقق المتزامنة مناسبة للتحقق المنطقي البحت — أي القواعد التي لا تتضمن استعلامات قاعدة البيانات. الأنماط الشائعة: 1. التحقق من التنسيق (يجب ألا تحتوي عناوين البريد الإلكتروني على الرمز «+»؛ ويجب ألا تبدأ أسماء المستخدمين برقم)؛ 2. التحقق من النطاق (العمر >= 18؛ معدل الخصم بين 0 و1)؛ 3. التحقق من الطول (كلمة المرور >= 8 أحرف؛ رقم الهاتف 11 رقمًا)؛ 4. التحقق المركب (تاريخ الانتهاء >= تاريخ البدء). مبادئ التصميم: يجب أن تُرجع دالة أداة التحقق قيمة منطقية فقط (صحيح في حالة النجاح، خطأ في حالة الفشل) ويجب ألا تحدث أي آثار جانبية (لا تُعدّل this ولا تُطلق استثناءات).

تخصيص رسائل الخطأ لأدوات التحقق المخصصة: يجب أن تُعلم رسالة الخطأ الجيدة المطورين بطبيعة المشكلة بمجرد النظر إليها — validator: {validator: v => /^[a-z]/.test(v), message: 'يجب أن يبدأ اسم المستخدم بحرف صغير (القيمة الحالية: {VALUE})'}. تدعم قوالب الرسائل {VALUE} (القيمة الحالية)، و{PATH} (اسم الحقل)، و{MIN}/{MAX} (قيم الحدود)، و{LENGTH} (الطول الحالي). أمثلة على رسائل الخطأ باللغة الصينية: «يجب أن يتراوح العمر بين {MIN} و{MAX}؛ القيمة الحالية {VALUE}»، «يجب ألا يقل طول كلمة المرور عن {MINLENGTH} حرفًا؛ الطول الحالي {LENGTH}». كلما كانت الرسالة أكثر تحديدًا، زادت كفاءة اختبار تكامل الواجهة الأمامية — فعبارة «تنسيق اسم المستخدم غير صحيح» أكثر فائدة بمقدار 100 مرة من عبارة «فشل المدقق».

الحد الفاصل بين أدوات التحقق ومنطق الأعمال: يجب أن تقتصر أدوات التحقق في Mongoose على التحقق من صحة البيانات نفسها («هل البيانات صحيحة؟») وألا تتضمن منطق الأعمال («هل العملية مسموحة؟»). معايير التمييز: 1. التحقق من صحة البيانات (جزء من المخطط): تنسيق البريد الإلكتروني، وطول كلمة المرور، ونطاق المبلغ — هذه القواعد لا تتغير مع تغير سيناريوهات الأعمال؛ 2. منطق الأعمال (ينتمي إلى وحدة التحكم/الخدمة): ما إذا كان المستخدم يمتلك الإذن لإجراء تغييرات، وما إذا كان الرصيد كافيًا، وما إذا كان المخزون متاحًا — هذه القواعد تختلف حسب السيناريو. عواقب الخلط بين الاثنين: تتطلب عمليات تغيير كلمة المرور وإعادة تعيينها قواعد تحقق مختلفة، لكن المخطط يحتوي على مجموعة واحدة فقط من أدوات التحقق، مما يؤدي إلى تخطي التحقق من طول كلمة المرور أو ترميزه بشكل ثابت أثناء عمليات إعادة تعيين كلمة المرور.

JAVASCRIPT
const UserSchema = new mongoose.Schema({
  email: {
    type: String,
    validate: {
      validator: function(v) {
        // Email addresses cannot contain + sign (Email addresses with tags are not accepted.)
        return !v.includes('+');
      },
      message: 'Email cannot contain + character'
    }
  },
  age: {
    type: Number,
    validate: {
      validator: function(v) {
        return v >= 18;
      },
      message: 'Must be at least 18 years old'
    }
  }
});

(2) أداة التحقق غير المتزامنة (غير متزامنة)

الاختيار بين أدوات التحقق المتزامنة وغير المتزامنة: تُعد أدوات التحقق المتزامنة مناسبة للحسابات التي تتم داخل الذاكرة (المقارنات العددية، ومطابقات التعبيرات النمطية، وفحوصات التعداد)، في حين تتطلب أدوات التحقق غير المتزامنة عمليات بحث في قاعدة البيانات أو استدعاءات لواجهات برمجة التطبيقات الخارجية. مبادئ الاختيار — 1. استخدم أدوات التحقق المتزامنة كلما أمكن ذلك (أداء أفضل، ولا توجد آثار جانبية)؛ 2. استخدم أدوات التحقق غير المتزامنة فقط عندما يكون البحث في قاعدة البيانات مطلوبًا (على سبيل المثال، التحقق من تفرد اسم المستخدم، وكشف الكلمات الحساسة)؛ 3. العبء الإضافي على الأداء لأدوات التحقق غير المتزامنة: تتطلب كل عملية save عملية await لاستعلام قاعدة البيانات؛ أثناء العمليات الدفعية، N عملية تحقق = N استعلامات لقاعدة البيانات؛ 4. البديل لأدوات التحقق غير المتزامنة: وضع عمليات التحقق من التفرد في البرمجيات الوسيطة pre-save (التي يمكن تحسينها للمعالجة المجمعة) بدلاً من استخدام أداة التحقق async للحقول الفردية.

المخاطر واستراتيجيات التخفيف الخاصة بآليات التحقق غير المتزامنة: تُطلق آليات التحقق غير المتزامنة استعلامًا إلى قاعدة البيانات في كل مرة يتم فيها حفظ البيانات، مما قد يشكل عائقًا أمام الأداء في سيناريوهات التزامن العالي. استراتيجيات التخفيف: 1. استخدامها فقط عند الضرورة (مثل: فحوصات التفرّد، وفحوصات القائمة السوداء)؛ 2. استبدال فحوصات التفرّد غير المتزامنة بفهارس فريدة (وهي أكثر كفاءة على مستوى قاعدة البيانات)؛ 3. تخفيض مستوى عمليات التحقق غير الحرجة إلى فحوصات دورية مجمعة؛ 4. تخزين نتائج الاستعلامات المتكررة مؤقتًا (على سبيل المثال، يمكن تخزين إدخالات القائمة السوداء مؤقتًا لمدة 5 دقائق).

نمط تكوين أدوات التحقق: غالبًا ما تتطلب القواعد التجارية المعقدة مزيجًا من أدوات التحقق المتعددة — على سبيل المثال، يجب أن يستوفي اسم المستخدم المعايير التالية في آن واحد: ألا يحتوي على كلمات حساسة (غير متزامن)، وألا يبدأ برقم (متزامن)، وأن يتراوح طوله بين 3 و30 حرفًا (مدمج). يقوم Mongoose بتنفيذ جميع أدوات التحقق بالتسلسل؛ وإذا فشلت أول أداة، تتوقف العملية. يُنصح بوضع عمليات التحقق المتزامنة الخفيفة أولاً (للفشل السريع) وعمليات التحقق غير المتزامنة الثقيلة في النهاية (لتجنب عمليات الإدخال/الإخراج غير الضرورية).

JAVASCRIPT
const UserSchema = new mongoose.Schema({
  username: {
    type: String,
    validate: {
      validator: async function(v) {
        // Check if the username contains sensitive words
        const banned = await BannedWords.findOne({ word: v });
        return !banned;
      },
      message: 'Username contains banned word'
    }
  },
  email: {
    type: String,
    validate: {
      validator: async function(v) {
        // Check if the email domain has been blocked
        const domain = v.split('@')[1];
        const blocked = await BlockedDomains.findOne({ domain });
        return !blocked;
      },
      message: 'Email domain is blocked'
    }
  }
});

(3) الخيار validateBeforeSave

JAVASCRIPT
// === Default behavior:save Front Auto validate ===
const user = new User({ email: 'invalid' });
await user.save();  // ValidationError

// === Skip validate(Not recommended)===
const user = new User({ email: 'invalid' });
await user.save({ validateBeforeSave: false });

// === Manual validate ===
const user = new User({ email: 'invalid' });
try {
  await user.validate();
} catch (err) {
  console.error(err.message);  // ValidationError
}


4. تجربة عملية مع البرمجيات الوسيطة

نظرة عامة على المفهوم: برامج الوسيطة في Mongoose هي وظائف ربط يتم تشغيلها تلقائيًا خلال دورة حياة عمليات البيانات. يركز هذا القسم على أنماط البرمجيات الوسيطة الأكثر استخدامًا في التطوير العملي: تجزئة كلمات المرور (قبل الحفظ)، وإدارة الطوابع الزمنية (قبل الحفظ / الطوابع الزمنية المدمجة)، والحذف المؤقت (التصفية قبل البحث + طرق المثيل)، والتعبئة التلقائية (التعبئة قبل البحث). تغطي هذه الأنماط 80% من حالات استخدام البرمجيات الوسيطة.

كيفية العمل: يتم تسجيل البرامج الوسيطة (Middleware) في مخطط (Schema)، ويقوم Mongoose باستدعائها تلقائيًا عند تنفيذ العمليات ذات الصلة. يتم تنفيذ pre('save') قبل عملية الكتابة ويمكنها تعديل بيانات المستند؛ ويتم تنفيذ pre(/^find/) قبل الاستعلام ويمكنها تعديل شروط الاستعلام؛ ويتم تنفيذ post('save') بعد عملية الكتابة ويمكنها إحداث تأثيرات جانبية (التسجيل، والإشعارات). يتم تنفيذ البرامج الوسيطة بشكل متسلسل، ويجب على كل دالة استدعاء next() لتمرير التحكم.

100%
graph TB
    A[Middleware Patterns] --> B[Password Hash<br/>pre save<br/>isModifiedTesting]
    A --> C[Timestamp<br/>pre save / timestamps option]
    A --> D[Soft Delete<br/>pre findFilter<br/>softDeleteMethods]
    A --> E[Auto-fill<br/>pre find populate]
    
    B --> F["Only when changing the password<br/>Re-hash"]
    C --> G["Automatic Maintenance<br/>createdAt/updatedAt"]
    D --> H["Query Auto-Exclusion<br/>isDeleted: true"]
    E --> I["Query Autocomplete<br/>Related Documents"]
    
    style B fill:#d4edda
    style C fill:#cce5ff
    style D fill:#fff3cd
    style E fill:#e2d5f1
أنماط البرمجيات الوسيطة شروط التشغيل واجهة برمجة التطبيقات الأساسية الاستخدامات النموذجية
تجزئة كلمة المرور قبل الحفظ isModified('password') تُخزَّن التجزئة فقط عند تغيير كلمة المرور
الطابع الزمني ما قبل الحفظ / الطوابع الزمنية isNew، Date.now الحفاظ تلقائيًا على الحقول الزمنية
الحذف المؤقت pre /^find/ this.find({isDeleted:{$ne:true}}) استبعاد العناصر المحذوفة تلقائيًا من البحث
الملء التلقائي البحث المسبق this.populate(path) تحميل الارتباط التلقائي للاستعلام
الحذف التسلسلي post findOneAndDelete Model.deleteMany() تنظيف الارتباطات عند حذف المستند الرئيسي

(1) البرمجيات الوسيطة لتشفير كلمات المرور

مبادئ تصميم أمان كلمات المرور: يُعد تجزئة كلمات المرور حجر الزاوية في أي نظام آمن. ويُعد bcrypt الخيار القياسي في هذا المجال — فهو يتضمن إضافة الملح (salting) المدمجة، وعامل تكلفة قابل للتعديل (10–12)، ومقاومة لهجمات GPU/ASIC. نقاط التصميم الرئيسية: 1. استخدم isModified() لتجنب إعادة التجزئة عند كل عملية حفظ؛ 2. بشكل افتراضي، لا يتم إرجاع حقول كلمات المرور باستخدام select: false؛ 3. طول إثبات ما قبل التجزئة (لمنع كلمة مرور مكونة من 60 حرفًا من تجاوز متطلبات minlength بعد التجزئة)؛ 4. استخدم الإصدار غير المتزامن لتجنب حجب حلقة الأحداث.

JAVASCRIPT
UserSchema.pre('save', async function(next) {
  // Re-hash only when the password field is modified
  if (!this.isModified('passwordHash')) return next();

  try {
    const salt = await bcrypt.genSalt(10);
    this.passwordHash = await bcrypt.hash(this.passwordHash, salt);
    next();
  } catch (err) {
    next(err);
  }
});

(2) البرمجيات الوسيطة للتوقيت

خيار "timestamps" مقابل البرامج الوسيطة اليدوية: يقوم خيار timestamps: true في Mongoose بإدارة createdAt وupdatedAt تلقائيًا، مما يلغي الحاجة إلى كتابة برامج وسيطة يدوية قبل الحفظ — وهذه هي الطريقة الموصى بها. يجب استخدام البرامج الوسيطة اليدوية فقط عند وجود متطلبات محددة: 1. أسماء حقول الطوابع الزمنية المخصصة (على سبيل المثال، created_at بدلاً من createdAt)؛ 2. عندما يلزم ربط الطوابع الزمنية بمعرف المستخدم (على سبيل المثال، updatedBy)؛ 3. عندما يلزم تشغيل منطق إضافي عند تحديث الطابع الزمني. في 95% من الحالات، يكفي استخدام timestamps: true ببساطة.

JAVASCRIPT
// === mongoose Built-in timestamps option ===
const schema = new mongoose.Schema({...}, { timestamps: true });
// Auto-add createdAt/updatedAt

// === Custom Timestamp Middleware ===
schema.pre('save', function(next) {
  this.updatedAt = new Date();
  if (this.isNew) {
    this.createdAt = new Date();
  }
  next();
});

(3) برمجيات وسيطة للحذف المؤقت

قرار هندسي بشأن الحذف المؤقت: الحذف الفعلي (الحذف النهائي) لا رجعة فيه وينتهك متطلبات الامتثال الخاصة بالبيانات (على سبيل المثال، ينص «الحق في النسيان» الوارد في اللائحة العامة لحماية البيانات (GDPR) على إخفاء الهوية بدلاً من الحذف). يُنفذ الحذف المؤقت الحذف المنطقي باستخدام علامة isDeleted — حيث تقوم البرمجيات الوسيطة pre-find تلقائيًا بتصفية المستندات المحذوفة، مما يجعل العملية شفافة بالنسبة لرمز الأعمال. الأسباب الرئيسية لاختيار الحذف المؤقت هي: 1. قابلية استعادة البيانات (يمكن استعادة البيانات المحذوفة عن طريق الخطأ)؛ 2. متطلبات التدقيق (الاحتفاظ بسجل العمليات)؛ 3. سلامة المراجع (تظل المراجع من المستندات الأخرى سليمة).

تكاليف التخزين واستراتيجيات التنظيف في حالة الحذف المؤقت: تكمن تكلفة الحذف المؤقت في أن «البيانات الزومبي» تستمر في شغل مساحة التخزين والفهرس — 1. تضخم التخزين: بافتراض أن 30% من البيانات تم حذفها مؤقتًا، يتوسع حجم المجموعة الأساسية بنسبة 43% (100 / 70 ≈ 1.43)، ويتوسع الفهرس بنفس النسبة؛ 2. تأثير الاستعلام: تضيف البرمجيات الوسيطة الخاصة بالبحث المسبق الشرط isDeleted: {$ne: true} إلى كل استعلام؛ وعلى الرغم من توفر الفهارس، فإن هذا يزيد من تعقيد الاستعلام؛ 3. استراتيجية التنظيف: تقوم مهمة كرون (cron) بترحيل البيانات التي تم حذفها مؤقتًا منذ أكثر من 90 يومًا إلى مجموعة أرشيفية (مع الحذف الفعلي للسجلات من المجموعة الرئيسية)؛ ويتم الاحتفاظ بالمجموعة الأرشيفية لمدة عام واحد قبل حذفها نهائيًا؛ 4. الامتثال للائحة العامة لحماية البيانات (GDPR): عندما يطلب المستخدم الحذف، يتم استبدال حقول المعلومات الشخصية بـ [REDACTED] (مُجهَّلة الهوية)، بدلاً من مجرد وضع علامة isDeleted عليها — وهذا يفي بالمتطلبات القانونية بأن تكون البيانات «غير قابلة للتعريف»، مع الاحتفاظ بالبيانات لأغراض التحليل الإحصائي.

مقارنة بين نماذج تنفيذ الحذف المؤقت:

النمط التنفيذ تأثير الاستعلام صعوبة الاستعادة
علامة منطقية isDeleted: منطقية التصفية التلقائية قبل البحث ما عليك سوى تعيينها على «false»
الطابع الزمني تم الحذف في: التاريخ تم الحذف في: {$ne: null} حقل غير محدد
استبدال المحتوى المحتوى: '[تم حذفه]' لا حاجة إلى تصفية لا يمكن استعادة النص الأصلي
JAVASCRIPT
// === Soft-Delete Field ===
const schema = new mongoose.Schema({
  isDeleted: { type: Boolean, default: false },
  deletedAt: Date,
  deletedBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }
});

// === pre find Filter: Deleted ===
schema.pre(/^find/, function(next) {
  this.find({ isDeleted: { $ne: true } });
  next();
});

// === softDelete Instance Methods ===
schema.methods.softDelete = async function(deletedBy) {
  this.isDeleted = true;
  this.deletedAt = new Date();
  this.deletedBy = deletedBy;
  return await this.save();
};

// === restore Instance Methods ===
schema.methods.restore = async function() {
  this.isDeleted = false;
  this.deletedAt = undefined;
  this.deletedBy = undefined;
  return await this.save();
};

(4) الملء التلقائي للبرمجيات الوسيطة

المفاضلات التصميمية لميزة «التعبئة التلقائية»: تعمل ميزة «التعبئة التلقائية» في pre find على تحسين تجربة التطوير — حيث يتم استرداد البيانات ذات الصلة تلقائيًا مع كل استعلام، مما يلغي الحاجة إلى استدعاء populate يدويًّا في كل وحدة تحكم. ومع ذلك، فإن هذه السهولة تأتي بتكلفة: 1. يتم تنفيذ عمليات إدخال/إخراج إضافية مع كل استعلام (حتى عندما لا تكون البيانات ذات الصلة مطلوبة)؛ 2. تؤدي استدعاءات populate المتداخلة إلى مشكلة الاستعلام N+1؛ 3. يصعب ملء البيانات بشكل انتقائي بناءً على سيناريوهات محددة. النهج الموصى به: استخدم setOptions() لتشغيل عملية الملء بشكل مشروط، بدلاً من الاعتماد على الملء التلقائي غير المشروط.

التحكم في أداء ميزة «التعبئة التلقائية»: يمكن معالجة مشكلات الأداء المتعلقة بميزة «التعبئة التلقائية» بالطرق التالية: 1. التعبئة المتفرقة: التعبئة عند الحاجة فقط (يتم تشغيلها بواسطة req.query.populate=true، بدلاً من التعبئة التلقائية الافتراضية)؛ 2. قائمة الحقول المسموح بها: لا تملأ الميزة التلقائية سوى الحقول الرئيسية (على سبيل المثال، author: 'username avatar'، بدلاً من جميع معلومات المستخدم)؛ 3. lean + $lookup يدويًا: استبدال populate بـ lean() وخطوط أنابيب التجميع لاستعلامات القوائم الحساسة للأداء؛ 4. تخزين نتائج populate مؤقتًا: بالنسبة للبيانات المرتبطة التي نادرًا ما تتغير (مثل الصور الرمزية للمستخدمين أو الأدوار)، قم بتخزين نتائج populate مؤقتًا في Redis. أفضل الممارسات في بيئة الإنتاج: «استخدم التعبئة التلقائية فقط من أجل كفاءة التطوير، وليس من أجل أداء بيئة الإنتاج.»

JAVASCRIPT
// === Default populate Related Fields ===
UserSchema.pre('find', function(next) {
  this.populate({
    path: 'profileId',
    select: 'avatar bio'
  });
  next();
});

// === Conditions populate ===
UserSchema.pre('find', function(next) {
  if (this.options.includeOrders) {
    this.populate('orders');
  }
  next();
});

// Usage:
const user = await User.findById(userId);  // Automatic populate profileId
const userWithOrders = await User.findById(userId).setOptions({ includeOrders: true });

▶ المثال 2: تنفيذ كامل للبرمجيات الوسيطة

نمط تكوين سلسلة البرامج الوسيطة: في المشاريع الواقعية، عادةً ما يقوم مخطط واحد بتسجيل عدة مكونات من البرامج الوسيطة لتشكيل «سلسلة معالجة» كاملة. مبادئ التكوين: 1. وضع البرامج الوسيطة الخاصة بتحويل البيانات في البداية (مثل تحويل عناوين البريد الإلكتروني إلى أحرف صغيرة، واقتطاع السلاسل) لضمان حصول عمليات التحقق من الصحة اللاحقة على بيانات موحدة؛ 2. وضع البرمجيات الوسيطة الخاصة بالتحقق من الصحة في المنتصف (مثل: التحقق من طول كلمة المرور النصية العادية قبل التجزئة)؛ 3. توضع البرمجيات الوسيطة القائمة على الآثار الجانبية في النهاية (سجلات التدقيق، وإشعارات الدفع)، حيث تكون البيانات قد تم التحقق من صحتها بالفعل عند هذه المرحلة. تعمل البرمجيات الوسيطة الخاصة بمعالجة الأخطاء (الإصدار ذو المعلمات الأربعة) كشبكة أمان لالتقاط جميع الاستثناءات.

JAVASCRIPT
// === Complete User Model Middleware ===
UserSchema.pre('save', async function(next) {
  if (this.isModified('passwordHash') && !this.passwordHash.startsWith('$2b$')) {
    this.passwordHash = await bcrypt.hash(this.passwordHash, 10);
  }
  next();
});

UserSchema.pre(/^find/, function(next) {
  this.where({ isDeleted: { $ne: true } });
  next();
});

UserSchema.post('save', function(doc, next) {
  if (this.wasNew) {
    logger.info(`New user: ${doc.email}`);
  }
  next();
});

UserSchema.post('findOneAndDelete', function(doc) {
  if (doc) {
    // Cascading Deletion of Related Data
    Session.deleteMany({ userId: doc._id });
    Cart.deleteMany({ userId: doc._id });
  }
});

الإخراج:

TEXT 📖 للعرض فقط
New user: alice@example.com
تم حذف الجلسات وعربة التسوق المرتبطة بالمستخدم المحذوف.


5. رسائل الخطأ المخصصة

شرح المفهوم: تتيح لك Mongoose تخصيص رسائل الخطأ لكل أداة تحقق، كما تدعم متغيرات القوالب (مثل {VALUE}، {PATH}، {MIN})، مما يجعل رسائل الخطأ أكثر سهولة في الاستخدام. هناك طريقتان لتخصيص الرسائل: (1) صيغة المصفوفة [validator, message]؛ (2) صيغة الكائن { validator, message }. نوصي باستخدام صيغة الكائن، لأنها أكثر مرونة ويمكنها تضمين متغيرات القوالب.

الدعم متعدد اللغات لرسائل الخطأ: يجب أن تدعم رسائل الخطأ في تطبيقات الإنتاج لغات متعددة — 1. قوالب الرسائل: تعريف رسائل الخطأ كسلاسل قوالب (على سبيل المثال، 'validation.{PATH}.min') بدلاً من ترميزها بشكل ثابت باللغة الصينية أو الإنجليزية؛ 2. الاستبدال أثناء التشغيل: تختار طبقة وحدة التحكم حزمة لغة بناءً على رأس Accept-Language وتستبدل متغيرات القوالب؛ 3. التخصيص على مستوى الحقول: استخدم الدوال بدلاً من السلاسل لكل message من حقول المخطط — message: (props) => i18n.t('validation.age.min', { value: props.value })؛ 4. برمجيات وسيطة موحدة لتنسيق الأخطاء: قم بتحويل ValidationError في Mongoose بشكل متسق إلى تنسيق i18n داخل البرمجيات الوسيطة لمعالجة الأخطاء. تتيح هذه الآلية لواجهة برمجة تطبيقات (API) واحدة خدمة المستخدمين في جميع أنحاء العالم.

كيفية العمل: عند فشل عملية التحقق من الصحة، يقوم Mongoose باستبدال العناصر النائبة في الرسالة بمتغيرات القالب. يتم استبدال {VALUE} بالقيمة الفعلية، و{PATH} بمسار الحقل، و{MIN}/{MAX} بقيم القيود. تساعد هذه المعلومات الواجهة الأمامية على عرض سبب الخطأ بدقة.

متغير القالب المعنى مثال على الناتج
{VALUE} قيمة الإدخال الفعلية 'Age must be at least 18, got 15'
{PATH} مسار الحقل 'email is required'
{MIN} / {MAX} قيمة حدية القيد 'Age must be >= 0'
قيد الطول
JAVASCRIPT
const UserSchema = new mongoose.Schema({
  email: {
    type: String,
    required: [true, 'Email is required'],
    match: [/\S+@\S+\.\S+/, 'Invalid email format: {VALUE}'],
    unique: true
  },
  age: {
    type: Number,
    min: [18, 'Age must be at least 18, got {VALUE}'],
    max: [150, 'Age cannot exceed 150']
  },
  password: {
    type: String,
    minlength: [8, 'Password must be at least 8 characters'],
    validate: {
      validator: function(v) {
        return /[A-Z]/.test(v) && /[0-9]/.test(v);
      },
      message: 'Password must contain uppercase and digit'
    }
  }
});


6. معالجة الأخطاء في validate

مبادئ بنية معالجة الأخطاء: يحتوي ValidationError على معلومات الأخطاء لجميع الحقول (وليس الحقل الأول فقط)، مما يتيح للواجهة الأمامية عرض جميع مشكلات التحقق من الصحة دفعة واحدة. قم بالتكرار على كائن err.errors لاسترداد تفاصيل الأخطاء لكل حقل — field (مسار الحقل)، message (رسالة الخطأ)، value (القيمة الفعلية)، وkind (نوع أداة التحقق). في بيئة الإنتاج، يجب تحويل ValidationError إلى تنسيق استجابة أخطاء موحد بدلاً من الكشف المباشر عن البنية الداخلية لـ Mongoose.

تصنيف الأخطاء واستراتيجيات الاسترداد: يتم التعامل مع الأنواع الثلاثة من الأخطاء بطرق مختلفة تمامًا — يشير خطأ التحقق من الصحة (ValidationError) (4xx) إلى مشكلة في مدخلات المستخدم ويجب أن يحدد الحقل المحدد الذي تسبب في الخطأ؛ ويشير خطأ التحويل (CastError) (4xx) عادةً إلى خطأ في تنسيق المعرّف ويجب أن يعرض الرسالة «معرّف المورد غير صالح»؛ يشير E11000 (4xx) إلى تعارض في قيد التفرد ويجب أن يحدد الحقل والقيمة المكررين. يجب ألا تكشف أخطاء 5xx عن التفاصيل الفنية؛ بل يجب أن تعرض بشكل موحد رسالة «الخدمة غير متاحة مؤقتًا» وتطلق تنبيهًا.

100%
graph TD
    A[ValidationError] --> B[errors Object]
    B --> C["errors.email<br/>ValidatorError<br/>message: 'Invalid email'"]
    B --> D["errors.age<br/>ValidatorError<br/>message: 'Age must be >= 18'"]
    B --> E["errors._id<br/>CastError<br/>message: 'invalid ObjectId'"]
    
    F[MongoServerError] --> G["code: 11000<br/>Unique Index Conflict"]
    
    style A fill:#f8d7da
    style F fill:#fff3cd
نوع الخطأ شروط حدوثه طريقة الكشف
ValidationError فشل أداة التحقق err.name === 'ValidationError'
CastError فشل تحويل النوع err instanceof mongoose.Error.CastError
E11000 تعارض في الفهرس الفريد err.code === 11000

استراتيجية موحدة للتعامل مع أخطاء التحقق من الصحة: يجب أن تتعامل تطبيقات الإنتاج مع أخطاء التحقق من الصحة في Mongoose بطريقة متسقة، بدلاً من تكرار كتل try-catch في كل وحدة تحكم — 1. البرمجيات الوسيطة الخاصة بالأخطاء: في البرمجيات الوسيطة الخاصة بمعالجة الأخطاء في Express، قم بتحويل ValidationError بشكل موحد إلى استجابة 400 واستخرج معلومات الخطأ لكل حقل لإنشاء رسائل سهلة الفهم للمستخدم؛ 2. معالجة CastError: يجب تعيين أخطاء CastError (مثل ObjectId غير الصالحة) إلى رمز الحالة 400 بدلاً من 500 — حيث إن قيام المستخدم بإرسال معرّف بتنسيق غير صحيح يُعد خطأً من جانب العميل؛ 3. معالجة E11000: يجب ربط تعارضات الفهرس الفريدة برمز الحالة 409 Conflict بالإضافة إلى رسالة سهلة الفهم («عنوان البريد الإلكتروني هذا مسجل بالفعل»)، بدلاً من الكشف عن خطأ MongoDB الخام؛ 4. الأخطاء غير المعروفة: جميع الأخطاء الأخرى تُرجع رمز الحالة 500 مع رسالة عامة (دون الكشف عن التفاصيل الداخلية). وتتمثل فائدة هذا التعامل الموحد في أن الواجهة الأمامية لا تحتاج سوى إلى مجموعة واحدة من منطق معالجة الأخطاء، ولا تحتاج وحدة التحكم الخلفية إلى الاهتمام بتنسيق الأخطاء.

عرض أخطاء التحقق في الواجهة الأمامية: يجب أن تكون أخطاء التحقق محددة على مستوى الحقل — 1. أخطاء على مستوى الحقل: كل حقل في err.errors له message خاص به؛ يمكن للواجهة الأمامية عرض رسالة خطأ باللون الأحمر أسفل حقل الإدخال المقابل؛ 2. أولوية الخطأ: أخطاء required > أخطاء type > أخطاء التحقق من الصحة المخصصة (عرض "مطلوب" أولاً، ثم "تنسيق غير صالح")؛ 3. التحقق من الصحة في الوقت الفعلي: تستخدم الواجهة الأمامية Joi للتحقق المسبق من الصحة (تقديم ردود فعل فورية أثناء كتابة المستخدم)، بينما يعمل التحقق من الصحة باستخدام Mongoose في الخلفية كخط دفاع أخير (نظرًا لإمكانية تجاوز التحقق من الصحة في الواجهة الأمامية)؛ 4. ترجمة رسائل الخطأ: يستخدم err.errors[field].message قوالب باللغتين الصينية والإنجليزية ويعرض اللغة المقابلة بناءً على رأس Accept-Language. يتيح العرض الدقيق للأخطاء للمستخدمين تحديد المشكلات وتصحيحها بسرعة، بدلاً من مواجهة رسالة غامضة مثل «إدخال غير صالح».

JAVASCRIPT
// === Handling Validation Errors ===
try {
  await User.create({ email: 'invalid', age: 200 });
} catch (err) {
  if (err.name === 'ValidationError') {
    // Handling Field Errors
    for (const field in err.errors) {
      console.error(`${field}: ${err.errors[field].message}`);
    }
  }
}

// === mongoose Error Type ===
const mongoose = require('mongoose');

if (err instanceof mongoose.Error.ValidationError) {
  // Validation Error
}
if (err instanceof mongoose.Error.CastError) {
  // Type Conversion Error (e.g. ObjectId Format error)
}
if (err.code === 11000) {
  // Unique Index Conflict
}

▶ المثال 3:نظام تحقق متكامل من صحة البيانات في ShopHub(الصعوبة ⭐⭐)

JAVASCRIPT
// Scene:ShopHub A user registration system that requires multi-layer validation
const userSchema = new mongoose.Schema({
  username: {
    type: String,
    required: [true, 'اسم المستخدم مطلوب'],
    minlength: [3, 'اسم المستخدم يجب أن يكون 3 أحرف على الأقل'],
    maxlength: [30, 'اسم المستخدم يجب أن يكون 30 حرفًا كحد أقصى'],
    match: [/^[a-zA-Z0-9_]+$/, 'اسم المستخدم يجب أن يحتوي على أحرف وأرقام وشرطات سفلية فقط'],
    validate: {
      validator: async function(v) {
        // تحقق غير متزامن من عدم وجود اسم المستخدم مسبقًا
        const existing = await this.constructor.findOne({ username: v });
        return !existing;
      },
      message: 'اسم المستخدم موجود بالفعل'
    }
  },
  email: {
    type: String,
    required: [true, 'البريد الإلكتروني مطلوب'],
    match: [/^\S+@\S+\.\S+$/, 'تنسيق البريد الإلكتروني غير صالح'],
    lowercase: true,
    trim: true
  },
  password: {
    type: String,
    required: [true, 'كلمة المرور مطلوبة'],
    minlength: [8, 'كلمة المرور يجب أن تكون 8 أحرف على الأقل'],
    validate: {
      validator: function(v) {
        // يجب أن تحتوي على حرف كبير ورقم
        return /[A-Z]/.test(v) && /[0-9]/.test(v);
      },
      message: 'كلمة المرور يجب أن تحتوي على حرف كبير ورقم'
    }
  },
  age: {
    type: Number,
    min: [13, 'يجب أن يكون العمر 13 سنة على الأقل'],
    max: [120, 'العمر غير صالح']
  },
  role: {
    type: String,
    enum: {
      values: ['customer', 'seller', 'admin'],
      message: '{VALUE} ليس دورًا صالحًا'
    },
    default: 'customer'
  }
}, { timestamps: true });

// تجزئة كلمة المرور تلقائيًا
userSchema.pre('save', async function(next) {
  if (!this.isModified('password')) return next();
  const bcrypt = require('bcrypt');
  this.password = await bcrypt.hash(this.password, 12);
  next();
});

// استخدام النموذج
const User = mongoose.model('User', userSchema);

try {
  await User.create({
    username: 'alice2026',
    email: 'alice@example.com',
    password: 'SecurePass123',
    age: 25
  });
  console.log('تم إنشاء المستخدم بنجاح');
} catch (err) {
  if (err.name === 'ValidationError') {
    for (const field in err.errors) {
      console.error(`${field}: ${err.errors[field].message}`);
    }
  }
}

الإخراج:

TEXT 📖 للعرض فقط
تم إنشاء المستخدم بنجاح

❓ أسئلة شائعة

س أيهما يتم تقييمه أولاً، required أم default؟
ج يتم تقييم required أولاً، ثم يتم تطبيق default. إذا لم يتم توفير أي قيمة وكانت قيمة default موجودة، يتم استخدام قيمة default؛ أما إذا كانت required: true موجودة ولم يتم توفير أي قيمة، يتم إصدار خطأ.
س ماذا يحدث عندما يُصدر مُحقق صحة مخصص خطأً؟
ج يصبح الخطأ Error.message الذي تم إصداره هو رسالة الخطأ الخاصة بهذا الحقل. لتخصيص التنسيق، استخدم الخيار message.
س هل validateBeforeSave: false آمن؟
ج لا، إنه ليس آمنًا. استخدمه فقط في الحالات التي تكون فيها البيانات موثوقة (مثل ترحيل البرامج النصية). قم بتعطيله في بيئات الإنتاج.
س هل أداء أداة التحقق غير المتزامن ضعيف؟
ج ضعيف نسبيًا، لأن كل سجل يتطلب استعلامًا غير متزامن. نوصي بتنفيذ قيود التكامل خلال مرحلة تصميم المخطط واستخدام أداة التحقق غير المتزامن فقط للتحقق عن بُعد الضروري.

📖 ملخص


📝 تمارين

  1. تمرين أساسي (⭐): حدد مخطط المستخدم وقم بتطبيق جميع أدوات التحقق المدمجة (الحقول الإلزامية، البريد الإلكتروني، الحد الأدنى والأقصى للعمر، قائمة الأدوار).
  2. سؤال أساسي (⭐): أضف أداة تحقق مخصصة: لا يجوز أن تبدأ أسماء المستخدمين برقم.
  3. تمرين متقدم (⭐⭐): قم بتنفيذ عملية تجزئة كلمة المرور (التحقق من isModified) باستخدام البرمجية الوسيطة pre-save.
  4. تمرين متقدم (⭐⭐): قم بتنفيذ الحذف المؤقت (باستخدام مرشح preFind وطريقة المثيل softDelete).
  5. التحدي (⭐⭐⭐): نموذج تسجيل مستخدم كامل (5+ عوامل تحقق + تجزئة كلمة المرور + الطابع الزمني + الحذف المؤقت + معالجة الأخطاء).
Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%