167 lines
5.5 KiB
Python
167 lines
5.5 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"},
|
||
)
|
||
|
||
def batch_update_cap4_form_soap(
|
||
self,
|
||
*,
|
||
formCode: str,
|
||
loginName: str,
|
||
rightId: str,
|
||
dataList: list[dict[str, Any]],
|
||
uniqueFiled: list[str] | None = None,
|
||
doTrigger: bool | None = None,
|
||
) -> httpx.Response:
|
||
"""
|
||
无流程批量更新:
|
||
POST /seeyon/rest/cap4/form/soap/batch-update
|
||
|
||
参数对齐致远接口:
|
||
- formCode/loginName/rightId/dataList 必填
|
||
- uniqueFiled/doTrigger 可选
|
||
"""
|
||
form_code = str(formCode or "").strip()
|
||
login_name = str(loginName or "").strip()
|
||
right_id = str(rightId or "").strip()
|
||
if not form_code:
|
||
raise ValueError("formCode is required")
|
||
if not login_name:
|
||
raise ValueError("loginName is required")
|
||
if not right_id:
|
||
raise ValueError("rightId is required")
|
||
if not isinstance(dataList, list) or len(dataList) == 0:
|
||
raise ValueError("dataList is required and must be a non-empty list")
|
||
if uniqueFiled is not None and not isinstance(uniqueFiled, list):
|
||
raise ValueError("uniqueFiled must be a list if provided")
|
||
|
||
body: dict[str, Any] = {
|
||
"formCode": form_code,
|
||
"loginName": login_name,
|
||
"rightId": right_id,
|
||
"dataList": dataList,
|
||
}
|
||
if uniqueFiled is not None:
|
||
body["uniqueFiled"] = uniqueFiled
|
||
if doTrigger is not None:
|
||
body["doTrigger"] = doTrigger
|
||
|
||
return self.request(
|
||
"POST",
|
||
"/seeyon/rest/cap4/form/soap/batch-update",
|
||
json=body,
|
||
headers={"Content-Type": "application/json"},
|
||
)
|