تخطَّ إلى المحتوى
getnextpdf.com

Enterprise الإصدار

SaaS — مرجع متعمّق

توفّر وحدة ⁨SaaS⁩ في ⁨Enterprise⁩ اللبنات متعددة المستأجرين لخدمة قائمة على ⁨NextPDF⁩.

  • TenantContext كائن قيمة هوية غير قابل للتغيير، يُحلّ من السياق المُصادَق عليه فقط.
  • ApiKeyGenerator وApiKeyAuthenticator يُصدِران ويتحقّقان من مفاتيح ⁨API⁩ ذات بادئة ومجموع تحقّق ومُخزّنة كتجزئة.
  • QuotaChecker يضبط الطلبات وفق حصص كل مستأجر: تحذير عند 80%، ورفض عند 100%، ومنع بالفشل المغلق عندما يكون الاستخدام غير معروف.
  • SidecarJwtMinter يسكّ رموز خدمة ⁨HS256⁩ قصيرة العمر للنداءات بين المكوّنات.
  • UsageMeter وStripeMeteringSyncer يسحبان أحداث الاستخدام ويزامنانها مع مزوّد الفوترة بعدم تكرار حتمي.

تُشحن هذه الإمكانية في ⁨NextPDF Enterprise⁩ (nextpdf/enterprise) وتُفعَّل بغلاف ترخيص من فئة ⁨Enterprise⁩. لا تُحمّل عمليةُ نشرٍ بلا ذلك الاستحقاق أصنافَ الإمكانية. قارن الإصدارات واحصل على ترخيص.

سطح ⁨SaaS⁩ إمكانية أساسية في ⁨Enterprise⁩؛ ولا توجد راية منفصلة لكل ميزة. لا يملك ⁨NextPDF Core⁩ (⁨Apache-2.0⁩) و⁨NextPDF Pro⁩ أي نموذج للاستئجار أو مفتاح ⁨API⁩ أو حصة؛ ولهذه الإمكانية لا يوجد مكافئ في فئة أدنى.

Terminal window
composer require nextpdf/enterprise:^3

تقع كل الرموز تحت NextPDF\Enterprise\SaaS.

الرمزالمعاملاتالسلوك الافتراضيالقيمة المُعادةيرمي أو يفشل بـملاحظات
TenantContextstring $tenantId، string $source، array $scopes = ['read']كائن قيمة هوية غير قابل للتغييركائن قيمةلا شيءالمصادر: jwt، mtls، api_key؛ وhasScope() / hasAnyScope() تختبران النطاقات
TenantContext::singleTenant()لا شيءمستأجر default ثابت مع read، write، adminTenantContextلا شيءعمليات النشر أحادية المستأجر
ApiKeyAuthenticator::authenticate()string $rawKeyتحقّق من ست خطوات، ثم حلّ السياقTenantContextApiKeyAuthenticationException (⁨HTTP 401⁩)سياق source هو api_key؛ وتُنسخ النطاقات من سجل المفتاح
ApiKeyAuthenticator::requireScope()TenantContext $context، ApiKeyScope $requiredScopeتأكيد صريح للنطاقvoidApiKeyAuthenticationException::insufficientScope() (⁨HTTP 403⁩)فرض النطاق خطوة منفصلة وصريحة
ApiKeyGenerator::generateLive() / ::generateTest()لا شيءمفتاح جديد: بادئة، وجسم base62 من 32 حرفاً (⁨192-bit⁩ من الإنتروبيا)، ومجموع تحقّق من 4 أحرفarray{key, hash, prefix}لا شيءالبادئتان npf_live_ / npf_test_؛ وhash هو ملخّص التخزين
ApiKeyGenerator::validateChecksum()string $keyفحص شكل البادئة والطول ومجموع تحقّق ⁨CRC32⁩boolلا شيءحماية من الأخطاء المطبعية قبل أي بحث في مخزن البيانات؛ وليست ضابطاً أمنياً
ApiKeyGenerator::hashKey() (ساكنة)string $keyملخّص ⁨SHA-256⁩ السداسي عشري للمفتاح الخامstringلا شيءالتمثيل الوحيد المُخزَّن للمفتاح
ApiKeyGenerator::isLiveKey() / ::isTestKey()string $keyفحص البادئةboolلا شيءالبيئة مرئية دون بحث
ApiKeyالمعرّف، والمستأجر، وتجزئة المفتاح، وبادئة العرض، وقناع النطاق، ولحظات الإنشاء/الانتهاء/الإبطالسجل مفتاح مُخزَّن؛ ولا يُحفظ النص الصريح أبداًكائن قيمةلا شيءisActive()، isRevoked()، isExpired()، scopeNames()
ApiKeyScopeتعداد مدعوم: Read = 1، Write = 2، Admin = 4نموذج نطاق بقناع بتّيتعدادلا شيءmaskFromNames()، fromName()، fullAccess()؛ ويتجاهل باني القناع الأسماء غير المعروفة
ApiKeyRepositoryInterfaceعقد التخزين؛ وحفظ التجزئة فقطمُحدَّد بالتنفيذfindByHash()، findActiveByTenant()، store()، revoke()
SidecarJwtMinter::__construct()string $secret، والمُصدِر، والجمهور، وint $ttlSeconds = 300يرفض سرّ التوقيع الأقل من 16 بايت عند الإنشاءنسخةInvalidArgumentExceptionحدّ أدنى لقوة المفتاح ⁨128-bit⁩؛ ويُوصى بـ 32 بايت عشوائياً أو أكثر
SidecarJwtMinter::mint()TenantContext $tenant⁨HS256 JWT⁩ مع iss، aud، sub، scope، tenant_id، iat، exp، jtistringJsonException عند فشل ترميز المطالبةعمر افتراضي خمس دقائق؛ وjti هو 16 بايت عشوائياً مُرمّزاً بالسداسي عشري
QuotaChecker::check()TenantContext $tenant، TenantQuota $quotaيقرأ الاستخدام الحالي؛ ويحذّر عند 80%؛ ويرفض عند 100%؛ ويمنع عندما يكون الاستخدام غير معروفarray{allowed: bool, warning_percentage: float|null}QuotaExceededException، QuotaUnavailableExceptionيُستدعى ردّ نداء التنبيه عند كلا الحدّين
TenantQuotafloat $maxCuPerPeriod، والمجموعات، وبايتات التخزين، والمهام المتزامنةحدود لكل فترة؛ وثابت حدّ ليّن 80%كائن قيمةلا شيءافتراضات fromConfig(): ⁨10,000 CU⁩، و100 مجموعة، و⁨10 GB⁩، و10 مهام
QuotaExceededException::toErrorEnvelope()لا شيءغلاف خطأ SPEC-QUOTA-001array⁨HTTP 402⁩، غير قابل لإعادة المحاولة؛ ويحمل الحالي والحدّ ولحظة إعادة التعيين
QuotaUnavailableException::toErrorEnvelope()لا شيءغلاف خطأ SPEC-QUOTA-503array⁨HTTP 503⁩، قابل لإعادة المحاولة؛ والسبب usage_undeterminable
UsageMeter::pullUsage()array<string, int> $watermarksيستطلع كل مضيف مصدر استخدام مُهيّأ من مؤشّرهarray{events, instance_id}UsageMeterException عندما يتعذّر الوصول إلى كل مضيفيُحتمَل الانقطاع الجزئي؛ وتُسجَّل المضيفات المتعذّر الوصول إليها وتُتخطّى
UsageMeter::getCurrentUsage()string $tenantIdاستخدام وحدات الحوسبة للفترة الحاليةfloatUsageMeterException عندما يتعذّر تحديد الاستخدامالصفر القابل للتحليل مرجعيّ؛ والاستخدام غير المعروف يرمي استثناءً
StripeMeteringSyncer::sync()array<string, int> $watermarksدورة سحب وتحويل وإرسال واحدةarray{watermarks, sent, failed}لا شيء؛ وتُوجَّه إخفاقات الإرسال إلى ردّ نداء ⁨DLQ⁩يُعيد فشل السحب دورة بلا عمل تحفظ المؤشّر
StripeAdapter::sendMeterEvent()MeterEvent $event⁨POST⁩ إلى المزوّد مع ترويسة عدم التكرارvoidStripeSyncException⁨HTTP 429⁩ و⁨5xx⁩ قابلان لإعادة المحاولة؛ وباقي ⁨4xx⁩ غير قابل لإعادة المحاولة
StripeAdapter::sendBatch()list<MeterEvent> $eventsيرسل كل حدث؛ ويجمع الإخفاقاتlist<StripeSyncException>لا شيءالقائمة الفارغة تعني نجاح كل حدث
MeterEventاسم العدّاد، والمستأجر، والقيمة، ومفتاح عدم التكرار، والطابع الزمنيكائن قيمة حدث عدّاد غير قابل للتغييركائن قيمةلا شيءtoStripePayload() تسلسل حمولة المزوّد
final readonly class ApiKeyAuthenticator
{
public function __construct(
private ApiKeyRepositoryInterface $repository,
private ApiKeyGenerator $generator,
private LoggerInterface $logger,
) {}
public function authenticate(string $rawKey): TenantContext {}
public function requireScope(TenantContext $context, ApiKeyScope $requiredScope): void {}
}
final class QuotaChecker
{
public function __construct(
private readonly UsageMeterInterface $usageMeter,
private readonly LoggerInterface $logger,
private readonly Closure $quotaAlertCallback,
) {}
/** @return array{allowed: bool, warning_percentage: float|null} */
public function check(TenantContext $tenant, TenantQuota $quota): array {}
}
interface UsageMeterInterface
{
/** @return array<string, mixed> */
public function pullUsage(array $watermarks): array;
public function getCurrentUsage(string $tenantId): float;
}
final class StripeMeteringSyncer
{
public function __construct(
private readonly UsageMeterInterface $usageMeter,
private readonly StripeAdapterInterface $stripeAdapter,
private readonly LoggerInterface $logger,
private readonly Closure $dlqCallback,
) {}
/** @return array{watermarks: array<string, int>, sent: int, failed: int} */
public function sync(array $watermarks): array {}
}
final readonly class SidecarJwtMinter
{
public function __construct(
private string $secret,
private string $issuer = 'nextpdf-enterprise',
private string $audience = 'nextpdf-spectrum',
private int $ttlSeconds = self::DEFAULT_TTL_SECONDS,
) {}
public function mint(TenantContext $tenant): string {}
}
  • هوية المستأجر. سياق المستأجر غير قابل للتغيير: معرّف المستأجر، ومصدر الحلّ، والنطاقات. تُحلّ الهوية من السياق المُصادَق عليه فقط (jwt، mtls، api_key) — ولا تُحلّ أبداً من ترويسة أو معامِل استعلام يوفّرهما العميل. تستخدم عملية النشر أحادية المستأجر سياق default الثابت بكامل النطاقات.
  • ترتيب المصادقة. تسير مصادقة مفتاح ⁨API⁩ بترتيب ثابت: مجموع التحقّق، وتجزئة ⁨SHA-256⁩، والبحث في المستودع، وفحص الإبطال، وفحص الانتهاء، وحلّ السياق. المفاتيح غير المعروفة والمُبطَلة والمنتهية ثلاث نتائج متمايزة، كلها ⁨HTTP 401⁩؛ والنطاق غير الكافي ⁨HTTP 403⁩.
  • سرّية المفتاح. لا يُخزَّن المفتاح الخام أو يُسجَّل أبداً؛ ويُحفظ ملخّصه ⁨SHA-256⁩ فقط ويُبحث عنه. لا يُجري المُصادِق أي مقارنة بايتية للسرّ بنفسه؛ والبحث عن الملخّص بزمن ثابت هو عقد تنفيذ المستودع.
  • حدود الحصة. عند الحدّ الليّن 80% يمضي الطلب، وتُعاد نسبة التحذير، ويُطلَق ردّ نداء التنبيه. وعند الحدّ الصارم 100% يُرفض الطلب بـ SPEC-QUOTA-001 (⁨HTTP 402⁩) حاملاً لحظة إعادة التعيين — أول يوم من الشهر التالي، منتصف الليل ⁨UTC⁩.
  • فشل الحصة المغلق. الاستخدام المتعذّر تحديده يمنع الطلب بـ SPEC-QUOTA-503 (⁨HTTP 503⁩، قابل لإعادة المحاولة). لا يُعامَل الاستخدام غير المعروف كصفر أبداً. والاستخدام الصفري الحقيقي القابل للتحليل مرجعيّ ويسمح بالطلب.
  • إزالة تكرار التنبيهات. لا يزيل الفاحص تكرار التنبيهات؛ وإزالة التكرار لكل فترة مسؤولية ردّ النداء.
  • مزامنة القياس. الدورة مُجدوَلة، ولا تكون أبداً على مسار الطلب. تستأنف من علامات مائية لكل مصدر وتُقدّم كل مؤشّر إلى أعلى هوية حدث أُرسل بنجاح. مفتاح عدم التكرار حتمي — المستأجر، والفترة، وهوية الحدث — لذا ينهار الحدث المُعاد إرساله على إزالة التكرار لدى المزوّد.
  • فشل السحب. يُعيد السحب الفاشل دورة بلا عمل (sentfailed 0) تحفظ العلامات المائية؛ وتُعيد الدورة التالية المحاولة على النافذة نفسها بدلاً من تخطّيها.
  • رموز الخدمة. الرموز من نوع ⁨HS256⁩ بسرّ مشترك وتحمل iss، aud، sub، scope، tenant_id، iat، exp، وjti فريداً. العمر الافتراضي خمس دقائق. يرفض الإنشاء سرّاً أقل من 16 بايت، بفشل مغلق.

الحالات الحديّة وأنماط الفشل

قسم بعنوان «الحالات الحديّة وأنماط الفشل»
  • المفتاح المشوّه يفشل في مجموع التحقّق ويُرفض قبل أي وصول لمخزن البيانات. والمفتاح جيّد التكوين لكنه غير معروف يُرفض بعد البحث. وكلاهما يظهر كنتيجة مفتاح غير صالح.
  • المفاتيح غير المعروفة والمُبطَلة والمنتهية تستخدم مصانع استثناء متمايزة؛ وراية keyExpired صحيحة فقط في النتيجة المنتهية. اربطها باستجابات عميل متمايزة.
  • QuotaChecker::check() تعود فقط عند القبول؛ وقيمة allowed المُعادة دائماً true. والرفض وعدم التوفّر نتيجتان استثنائيتان.
  • TenantQuota::usagePercentage() تُعيد 0.0 للحصة غير الموجبة؛ وfromConfig() تستبدل الافتراضات بالقيم الغائبة وتُقيّد الحدود الصحيحة إلى 1 على الأقل.
  • العلامات المائية لكل مصدر؛ والعلامة المائية الغائبة تبدأ من بداية دفق ذلك المصدر (المؤشّر 0). وعملية النشر متعددة المصادر تحافظ على علامات مائية مستقلة.
  • يتخطّى التحويل الأحداث غير المصفوفة، والأحداث ذات العملية أو المستأجر الغائب أو الفارغ، والقيمة غير الموجبة، أو العملية غير المرتبطة — دون إفشال الدورة. ويُرفض الحدث الذي يفتقر إلى هوية عدد صحيح موجب صالحة للاستخدام مع تحذير: مفتاح احتياطي عشوائي سيُبطل إزالة التكرار من جهة المزوّد وقد يُفوتر المستأجر مرتين.
  • عشرة إخفاقات إرسال متتالية تتصاعد إلى إدخال سجل حَرِج؛ ويُعاد ضبط العدّاد عند أي إرسال ناجح. وكل حدث فاشل يصل مع ذلك إلى ردّ نداء الرسائل الميتة.
  • جسم ⁨JSON⁩ مشوّه من مضيف مصدر استخدام يُنتج قائمة أحداث فارغة، لا فشلاً في الدورة. وpullUsage() ترمي استثناءً فقط عندما يتعذّر الوصول إلى كل مضيف مُهيّأ.
  • بدائيات الملخّص و⁨MAC⁩ هي ⁨SHA-256⁩ و⁨HMAC-SHA256⁩ عبر مزوّد التشفير ⁨PHP⁩ للمضيف. والبناء المقيّد بـ ⁨FIPS⁩ يفشل مغلقاً على خوارزمية غير معتمدة بدلاً من التخفيض؛ ولا تضيف طبقة ⁨SaaS⁩ أي سياسة تشفير خاصة بها.
  • أجسام المفاتيح ومعرّفات الرموز تأتي من ⁨CSPRNG⁩ (random_int()، random_bytes()).
  • مجموع تحقّق ⁨CRC32⁩ ليس ضابطاً تشفيرياً ولا يتأثّر بوضع ⁨FIPS⁩.

تصف العبارات أدناه الإمكانية مقابل البنود المُستشهَد بها. وهي ليست ادّعاءات اعتماد؛ ولا يملك ⁨NextPDF⁩ أي اعتماد لهذه الوحدة.

السلوكالمرجع
دلالات عدم-بعد لـ exp رمز الخدمةRFC 7519 §4.1.4
تسلسل ⁨JWS⁩ المضغوط لرمز الخدمةRFC 7515 §3.1
حدّ أدنى لسرّ ⁨HS256⁩ مقداره 16 بايت؛ ولا كلمات مرور يحفظها البشر كمفاتيح ⁨MAC⁩RFC 8725 §3.5 (threat: §2.2)
عقد البحث عن الملخّص بزمن ثابت في المستودعOWASP ASVS 5.0 §11.2.4
ملخّص تخزين مفتاح ⁨API⁩ بـ ⁨SHA-256⁩FIPS 180-4 (code-declared)

استشهادات ⁨RFC 8725⁩ و⁨OWASP ASVS 5.0⁩ مُتحقَّق منها عبر ⁨RAG⁩؛ ومعرّفات المراجع الكاملة مُسجَّلة في مقدّمة هذه الصفحة. ومراجع ⁨FIPS 180-4⁩ و⁨FIPS 198-1⁩ و⁨BSI TR-02102-1⁩ مُعلَنة في الكود ضمن مصدر المنتج (hash('sha256', …) وحدّ المفتاح المُوثّق للساكّ)؛ ولم تُسترجَع من مجموعة ⁨RAG⁩ لهذه الصفحة. ومتطلّب الزمن الثابت في ⁨ASVS §11.2.4⁩ يُلزِم تنفيذ المستودع الذي يوفّره المشغّل، لا صنف المُصادِق نفسه.

  • وفّر تنفيذات دائمة لـ ApiKeyRepositoryInterface وStripeAdapterInterface؛ فالحزمة تشحن العقود وعميل مزوّد ⁨PSR-18⁩، لا الحفظ.
  • التبعيات تجريدات ⁨PSR⁩ فقط: مُسجِّل ⁨PSR-3⁩، وعميل ⁨PSR-18 HTTP⁩، ومصنعا طلب ودفق ⁨PSR-17⁩. ولا يلزم أي ⁨SDK⁩ للمزوّد.
  • شغّل مزامنة القياس كمهمة مُجدوَلة. واحفظ العلامات المائية المُعادة بدوام بعد كل دورة.
  • اعرض نسبة تحذير الحصة للعملاء، مثلاً كترويسة تحذير، وأزل تكرار تنبيهات الحصة لكل فترة في ردّ النداء.
  • زوّد سرّ ساكّ الرموز من التهيئة كقيمة عشوائية عالية الإنتروبيا؛ ويُوصى بـ 32 بايت عشوائياً أو أكثر. ولا تشتقّه من كلمة مرور أبداً.
  • بادئات المفاتيح تجعل البيئة مرئية دون بحث؛ ولا تتصادم مفاتيح البيئة التجريبية والإنتاج أبداً لأن البادئة تشارك في الملخّص المُخزَّن.
  • تفاصيل الآلية الداخلية تبقى في التوثيق الداخلي لمستودع المصدر وهي خارج نطاق هذا الدليل.

توثّق هذه الصفحة السلوك القابل للملاحظة خارجياً وسطح ⁨API⁩ العام المدعوم فقط. أما مسارات فضاء الأسماء الداخلية، والأصناف المساعِدة، وجداول الآلية، وأسماء ملفات كتيّب التشغيل، وبادئات التذاكر فهي خارج النطاق.