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(), }