117 lines
3.8 KiB
Python
117 lines
3.8 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
|
||
|
||
def export_cap4_form_soap(
|
||
self,
|
||
*,
|
||
templateCode: str,
|
||
senderLoginName: str | None = None,
|
||
rightId: str | None = None,
|
||
doTrigger: str | bool | None = None,
|
||
param: str | None = None,
|
||
extra: dict[str, Any] | None = None,
|
||
) -> httpx.Response:
|
||
"""
|
||
无流程表单导出(CAP4):
|
||
POST /seeyon/rest/cap4/form/soap/export
|
||
|
||
返回 httpx.Response,调用方可自行读取 resp.text / resp.headers 等信息。
|
||
"""
|
||
body: dict[str, Any] = {"templateCode": templateCode}
|
||
if senderLoginName:
|
||
body["senderLoginName"] = senderLoginName
|
||
if rightId:
|
||
body["rightId"] = rightId
|
||
if doTrigger is not None:
|
||
body["doTrigger"] = doTrigger
|
||
if param is not None:
|
||
body["param"] = param
|
||
if extra:
|
||
# 兜底扩展字段:仅当 key 不冲突时注入,避免覆盖已显式指定的参数
|
||
for k, v in extra.items():
|
||
if k not in body:
|
||
body[k] = v
|
||
|
||
return self.request(
|
||
"POST",
|
||
"/seeyon/rest/cap4/form/soap/export",
|
||
json=body,
|
||
headers={"Content-Type": "application/json"},
|
||
)
|