--- a/wechat_rpa/archive_auto_backup.py +++ b/wechat_rpa/archive_auto_backup.py @@ -271,27 +271,21 @@ def _api_candidates(explicit: str = "") -> list[str]: - values: list[str] = [] + """安装版沿用登录云端;源码模式保留显式开发地址。""" + if is_frozen(): + return [backend_client.resolve_backend_url()] configured = explicit or os.environ.get("WECOM_ARCHIVE_API_URL", "").strip() if configured: - values.append(configured.rstrip("/")) + return [backend_client.normalize_server_url(configured)] try: - server_url = str(backend_client.load_settings().get("server_url") or "").rstrip("/") + saved = backend_client.load_settings().get("server_url") except Exception: - server_url = "" - if server_url: - parsed = urlparse(server_url) - if parsed.port == 8766: - values.append(server_url) - if parsed.hostname in {"127.0.0.1", "localhost", "::1"}: - values.append(_with_port(server_url, 8766)) - values.append("http://127.0.0.1:8766") - result: list[str] = [] - for value in values: - normalized = str(value or "").strip().rstrip("/") - if normalized and normalized not in result: - result.append(normalized) - return result + saved = None + server_url = backend_client.resolve_backend_url(saved) + values = [server_url] + if urlparse(server_url).hostname in {"127.0.0.1", "localhost", "::1"}: + values.append(_with_port(server_url, 8766)) + return list(dict.fromkeys(value for value in values if value)) @dataclass(frozen=True) @@ -1500,6 +1494,35 @@ return result +def _prepare_archive_databases(config: AutoBackupConfig) -> list[tuple[str, str, str]]: + """后台归档复用桌面密钥与 WAL 解密器。""" + import wxwork_db + from wxwork_local_setup import acquire_local_keys, select_source_directory + + try: + keys = _load_exporter_keys(config.exporter_root) + except ArchiveBackupError: + keys = {} + try: + keys.update(wxwork_db.load_keys()) + except ValueError: + pass + output = str(config.work_root / "decrypted") + decrypted = wxwork_db.decrypt_with_keys(str(config.source_root), output, keys, use_cache=True) + if any(name == "message.db" for _, name, _ in decrypted): + return decrypted + select_source_directory(str(config.source_root)) + try: + acquire_local_keys() + except (OSError, ValueError) as exc: + raise ArchiveBackupError(f"自动获取数据库密钥失败:{exc};可到微信消息库手动处理") from exc + keys.update(wxwork_db.load_keys()) + decrypted = wxwork_db.decrypt_with_keys(str(config.source_root), output, keys, use_cache=True) + if not any(name == "message.db" for _, name, _ in decrypted): + raise ArchiveBackupError("没有解密出可用的企业微信 message.db,请检查数据目录和密钥") + return decrypted + + def run_backup_once(config: AutoBackupConfig) -> dict[str, Any]: # 子进程真正执行时再校验一次,覆盖软件启动后路径迁移或磁盘重连的情况。 detected_source_root = _configured_source_root( @@ -1527,15 +1550,7 @@ f"最后检查路径:{config.source_root}" ) exporter, media_exporter = _load_exporter_modules(config.exporter_root) - keys = _load_exporter_keys(config.exporter_root) - decrypted = exporter.decrypt_with_keys( - str(config.source_root), - str(config.work_root / "decrypted"), - keys, - use_cache=True, - ) - if not any(item[1] == "message.db" for item in decrypted): - raise ArchiveBackupError("没有解密出可用的企业微信 message.db") + decrypted = _prepare_archive_databases(config) api = ArchiveApiClient.connect(config.api_urls) summary = IncrementalArchiveImporter( config, api, exporter, media_exporter @@ -1674,13 +1689,14 @@ log.write("自动增量备份已由后台停用\n") next_run = None last_interval = interval - elif next_run is None or interval != last_interval: - next_run = now + interval * 60 + elif next_run is None: + next_run = now last_interval = interval - log.write( - f"自动增量备份已排期:{interval} 分钟后执行,之后每 {interval} 分钟一次\n" - ) - elif now >= next_run: + log.write(f"自动增量备份立即启动,之后每 {interval} 分钟一次\n") + elif interval != last_interval: + next_run = min(next_run, now + interval * 60) + last_interval = interval + if enabled and next_run is not None and now >= next_run: _run_worker_with_retries(config, log_path) # 一轮可能运行很久,从完成时刻重新计时,避免任务重叠或追赶执行。 enabled, interval = _backup_policy()