99 lines
4.0 KiB
Python
99 lines
4.0 KiB
Python
|
|
import time
|
|||
|
|
from typing import Any, Dict, List, Optional
|
|||
|
|
|
|||
|
|
import httpx
|
|||
|
|
from pydantic import BaseModel
|
|||
|
|
from tenacity import retry, stop_after_attempt, wait_exponential
|
|||
|
|
|
|||
|
|
from .config import settings
|
|||
|
|
|
|||
|
|
|
|||
|
|
class AuthResponse(BaseModel):
|
|||
|
|
token: str
|
|||
|
|
exp: Optional[int] = None # epoch seconds
|
|||
|
|
|
|||
|
|
|
|||
|
|
class EipClient:
|
|||
|
|
def __init__(self, base_url: str, username: str, password: str) -> None:
|
|||
|
|
self.base_url = base_url.rstrip("/")
|
|||
|
|
self.username = username
|
|||
|
|
self.password = password
|
|||
|
|
self._token: Optional[str] = None
|
|||
|
|
self._token_exp: Optional[int] = None
|
|||
|
|
self._client = httpx.Client(timeout=15.0)
|
|||
|
|
|
|||
|
|
def _is_token_valid(self) -> bool:
|
|||
|
|
if not self._token or not self._token_exp:
|
|||
|
|
return False
|
|||
|
|
# refresh 60s earlier
|
|||
|
|
return time.time() < (self._token_exp - 60)
|
|||
|
|
|
|||
|
|
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=0.5, min=0.5, max=2))
|
|||
|
|
def authenticate(self) -> AuthResponse:
|
|||
|
|
url = f"{self.base_url}/client/auth"
|
|||
|
|
resp = self._client.post(url, json={"username": self.username, "password": self.password})
|
|||
|
|
resp.raise_for_status()
|
|||
|
|
data = resp.json()
|
|||
|
|
# 尝试从响应中提取 exp;如果没有,设置 1 小时后过期
|
|||
|
|
exp = data.get("exp") or int(time.time()) + 3600
|
|||
|
|
token = data.get("token") or data.get("X-Token") or data.get("access_token")
|
|||
|
|
if not token:
|
|||
|
|
# 兼容后端直接返回字符串 token 的情况
|
|||
|
|
if isinstance(data, str) and data:
|
|||
|
|
token = data
|
|||
|
|
else:
|
|||
|
|
raise ValueError("Auth response missing token")
|
|||
|
|
self._token = token
|
|||
|
|
self._token_exp = exp
|
|||
|
|
return AuthResponse(token=token, exp=exp)
|
|||
|
|
|
|||
|
|
def _get_headers(self) -> Dict[str, str]:
|
|||
|
|
if not self._is_token_valid():
|
|||
|
|
self.authenticate()
|
|||
|
|
assert self._token
|
|||
|
|
return {"X-Token": self._token, "Accept": "application/json"}
|
|||
|
|
|
|||
|
|
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=0.5, min=0.5, max=2))
|
|||
|
|
def list_cities(self) -> List[Dict[str, Any]]:
|
|||
|
|
url = f"{self.base_url}/edge/city"
|
|||
|
|
resp = self._client.get(url, headers=self._get_headers())
|
|||
|
|
resp.raise_for_status()
|
|||
|
|
data = resp.json()
|
|||
|
|
return data if isinstance(data, list) else data.get("data", [])
|
|||
|
|
|
|||
|
|
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=0.5, min=0.5, max=2))
|
|||
|
|
def list_devices(self, geo: str, offset: int, num: int) -> List[Dict[str, Any]]:
|
|||
|
|
url = f"{self.base_url}/edge/device"
|
|||
|
|
payload = {"geo": geo, "offset": offset, "num": num}
|
|||
|
|
resp = self._client.post(url, headers=self._get_headers(), json=payload)
|
|||
|
|
resp.raise_for_status()
|
|||
|
|
data = resp.json()
|
|||
|
|
return data if isinstance(data, list) else data.get("data", [])
|
|||
|
|
|
|||
|
|
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=0.5, min=0.5, max=2))
|
|||
|
|
def gateway_config_get(self, macaddr: str) -> Dict[str, Any]:
|
|||
|
|
url = f"{self.base_url}/gateway/config/get"
|
|||
|
|
resp = self._client.post(url, headers=self._get_headers(), json={"macaddr": macaddr})
|
|||
|
|
resp.raise_for_status()
|
|||
|
|
return resp.json()
|
|||
|
|
|
|||
|
|
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=0.5, min=0.5, max=2))
|
|||
|
|
def gateway_config_set(self, macaddr: str, config: Dict[str, Any]) -> Dict[str, Any]:
|
|||
|
|
url = f"{self.base_url}/gateway/config/set"
|
|||
|
|
payload = {"macaddr": macaddr, "config": config}
|
|||
|
|
resp = self._client.post(url, headers=self._get_headers(), json=payload)
|
|||
|
|
resp.raise_for_status()
|
|||
|
|
return resp.json()
|
|||
|
|
|
|||
|
|
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=0.5, min=0.5, max=2))
|
|||
|
|
def gateway_status(self, macaddr: str) -> Dict[str, Any]:
|
|||
|
|
url = f"{self.base_url}/gateway/status"
|
|||
|
|
resp = self._client.post(url, headers=self._get_headers(), json={"macaddr": macaddr})
|
|||
|
|
resp.raise_for_status()
|
|||
|
|
return resp.json()
|
|||
|
|
|
|||
|
|
|
|||
|
|
client_singleton = EipClient(settings.eip_base_url, settings.eip_username, settings.eip_password)
|
|||
|
|
|
|||
|
|
|