diff --git a/app/gui/__init__.py b/app/gui/__init__.py index b5745a3..67347df 100644 --- a/app/gui/__init__.py +++ b/app/gui/__init__.py @@ -189,7 +189,6 @@ def main() -> int: if runtime_lease is not None: app.aboutToQuit.connect(runtime_lease.release) window.show() - QTimer.singleShot(0, window.begin_subscription_check) QTimer.singleShot( 0, lambda: update_health.write_health(health_context, "main_window_ready"), diff --git a/app/gui/main_window.py b/app/gui/main_window.py index e504502..bdd44ca 100644 --- a/app/gui/main_window.py +++ b/app/gui/main_window.py @@ -20,6 +20,8 @@ from .workers import SubscriptionCheckWorker PREFERRED_WINDOW_SIZE = (1180, 760) MIN_WINDOW_SIZE = (960, 640) WINDOW_SCREEN_MARGIN = 40 +# 订阅服务仍在联调期,暂不让客户端检查或限制本地工作流。 +SUBSCRIPTION_CHECK_ENABLED = False def _screen_available_geometry(): @@ -132,7 +134,12 @@ class MainWindow(QMainWindow): self._header_title_label.setStyleSheet( "font-size: 15px; font-weight: 600; color: #24292f;" ) - self._subscription_label = QLabel("会员:正在验证") + subscription_label_text = ( + "会员:正在验证" + if SUBSCRIPTION_CHECK_ENABLED + else "会员:订阅检测已暂停" + ) + self._subscription_label = QLabel(subscription_label_text) self._subscription_label.setObjectName("subscriptionStatusLabel") self._subscription_label.setAccessibleName("会员状态") self._subscription_label.setStyleSheet("color: #0969da;") @@ -232,7 +239,6 @@ class MainWindow(QMainWindow): widget.refresh_gateway_state() label = "自定义网关" if str(backend) == "direct" else "默认网关" self.show_status("设置已保存,当前使用%s" % label, level="success") - self.begin_subscription_check() @property def subscription_status(self): @@ -241,6 +247,10 @@ class MainWindow(QMainWindow): def begin_subscription_check(self): """Refresh membership state without blocking the Qt GUI thread.""" + if not SUBSCRIPTION_CHECK_ENABLED: + self._set_product_access(True) + self._set_subscription_label("会员:订阅检测已暂停", "muted") + return self._subscription_request_token += 1 token = self._subscription_request_token if self._subscription_worker is not None: @@ -272,6 +282,8 @@ class MainWindow(QMainWindow): def ensure_subscription_for_new_submit(self, action_name="生成"): """Return whether a new product request may start from the current UI.""" + if not SUBSCRIPTION_CHECK_ENABLED: + return True status = self._subscription_status if status.allows_product_workflows: return True diff --git a/docs/04-architecture.md b/docs/04-architecture.md index 33a9c98..e9a85af 100644 --- a/docs/04-architecture.md +++ b/docs/04-architecture.md @@ -79,7 +79,7 @@ imported → collected → generated → applied - `cdp`:连接调试端口、找/开 tab、执行 JS、拖拽、注入文件。 - `editor`:登录检测、读取商品状态、**采集**(读旧标题、下载旧封面)、改标题、换封面、点更新。 - `product_status`:商品状态代码 `normal/unlisted/reviewing/unknown` 的归一化、中文显示、EDS 提示分类和下游任务分组;①②③只能复用该模块,不各自判断。 -- `subscription`:使用 cmhub API Key 查询 `GET /api/v1/cmshopee/subscription/status`,把远端权益结果归一化为有效、宽限、未订阅、到期、撤销、账号不可用、Key 无效、暂时不可用或旧服务兼容状态;不保存、展示或记录 API Key,服务端始终是最终授权方。 +- `subscription`:使用 cmhub API Key 查询 `GET /api/v1/cmshopee/subscription/status`,把远端权益结果归一化为有效、宽限、未订阅、到期、撤销、账号不可用、Key 无效、暂时不可用或旧服务兼容状态;不保存、展示或记录 API Key,服务端始终是最终授权方。当前联调期由 `app.gui.main_window.SUBSCRIPTION_CHECK_ENABLED = False` 暂停客户端启动检查与本地预检,不请求接口、不限制工作流;恢复发布前必须改回启用并回归验证。 - `ai`:`gen_title(prompt, old_title)`、`gen_cover(prompt, old_cover_path)`、`analyze_product_images(instruction, context, image_paths)`;前两者分别负责②标题/生图,后者只供商品套图中的「AI帮写」调用 cmhub 图片理解接口,读取1至8张按 `source_order` 排序的本地商品原图并返回可编辑卖点与白名单计费元数据。 - `cmhub_models`:格式化 cmhub 模型别名,并维护仅进程内有效的短期模型目录缓存;缓存键使用规整网关地址和别名,不含 API Key,不写入配置、SQLite、日志或导出文件。 diff --git a/docs/routes.md b/docs/routes.md index 8e4674a..3824944 100644 --- a/docs/routes.md +++ b/docs/routes.md @@ -27,6 +27,7 @@ ## 会员订阅接入 +- 当前处于联调测试期:`app.gui.main_window.SUBSCRIPTION_CHECK_ENABLED = False`,启动、保存设置和新提交均不请求订阅接口,也不限制六个工作流;顶部只显示“会员:订阅检测已暂停”。恢复发布前必须重新启用后再按下列流程验收。 - 强制升级检查完成、主窗口显示后,后台用 `data/config/cmhub.json` 的 API Key 请求 `GET /api/v1/cmshopee/subscription/status`;不会阻塞 Qt 主线程或把 Key 放入 URL、状态栏、日志和错误提示。 - 状态接口已启用时,未配置 Key、Key 无效、账号禁用、未订阅、到期、撤销或暂时无法确认会员状态,主窗口只保留「设置」Tab,并在顶部显示中文原因;用户可打开设置重填 Key、按服务端返回的 `manage_url` 用默认浏览器前往线上会员中心,或退出。已接受的异步任务、本地数据和导出不删除。 - 订阅有效时,应用顶部标题区域右侧显示“账号名 · 套餐名 · 有效至日期”;宽限期显示宽限截止日。不得把长会员文本拼进 Windows 原生窗口标题,也不得展示 API Key。 diff --git a/docs/tasks/T-686.md b/docs/tasks/T-686.md index 12ce3f1..708450f 100644 --- a/docs/tasks/T-686.md +++ b/docs/tasks/T-686.md @@ -89,3 +89,5 @@ git diff --check - 2026-07-21:验证通过:`py -3.10 -m compileall app main.py`、`py -3.10 -m ruff check app tests main.py`、订阅/应用配置/AI/套图/GUI 定向测试,以及 `py -3.10 -m unittest discover -s tests`(664 项)。无凭据请求线上订阅状态接口返回 `404`,已由单测和运行时兼容分支覆盖;服务端上线后需使用测试 API Key 做一次真实有效/到期/未订阅联调,不属于本仓库可完成的接口发布工作。 - 2026-07-22:产品决策调整:移除未配置或未开通会员时的阻断式弹窗;保留工作流访问限制、自动切换“设置”和状态栏提示,避免每次启动或新提交被会员配置窗口打断。 - 2026-07-22:验证通过:`py -3.10 -m compileall app main.py`、`py -3.10 -m ruff check app tests main.py`、`py -3.10 -m unittest discover -s tests -p test_gui.py`,以及完整单测 `py -3.10 -m unittest discover -s tests`(664 项)。 +- 2026-07-22:为测试其他完整功能,临时设置 `app.gui.main_window.SUBSCRIPTION_CHECK_ENABLED = False`:启动和保存设置不查询订阅接口,AI生成、AI帮写和商品套图的新提交不做客户端预检;顶部明确显示“会员:订阅检测已暂停”。订阅模块与服务端最终授权设计保留,恢复发布前需重新启用并验证。 +- 2026-07-22:验证通过:`py -3.10 -m compileall app main.py`、`py -3.10 -m ruff check app tests main.py`、`py -3.10 -m unittest discover -s tests -p test_gui.py`(217 项)以及完整单测 `py -3.10 -m unittest discover -s tests`(665 项)。 diff --git a/tests/test_gui.py b/tests/test_gui.py index e686e39..9904b83 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -74,6 +74,7 @@ from app.gui import ( ) from app.gui import file_manager import app.gui.workers as gui_workers +import app.gui.main_window as main_window from app.gui.tabs.collect import _format_collect_elapsed from app.gui.tabs.generate import CoverGalleryDialog, OriginalImageDialog from app.gui.tabs.settings import SettingsSpinBox @@ -11185,6 +11186,22 @@ class GuiTests(TempDirMixin, unittest.TestCase): self.assertEqual(title == "设置", window.tabs.isTabEnabled(index)) self.assertEqual(TAB_TITLES.index("设置"), window.tabs.currentIndex()) + def test_main_window_temporarily_bypasses_subscription_checks(self): + self.assertFalse(main_window.SUBSCRIPTION_CHECK_ENABLED) + with self.make_temp_dir() as temp_dir: + window = MainWindow(config=self.make_config(temp_dir)) + self.addCleanup(window.close) + + with mock.patch("app.gui.main_window.SubscriptionCheckWorker") as worker: + window.begin_subscription_check() + + worker.assert_not_called() + self.assertEqual("会员:订阅检测已暂停", window._subscription_label.text()) + self.assertTrue( + all(window.tabs.isTabEnabled(index) for index in range(window.tabs.count())) + ) + self.assertTrue(window.ensure_subscription_for_new_submit("开始 AI 生成")) + def test_generate_and_suite_preflight_can_stop_new_submissions(self): with self.make_temp_dir() as temp_dir: config = self.make_config(temp_dir)