落地并存测服全端:智能体 Graph/路由、后台切 8180、主账与部署脚本齐备,便于线上重发与验收。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,25 +2,152 @@
|
||||
主账 HTTP 客户端:只打主账 API,不直连 TMS / MySQL。
|
||||
|
||||
正式写操作必须带幂等键;Graph 成功 ≠ 主账成功。
|
||||
骨架:仅占位客户端。
|
||||
本文件提供可联调的 ping / JSON 请求骨架;不做六态业务分支。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Optional
|
||||
|
||||
import httpx
|
||||
|
||||
from agent.config import Settings, get_settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class LedgerResponse:
|
||||
"""主账一次 HTTP 调用的结果摘要(不含密钥)。"""
|
||||
|
||||
ok: bool
|
||||
status_code: int
|
||||
data: Any = None
|
||||
error: str = ""
|
||||
|
||||
|
||||
@dataclass
|
||||
class LedgerClient:
|
||||
"""
|
||||
主账客户端骨架。
|
||||
主账客户端。
|
||||
|
||||
参数:base_url 来自 BIZ_API_BASE_URL。
|
||||
禁止:在本类拼 TMS 签名或写工单 SQL。
|
||||
base_url:BIZ_API_BASE_URL(新系统多为 http://127.0.0.1:8180/jeecgboot)。
|
||||
超时:默认 10s,禁止无限阻塞 Worker 槽。
|
||||
禁止:在本类拼 TMS HMAC 或写工单 SQL。
|
||||
"""
|
||||
|
||||
base_url: str
|
||||
timeout_seconds: float = 10.0
|
||||
service_token: str = ""
|
||||
_client: Optional[httpx.Client] = field(default=None, repr=False)
|
||||
|
||||
@classmethod
|
||||
def from_settings(cls, settings: Optional[Settings] = None) -> "LedgerClient":
|
||||
"""从环境组装客户端。"""
|
||||
cfg = settings or get_settings()
|
||||
return cls(
|
||||
base_url=(cfg.biz_api_base_url or "").rstrip("/"),
|
||||
timeout_seconds=float(getattr(cfg, "biz_api_timeout_seconds", 10) or 10),
|
||||
service_token=(getattr(cfg, "biz_api_token", None) or ""),
|
||||
)
|
||||
|
||||
def _http(self) -> httpx.Client:
|
||||
if self._client is None:
|
||||
headers = {"Accept": "application/json"}
|
||||
if self.service_token:
|
||||
headers["X-Access-Token"] = self.service_token
|
||||
self._client = httpx.Client(
|
||||
base_url=self.base_url,
|
||||
timeout=self.timeout_seconds,
|
||||
headers=headers,
|
||||
)
|
||||
return self._client
|
||||
|
||||
def close(self) -> None:
|
||||
"""关闭连接池。"""
|
||||
if self._client is not None:
|
||||
self._client.close()
|
||||
self._client = None
|
||||
|
||||
def health_url(self) -> str:
|
||||
"""返回主账健康检查 URL,供后续联调;骨架不发请求。"""
|
||||
return f"{self.base_url.rstrip('/')}/health"
|
||||
"""健康检查相对路径候选的完整 URL(日志用)。"""
|
||||
return f"{self.base_url}/"
|
||||
|
||||
def ping(self) -> LedgerResponse:
|
||||
"""
|
||||
探测主账是否可达。
|
||||
|
||||
尝试 GET / 或常见健康路径;4xx/5xx 记入 error,不抛给回调线程。
|
||||
副作用:出站 HTTP。
|
||||
"""
|
||||
if not self.base_url:
|
||||
return LedgerResponse(ok=False, status_code=0, error="BIZ_API_BASE_URL 未配置")
|
||||
paths = ["/", "/sys/randomImage/ping-check"]
|
||||
last: LedgerResponse = LedgerResponse(ok=False, status_code=0, error="no_attempt")
|
||||
for path in paths:
|
||||
try:
|
||||
resp = self._http().get(path)
|
||||
last = LedgerResponse(
|
||||
ok=resp.status_code < 500,
|
||||
status_code=resp.status_code,
|
||||
data={"path": path},
|
||||
)
|
||||
if resp.status_code < 500:
|
||||
logger.info(
|
||||
"ledger.ping ok status=%s path=%s",
|
||||
resp.status_code,
|
||||
path,
|
||||
)
|
||||
return last
|
||||
except httpx.HTTPError as exc:
|
||||
last = LedgerResponse(ok=False, status_code=0, error=str(exc))
|
||||
logger.warning("ledger.ping 失败 path=%s err=%s", path, exc)
|
||||
return last
|
||||
|
||||
def request_json(
|
||||
self,
|
||||
method: str,
|
||||
path: str,
|
||||
*,
|
||||
json_body: Optional[dict[str, Any]] = None,
|
||||
idempotency_key: str = "",
|
||||
) -> LedgerResponse:
|
||||
"""
|
||||
通用 JSON 请求骨架。
|
||||
|
||||
写操作应传入 idempotency_key(写入头 X-Idempotency-Key)。
|
||||
不做业务语义解析;调用方根据主账合同处理 result。
|
||||
"""
|
||||
headers: dict[str, str] = {}
|
||||
if idempotency_key:
|
||||
headers["X-Idempotency-Key"] = idempotency_key
|
||||
try:
|
||||
resp = self._http().request(
|
||||
method.upper(),
|
||||
path,
|
||||
json=json_body,
|
||||
headers=headers,
|
||||
)
|
||||
data: Any
|
||||
try:
|
||||
data = resp.json()
|
||||
except ValueError:
|
||||
data = {"text": resp.text[:500]}
|
||||
ok = 200 <= resp.status_code < 300
|
||||
if not ok:
|
||||
logger.warning(
|
||||
"ledger.request 非成功 status=%s path=%s",
|
||||
resp.status_code,
|
||||
path,
|
||||
)
|
||||
return LedgerResponse(ok=ok, status_code=resp.status_code, data=data)
|
||||
except httpx.HTTPError as exc:
|
||||
logger.warning("ledger.request 异常 path=%s err=%s", path, exc)
|
||||
return LedgerResponse(ok=False, status_code=0, error=str(exc))
|
||||
|
||||
|
||||
from agent.ledger.api_shell import LedgerApiShell # noqa: E402
|
||||
|
||||
__all__ = ["LedgerClient", "LedgerResponse", "LedgerApiShell"]
|
||||
|
||||
Reference in New Issue
Block a user