Files
cmautobuy/client/src/current_client_service.py
T

107 lines
3.3 KiB
Python

"""当前 Windows Client 身份的本地保存与读取服务。"""
import hashlib
import platform
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:
"""根据本机特征生成稳定设备号,只保留不可逆哈希。"""
fingerprint = "|".join(
(
platform.system(),
platform.node(),
platform.machine(),
f"{uuid.getnode():012x}",
)
)
digest = hashlib.sha256(fingerprint.encode("utf-8")).hexdigest()[:16]
return f"CLIENT-{digest.upper()}"
@dataclass(frozen=True)
class CurrentClientSettings:
"""本地已经确认保存的当前 Client 信息。"""
client_id: str
client_name: str
class CurrentClientService:
"""保证设备号只生成一次,并与名称一起原子保存。"""
def __init__(
self,
repository: SettingsRepository,
device_id_factory: Callable[[], str] = generate_client_device_id,
):
self._repository = repository
self._device_id_factory = device_id_factory
def load(self) -> CurrentClientSettings:
"""读取已保存的信息;缺少的字段返回空字符串。"""
client_id = self._clean_text(self._repository.get(CLIENT_ID_KEY, ""))
client_name = self._clean_text(
self._repository.get(CLIENT_NAME_KEY, "")
)
return CurrentClientSettings(client_id, client_name)
def save(
self,
client_name: str,
admin_base_url: Optional[str] = None,
) -> CurrentClientSettings:
"""原子保存身份;传入管理端地址时一并保存。"""
normalized_name = self._clean_text(client_name)
if len(normalized_name) > 50:
raise ValueError("设备名最多 50 个字")
existing = self.load()
client_id = existing.client_id
if not client_id:
client_id = self._clean_text(self._device_id_factory())
if not client_id:
raise ValueError("生成的设备号不能为空")
values = {
CLIENT_ID_KEY: client_id,
CLIENT_NAME_KEY: normalized_name,
}
if admin_base_url is not None:
values[ADMIN_BASE_URL_KEY] = self._clean_text(admin_base_url)
self._repository.set_many(values)
return CurrentClientSettings(client_id, normalized_name)
@staticmethod
def _clean_text(value: object) -> str:
return value.strip() if isinstance(value, str) else ""