اكتشفت هذه المشكلة وحللتها بنفسي بعد أن أبلغ عنها أحد العملاء. استخدمت الذكاء الاصطناعي لتوليد تقرير الخطأ لأنني كنت كسولًا، ولأنه يؤدي المهمة بشكل أفضل مما أفعل - وقد اكتشف خطأً في الكود الأساسي أثناء ذلك، والذي قمت بتحقق منه يدويًا.
ليست مشكلة كبيرة، لكن قد يكون من المنطقي التحقق مما إذا كان هذا النمط يسبب مشاكل في أماكن أخرى أيضًا.
بدأ الأمر عندما قام شخص ما بتمكين enable_direct_s3_uploads دون إعداد S3 وتمكينه فعليًا، مما أدى إلى تعطيل جميع عمليات الرفع، وإرجاع أخطاء 500 في الخلفية. اعتقدت أن الخطأ 500 يستحق التحقيق.
ملخص
عند تمكين enable_direct_s3_uploads بينما تكون وحدة تخزين الرفع النشطة محلية، قد يؤدي محاولة الرفع إلى ظهور:
NoMethodError (undefined method 'signed_request_for_temporary_upload' for an instance of FileStore::LocalStore)
app/services/external_upload_manager.rb:34:in 'ExternalUploadManager.create_direct_upload'
lib/external_upload_helpers.rb:56:in 'ExternalUploadHelpers#generate_presigned_put'
app/controllers/application_controller.rb:443:in 'block in ApplicationController#with_resolved_locale'
app/controllers/application_controller.rb:443:in 'ApplicationController#with_resolved_locale'
يبدو أن السبب وراء ذلك هو مزيج من حالة إعدادات غير صالحة واستدعاء before_action مُعاد كتابته.
الإعدادات
الحالة المشكلة هي فعليًا:
SiteSetting.enable_direct_s3_uploads == true
SiteSetting.enable_s3_uploads == false
Discourse.store.is_a?(FileStore::LocalStore) == true
بالنسبة للتخزين المحلي، لا تنفذ Discourse.store الطريقة التالية:
signed_request_for_temporary_upload
وهو أمر متوقع، نظرًا لأن هذه العملية لا معنى لها إلا لوحدة تخزين خارجية/S3.
السلوك المتوقع
إذا تم تمكين عمليات الرفع المباشرة إلى S3 بينما تكون وحدة التخزين النشطة محلية، فيجب رفض الطلب بشكل نظيف بواسطة external_store_check.
ويفضل أيضًا منع أو التحقق من صحة مجموعة إعدادات الموقع غير الصالحة.
السلوك الفعلي
يصل الطلب إلى ExternalUploadManager.create_direct_upload، الذي يستدعي:
store.signed_request_for_temporary_upload(...)
على FileStore::LocalStore، مما يؤدي إلى ظهور NoMethodError.
السبب المحتمل
يسجل ExternalUploadHelpers الاستدعاء التالي:
before_action :external_store_check,
only: %i[
generate_presigned_put
complete_external_upload
create_multipart
batch_presign_multipart_parts
complete_multipart
abort_multipart
]
يجب أن يمنع هذا وصول الطلب إلى كود الرفع الخارجي عندما تكون وحدة التخزين محلية.
ومع ذلك، تسجل UploadsController لاحقًا استدعاءً آخر باستخدام نفس طريقة الفلتر:
before_action :external_store_check,
only: %i[_show_secure_deprecated show_secure]
يعتبر Rails التسجيل المتكرر لنفس الاستدعاء إعادة تعريف، لذا يبدو أن الاستدعاء اللاحق يحل محل شروط only: السابقة.

نتيجة لذلك، لم يعد يتم تنفيذ external_store_check لـ generate_presigned_put، مما يسمح لوحدة التخزين المحلية بالوصول إلى مسار كود الرفع المباشر.
إعادة الإنتاج
- قم بإعداد نسخة من Discourse مع تخزين الرفع المحلي.
- تأكد من:
SiteSetting.enable_s3_uploads = false
SiteSetting.enable_direct_s3_uploads = true
- حاول رفع ملف.
- لاحظ ظهور
NoMethodErrorمنFileStore::LocalStore.
يمكن إجراء فحص للحالة عبر وحدة التحكم باستخدام:
[
SiteSetting.enable_direct_s3_uploads,
SiteSetting.enable_s3_uploads,
Discourse.store.class,
Discourse.store.external?
]
الإصلاح المقترح
يجب ألا تتجاوز تسجيلات الاستدعاءات بعضها البعض.
على سبيل المثال، شمل إجراءات الرفع الآمنة في استدعاء external_store_check الموجود:
before_action :external_store_check,
only: %i[
generate_presigned_put
complete_external_upload
create_multipart
batch_presign_multipart_parts
complete_multipart
abort_multipart
_show_secure_deprecated
show_secure
]
أو، استخدم طريقة استدعاء منفصلة لإجراءات الرفع الآمنة.
قد يكون من الجدير أيضًا إضافة تحقق بحيث لا يمكن تمكين enable_direct_s3_uploads بينما تكون عمليات الرفع إلى S3/الخارجية معطلة.
حل مؤقت
للمواقع التي تستخدم تخزين الرفع المحلي:
SiteSetting.enable_direct_s3_uploads = false
يمنع هذا الخطأ.