跳到內容
getnextpdf.com

Enterprise 版本

SaaS — 深入參考

Enterprise SaaS 模組為以 NextPDF 為基礎的服務提供多租戶的建構元件。

  • TenantContext 是一個不可變的身分值物件,僅從已驗證情境解析。
  • ApiKeyGeneratorApiKeyAuthenticator 負責簽發並驗證帶前綴、帶 checksum、以雜湊儲存的 API key。
  • QuotaChecker 依逐租戶配額對請求設下閘門:80% 時警告、100% 時拒絕、用量未知時 fail-closed 拒絕。
  • SidecarJwtMinter 為元件間呼叫簽鑄短效的 HS256 服務 token。
  • UsageMeterStripeMeteringSyncer 拉取用量事件,並以確定性的冪等性將其同步至計費供應商。

此能力隨附於 NextPDF Enterprisenextpdf/enterprise),並在具備 Enterprise 層級授權信封時啟用。缺少該授權的部署不會載入此能力的類別。比較版本並取得授權

SaaS 介面是一項基礎 Enterprise 能力;不存在獨立的逐功能旗標。NextPDF Core(Apache-2.0)與 NextPDF Pro 沒有租戶、API-key 或配額模型;此能力沒有較低層級的對應。

Terminal window
composer require nextpdf/enterprise:^3

所有符號都位於 NextPDF\Enterprise\SaaS 之下。

符號參數預設行為回傳拋出或失敗於備註
TenantContextstring $tenantIdstring $sourcearray $scopes = ['read']不可變的身分值物件值物件來源:jwtmtlsapi_keyhasScope() / hasAnyScope() 測試範圍
TenantContext::singleTenant()固定的 default 租戶,帶有 readwriteadminTenantContext單租戶部署
ApiKeyAuthenticator::authenticate()string $rawKey六步驗證,接著解析情境TenantContextApiKeyAuthenticationException(HTTP 401)情境的 sourceapi_key;範圍由金鑰紀錄複製而來
ApiKeyAuthenticator::requireScope()TenantContext $contextApiKeyScope $requiredScope明確的範圍斷言voidApiKeyAuthenticationException::insufficientScope()(HTTP 403)範圍強制是一個獨立而明確的步驟
ApiKeyGenerator::generateLive() / ::generateTest()新金鑰:前綴、32 字元的 base62 主體(192-bit 熵)、4 字元 checksumarray{key, hash, prefix}前綴 npf_live_ / npf_test_hash 是儲存用摘要
ApiKeyGenerator::validateChecksum()string $key前綴、長度與 CRC32-checksum 形態檢查bool任何資料存放區查詢之前的打字錯誤防護;不是安全控制
ApiKeyGenerator::hashKey()(靜態)string $key原始金鑰的 SHA-256 十六進位摘要string金鑰唯一被儲存的表示形式
ApiKeyGenerator::isLiveKey() / ::isTestKey()string $key前綴檢視bool不必查詢即可看出環境
ApiKeyid、租戶、金鑰雜湊、顯示前綴、範圍遮罩、建立/到期/撤銷時點已儲存的金鑰紀錄;明文永不留存值物件isActive()isRevoked()isExpired()scopeNames()
ApiKeyScope帶值列舉:Read = 1Write = 2Admin = 4位元遮罩範圍模型列舉maskFromNames()fromName()fullAccess();遮罩建構器會忽略未知的名稱
ApiKeyRepositoryInterface儲存合約;僅以雜湊留存由實作定義findByHash()findActiveByTenant()store()revoke()
SidecarJwtMinter::__construct()string $secret、issuer、audience、int $ttlSeconds = 300在建構時拒絕小於 16 bytes 的簽章密鑰實例InvalidArgumentException128-bit 金鑰強度下限;建議 32 bytes 以上的隨機位元組
SidecarJwtMinter::mint()TenantContext $tenant帶有 issaudsubscopetenant_idiatexpjti 的 HS256 JWTstringclaim 編碼失敗時拋出 JsonException預設五分鐘生命週期;jti 為 16 隨機位元組,以十六進位編碼
QuotaChecker::check()TenantContext $tenantTenantQuota $quota讀取當前用量;80% 時警告;100% 時拒絕;用量未知時拒絕array{allowed: bool, warning_percentage: float|null}QuotaExceededExceptionQuotaUnavailableException兩道門檻都會呼叫警示回呼
TenantQuotafloat $maxCuPerPeriod、集合、儲存位元組、並行工作逐期限制;80% 軟門檻常數值物件fromConfig() 預設值:10,000 CU、100 集合、10 GB、10 工作
QuotaExceededException::toErrorEnvelope()SPEC-QUOTA-001 錯誤信封arrayHTTP 402,不可重試;攜帶當前值、上限與重設時點
QuotaUnavailableException::toErrorEnvelope()SPEC-QUOTA-503 錯誤信封arrayHTTP 503,可重試;原因 usage_undeterminable
UsageMeter::pullUsage()array<string, int> $watermarks從各自的游標輪詢每個已設定的用量來源主機array{events, instance_id}當每個主機都無法連線時拋出 UsageMeterException容忍部分中斷;無法連線的主機會被記錄並略過
UsageMeter::getCurrentUsage()string $tenantId當期的運算單元用量float當用量無法判定時拋出 UsageMeterException可解析的零具權威性;未知的用量會拋出例外
StripeMeteringSyncer::sync()array<string, int> $watermarks一次拉取、轉換、送出的週期array{watermarks, sent, failed}無;送出失敗會路由至 DLQ 回呼拉取失敗會回傳保留游標的無操作週期
StripeAdapter::sendMeterEvent()MeterEvent $event帶著冪等標頭 POST 至供應商voidStripeSyncExceptionHTTP 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 {}
}
  • 租戶身分。 租戶情境是不可變的:租戶識別碼、解析來源、範圍。身分僅從已驗證情境解析(jwtmtlsapi_key)——絕不從客戶端提供的標頭或查詢參數取得。單租戶部署使用帶有完整範圍的固定 default 情境。
  • 驗證順序。 API-key 驗證以固定順序進行:checksum、SHA-256 雜湊、儲存庫查詢、撤銷檢查、到期檢查、情境解析。未知、已撤銷與已過期的金鑰是三種各自獨立的結果,全部為 HTTP 401;範圍不足則為 HTTP 403。
  • 金鑰保密。 原始金鑰絕不儲存或記錄;只有其 SHA-256 摘要會被留存並查詢。驗證器本身不執行任何逐位元組的密鑰比較;時序安全的摘要查詢是儲存庫實作的合約。
  • 配額門檻。 在 80% 軟上限時,請求會繼續進行、回傳警告百分比,並觸發警示回呼。在 100% 硬上限時,請求會以 SPEC-QUOTA-001(HTTP 402)被拒絕,並攜帶重設時點——下個月第一天的午夜 UTC。
  • 配額 fail-closed。 無法判定的用量會以 SPEC-QUOTA-503(HTTP 503,可重試)拒絕請求。未知的用量絕不被視為零。真正、可解析的零用量具權威性並允許通過。
  • 警示去重。 檢查器不對警示去重;逐期去重是回呼的責任。
  • 計量同步。 此週期採排程,絕不在請求路徑上。它從逐來源的 watermark 續行,並將每個游標推進到最後一個成功送出的事件身分。冪等鍵是確定性的——租戶、期間、事件身分——因此重送的事件會在供應商端的去重中收斂。
  • 拉取失敗。 失敗的拉取會回傳一個保留 watermark 的無操作週期(sent 為 0、failed 為 0);下一個週期會重試同一個視窗,而不是略過它。
  • 服務 token。 token 採用帶共用密鑰的 HS256,並攜帶 issaudsubscopetenant_idiatexp,以及一個唯一的 jti。預設生命週期為五分鐘。建構會 fail-closed 地拒絕小於 16 bytes 的密鑰。
  • 格式錯誤的金鑰會未通過 checksum,並在任何資料存放區存取之前被拒絕。格式正確但未知的金鑰會在查詢後被拒絕。兩者都以無效金鑰的結果呈現。
  • 未知、已撤銷與已過期的金鑰使用各自獨立的例外工廠;keyExpired 旗標只在已過期的結果為 true。請將它們對應到各自獨立的客戶端回應。
  • QuotaChecker::check() 只在允許通過時回傳;回傳的 allowed 永遠為 true。拒絕與不可用都是例外的結果。
  • TenantQuota::usagePercentage() 在配額為非正值時回傳 0.0fromConfig() 會以預設值取代缺失的值,並將整數上限夾制到至少 1。
  • watermark 是逐來源的;缺失的 watermark 會從該來源串流的開頭開始(游標 0)。多來源部署會維護各自獨立的 watermark。
  • 轉換會略過非陣列事件、操作或租戶缺失或為空的事件、非正值,或未對應的操作——而不會使週期失敗。缺少可用正整數身分的事件會伴隨一則警告被拒絕:隨機的後備鍵會破壞供應商端的去重,並可能對租戶重複計費。
  • 連續十次送出失敗會升級為一則 critical 日誌項目;計數器會在任何一次成功送出時重設。每個失敗的事件仍會抵達死信回呼。
  • 來自用量來源主機的格式錯誤 JSON 主體會產生一個空的事件清單,而非週期失敗。pullUsage() 只在每個已設定的主機都無法連線時才拋出例外。
  • 摘要與 MAC 原語透過主機 PHP 密碼學供應者採用 SHA-256 與 HMAC-SHA256。FIPS 受限的建置在遇到非核准演算法時會 fail closed,而非降級;SaaS 層不另加任何自有的密碼學原則。
  • 金鑰主體與 token 識別碼來自 CSPRNG(random_int()random_bytes())。
  • CRC32 checksum 不是密碼學控制,且不受 FIPS 模式影響。

以下陳述描述本能力對照所引用條款的表現。它們不是認證聲明;NextPDF 未針對此模組持有任何認證。

行為參考
服務 token 的 exp not-after 語意RFC 7519 §4.1.4
服務 token 的 JWS compact serializationRFC 7515 §3.1
16-byte HS256 密鑰下限;不以人類可記憶的密碼作為 MAC 金鑰RFC 8725 §3.5(威脅:§2.2)
儲存庫摘要查詢的時序安全合約OWASP ASVS 5.0 §11.2.4
API-key 儲存摘要 SHA-256FIPS 180-4(程式碼宣告)

RFC 8725 與 OWASP ASVS 5.0 的引用經 RAG 驗證;完整的 reference identifier 記錄於本頁的 frontmatter。FIPS 180-4、FIPS 198-1 與 BSI TR-02102-1 的參考是在產品原始碼中由程式碼宣告的(hash('sha256', …) 與 minter 所記載的金鑰下限);本頁並未從 RAG 語料庫取得它們。ASVS §11.2.4 的時序安全要求約束的是操作者所提供的儲存庫實作,而非驗證器類別本身。

  • 請提供 ApiKeyRepositoryInterfaceStripeAdapterInterface 的持久化實作;此套件隨附的是合約與一個 PSR-18 供應商客戶端,而非持久化。
  • 相依項僅為 PSR 抽象:PSR-3 logger、PSR-18 HTTP client、PSR-17 request 與 stream factory。不需要任何供應商 SDK。
  • 請將計量同步作為排程工作執行。每個週期後請持久化地留存回傳的 watermark。
  • 請將配額警告百分比呈現給客戶端,例如作為警告標頭,並在回呼中逐期對配額警示去重。
  • 請從設定提供 token minter 的密鑰,作為高熵的隨機值;建議使用 32 bytes 以上的隨機位元組。絕不要從密碼衍生它。
  • 金鑰前綴讓環境不必查詢就能看出;sandbox 與 production 金鑰絕不會相撞,因為前綴會參與到儲存的摘要中。
  • 內部機制細節保留在原始碼儲存庫的內部文件中,不在本手冊的範圍內。

本頁僅記載外部可觀察的行為與受支援的公開 API 介面。內部命名空間路徑、輔助類別、機制表格、runbook 檔名與工單前綴都不在範圍內。