refactor: establish standalone application boundary
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from http import HTTPStatus
|
||||
|
||||
|
||||
class SystemRoutesMixin:
|
||||
def _handle_system_public_get(self, parsed) -> bool:
|
||||
if parsed.path == "/api/health":
|
||||
self.send_json(
|
||||
{
|
||||
"ok": True,
|
||||
"storage": "sqlite",
|
||||
"account_required": True,
|
||||
"time": datetime.now().astimezone().isoformat(timespec="seconds"),
|
||||
}
|
||||
)
|
||||
return True
|
||||
return False
|
||||
|
||||
def _handle_system_get(self, parsed) -> bool:
|
||||
if parsed.path == "/api/admin/settings":
|
||||
self.send_json(
|
||||
{"ok": True, **self.application_service.system_status(), "users": self.application_service.admin_users()}
|
||||
)
|
||||
return True
|
||||
return False
|
||||
|
||||
def backfill_data(self) -> None:
|
||||
try:
|
||||
body = self.read_json_body()
|
||||
results = self.application_service.backfill(
|
||||
str(body.get("start_date") or ""),
|
||||
str(body.get("end_date") or ""),
|
||||
)
|
||||
self.send_json({"ok": True, "results": results})
|
||||
except ValueError as exc:
|
||||
self.send_json({"error": str(exc)}, HTTPStatus.BAD_REQUEST)
|
||||
except Exception as exc:
|
||||
self.send_json({"error": f"历史回补失败:{exc}"}, HTTPStatus.INTERNAL_SERVER_ERROR)
|
||||
@@ -0,0 +1,254 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import secrets
|
||||
from typing import Any
|
||||
|
||||
from backend.bootstrap.config import TOKEN_PATTERN, validate_text
|
||||
|
||||
|
||||
class SystemServiceMixin:
|
||||
def _load_system_credentials(self, environment: dict[str, str]) -> dict[str, Any]:
|
||||
encrypted = self.database.get_system_setting("credentials")
|
||||
current = self.vault.decrypt_json(encrypted) if encrypted else {}
|
||||
changed = False
|
||||
first_user_id = self.database.first_user_id()
|
||||
first_personal: dict[str, Any] = {}
|
||||
if first_user_id:
|
||||
first_encrypted = self.database.get_user_credentials(first_user_id)
|
||||
first_personal = self.vault.decrypt_json(first_encrypted) if first_encrypted else {}
|
||||
defaults = {
|
||||
"tushare_token": environment.get("tushare_token") or first_personal.get("tushare_token") or "",
|
||||
"ifind_refresh_token": environment.get("ifind_refresh_token") or "",
|
||||
"ifind_access_token": environment.get("ifind_access_token") or "",
|
||||
"platform_llm_primary_api_key": environment.get("platform_llm_primary_api_key") or first_personal.get("llm_primary_api_key") or "",
|
||||
"platform_llm_primary_base_url": environment.get("platform_llm_primary_base_url") or first_personal.get("llm_primary_base_url") or "https://api.openai.com/v1",
|
||||
"platform_llm_primary_model": environment.get("platform_llm_primary_model") or first_personal.get("llm_primary_model") or "",
|
||||
"platform_llm_fallback_api_key": environment.get("platform_llm_fallback_api_key") or first_personal.get("llm_fallback_api_key") or "",
|
||||
"platform_llm_fallback_base_url": environment.get("platform_llm_fallback_base_url") or first_personal.get("llm_fallback_base_url") or "",
|
||||
"platform_llm_fallback_model": environment.get("platform_llm_fallback_model") or first_personal.get("llm_fallback_model") or "",
|
||||
"member_daily_limit": 50,
|
||||
"background_refresh_enabled": True,
|
||||
}
|
||||
for key, value in defaults.items():
|
||||
if key not in current:
|
||||
current[key] = value
|
||||
changed = True
|
||||
if not isinstance(current.get("llm_models"), list):
|
||||
migrated_models: list[dict[str, str]] = []
|
||||
for role, label in (("primary", "原主模型"), ("fallback", "原辅助模型")):
|
||||
profile = {
|
||||
"api_key": str(current.get(f"platform_llm_{role}_api_key") or ""),
|
||||
"base_url": str(current.get(f"platform_llm_{role}_base_url") or ""),
|
||||
"model": str(current.get(f"platform_llm_{role}_model") or ""),
|
||||
}
|
||||
if profile["api_key"] or profile["model"]:
|
||||
model_id = f"migrated-{role}"
|
||||
migrated_models.append(
|
||||
{"id": model_id, "name": label, **profile}
|
||||
)
|
||||
current[f"{role}_model_id"] = model_id
|
||||
current["llm_models"] = migrated_models
|
||||
current.setdefault("primary_model_id", "")
|
||||
current.setdefault("fallback_model_id", "")
|
||||
changed = True
|
||||
if changed or not encrypted:
|
||||
self.database.save_system_setting("credentials", self.vault.encrypt_json(current))
|
||||
for row in self.database.list_user_credentials():
|
||||
personal = self.vault.decrypt_json(str(row.get("encrypted_payload") or ""))
|
||||
if "tushare_token" in personal:
|
||||
personal.pop("tushare_token", None)
|
||||
self.database.save_user_credentials(
|
||||
int(row["user_id"]), self.vault.encrypt_json(personal)
|
||||
)
|
||||
return current
|
||||
|
||||
def _save_system_credentials(self, credentials: dict[str, Any]) -> None:
|
||||
with self.system_lock:
|
||||
self.database.save_system_setting("credentials", self.vault.encrypt_json(credentials))
|
||||
self._system_credentials = dict(credentials)
|
||||
if hasattr(self, "ifind"):
|
||||
self.ifind.set_credentials(
|
||||
str(credentials.get("ifind_refresh_token") or ""),
|
||||
str(credentials.get("ifind_access_token") or ""),
|
||||
)
|
||||
|
||||
@property
|
||||
def configured(self) -> bool:
|
||||
return bool(self.token)
|
||||
|
||||
def _credentials(self) -> dict[str, str]:
|
||||
credentials = getattr(self._request_context, "credentials", {})
|
||||
return {
|
||||
"llm_primary_api_key": str(credentials.get("llm_primary_api_key") or ""),
|
||||
"llm_primary_base_url": str(
|
||||
credentials.get("llm_primary_base_url") or "https://api.openai.com/v1"
|
||||
),
|
||||
"llm_primary_model": str(credentials.get("llm_primary_model") or ""),
|
||||
"llm_fallback_api_key": str(credentials.get("llm_fallback_api_key") or ""),
|
||||
"llm_fallback_base_url": str(credentials.get("llm_fallback_base_url") or ""),
|
||||
"llm_fallback_model": str(credentials.get("llm_fallback_model") or ""),
|
||||
}
|
||||
|
||||
def _save_credentials(self, credentials: dict[str, str]) -> None:
|
||||
self.database.save_user_credentials(
|
||||
self.current_user_id,
|
||||
self.vault.encrypt_json(credentials),
|
||||
)
|
||||
self._request_context.credentials = dict(credentials)
|
||||
|
||||
@property
|
||||
def token(self) -> str:
|
||||
return str(self._system_credentials.get("tushare_token") or "")
|
||||
|
||||
def system_status(self) -> dict[str, Any]:
|
||||
platform = self._platform_llm_profile()
|
||||
model_pool = []
|
||||
for item in self._system_credentials.get("llm_models") or []:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
profile = {
|
||||
"api_key": str(item.get("api_key") or ""),
|
||||
"base_url": str(item.get("base_url") or ""),
|
||||
"model": str(item.get("model") or ""),
|
||||
}
|
||||
model_pool.append(
|
||||
{
|
||||
"id": str(item.get("id") or ""),
|
||||
"name": str(item.get("name") or ""),
|
||||
"base_url": profile["base_url"],
|
||||
"model": profile["model"],
|
||||
"configured": self._profile_configured(profile),
|
||||
}
|
||||
)
|
||||
return {
|
||||
"data": {
|
||||
"configured": self.configured,
|
||||
"ifind": self.ifind.status(),
|
||||
"background_refresh_enabled": bool(
|
||||
self._system_credentials.get("background_refresh_enabled", True)
|
||||
),
|
||||
**self.database.status(),
|
||||
"jobs": self.jobs.repository.recent(12),
|
||||
},
|
||||
"llm": {
|
||||
"primary_configured": self._profile_configured(platform["primary"]),
|
||||
"fallback_configured": self._profile_configured(platform["fallback"]),
|
||||
"models": model_pool,
|
||||
"primary_model_id": str(self._system_credentials.get("primary_model_id") or ""),
|
||||
"fallback_model_id": str(self._system_credentials.get("fallback_model_id") or ""),
|
||||
},
|
||||
"membership": {
|
||||
"member_daily_limit": max(
|
||||
1, int(self._system_credentials.get("member_daily_limit") or 50)
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
def save_system_settings(self, payload: dict[str, Any]) -> dict[str, Any]:
|
||||
current = dict(self._system_credentials)
|
||||
token = str(payload.get("tushare_token") or current.get("tushare_token") or "").strip()
|
||||
if token and not TOKEN_PATTERN.fullmatch(token):
|
||||
raise ValueError("Tushare Token 格式不正确。")
|
||||
ifind_refresh_token = str(
|
||||
payload.get("ifind_refresh_token")
|
||||
or current.get("ifind_refresh_token")
|
||||
or ""
|
||||
).strip()
|
||||
if ifind_refresh_token and (
|
||||
len(ifind_refresh_token) > 2048
|
||||
or any(character.isspace() for character in ifind_refresh_token)
|
||||
):
|
||||
raise ValueError("iFinD Refresh Token 格式不正确。")
|
||||
existing_models = {
|
||||
str(item.get("id") or ""): item
|
||||
for item in current.get("llm_models") or []
|
||||
if isinstance(item, dict) and item.get("id")
|
||||
}
|
||||
raw_models = payload.get("models")
|
||||
models: list[dict[str, str]] = []
|
||||
if raw_models is not None:
|
||||
if not isinstance(raw_models, list) or len(raw_models) > 20:
|
||||
raise ValueError("模型池格式不正确,最多可保存 20 个模型。")
|
||||
seen_ids: set[str] = set()
|
||||
seen_names: set[str] = set()
|
||||
for index, raw in enumerate(raw_models, start=1):
|
||||
if not isinstance(raw, dict):
|
||||
raise ValueError("模型池条目格式不正确。")
|
||||
model_id = str(raw.get("id") or f"model-{secrets.token_hex(6)}").strip()
|
||||
if not re.fullmatch(r"[A-Za-z0-9_-]{3,80}", model_id) or model_id in seen_ids:
|
||||
raise ValueError("模型 ID 不正确或重复。")
|
||||
name = validate_text(raw.get("name"), f"模型 {index} 名称", 50, required=True)
|
||||
normalized_name = name.casefold()
|
||||
if normalized_name in seen_names:
|
||||
raise ValueError("模型名称不能重复。")
|
||||
profile = self._validate_llm_profile(
|
||||
raw,
|
||||
existing_models.get(model_id) or {},
|
||||
required=True,
|
||||
label=name,
|
||||
)
|
||||
models.append({"id": model_id, "name": name, **profile})
|
||||
seen_ids.add(model_id)
|
||||
seen_names.add(normalized_name)
|
||||
else:
|
||||
models = [dict(item) for item in existing_models.values()]
|
||||
model_ids = {item["id"] for item in models}
|
||||
primary_model_id = str(
|
||||
payload.get("primary_model_id", current.get("primary_model_id") or "") or ""
|
||||
).strip()
|
||||
fallback_model_id = str(
|
||||
payload.get("fallback_model_id", current.get("fallback_model_id") or "") or ""
|
||||
).strip()
|
||||
if models and primary_model_id not in model_ids:
|
||||
raise ValueError("请从模型池选择主模型。")
|
||||
if not models:
|
||||
primary_model_id = ""
|
||||
fallback_model_id = ""
|
||||
if fallback_model_id and fallback_model_id not in model_ids:
|
||||
raise ValueError("辅助模型不在模型池中。")
|
||||
if fallback_model_id and fallback_model_id == primary_model_id:
|
||||
raise ValueError("主模型与辅助模型不能相同。")
|
||||
try:
|
||||
daily_limit = max(
|
||||
1,
|
||||
min(
|
||||
1000,
|
||||
int(payload.get("member_daily_limit", current.get("member_daily_limit") or 50)),
|
||||
),
|
||||
)
|
||||
except (TypeError, ValueError) as exc:
|
||||
raise ValueError("会员每日额度应为 1 至 1000。") from exc
|
||||
current.update(
|
||||
{
|
||||
"tushare_token": token,
|
||||
"ifind_refresh_token": ifind_refresh_token,
|
||||
"llm_models": models,
|
||||
"primary_model_id": primary_model_id,
|
||||
"fallback_model_id": fallback_model_id,
|
||||
"member_daily_limit": daily_limit,
|
||||
"background_refresh_enabled": bool(
|
||||
payload.get(
|
||||
"background_refresh_enabled",
|
||||
current.get("background_refresh_enabled", True),
|
||||
)
|
||||
),
|
||||
}
|
||||
)
|
||||
self._save_system_credentials(current)
|
||||
return self.system_status()
|
||||
|
||||
def status(self) -> dict[str, Any]:
|
||||
llm_access = self.llm_access_status()
|
||||
return {
|
||||
"configured": self.configured,
|
||||
"mode": "tushare" if self.configured else "unavailable",
|
||||
"llm_configured": self.llm_configured,
|
||||
"llm_model": self.llm_primary_model if self.llm_configured else "",
|
||||
"llm_fallback_configured": self.llm_fallback_configured,
|
||||
"llm_fallback_model": self.llm_fallback_model if self.llm_fallback_configured else "",
|
||||
"llm_access": llm_access,
|
||||
"birth_profile_configured": bool(self.stored_birth_profile()),
|
||||
"birth_profile": self.stored_birth_profile(),
|
||||
**self.database.status(),
|
||||
}
|
||||
Reference in New Issue
Block a user