feat(platform): close AI expense value loop

Add tenant-safe value, telemetry, connector, commercial, and production-readiness foundations.
This commit is contained in:
caoxiaozhu
2026-07-17 14:14:08 +08:00
parent 242d68c36f
commit 787bc3a481
507 changed files with 82072 additions and 6344 deletions

View File

@@ -6,12 +6,15 @@ from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from app.api.deps import CurrentUserContext
from app.core.config import Settings, get_settings
from app.core import secret_box
from app.core.config import Settings, get_settings
from app.db.base import Base
from app.models.hermes_config import HermesTaskConfig
from app.models.knowledge_security import KnowledgeOnlyOfficeSession
from app.models.system_model_setting import SystemModelSetting
from app.models.system_setting import SystemSetting
from app.models.system_setting_secret import SystemSettingSecret
from app.models.tenant import Tenant
from app.schemas.settings import SettingsWrite
from app.services.knowledge import KnowledgeService
from app.services.settings import SettingsService
@@ -25,9 +28,24 @@ def build_session_factory(db_file: Path):
SystemSetting.__table__.create(bind=engine)
SystemSettingSecret.__table__.create(bind=engine)
SystemModelSetting.__table__.create(bind=engine)
Tenant.__table__.create(bind=engine)
KnowledgeOnlyOfficeSession.__table__.create(bind=engine)
HermesTaskConfig.__table__.create(bind=engine)
return sessionmaker(bind=engine, autoflush=False, autocommit=False)
def seed_tenant(db) -> None:
db.add(
Tenant(
tenant_id="tenant-a",
tenant_code="TENANT-A",
name="租户 A",
status="active",
)
)
db.commit()
def test_onlyoffice_config_is_read_only_for_admin_users(tmp_path, monkeypatch) -> None:
env_file = tmp_path / ".env"
env_file.write_text(
@@ -36,7 +54,7 @@ def test_onlyoffice_config_is_read_only_for_admin_users(tmp_path, monkeypatch) -
"ONLYOFFICE_ENABLED=true",
"ONLYOFFICE_PUBLIC_URL=http://10.10.10.122:8082",
"ONLYOFFICE_BACKEND_URL=http://main:8000",
"ONLYOFFICE_JWT_SECRET=change-me-onlyoffice",
"ONLYOFFICE_JWT_SECRET=change-me-onlyoffice-secret-32bytes",
]
)
+ "\n",
@@ -47,32 +65,42 @@ def test_onlyoffice_config_is_read_only_for_admin_users(tmp_path, monkeypatch) -
get_settings.cache_clear()
try:
service = KnowledgeService(storage_root=tmp_path)
service.ensure_library_ready()
session_factory = build_session_factory(tmp_path / "onlyoffice.db")
with session_factory() as db:
seed_tenant(db)
service = KnowledgeService(
storage_root=tmp_path,
db=db,
tenant_id="tenant-a",
)
service.ensure_library_ready()
document_id = "readonly-docx"
folder = "制度政策"
stored_name = f"{document_id}__制度预览.docx"
target_path = tmp_path / "knowledge" / folder / stored_name
target_path.write_bytes(b"fake-docx-content")
document_id = "readonly-docx"
folder = "制度政策"
stored_name = f"{document_id}__制度预览.docx"
target_path = (
tmp_path / "knowledge" / "tenants" / "tenant-a" / folder / stored_name
)
target_path.write_bytes(b"fake-docx-content")
current_user = CurrentUserContext(
username="admin",
name="管理员",
role_codes=["manager"],
is_admin=True,
)
current_user = CurrentUserContext(
username="admin",
name="管理员",
role_codes=["manager"],
is_admin=True,
tenant_id="tenant-a",
)
config = service.build_onlyoffice_config(document_id, current_user)
permissions = config.config["document"]["permissions"]
customization = config.config["editorConfig"]["customization"]
config = service.build_onlyoffice_config(document_id, current_user)
permissions = config.config["document"]["permissions"]
customization = config.config["editorConfig"]["customization"]
assert config.documentServerUrl == "http://10.10.10.122:8082"
assert config.config["editorConfig"]["mode"] == "view"
assert permissions["edit"] is False
assert permissions["download"] is True
assert customization["autosave"] is False
assert customization["forcesave"] is False
assert config.documentServerUrl == "http://10.10.10.122:8082"
assert config.config["editorConfig"]["mode"] == "view"
assert permissions["edit"] is False
assert permissions["download"] is True
assert customization["autosave"] is False
assert customization["forcesave"] is False
finally:
monkeypatch.setitem(Settings.model_config, "env_file", original_env_file)
get_settings.cache_clear()
@@ -102,35 +130,45 @@ def test_onlyoffice_config_prefers_saved_settings_snapshot(tmp_path, monkeypatch
try:
with session_factory() as db:
seed_tenant(db)
service = SettingsService(db)
payload = service.get_settings_snapshot().model_dump()
payload["renderForm"]["enabled"] = True
payload["renderForm"]["publicUrl"] = "http://10.10.10.122:8082"
payload["renderForm"]["jwtSecret"] = "change-me-onlyoffice"
payload["renderForm"]["jwtSecret"] = "change-me-onlyoffice-secret-32bytes"
service.save_settings_snapshot(SettingsWrite(**payload))
service = KnowledgeService(storage_root=tmp_path)
service.ensure_library_ready()
with session_factory() as db:
service = KnowledgeService(
storage_root=tmp_path,
db=db,
tenant_id="tenant-a",
)
service.ensure_library_ready()
document_id = "db-backed-docx"
folder = "制度政策"
stored_name = f"{document_id}__制度预览.docx"
target_path = tmp_path / "knowledge" / folder / stored_name
target_path.write_bytes(b"fake-docx-content")
document_id = "db-backed-docx"
folder = "制度政策"
stored_name = f"{document_id}__制度预览.docx"
target_path = (
tmp_path / "knowledge" / "tenants" / "tenant-a" / folder / stored_name
)
target_path.write_bytes(b"fake-docx-content")
current_user = CurrentUserContext(
username="admin",
name="管理员",
role_codes=["manager"],
is_admin=True,
)
current_user = CurrentUserContext(
username="admin",
name="管理员",
role_codes=["manager"],
is_admin=True,
tenant_id="tenant-a",
)
config = service.build_onlyoffice_config(document_id, current_user)
config = service.build_onlyoffice_config(document_id, current_user)
assert config.documentServerUrl == "http://10.10.10.122:8082"
assert config.config["document"]["url"].startswith(
"http://main:8000/api/v1/knowledge/documents/db-backed-docx/onlyoffice/content?access_token="
)
assert config.documentServerUrl == "http://10.10.10.122:8082"
assert config.config["document"]["url"].startswith(
"http://main:8000/api/v1/knowledge/documents/"
"db-backed-docx/onlyoffice/content?access_token="
)
finally:
monkeypatch.setitem(Settings.model_config, "env_file", original_env_file)
get_settings.cache_clear()