Files

1 line
27 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "chapin-y/stardebate", "files": {"workers/api_config_encrypt/api_encrypt_engine.py": {"e4ffe6b0ceea44a7a30b58733a6b28dd2d2c8245": {"t": 1785747201.5759969, "n": 10559, "h": "ada826a03f526a3f", "c": "\"\"\"API 配置透明加解密引擎\n\n使用 cryptography.fernet (Fernet) 对称加密,自动管理密钥生成与存储。\n密钥文件: config/.api_key_store(二进制,Windows 下自动隐藏)\n配置文件: config/api_config.json(加密后为不可读的 base64 字符串)\n\n所有调用方无需感知加密的存在:\n - decrypted_load() 自动检测并解密密文\n - encrypted_save() 自动加密后写入\n - 向后兼容:如果文件仍是明文 JSON,自动识别\n\"\"\"\n\nimport os\nimport json\n\nfrom cryptography.fernet import Fernet, InvalidToken\n\n# ── 监视钩子 ──────────────────────────────────────────────────────────\ntry:\n from workers.debug_console.debug_monitor_manager import (\n DebugMonitorManager, MONITOR_TAGS\n )\n _TAG_FUNC = MONITOR_TAGS.get(\"function_watch\", \"FUNC\")\n _TAG_API = MONITOR_TAGS.get(\"api_watch\", \"API\")\n _TAG_VAR = MONITOR_TAGS.get(\"variable_watch\", \"VAR\")\nexcept Exception:\n DebugMonitorManager = None\n MONITOR_TAGS = {}\n _TAG_FUNC = \"FUNC\"\n _TAG_API = \"API\"\n _TAG_VAR = \"VAR\"\n\n\ndef _get_monitor():\n \"\"\"惰性获取 DebugMonitorManager 单例。\"\"\"\n if DebugMonitorManager is None:\n return None\n try:\n mgr = DebugMonitorManager.instance()\n return mgr\n except Exception:\n return None\n\n\ndef _emit_func(func_name: str, detail: str = \"\"):\n \"\"\"发射 function_watch 监视日志。\"\"\"\n mgr = _get_monitor()\n if mgr and mgr.is_monitor_enabled(\"function_watch\"):\n try:\n log_mgr = getattr(mgr, \"_log_mgr\", None)\n if log_mgr:\n msg = f\"{func_name}()\" + (f\" → {detail}\" if detail else \"\")\n log_mgr.info(f\"[{_TAG_FUNC}] {msg}\")\n except Exception:\n pass\n\n\ndef _emit_api(detail: str):\n \"\"\"发射 api_watch 监视日志。\"\"\"\n mgr = _get_monitor()\n if mgr and mgr.is_monitor_enabled(\"api_watch\"):\n try:\n log_mgr = getattr(mgr, \"_log_mgr\", None)\n if log_mgr:\n log_mgr.info(f\"[{_TAG_API}] APIEncrypt: {detail}\")\n except Exception:\n pass\n\n\ndef _emit_var(var_name: str, value_desc: str):\n \"\"\"发射 variable_watch 监视日志。\"\"\"\n mgr = _get_monitor()\n if mgr and mgr.is_monitor_enabled(\"variable_watch\"):\n try:\n log_mgr = getattr(mgr, \"_log_mgr\", None)\n if log_mgr:\n log_mgr.info(f\"[{_TAG_VAR}] APIEncrypt.{var_name} = {value_desc}\")\n except Exception:\n pass\n\n\nclass APIEncryptEngine:\n \"\"\"API 配置文件透明加解密引擎。\n\n 使用方式:\n engine = APIEncryptEngine(config_dir=\"e:/StarDebate/config\")\n\n # 保存(自动加密)\n engine.encrypted_save({\"api_key\": \"sk-xxx\", ...})\n\n # 读取(自动解密,无需关心明文/密文)\n config = engine.decrypted_load() # -> {\"api_key\": \"sk-xxx\", ...}\n \"\"\"\n\n # ── 文件名常量 ───────────────────────────────────────────────────\n KEY_FILENAME = \".api_key_store\" # 密钥文件(无扩展名)\n CONFIG_FILENAME = \"api_config.json\" # 配置文件\n\n def __init__(self, config_dir: str):\n \"\"\"初始化引擎。\n\n Args:\n config_dir: config 目录的绝对路径\n \"\"\"\n self._config_dir = config_dir\n self._key_path = os.path.join(config_dir, self.KEY_FILENAME)\n self._config_path = os.path.join(config_dir, self.CONFIG_FILENAME)\n self._cipher: Fernet | None = None # 延迟加载\n _emit_func(\"__init__\", f\"config_dir={config_dir}\")\n _emit_var(\"_config_dir\", config_dir)\n\n # ═════════════════════════════════════════════════════════════\n # 密钥管理\n # ═════════════════════════════════════════════════════════════\n\n def _ensure_key(self) -> Fernet | None:\n \"\"\"确保密钥存在并返回 Fernet 实例。\n\n 首次调用时:\n 如果 .api_key_store 已存在 → 读取并初始化 Fernet\n 如果 .api_key_store 不存在 → 自动生成新密钥并存入\n \"\"\"\n _emit_func(\"_ensure_key\", \"entry\")\n if self._cipher is not None:\n _emit_func(\"_ensure_key\", \"cipher cached\")\n return self._cipher\n\n # ── 已有密钥文件 → 直接读取 ──\n if os.path.isfile(self._key_path):\n try:\n with open(self._key_path, \"rb\") as f:\n key = f.read()\n self._cipher = Fernet(key)\n _emit_func(\"_ensure_key\", \"loaded existing key\")\n _emit_var(\"_cipher\", \"Fernet(loaded)\")\n return self._cipher\n except Exception:\n _emit_api(\"key_load_failed\")\n return None\n\n # ── 密钥不存在 → 自动生成 ──\n try:\n key = Fernet.generate_key()\n with open(self._key_path, \"wb\") as f:\n f.write(key)\n\n # Windows: 隐藏密钥文件(FILE_ATTRIBUTE_HIDDEN = 2)\n if os.name == \"nt\":\n try:\n import ctypes\n ctypes.windll.kernel32.SetFileAttributesW(\n self._key_path, 2\n )\n except Exception:\n pass\n\n self._cipher = Fernet(key)\n _emit_func(\"_ensure_key\", \"generated new key\")\n _emit_api(\"key_generated\")\n _emit_var(\"_cipher\", \"Fernet(new)\")\n return self._cipher\n except (OSError, IOError, Exception):\n _emit_api(\"key_generate_failed\")\n return None\n\n # ═════════════════════════════════════════════════════════════\n # 解密读取\n # ═════════════════════════════════════════════════════════════\n\n def decrypted_load(self) -> dict:\n \"\"\"读取 api_config.json,自动检测并解密。\n\n 逻辑:\n 1. 文件不存在 → 返回默认值\n 2. 尝试 JSON 解析 → 成功则直接返回(明文旧文件,向后兼容)\n 3. JSON 解析失败 → 尝试 Fernet 解密 → 返回解密结果\n 4. 解密失败 → 返回默认值\n\n Returns:\n dict: API 配置字典,失败时返回默认值\n \"\"\"\n _emit_func(\"decrypted_load\", \"entry\")\n defaults = {\n \"api_url\": \"\",\n \"api_key\": \"\",\n \"model\": \"deepseek-v4-flash\",\n }\n\n if not os.path.isfile(self._config_path):\n _emit_func(\"decrypted_load\", \"file not found → defaults\")\n return defaults\n\n try:\n with open(self._config_path, \"rb\") as f:\n raw = f.read()\n\n if not raw:\n _emit_func(\"decrypted_load\", \"empty file → defaults\")\n return defaults\n\n # ── 尝试 JSON 解析(未加密的旧文件)──\n try:\n config = json.loads(raw.decode(\"utf-8\"))\n if isinstance(config, dict):\n _emit_func(\"decrypted_load\", \"plaintext JSON (legacy)\")\n _emit_var(\"api_config_format\", \"plaintext\")\n return config\n except (json.JSONDecodeError, UnicodeDecodeError):\n pass\n\n # ── 非 JSON 文件 → Fernet 解密 ──\n cipher = self._ensure_key()\n if cipher is None:\n _emit_func(\"decrypted_load\", \"no cipher available → defaults\")\n return defaults\n\n try:\n decrypted = cipher.decrypt(raw)\n config = json.loads(decrypted.decode(\"utf-8\"))\n _emit_func(\"decrypted_load\", \"decrypted Fernet\")\n _emit_api(\"config_decrypted\")\n _emit_var(\"api_config_format\", \"encrypted_fernet\")\n return config if isinstance(config, dict) else defaults\n except InvalidToken:\n _emit_func(\"decrypted_load\", \"InvalidToken → defaults\")\n _emit_api(\"decrypt_failed_invalid_token\")\n return defaults\n\n except (OSError, IOError):\n _emit_func(\"decrypted_load\", \"IO error → defaults\")\n return defaults\n\n # ═════════════════════════════════════════════════════════════\n # 加密写入\n # ═════════════════════════════════════════════════════════════\n\n def encrypted_save(self, config: dict) -> bool:\n \"\"\"加密保存 API 配置到 api_config.json。\n\n 自动:\n 1. 序列化为 JSON\n 2. 使用 Fernet 加密\n 3. 以二进制写入文件\n 4. 若密钥不存在则自动生成\n\n Args:\n config: API 配置字典 (含 api_url, api_key, model 等)\n\n Returns:\n bool: 保存成功返回 True\n \"\"\"\n _emit_func(\"encrypted_save\", \"entry\")\n # 不记录 config 完整内容(含 API Key),只记录关键字段存在性\n has_key = bool(config.get(\"api_key\"))\n has_url = bool(config.get(\"api_url\"))\n model = config.get(\"model\", \"?\")\n _emit_var(\"config.hints\", f\"key={'yes' if has_key else 'no'}, url={'yes' if has_url else 'no'}, model={model}\")\n\n cipher = self._ensure_key()\n if cipher is None:\n _emit_func(\"encrypted_save\", \"no cipher → failed\")\n return False\n\n try:\n raw_json = json.dumps(config, ensure_ascii=False, indent=2)\n encrypted = cipher.encrypt(raw_json.encode(\"utf-8\"))\n os.makedirs(self._config_dir, exist_ok=True)\n with open(self._config_path, \"wb\") as f:\n f.write(encrypted)\n _emit_func(\"encrypted_save\", \"success\")\n _emit_api(\"config_saved_encrypted\")\n return True\n except (OSError, IOError, Exception):\n import traceback\n traceback.print_exc()\n _emit_func(\"encrypted_save\", \"exception → failed\")\n _emit_api(\"save_failed\")\n return False\n\n # ═════════════════════════════════════════════════════════════\n # 状态查询\n # ═════════════════════════════════════════════════════════════\n\n @property\n def is_encrypted_file(self) -> bool:\n \"\"\"检查当前磁盘上的配置文件是否为加密状态。\"\"\"\n _emit_func(\"is_encrypted_file\", \"entry\")\n if not os.path.isfile(self._config_path):\n return False\n try:\n with open(self._config_path, \"rb\") as f:\n raw = f.read()\n if not raw:\n return False\n try:\n json.loads(raw.decode(\"utf-8\"))\n return False # 合法 JSON → 未加密\n except (json.JSONDecodeError, UnicodeDecodeError):\n return True # 非 JSON → 已加密\n except (OSError, IOError):\n return False\n\n @property\n def has_key_file(self) -> bool:\n \"\"\"检查密钥文件 .api_key_store 是否存在。\"\"\"\n return os.path.isfile(self._key_path)\n\n def migrate_to_encrypted(self, config: dict | None = None) -> bool:\n \"\"\"强制将配置文件从明文迁移到加密状态。\n\n 如果配置已是明文 JSON,读取后用 encrypted_save 覆盖。\n 已在代码流程中自动执行,此方法供手动触发。\n\n Args:\n config: 可选,指定要写入的配置。为 None 时从当前文件读取。\n\n Returns:\n bool: 迁移成功返回 True\n \"\"\"\n _emit_func(\"migrate_to_encrypted\", \"entry\")\n if config is None:\n config = self.decrypted_load() # 自动兼容明文/密文\n result = self.encrypted_save(config)\n _emit_func(\"migrate_to_encrypted\", f\"→ {'ok' if result else 'failed'}\")\n _emit_api(\"migration_\" + (\"ok\" if result else \"failed\"))\n return result\n"}, "d6d027c2002cefbed3292f97ed37f2fa7dbcc7e7": {"t": 1785771328.872197, "n": 10559, "h": "ada826a03f526a3f", "c": "\"\"\"API 配置透明加解密引擎\n\n使用 cryptography.fernet (Fernet) 对称加密,自动管理密钥生成与存储。\n密钥文件: config/.api_key_store(二进制,Windows 下自动隐藏)\n配置文件: config/api_config.json(加密后为不可读的 base64 字符串)\n\n所有调用方无需感知加密的存在:\n - decrypted_load() 自动检测并解密密文\n - encrypted_save() 自动加密后写入\n - 向后兼容:如果文件仍是明文 JSON,自动识别\n\"\"\"\n\nimport os\nimport json\n\nfrom cryptography.fernet import Fernet, InvalidToken\n\n# ── 监视钩子 ──────────────────────────────────────────────────────────\ntry:\n from workers.debug_console.debug_monitor_manager import (\n DebugMonitorManager, MONITOR_TAGS\n )\n _TAG_FUNC = MONITOR_TAGS.get(\"function_watch\", \"FUNC\")\n _TAG_API = MONITOR_TAGS.get(\"api_watch\", \"API\")\n _TAG_VAR = MONITOR_TAGS.get(\"variable_watch\", \"VAR\")\nexcept Exception:\n DebugMonitorManager = None\n MONITOR_TAGS = {}\n _TAG_FUNC = \"FUNC\"\n _TAG_API = \"API\"\n _TAG_VAR = \"VAR\"\n\n\ndef _get_monitor():\n \"\"\"惰性获取 DebugMonitorManager 单例。\"\"\"\n if DebugMonitorManager is None:\n return None\n try:\n mgr = DebugMonitorManager.instance()\n return mgr\n except Exception:\n return None\n\n\ndef _emit_func(func_name: str, detail: str = \"\"):\n \"\"\"发射 function_watch 监视日志。\"\"\"\n mgr = _get_monitor()\n if mgr and mgr.is_monitor_enabled(\"function_watch\"):\n try:\n log_mgr = getattr(mgr, \"_log_mgr\", None)\n if log_mgr:\n msg = f\"{func_name}()\" + (f\" → {detail}\" if detail else \"\")\n log_mgr.info(f\"[{_TAG_FUNC}] {msg}\")\n except Exception:\n pass\n\n\ndef _emit_api(detail: str):\n \"\"\"发射 api_watch 监视日志。\"\"\"\n mgr = _get_monitor()\n if mgr and mgr.is_monitor_enabled(\"api_watch\"):\n try:\n log_mgr = getattr(mgr, \"_log_mgr\", None)\n if log_mgr:\n log_mgr.info(f\"[{_TAG_API}] APIEncrypt: {detail}\")\n except Exception:\n pass\n\n\ndef _emit_var(var_name: str, value_desc: str):\n \"\"\"发射 variable_watch 监视日志。\"\"\"\n mgr = _get_monitor()\n if mgr and mgr.is_monitor_enabled(\"variable_watch\"):\n try:\n log_mgr = getattr(mgr, \"_log_mgr\", None)\n if log_mgr:\n log_mgr.info(f\"[{_TAG_VAR}] APIEncrypt.{var_name} = {value_desc}\")\n except Exception:\n pass\n\n\nclass APIEncryptEngine:\n \"\"\"API 配置文件透明加解密引擎。\n\n 使用方式:\n engine = APIEncryptEngine(config_dir=\"e:/StarDebate/config\")\n\n # 保存(自动加密)\n engine.encrypted_save({\"api_key\": \"sk-xxx\", ...})\n\n # 读取(自动解密,无需关心明文/密文)\n config = engine.decrypted_load() # -> {\"api_key\": \"sk-xxx\", ...}\n \"\"\"\n\n # ── 文件名常量 ───────────────────────────────────────────────────\n KEY_FILENAME = \".api_key_store\" # 密钥文件(无扩展名)\n CONFIG_FILENAME = \"api_config.json\" # 配置文件\n\n def __init__(self, config_dir: str):\n \"\"\"初始化引擎。\n\n Args:\n config_dir: config 目录的绝对路径\n \"\"\"\n self._config_dir = config_dir\n self._key_path = os.path.join(config_dir, self.KEY_FILENAME)\n self._config_path = os.path.join(config_dir, self.CONFIG_FILENAME)\n self._cipher: Fernet | None = None # 延迟加载\n _emit_func(\"__init__\", f\"config_dir={config_dir}\")\n _emit_var(\"_config_dir\", config_dir)\n\n # ═════════════════════════════════════════════════════════════\n # 密钥管理\n # ═════════════════════════════════════════════════════════════\n\n def _ensure_key(self) -> Fernet | None:\n \"\"\"确保密钥存在并返回 Fernet 实例。\n\n 首次调用时:\n 如果 .api_key_store 已存在 → 读取并初始化 Fernet\n 如果 .api_key_store 不存在 → 自动生成新密钥并存入\n \"\"\"\n _emit_func(\"_ensure_key\", \"entry\")\n if self._cipher is not None:\n _emit_func(\"_ensure_key\", \"cipher cached\")\n return self._cipher\n\n # ── 已有密钥文件 → 直接读取 ──\n if os.path.isfile(self._key_path):\n try:\n with open(self._key_path, \"rb\") as f:\n key = f.read()\n self._cipher = Fernet(key)\n _emit_func(\"_ensure_key\", \"loaded existing key\")\n _emit_var(\"_cipher\", \"Fernet(loaded)\")\n return self._cipher\n except Exception:\n _emit_api(\"key_load_failed\")\n return None\n\n # ── 密钥不存在 → 自动生成 ──\n try:\n key = Fernet.generate_key()\n with open(self._key_path, \"wb\") as f:\n f.write(key)\n\n # Windows: 隐藏密钥文件(FILE_ATTRIBUTE_HIDDEN = 2)\n if os.name == \"nt\":\n try:\n import ctypes\n ctypes.windll.kernel32.SetFileAttributesW(\n self._key_path, 2\n )\n except Exception:\n pass\n\n self._cipher = Fernet(key)\n _emit_func(\"_ensure_key\", \"generated new key\")\n _emit_api(\"key_generated\")\n _emit_var(\"_cipher\", \"Fernet(new)\")\n return self._cipher\n except (OSError, IOError, Exception):\n _emit_api(\"key_generate_failed\")\n return None\n\n # ═════════════════════════════════════════════════════════════\n # 解密读取\n # ═════════════════════════════════════════════════════════════\n\n def decrypted_load(self) -> dict:\n \"\"\"读取 api_config.json,自动检测并解密。\n\n 逻辑:\n 1. 文件不存在 → 返回默认值\n 2. 尝试 JSON 解析 → 成功则直接返回(明文旧文件,向后兼容)\n 3. JSON 解析失败 → 尝试 Fernet 解密 → 返回解密结果\n 4. 解密失败 → 返回默认值\n\n Returns:\n dict: API 配置字典,失败时返回默认值\n \"\"\"\n _emit_func(\"decrypted_load\", \"entry\")\n defaults = {\n \"api_url\": \"\",\n \"api_key\": \"\",\n \"model\": \"deepseek-v4-flash\",\n }\n\n if not os.path.isfile(self._config_path):\n _emit_func(\"decrypted_load\", \"file not found → defaults\")\n return defaults\n\n try:\n with open(self._config_path, \"rb\") as f:\n raw = f.read()\n\n if not raw:\n _emit_func(\"decrypted_load\", \"empty file → defaults\")\n return defaults\n\n # ── 尝试 JSON 解析(未加密的旧文件)──\n try:\n config = json.loads(raw.decode(\"utf-8\"))\n if isinstance(config, dict):\n _emit_func(\"decrypted_load\", \"plaintext JSON (legacy)\")\n _emit_var(\"api_config_format\", \"plaintext\")\n return config\n except (json.JSONDecodeError, UnicodeDecodeError):\n pass\n\n # ── 非 JSON 文件 → Fernet 解密 ──\n cipher = self._ensure_key()\n if cipher is None:\n _emit_func(\"decrypted_load\", \"no cipher available → defaults\")\n return defaults\n\n try:\n decrypted = cipher.decrypt(raw)\n config = json.loads(decrypted.decode(\"utf-8\"))\n _emit_func(\"decrypted_load\", \"decrypted Fernet\")\n _emit_api(\"config_decrypted\")\n _emit_var(\"api_config_format\", \"encrypted_fernet\")\n return config if isinstance(config, dict) else defaults\n except InvalidToken:\n _emit_func(\"decrypted_load\", \"InvalidToken → defaults\")\n _emit_api(\"decrypt_failed_invalid_token\")\n return defaults\n\n except (OSError, IOError):\n _emit_func(\"decrypted_load\", \"IO error → defaults\")\n return defaults\n\n # ═════════════════════════════════════════════════════════════\n # 加密写入\n # ═════════════════════════════════════════════════════════════\n\n def encrypted_save(self, config: dict) -> bool:\n \"\"\"加密保存 API 配置到 api_config.json。\n\n 自动:\n 1. 序列化为 JSON\n 2. 使用 Fernet 加密\n 3. 以二进制写入文件\n 4. 若密钥不存在则自动生成\n\n Args:\n config: API 配置字典 (含 api_url, api_key, model 等)\n\n Returns:\n bool: 保存成功返回 True\n \"\"\"\n _emit_func(\"encrypted_save\", \"entry\")\n # 不记录 config 完整内容(含 API Key),只记录关键字段存在性\n has_key = bool(config.get(\"api_key\"))\n has_url = bool(config.get(\"api_url\"))\n model = config.get(\"model\", \"?\")\n _emit_var(\"config.hints\", f\"key={'yes' if has_key else 'no'}, url={'yes' if has_url else 'no'}, model={model}\")\n\n cipher = self._ensure_key()\n if cipher is None:\n _emit_func(\"encrypted_save\", \"no cipher → failed\")\n return False\n\n try:\n raw_json = json.dumps(config, ensure_ascii=False, indent=2)\n encrypted = cipher.encrypt(raw_json.encode(\"utf-8\"))\n os.makedirs(self._config_dir, exist_ok=True)\n with open(self._config_path, \"wb\") as f:\n f.write(encrypted)\n _emit_func(\"encrypted_save\", \"success\")\n _emit_api(\"config_saved_encrypted\")\n return True\n except (OSError, IOError, Exception):\n import traceback\n traceback.print_exc()\n _emit_func(\"encrypted_save\", \"exception → failed\")\n _emit_api(\"save_failed\")\n return False\n\n # ═════════════════════════════════════════════════════════════\n # 状态查询\n # ═════════════════════════════════════════════════════════════\n\n @property\n def is_encrypted_file(self) -> bool:\n \"\"\"检查当前磁盘上的配置文件是否为加密状态。\"\"\"\n _emit_func(\"is_encrypted_file\", \"entry\")\n if not os.path.isfile(self._config_path):\n return False\n try:\n with open(self._config_path, \"rb\") as f:\n raw = f.read()\n if not raw:\n return False\n try:\n json.loads(raw.decode(\"utf-8\"))\n return False # 合法 JSON → 未加密\n except (json.JSONDecodeError, UnicodeDecodeError):\n return True # 非 JSON → 已加密\n except (OSError, IOError):\n return False\n\n @property\n def has_key_file(self) -> bool:\n \"\"\"检查密钥文件 .api_key_store 是否存在。\"\"\"\n return os.path.isfile(self._key_path)\n\n def migrate_to_encrypted(self, config: dict | None = None) -> bool:\n \"\"\"强制将配置文件从明文迁移到加密状态。\n\n 如果配置已是明文 JSON,读取后用 encrypted_save 覆盖。\n 已在代码流程中自动执行,此方法供手动触发。\n\n Args:\n config: 可选,指定要写入的配置。为 None 时从当前文件读取。\n\n Returns:\n bool: 迁移成功返回 True\n \"\"\"\n _emit_func(\"migrate_to_encrypted\", \"entry\")\n if config is None:\n config = self.decrypted_load() # 自动兼容明文/密文\n result = self.encrypted_save(config)\n _emit_func(\"migrate_to_encrypted\", f\"→ {'ok' if result else 'failed'}\")\n _emit_api(\"migration_\" + (\"ok\" if result else \"failed\"))\n return result\n"}}}}