from __future__ import annotations import asyncio import logging import random import time from .format import mask_phone logger = logging.getLogger(__name__) PAIRING_CODE_LENGTH = 6 PAIRING_CODE_TTL = 600 PAIRING_CLEANUP_INTERVAL = 300 class AliyunSmsPairing: def __init__(self): self._codes: dict[str, dict] = {} self._authorized: dict[str, set[str]] = {} self._cleanup_task: asyncio.Task | None = None async def generate_code(self, account_id: str) -> str: code = str(random.randint(100000, 999999)) self._codes[code] = { "account_id": account_id, "expires_at": time.time() + PAIRING_CODE_TTL, } logger.info("阿里云短信配对码已生成: account=%s code=%s", account_id, code) self._ensure_cleanup() return code async def verify_code(self, account_id: str, peer_id: str, code: str) -> bool: entry = self._codes.get(code) if not entry: return False if entry["account_id"] != account_id: return False if time.time() > entry["expires_at"]: del self._codes[code] return False if account_id not in self._authorized: self._authorized[account_id] = set() self._authorized[account_id].add(peer_id) del self._codes[code] logger.info("阿里云短信配对验证成功: account=%s phone=%s", account_id, mask_phone(peer_id)) return True def is_authorized(self, account_id: str, phone_number: str) -> bool: return phone_number in self._authorized.get(account_id, set()) def revoke(self, account_id: str, phone_number: str): if account_id in self._authorized: self._authorized[account_id].discard(phone_number) def _ensure_cleanup(self): try: loop = asyncio.get_running_loop() if self._cleanup_task is None or self._cleanup_task.done(): self._cleanup_task = loop.create_task(self._cleanup_loop()) except RuntimeError: pass async def _cleanup_loop(self): while True: await asyncio.sleep(PAIRING_CLEANUP_INTERVAL) now = time.time() expired = [c for c, e in self._codes.items() if now > e["expires_at"]] for code in expired: del self._codes[code]