from __future__ import annotations import logging from typing import Any import httpx from app.integrations.base import BaseClient logger = logging.getLogger("connecthub.integrations.seeyon") class SeeyonClient(BaseClient): """ 致远 OA REST Client: - POST /seeyon/rest/token 获取 token(id) - 业务请求 header 自动携带 token - 遇到 401/Invalid token 自动刷新 token 并重试一次 """ def __init__(self, *, base_url: str, rest_user: str, rest_password: str, loginName: str | None = None) -> None: super().__init__(base_url=base_url) self.rest_user = rest_user self.rest_password = rest_password self.loginName = loginName self._token: str | None = None def authenticate(self) -> str: body: dict[str, Any] = { "userName": self.rest_user, "password": self.rest_password, } if self.loginName: body["loginName"] = self.loginName # 文档:POST /seeyon/rest/token resp = super().request( "POST", "/seeyon/rest/token", json=body, headers={"Accept": "application/json", "Content-Type": "application/json"}, ) data = resp.json() token = str(data.get("id", "") or "") if not token or token == "-1": raise RuntimeError("Seeyon auth failed (token id missing or -1)") self._token = token logger.info("Seeyon token acquired") return token def _get_token(self) -> str: return self._token or self.authenticate() def request(self, method: str, path: str, **kwargs: Any) -> httpx.Response: # type: ignore[override] token = self._get_token() headers = dict(kwargs.pop("headers", {}) or {}) headers["token"] = token try: return super().request(method, path, headers=headers, **kwargs) except httpx.HTTPStatusError as e: # token 失效:401 或返回包含 Invalid token resp = e.response text = "" try: text = resp.text or "" except Exception: text = "" if resp.status_code == 401 or ("Invalid token" in text): logger.info("Seeyon token invalid, refreshing and retrying once") self._token = None token2 = self._get_token() headers["token"] = token2 # 仅重试一次;仍失败则抛出 return super().request(method, path, headers=headers, **kwargs) raise