ForcePilot/backend/package/yuxi/channel/extensions/jd/client.py
Kris bb8ade9e2b feat(channel): 添加京东渠道扩展
新增京东(JD)渠道扩展,支持在 Yuxi 平台中集成京东客服渠道。

包含以下功能模块:
- client: 京东 API 客户端封装
- config: 渠道配置管理
- gateway: SSE/WebSocket 网关接入
- webhook: Webhook 事件处理
- outbound: 外发消息管理
- pairing: 用户配对与绑定
- security: 安全校验
- signature: 请求签名验证
- crypto: 加解密处理
- dedupe: 消息去重
- monitor: 渠道状态监控
- status: 会话状态管理
- session: 会话管理
- business: 业务逻辑处理
- types: 类型定义
2026-05-21 11:05:40 +08:00

84 lines
2.7 KiB
Python

import asyncio
import logging
import httpx
from yuxi.channel.extensions.jd.signature import build_signed_params
from yuxi.channel.extensions.jd.types import JOS_API_GATEWAY, JOS_MAX_RETRIES, JOS_RETRY_BASE_DELAY, OutboundResult
logger = logging.getLogger(__name__)
class JOSClient:
def __init__(self, app_key: str, app_secret: str, shop_id: str):
self._app_key = app_key
self._app_secret = app_secret
self._shop_id = shop_id
self._http = httpx.AsyncClient(timeout=15.0)
async def close(self):
await self._http.aclose()
async def call(self, method: str, biz_params: dict, access_token: str) -> dict:
params = build_signed_params(
method=method,
biz_params=biz_params,
app_key=self._app_key,
app_secret=self._app_secret,
access_token=access_token,
)
for attempt in range(JOS_MAX_RETRIES):
try:
resp = await self._http.post(JOS_API_GATEWAY, data=params)
resp.raise_for_status()
data = resp.json()
error_response = data.get("error_response")
if error_response:
code = error_response.get("code")
msg = error_response.get("zh_desc", "Unknown error")
logger.error("JOS API error [%s]: %s", code, msg)
raise ValueError(f"JOS API error {code}: {msg}")
return data
except (httpx.TimeoutException, httpx.NetworkError) as e:
logger.warning("JOS API retry %d/%d: %s", attempt + 1, JOS_MAX_RETRIES, e)
if attempt == JOS_MAX_RETRIES - 1:
raise
await asyncio.sleep(JOS_RETRY_BASE_DELAY * (2**attempt))
raise RuntimeError("JOS API max retries exceeded")
async def send_msg(
self,
chat_id: str,
to_id: str,
msg_type: int,
content: str,
access_token: str,
) -> OutboundResult:
biz_params = {
"chat_id": chat_id,
"from_id": self._shop_id,
"to_id": to_id,
"msg_type": msg_type,
"content": content,
}
try:
data = await self.call("jingdong.im.sendMsg", biz_params, access_token)
result = data.get("jingdong_im_sendMsg_responce", {}).get("result", {})
return OutboundResult(
message_id=result.get("msg_id", ""),
success=True,
)
except Exception as e:
return OutboundResult(
message_id="",
success=False,
error_code="JOS_ERROR",
error_msg=str(e),
)