1 line
27 KiB
JSON
1 line
27 KiB
JSON
{"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"}}}} |