From 5c2f4f65d9c6b38b2112339aa08553a553d6db65 Mon Sep 17 00:00:00 2001 From: chengma Date: Tue, 11 Aug 2026 11:50:29 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=87=E6=8D=A2=E6=AD=A3=E5=BC=8F?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E7=AB=AF=E9=BB=98=E8=AE=A4=E5=9C=B0=E5=9D=80?= =?UTF-8?q?=20(#148)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/current_client_service.py | 17 +++++++++ client/src/http_admin_gateway.py | 2 +- client/src/pdd_ui_event.py | 6 ++-- client/src/settings_ui.py | 2 +- client/src/settings_ui_event.py | 16 +++------ client/test/test_current_client_service.py | 41 +++++++++++++++++++++- client/test/test_http_admin_gateway.py | 6 ++-- client/test/test_settings_ui_event.py | 9 +++-- docs/client/05-ui-specification.md | 2 +- 9 files changed, 77 insertions(+), 24 deletions(-) diff --git a/client/src/current_client_service.py b/client/src/current_client_service.py index 3bd20fe..339b965 100644 --- a/client/src/current_client_service.py +++ b/client/src/current_client_service.py @@ -6,12 +6,29 @@ import uuid from dataclasses import dataclass from typing import Callable, Optional +from .http_admin_gateway import DEFAULT_ADMIN_BASE_URL from .settings_repository import SettingsRepository CLIENT_ID_KEY = "admin.client_id" CLIENT_NAME_KEY = "admin.client_name" ADMIN_BASE_URL_KEY = "admin.base_url" +LEGACY_ADMIN_BASE_URL = "http://127.0.0.1:8080" + + +def load_admin_base_url(repository: SettingsRepository) -> str: + """读取管理端地址,并把空值或旧默认值迁移到当前默认地址。""" + + saved_value = repository.get(ADMIN_BASE_URL_KEY) + normalized_value = ( + saved_value.strip().rstrip("/") + if isinstance(saved_value, str) + else "" + ) + if not normalized_value or normalized_value == LEGACY_ADMIN_BASE_URL: + repository.set(ADMIN_BASE_URL_KEY, DEFAULT_ADMIN_BASE_URL) + return DEFAULT_ADMIN_BASE_URL + return normalized_value def generate_client_device_id() -> str: diff --git a/client/src/http_admin_gateway.py b/client/src/http_admin_gateway.py index bed6b04..4360575 100644 --- a/client/src/http_admin_gateway.py +++ b/client/src/http_admin_gateway.py @@ -22,7 +22,7 @@ from .admin_gateway import ( from .task_models import TaskType -DEFAULT_ADMIN_BASE_URL = "http://127.0.0.1:8080" +DEFAULT_ADMIN_BASE_URL = "https://buy.833729.com" class HttpAdminGateway(AdminGateway): diff --git a/client/src/pdd_ui_event.py b/client/src/pdd_ui_event.py index f6de38f..a4c2d00 100644 --- a/client/src/pdd_ui_event.py +++ b/client/src/pdd_ui_event.py @@ -43,8 +43,8 @@ from .admin_gateway import ( AdminGateway, ) from .collect_task_service import CollectServiceFactory, CollectTaskService -from .current_client_service import CurrentClientService -from .http_admin_gateway import DEFAULT_ADMIN_BASE_URL, HttpAdminGateway +from .current_client_service import CurrentClientService, load_admin_base_url +from .http_admin_gateway import HttpAdminGateway from .pdd_ui import PDDTaskPage, TaskRow from .pdd_device_service import ( PersistentPddDeviceService, @@ -630,7 +630,7 @@ class PDDTaskPageEvent(QObject): self._owns_claim_gateway = claim_gateway is None self._claim_gateway_error = "" if self._claim_gateway is None: - base_url = settings.get("admin.base_url", DEFAULT_ADMIN_BASE_URL) + base_url = load_admin_base_url(settings) timeout_value = settings.get("admin.request_timeout_seconds", 3.0) try: timeout_seconds = float(timeout_value) diff --git a/client/src/settings_ui.py b/client/src/settings_ui.py index b39b8f4..4a4229a 100644 --- a/client/src/settings_ui.py +++ b/client/src/settings_ui.py @@ -244,7 +244,7 @@ class SettingsPage(QWidget): self.deviceNameInput.setAccessibleName("当前客户端设备名") self.adminBaseUrlInput = LineEdit(self) - self.adminBaseUrlInput.setPlaceholderText("http://127.0.0.1:8080") + self.adminBaseUrlInput.setPlaceholderText("https://buy.833729.com") self.adminBaseUrlInput.setClearButtonEnabled(True) self.adminBaseUrlInput.setAccessibleName("Admin 管理端地址") diff --git a/client/src/settings_ui_event.py b/client/src/settings_ui_event.py index dc3e7ef..cec7c41 100644 --- a/client/src/settings_ui_event.py +++ b/client/src/settings_ui_event.py @@ -30,11 +30,11 @@ from .admin_gateway import ( ClientRegistrationGateway, ) from .current_client_service import ( - ADMIN_BASE_URL_KEY, CurrentClientService, generate_client_device_id, + load_admin_base_url, ) -from .http_admin_gateway import DEFAULT_ADMIN_BASE_URL, HttpAdminGateway +from .http_admin_gateway import HttpAdminGateway from .selected_android_device_service import SelectedAndroidDeviceService from .settings_repository import SettingsRepository from .settings_ui import AndroidDeviceRow @@ -381,7 +381,7 @@ class SettingsPageEventBinder(QObject): self._pending_admin_gateway = None self._gateway_error = "" if self._admin_gateway is None: - base_url = repository.get("admin.base_url", DEFAULT_ADMIN_BASE_URL) + base_url = load_admin_base_url(repository) timeout_value = repository.get("admin.request_timeout_seconds", 3.0) try: timeout_seconds = float(timeout_value) @@ -946,14 +946,8 @@ class SettingsPageEventBinder(QObject): """页面创建时恢复本地 Client 信息。""" try: - base_url = self._settings_repository.get( - ADMIN_BASE_URL_KEY, - DEFAULT_ADMIN_BASE_URL, - ) - displayed_base_url = ( - base_url.strip() - if isinstance(base_url, str) and base_url.strip() - else DEFAULT_ADMIN_BASE_URL + displayed_base_url = load_admin_base_url( + self._settings_repository ) saved = self._client_service.load() except Exception as exc: diff --git a/client/test/test_current_client_service.py b/client/test/test_current_client_service.py index 961c525..6a3c4b1 100644 --- a/client/test/test_current_client_service.py +++ b/client/test/test_current_client_service.py @@ -4,7 +4,11 @@ import tempfile import unittest from pathlib import Path -from src.current_client_service import CurrentClientService +from src.current_client_service import ( + CurrentClientService, + load_admin_base_url, +) +from src.http_admin_gateway import DEFAULT_ADMIN_BASE_URL from src.settings_repository import SettingsRepository @@ -52,6 +56,41 @@ class CurrentClientServiceTest(unittest.TestCase): self.assertEqual(saved.client_id, "CLIENT-GENERATED-1") self.assertEqual(saved.client_name, "新设备") + def test_missing_admin_base_url_is_saved_as_current_default(self): + self.assertEqual( + load_admin_base_url(self.repository), + DEFAULT_ADMIN_BASE_URL, + ) + self.assertEqual( + self.repository.get("admin.base_url"), + DEFAULT_ADMIN_BASE_URL, + ) + + def test_legacy_admin_base_url_is_migrated(self): + self.repository.set("admin.base_url", "http://127.0.0.1:8080/") + + self.assertEqual( + load_admin_base_url(self.repository), + DEFAULT_ADMIN_BASE_URL, + ) + self.assertEqual( + self.repository.get("admin.base_url"), + DEFAULT_ADMIN_BASE_URL, + ) + + def test_custom_admin_base_url_is_preserved(self): + custom_url = "https://admin.example.com/service/" + self.repository.set("admin.base_url", custom_url) + + self.assertEqual( + load_admin_base_url(self.repository), + "https://admin.example.com/service", + ) + self.assertEqual( + self.repository.get("admin.base_url"), + custom_url, + ) + if __name__ == "__main__": unittest.main() diff --git a/client/test/test_http_admin_gateway.py b/client/test/test_http_admin_gateway.py index 9fb7944..73b0704 100644 --- a/client/test/test_http_admin_gateway.py +++ b/client/test/test_http_admin_gateway.py @@ -13,7 +13,7 @@ from src.admin_gateway import ( ClaimCapabilities, ClientInfo, ) -from src.http_admin_gateway import HttpAdminGateway +from src.http_admin_gateway import DEFAULT_ADMIN_BASE_URL, HttpAdminGateway from src.task_models import TaskType @@ -144,7 +144,7 @@ class HttpAdminGatewayTest(unittest.TestCase): self.assertTrue(receipt.accepted) self.assertEqual( opener.request.full_url, - "http://127.0.0.1:8080/api/v1/client/tasks/COL-001/result", + f"{DEFAULT_ADMIN_BASE_URL}/api/v1/client/tasks/COL-001/result", ) headers = { key.lower(): value for key, value in opener.request.header_items() @@ -256,7 +256,7 @@ class HttpAdminGatewayTest(unittest.TestCase): self.assertEqual(opener.request.get_method(), "POST") self.assertEqual( opener.request.full_url, - "http://127.0.0.1:8080/api/v1/client/tasks/claim", + f"{DEFAULT_ADMIN_BASE_URL}/api/v1/client/tasks/claim", ) headers = { key.lower(): value for key, value in opener.request.header_items() diff --git a/client/test/test_settings_ui_event.py b/client/test/test_settings_ui_event.py index 4b3d11d..35bc727 100644 --- a/client/test/test_settings_ui_event.py +++ b/client/test/test_settings_ui_event.py @@ -290,7 +290,7 @@ class SettingsPageEventTest(unittest.TestCase): { "admin.client_id": "CLIENT-EXISTING", "admin.client_name": "原设备名", - "admin.base_url": "http://127.0.0.1:8080", + "admin.base_url": "http://127.0.0.1:9090", } ) gateway = MockAdminGateway() @@ -309,7 +309,7 @@ class SettingsPageEventTest(unittest.TestCase): self.assertEqual(self.repository.get("admin.client_name"), "原设备名") self.assertEqual( self.repository.get("admin.base_url"), - "http://127.0.0.1:8080", + "http://127.0.0.1:9090", ) self.assertEqual(gateway.registration_count, 0) self.assertIn("Admin 服务地址必须", page.currentDeviceStatusLabel.text()) @@ -330,7 +330,10 @@ class SettingsPageEventTest(unittest.TestCase): page.currentDeviceSaveButton.click() self.app.processEvents() - self.assertIsNone(self.repository.get("admin.base_url")) + self.assertEqual( + self.repository.get("admin.base_url"), + DEFAULT_ADMIN_BASE_URL, + ) self.assertEqual(gateway.registration_count, 0) self.assertIn("不能包含账号、密码", page.currentDeviceStatusLabel.text()) page.eventBinder.shutdown() diff --git a/docs/client/05-ui-specification.md b/docs/client/05-ui-specification.md index edbf64c..86b891c 100644 --- a/docs/client/05-ui-specification.md +++ b/docs/client/05-ui-specification.md @@ -284,7 +284,7 @@ class TaskTableModel(QAbstractTableModel): ### 当前设备 - 第一行依次显示设备号和设备名;设备号只读,设备名可以修改,两组输入随窗口宽度平均伸缩; -- 第二行显示“管理端地址”,对应 SQLite 中已有的 `admin.base_url`,没有保存值时显示默认地址; +- 第二行显示“管理端地址”,对应 SQLite 中已有的 `admin.base_url`;默认地址为 `https://buy.833729.com`,没有保存值或保存值仍是旧默认地址 `http://127.0.0.1:8080` 时自动迁移,用户手动保存的其他地址不得覆盖; - 点击“保存”前先验证地址;只接受有效的 HTTP/HTTPS 地址,不能包含账号、密码、查询参数或片段。验证失败时不保存设备号、设备名或地址,并把焦点放回地址输入框; - 保存成功后立即用新地址登记 Client,后续任务领取、结果提交和失败提交也统一切换到新地址;已经发出的请求等待自身结束,不强制中断; - 保存不新增数据库表,不增加心跳,也不连接或操作 Android 设备。