From e25d695ce7311e7a9f676b527eacd52fc207e892 Mon Sep 17 00:00:00 2001 From: chengma Date: Wed, 8 Jul 2026 17:49:54 +0800 Subject: [PATCH] fix: hide cmhub curl download window --- app/ai.py | 16 ++++++++++++++++ docs/tasks/T-561.md | 6 +++++- tests/test_ai.py | 16 +++++++++++++++- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/app/ai.py b/app/ai.py index e4c747e..90d4b28 100644 --- a/app/ai.py +++ b/app/ai.py @@ -1452,6 +1452,21 @@ def _find_system_curl(): return "" +def _subprocess_hidden_window_kwargs(): + if os.name != "nt": + return {} + creationflags = getattr(subprocess, "CREATE_NO_WINDOW", 0) + if creationflags: + return {"creationflags": creationflags} + startupinfo_cls = getattr(subprocess, "STARTUPINFO", None) + if startupinfo_cls is None: + return {} + startupinfo = startupinfo_cls() + startupinfo.dwFlags |= getattr(subprocess, "STARTF_USESHOWWINDOW", 1) + startupinfo.wShowWindow = getattr(subprocess, "SW_HIDE", 0) + return {"startupinfo": startupinfo} + + def _download_cmhub_image_with_curl( url, connect_timeout, @@ -1501,6 +1516,7 @@ def _download_cmhub_image_with_curl( timeout=max(2, int(connect_timeout) + int(read_timeout) + 10), check=False, shell=False, + **_subprocess_hidden_window_kwargs(), ) except (OSError, subprocess.TimeoutExpired) as exc: raise AIError("下载 cmhub 图片失败: curl 执行失败") from exc diff --git a/docs/tasks/T-561.md b/docs/tasks/T-561.md index c6bc62e..00dea4b 100644 --- a/docs/tasks/T-561.md +++ b/docs/tasks/T-561.md @@ -3,7 +3,7 @@ id: T-561 title: cmhub curl 下载隐藏 Windows 黑窗 phase: 7 deps: [T-548, T-524] -status: TODO +status: DONE created: 2026-07-08 --- @@ -46,3 +46,7 @@ T-548 后,②AI生成在 Windows 下默认优先调用系统 `curl.exe` 下载 ## 执行记录 +- 2026-07-08:`app/ai.py` 新增 `_subprocess_hidden_window_kwargs()`,Windows 下优先使用 `subprocess.CREATE_NO_WINDOW` 隐藏 curl 子进程控制台窗口;缺少该常量时兜底 `STARTUPINFO + STARTF_USESHOWWINDOW + SW_HIDE`;非 Windows 返回空参数。 +- 2026-07-08:`_download_cmhub_image_with_curl()` 调用 `subprocess.run()` 时传入隐藏窗口 kwargs,保留 `shell=False`、`-K` 临时配置文件、`--noproxy`、超时、大小上限、stderr/stdout 捕获和 requests fallback 语义。 +- 2026-07-08:更新 `tests/test_ai.py`,覆盖 Windows curl 调用携带隐藏窗口参数、URL 不在 argv、`shell=False` 保持,以及非 Windows 不加隐藏窗口参数。 +- 验证通过:`py -3.10 -m unittest tests.test_ai.AITests.test_cmhub_image_download_uses_curl_without_url_in_argv tests.test_ai.AITests.test_cmhub_curl_hidden_window_kwargs_are_windows_only tests.test_ai.AITests.test_cmhub_image_download_falls_back_to_requests_when_curl_fails tests.test_ai.AITests.test_cmhub_image_download_auto_without_curl_uses_requests`、`python -m ruff check app tests main.py`。 diff --git a/tests/test_ai.py b/tests/test_ai.py index d00dc8c..c9ea117 100644 --- a/tests/test_ai.py +++ b/tests/test_ai.py @@ -725,12 +725,15 @@ class AITests(TempDirMixin, unittest.TestCase): self.assertIn("--noproxy", args) self.assertEqual("*", args[args.index("--noproxy") + 1]) self.assertFalse(kwargs["shell"]) + self.assertEqual(0x08000000, kwargs["creationflags"]) output_path = args[args.index("--output") + 1] with open(output_path, "wb") as fh: fh.write(generated_png) return SimpleNamespace(returncode=0, stdout=b"", stderr=b"") - with mock.patch("app.ai._find_system_curl", return_value=r"C:\Windows\System32\curl.exe"), \ + with mock.patch("app.ai.os.name", "nt"), \ + mock.patch("app.ai.subprocess.CREATE_NO_WINDOW", 0x08000000, create=True), \ + mock.patch("app.ai._find_system_curl", return_value=r"C:\Windows\System32\curl.exe"), \ mock.patch("app.ai.subprocess.run", side_effect=fake_run), \ mock.patch("app.ai.socket.getaddrinfo", return_value=public_dns): image_bytes = ai._download_cmhub_image( @@ -744,6 +747,17 @@ class AITests(TempDirMixin, unittest.TestCase): self.assertEqual(generated_png, image_bytes) self.assertEqual(1, len(calls)) + def test_cmhub_curl_hidden_window_kwargs_are_windows_only(self): + with mock.patch("app.ai.os.name", "nt"), \ + mock.patch("app.ai.subprocess.CREATE_NO_WINDOW", 0x08000000, create=True): + self.assertEqual( + {"creationflags": 0x08000000}, + ai._subprocess_hidden_window_kwargs(), + ) + + with mock.patch("app.ai.os.name", "posix"): + self.assertEqual({}, ai._subprocess_hidden_window_kwargs()) + def test_cmhub_image_download_skips_curl_for_private_url(self): with mock.patch("app.ai._find_system_curl", return_value=r"C:\Windows\System32\curl.exe"), \ mock.patch("app.ai.subprocess.run") as run: