feat(startup): 服务端启动 bootstrap 与缓存预热

- 新增 STARTUP_BOOTSTRAP_ENABLED / STARTUP_CACHE_WARMUP_ENABLED 配置开关
- lifespan 拆分 bootstrap 步骤并后台线程预热缓存,失败可降级继续启动
- server_start.sh / web_start.sh 扩展 SERVER_PORT、启动与调度开关的 env 覆盖
- bootstrap_paddleocr_mobile.sh 改用 python3 并补充 poppler-utils 依赖
- 补充启动 bootstrap 与 env 覆盖优先级测试
This commit is contained in:
caoxiaozhu
2026-06-18 22:11:37 +08:00
parent 35372c6661
commit 59ba76c74a
7 changed files with 236 additions and 8 deletions

View File

@@ -79,3 +79,63 @@ def test_root_start_can_prefer_env_file_over_inherited_onlyoffice_values(tmp_pat
assert result.returncode == 0, result.stderr
assert "ONLYOFFICE_ENABLED=true" in result.stdout
assert "ONLYOFFICE_PUBLIC_URL=http://10.10.10.122:8082" in result.stdout
def test_web_start_preserves_inherited_runtime_ports(tmp_path: Path) -> None:
result = _run_script_prefix(
tmp_path,
"web/web_start.sh",
env_file_content=(
"WEB_HOST=10.10.10.122\n"
"WEB_PORT=5273\n"
"SERVER_HOST=10.10.10.122\n"
"SERVER_PORT=9000\n"
"POSTGRES_HOST=10.10.10.189\n"
),
env={
"WEB_HOST": "0.0.0.0",
"WEB_PORT": "5173",
"SERVER_HOST": "0.0.0.0",
"SERVER_PORT": "8000",
"POSTGRES_HOST": "www.caoxiaozhu.com",
},
output_vars=["WEB_HOST", "WEB_PORT", "SERVER_HOST", "SERVER_PORT", "POSTGRES_HOST"],
)
assert result.returncode == 0, result.stderr
assert "WEB_HOST=0.0.0.0" in result.stdout
assert "WEB_PORT=5173" in result.stdout
assert "SERVER_HOST=0.0.0.0" in result.stdout
assert "SERVER_PORT=8000" in result.stdout
assert "POSTGRES_HOST=www.caoxiaozhu.com" in result.stdout
def test_server_start_preserves_inherited_runtime_guards(tmp_path: Path) -> None:
result = _run_script_prefix(
tmp_path,
"server/server_start.sh",
env_file_content=(
"SERVER_HOST=10.10.10.122\n"
"SERVER_PORT=9000\n"
"STARTUP_BOOTSTRAP_ENABLED=true\n"
"BACKGROUND_SCHEDULERS_ENABLED=true\n"
),
env={
"SERVER_HOST": "0.0.0.0",
"SERVER_PORT": "8000",
"STARTUP_BOOTSTRAP_ENABLED": "false",
"BACKGROUND_SCHEDULERS_ENABLED": "false",
},
output_vars=[
"SERVER_HOST",
"SERVER_PORT",
"STARTUP_BOOTSTRAP_ENABLED",
"BACKGROUND_SCHEDULERS_ENABLED",
],
)
assert result.returncode == 0, result.stderr
assert "SERVER_HOST=0.0.0.0" in result.stdout
assert "SERVER_PORT=8000" in result.stdout
assert "STARTUP_BOOTSTRAP_ENABLED=false" in result.stdout
assert "BACKGROUND_SCHEDULERS_ENABLED=false" in result.stdout

View File

@@ -0,0 +1,70 @@
from __future__ import annotations
from fastapi.testclient import TestClient
from app.core.config import get_settings
import app.main as main_module
def _set_bootstrap_step(
monkeypatch,
name: str,
calls: list[str],
*,
should_fail: bool = False,
) -> None:
def step() -> None:
calls.append(name)
if should_fail:
raise RuntimeError(f"{name} failed")
monkeypatch.setattr(main_module, name, step)
def test_lifespan_can_skip_startup_bootstrap(monkeypatch) -> None:
calls: list[str] = []
monkeypatch.setenv("STARTUP_BOOTSTRAP_ENABLED", "false")
monkeypatch.setenv("BACKGROUND_SCHEDULERS_ENABLED", "false")
get_settings.cache_clear()
for name in (
"prepare_employee_directory",
"prepare_agent_foundation",
"prepare_knowledge_library",
"sync_repository_hermes_skills",
):
_set_bootstrap_step(monkeypatch, name, calls)
try:
with TestClient(main_module.create_app()) as client:
response = client.get("/")
finally:
get_settings.cache_clear()
assert response.status_code == 200
assert calls == []
def test_lifespan_continues_when_startup_bootstrap_fails(monkeypatch) -> None:
calls: list[str] = []
monkeypatch.setenv("STARTUP_BOOTSTRAP_ENABLED", "true")
monkeypatch.setenv("BACKGROUND_SCHEDULERS_ENABLED", "false")
get_settings.cache_clear()
steps: tuple[tuple[str, bool], ...] = (
("prepare_employee_directory", True),
("prepare_agent_foundation", False),
("prepare_knowledge_library", False),
("sync_repository_hermes_skills", False),
)
for name, should_fail in steps:
_set_bootstrap_step(monkeypatch, name, calls, should_fail=should_fail)
try:
with TestClient(main_module.create_app()) as client:
response = client.get("/")
finally:
get_settings.cache_clear()
assert response.status_code == 200
assert calls == [name for name, _ in steps]