ForcePilot/backend/package/yuxi/channel/extensions/pinduoduo/webhook.py
Kris 1476e82ef0 feat(channel): 添加拼多多渠道扩展
新增拼多多(Pinduoduo)渠道扩展,支持在 Yuxi 平台中集成拼多多电商客服渠道。

包含以下功能模块:
- client: 拼多多 API 客户端封装
- config: 渠道配置管理
- gateway: SSE/WebSocket 网关接入
- webhook: Webhook 事件处理
- outbound: 外发消息管理
- pairing: 用户配对与绑定
- security: 安全校验
- signature: 请求签名验证
- token: Token 管理
- dedupe: 消息去重
- monitor: 渠道状态监控
- status: 会话状态管理
- tools: Agent 工具集成
- window: 窗口管理
- types: 类型定义
2026-05-21 11:33:31 +08:00

94 lines
3.1 KiB
Python

from __future__ import annotations
import logging
from fastapi import APIRouter, HTTPException, Request
from yuxi.channel.extensions.pinduoduo.dedupe import PddMessageDeduplicator
from yuxi.channel.extensions.pinduoduo.monitor import PinduoduoMonitor
from yuxi.channel.extensions.pinduoduo.signature import verify_push_sign
from yuxi.channel.extensions.pinduoduo.types import PinduoduoAccount
from yuxi.channel.extensions.pinduoduo.window import PinduoduoWindowTracker
logger = logging.getLogger(__name__)
_handlers: dict[str, dict] = {}
def register_handler(
account_id: str,
account: PinduoduoAccount,
monitor: PinduoduoMonitor,
deduplicator: PddMessageDeduplicator,
put_fn,
window_tracker: PinduoduoWindowTracker | None = None,
) -> None:
_handlers[account_id] = {
"account": account,
"monitor": monitor,
"deduplicator": deduplicator,
"put_fn": put_fn,
"window_tracker": window_tracker,
}
logger.info("Webhook handler registered for account=%s", account_id)
def unregister_handler(account_id: str) -> None:
_handlers.pop(account_id, None)
logger.info("Webhook handler unregistered for account=%s", account_id)
def create_webhook_router() -> APIRouter:
router = APIRouter(prefix="/webhook/pinduoduo", tags=["pinduoduo"])
@router.post("/callback")
async def handle_push(request: Request):
try:
body = await request.json()
except Exception:
raise HTTPException(status_code=400, detail="Invalid JSON")
sign = request.headers.get("X-Pdd-Sign", "")
mall_id = str(body.get("mall_id", ""))
handler = None
for h in _handlers.values():
if h["account"].mall_id == mall_id:
handler = h
break
if handler is None:
logger.warning("No handler found for mall_id=%s", mall_id)
return {"status": "no_handler"}
if handler["account"].client_secret:
body_str = (await request.body()).decode("utf-8")
if not verify_push_sign(body_str, handler["account"].client_secret, sign, handler["account"].sign_method):
logger.warning("Push signature verification failed for mall_id=%s", mall_id)
raise HTTPException(status_code=403, detail="Invalid signature")
monitor: PinduoduoMonitor = handler["monitor"]
messages = monitor.parse_push_event(body)
for msg in messages:
if handler["deduplicator"].is_duplicate(msg.msg_id):
continue
if handler.get("window_tracker") and msg.buyer_id:
handler["window_tracker"].record(msg.buyer_id)
unified = monitor.to_unified_message(msg)
await handler["put_fn"](unified)
return {"status": "ok", "message_count": len(messages)}
@router.post("/callback/verify")
async def verify_push_url(request: Request):
params = request.query_params
echostr = params.get("echostr", "")
if echostr:
return {"echostr": echostr}
return {"status": "ok"}
return router