81 lines
2.6 KiB
Python
81 lines
2.6 KiB
Python
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
|
||
|
||
|