feat: platform.db 模型与超管引导(WAL/scrypt/幂等bootstrap)

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-10-13 07:57:15 +08:00
parent d088264ca0
commit 2d62108c73
5 changed files with 520 additions and 6 deletions
+25 -2
View File
@@ -5,7 +5,12 @@ import logging
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from app.engine.config import STATIC_DIR
from app.engine.config import STATIC_DIR, settings
from app.platform.db import (
bootstrap_superuser,
ensure_platform_schema,
make_platform_engine,
)
from app.platform.host_middleware import TenantHostMiddleware
from app.platform.registry import TenantRegistry
@@ -15,6 +20,7 @@ logger = logging.getLogger(__name__)
BASE_DIR = Path(__file__).resolve().parents[1]
SEED_DIR = BASE_DIR / "seed_profiles"
TENANTS_DIR = BASE_DIR / "tenants" # .gitignore 已忽略 tenants/*.db
PLATFORM_DB = BASE_DIR / "platform.db" # .gitignore 已忽略 platform.db*
@asynccontextmanager
@@ -26,7 +32,22 @@ async def lifespan(app: FastAPI):
registry = TenantRegistry.load_from_dir(SEED_DIR, data_dir=TENANTS_DIR)
app.state.registry = registry
# 平台库测试缝(Phase 2 Task 1,同 registry 缝模式):预置过
# app.state.platform_engine(临时库)则沿用——不预置则每个 TestClient
# 测试都会在仓库根建出真实 platform.db。schema+bootstrap 无条件跑
# (两者幂等):预置引擎同样被建到可用,Task 2 起认证测试直接可用。
platform_engine = getattr(app.state, "platform_engine", None)
if platform_engine is None:
platform_engine = make_platform_engine(PLATFORM_DB)
app.state.platform_engine = platform_engine
try:
await ensure_platform_schema(platform_engine)
await bootstrap_superuser(
platform_engine,
username=settings.ADMIN_USERNAME,
password=settings.ADMIN_PASSWORD)
# 询盘推送(设计 8.2): 清扫崩溃遗留的 stale pending → failed。逐租户、
# 用该租户自己的 session_factory;db 文件不存在的租户跳过——全新库
# 无遗留行,保持 engine 懒建纪律(租户 engine 由 Host 中间件按请求
@@ -48,8 +69,10 @@ async def lifespan(app: FastAPI):
yield
finally:
# 幂等于"更少的引擎":启动半途失败也把已建好的 engine 关掉
# (Windows 文件锁: 不关则库文件删不掉)
# (Windows 文件锁: 不关则库文件删不掉)。platform_engine 含预置的
# 测试引擎——dispose 后 engine 对象仍可重连,测试侧重置引用即可。
await registry.dispose_all()
await platform_engine.dispose()
app = FastAPI(title="HESUPIPE AI 客服", lifespan=lifespan)