from typing import Any, Dict, List, Optional, Tuple from .config import settings from .eip_client import client_singleton as eip from .redis_store import store_singleton as kv def _extract_device_ip_and_id(device: Dict[str, Any]) -> Tuple[Optional[str], Optional[str]]: # 由于未知后端返回结构,尽可能容错地提取 ip = ( device.get("ip") or device.get("public_ip") or device.get("eip") or device.get("addr") or device.get("address") ) edge_id = device.get("id") or device.get("edge") or device.get("mac") or device.get("device_id") # 若 edge 是数组或对象,尝试取可打印的第一项 if isinstance(edge_id, list) and edge_id: edge_id = edge_id[0] if isinstance(edge_id, dict): edge_id = edge_id.get("id") or edge_id.get("mac") or edge_id.get("edge") return (str(ip) if ip else None, str(edge_id) if edge_id else None) def select_unused_ip(devices: List[Dict[str, Any]]) -> Tuple[Optional[str], Optional[str]]: for d in devices: ip, edge_id = _extract_device_ip_and_id(d) if not ip: # 没有可识别的 IP,跳过 continue if not kv.is_ip_used_today(ip): return ip, edge_id return None, None def apply_gateway_route(edge_id: Optional[str], ip: str) -> Dict[str, Any]: # 将选中的 edge 配置到网关规则中 rule = { "table": 1, "enable": True, "edge": [edge_id] if edge_id else [], "network": [], "cityhash": settings.eip_default_cityhash or "", } config = {"id": 1, "rules": [rule]} return eip.gateway_config_set(settings.eip_gateway_mac, config) def rotate(cityhash: Optional[str] = None, num: Optional[int] = None) -> Dict[str, Any]: geo = cityhash or settings.eip_default_cityhash if not geo: raise ValueError("cityhash 不能为空,请在请求中或环境变量中提供") n = int(num or settings.eip_default_num or 10) devices = eip.list_devices(geo=geo, offset=0, num=n) ip, edge_id = select_unused_ip(devices) if not ip: return {"changed": False, "reason": "没有可用且今天未使用的 IP"} _ = apply_gateway_route(edge_id=edge_id, ip=ip) kv.add_used_ip_today(ip) kv.set_current(ip=ip, edge_id=edge_id) status = eip.gateway_status(settings.eip_gateway_mac) return {"changed": True, "ip": ip, "edge": edge_id, "status": status} def status() -> Dict[str, Any]: cur = kv.get_current() used_count = kv.get_used_count_today() gw = {} try: gw = eip.gateway_status(settings.eip_gateway_mac) except Exception: pass return {"current": cur, "used_today": used_count, "gateway": gw}