"""当前 Windows Client 身份的本地保存与读取服务。""" import hashlib import platform import uuid from dataclasses import dataclass from typing import Callable, Optional from .settings_repository import SettingsRepository CLIENT_ID_KEY = "admin.client_id" CLIENT_NAME_KEY = "admin.client_name" 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) -> 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("生成的设备号不能为空") self._repository.set_many( { CLIENT_ID_KEY: client_id, CLIENT_NAME_KEY: normalized_name, } ) return CurrentClientSettings(client_id, normalized_name) @staticmethod def _clean_text(value: object) -> str: return value.strip() if isinstance(value, str) else ""