"""配置加载模块""" import os import secrets from pathlib import Path import yaml class Config: """全局配置,从 config.yaml 加载""" def __init__(self, config_path: str = "config.yaml"): self.config_path = Path(config_path) self._data = {} if self.config_path.exists(): with open(self.config_path, "r", encoding="utf-8") as f: self._data = yaml.safe_load(f) or {} def save(self): with open(self.config_path, "w", encoding="utf-8") as f: yaml.dump(self._data, f, allow_unicode=True, default_flow_style=False) def _resolve(self, p: str) -> Path: """将相对路径解析为配置文件所在目录的绝对路径""" path = Path(p) if not path.is_absolute(): path = self.config_path.parent / path return path.resolve() # ── 路径 ────────────────────────────────────────────── @property def data_dir(self) -> Path: return self._resolve(self._get("paths.data_dir", "data")) @property def problems_dir(self) -> Path: return self._resolve(self._get("paths.problems_dir", "data/problems")) @property def papers_dir(self) -> Path: return self._resolve(self._get("paths.papers_dir", "data/papers")) @property def db_path(self) -> Path: return self._resolve(self._get("paths.db_path", "data/bank.db")) @property def backups_dir(self) -> Path: return self._resolve(self._get("paths.backups_dir", "data/backups")) @property def knowledge_tree_path(self) -> Path: return self._resolve(self._get("paths.knowledge_tree", "src/知识点树.md")) @property def knowledge_map_path(self) -> Path: return self._resolve(self._get("paths.knowledge_map", "src/knowledge_map.json")) # ── 服务端 ──────────────────────────────────────────── @property def host(self) -> str: return self._get("server.host", "0.0.0.0") @property def port(self) -> int: return int(self._get("server.port", 8765)) @property def token(self) -> str: t = self._get("server.token", "") if not t: t = secrets.token_hex(16) self._set("server.token", t) self.save() return t def regenerate_token(self) -> str: t = secrets.token_hex(16) self._set("server.token", t) self.save() return t # ── 默认值 ──────────────────────────────────────────── @property def default_import_type(self) -> str: return self._get("defaults.import_type", "精选题") @property def max_backups(self) -> int: return int(self._get("defaults.max_backups", 30)) # ── WebDAV ──────────────────────────────────────────── @property def webdav_enabled(self) -> bool: return bool(self._get("webdav.enabled", False)) @property def webdav_url(self) -> str: return self._get("webdav.url", "") @property def webdav_username(self) -> str: return self._get("webdav.username", "") @property def webdav_password(self) -> str: return self._get("webdav.password", "") @property def webdav_remote_path(self) -> str: return self._get("webdav.remote_path", "/tiku-backup") def set_webdav(self, url: str, username: str, password: str, remote_path: str = "/tiku-backup"): self._set("webdav.enabled", True) self._set("webdav.url", url) self._set("webdav.username", username) self._set("webdav.password", password) self._set("webdav.remote_path", remote_path) self.save() # ── helpers ─────────────────────────────────────────── def _get(self, dotted_key: str, default=None): keys = dotted_key.split(".") val = self._data for k in keys: if isinstance(val, dict): val = val.get(k) else: return default return val if val is not None else default def _set(self, dotted_key: str, value): keys = dotted_key.split(".") d = self._data for k in keys[:-1]: if k not in d: d[k] = {} d = d[k] d[keys[-1]] = value # 全局单例,通过 app 的 state 注入 _config_instance: Config | None = None def get_config(config_path: str = "config.yaml") -> Config: global _config_instance if _config_instance is None: _config_instance = Config(config_path) return _config_instance def set_config(cfg: Config): global _config_instance _config_instance = cfg