feat(registry): resolve_by_slug 路径模式解析 + TENANT_SLUG_RE 常量上提
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -29,6 +29,7 @@ FileTenantSource(seed_profiles/,测试/出厂档案)与 DbTenantSource
|
||||
"""
|
||||
import asyncio
|
||||
import pathlib
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
|
||||
from sqlalchemy import event
|
||||
@@ -49,6 +50,10 @@ from app.platform.tenant_source import (
|
||||
TenantSource,
|
||||
)
|
||||
|
||||
# 租户 slug 形态(创建侧 tenant_routes 原 SLUG_RE 的单一事实源——路径前缀
|
||||
# 解析与创建校验共用,永不漂移;spec 2026-10-19 §3.1)
|
||||
TENANT_SLUG_RE = re.compile(r"^[a-z0-9-]+$")
|
||||
|
||||
|
||||
class UnknownTenantError(LookupError):
|
||||
"""Host/域名无法映射到任何租户(调用方语义:聊天/公开端点 404)。"""
|
||||
@@ -225,6 +230,23 @@ class TenantRegistry:
|
||||
"""resolve_site 的便捷形式(双 API 一层实现)。"""
|
||||
return (await self.resolve_site(host))[0]
|
||||
|
||||
async def resolve_by_slug(self, slug: str) -> tuple[TenantRuntime, Site]:
|
||||
"""路径前缀模式(/t/<slug>/,spec 2026-10-19 §5):slug → (runtime,
|
||||
首站点)。语义与 resolve_site 同款:未知 slug / 空站点表 →
|
||||
UnknownTenantError;停用 → TenantDisabledError;懒建 engine、
|
||||
reload 竞态取 _ensure_ready 返回值(与 resolve_site 同理)。"""
|
||||
rt = self._by_slug.get(slug)
|
||||
if rt is None:
|
||||
raise UnknownTenantError(f"未知租户 slug: {slug!r}")
|
||||
if rt.config.status != "active":
|
||||
raise TenantDisabledError(f"租户已停用: {slug!r}")
|
||||
rt = await self._ensure_ready(rt)
|
||||
if not rt.config.sites:
|
||||
# 创建向导恒建 main 站点,此分支为防御(spec §5)
|
||||
raise UnknownTenantError(
|
||||
f"租户 {slug!r} 无站点——路径模式无从选站点")
|
||||
return rt, rt.config.sites[0]
|
||||
|
||||
# ---- 装配期辅助(Task 5b: main.lifespan 逐租户启动清扫)------------------
|
||||
|
||||
def runtimes(self) -> tuple[TenantRuntime, ...]:
|
||||
|
||||
Reference in New Issue
Block a user