from __future__ import annotations import logging import time from typing import Any, Dict import httpx from app.config.settings import get_settings logger = logging.getLogger(__name__) _token_cache: Dict[str, Any] = {"token": "", "expires_at": 0.0} class FeishuClient: def __init__(self) -> None: self.settings = get_settings() async def _get_tenant_token(self) -> str: if _token_cache["token"] and time.time() < _token_cache["expires_at"]: return _token_cache["token"] url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal" payload = { "app_id": self.settings.feishu_app_id, "app_secret": self.settings.feishu_app_secret, } async with httpx.AsyncClient(timeout=self.settings.request_timeout) as client: resp = await client.post(url, json=payload) resp.raise_for_status() data = resp.json() token = data.get("tenant_access_token", "") expire = int(data.get("expire", 0)) _token_cache["token"] = token _token_cache["expires_at"] = time.time() + max(expire - 60, 0) return token async def get_approval_instance(self, instance_id: str) -> Dict[str, Any]: token = await self._get_tenant_token() url = f"https://open.feishu.cn/open-apis/approval/v4/instances/{instance_id}" headers = {"Authorization": f"Bearer {token}"} async with httpx.AsyncClient(timeout=self.settings.request_timeout) as client: resp = await client.get(url, headers=headers) resp.raise_for_status() return resp.json() async def subscribe_approval(self, approval_code: str) -> Dict[str, Any]: token = await self._get_tenant_token() url = ( "https://open.feishu.cn/open-apis/approval/v4/approvals/" f"{approval_code}/subscribe" ) headers = {"Authorization": f"Bearer {token}"} async with httpx.AsyncClient(timeout=self.settings.request_timeout) as client: resp = await client.post(url, headers=headers) data = {} try: data = resp.json() except Exception: data = {} code = data.get("code") if resp.status_code != 200 and code != 1390007: logger.error("订阅审批定义失败: status=%s body=%s", resp.status_code, resp.text) resp.raise_for_status() return data