84 lines
2.5 KiB
Python
84 lines
2.5 KiB
Python
"""当前 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 ""
|