feat: 保存并登记当前 Client (#11)
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
"""当前 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 ""
|
||||
Reference in New Issue
Block a user