ForcePilot/backend/package/yuxi/channel/extensions/lazada/config.py
Kris d523babb91 feat(channel): 添加 Lazada 渠道扩展
新增 Lazada 渠道扩展,支持在 Yuxi 平台中集成 Lazada 电商客服渠道。

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

163 lines
5.8 KiB
Python

from __future__ import annotations
import os
from yuxi.channel.extensions.lazada.types import LazadaAccount
LAZADA_SITE_ENDPOINTS = {
"SG": "https://api.lazada.sg/rest",
"MY": "https://api.lazada.com.my/rest",
"TH": "https://api.lazada.co.th/rest",
"VN": "https://api.lazada.vn/rest",
"ID": "https://api.lazada.co.id/rest",
"PH": "https://api.lazada.com.ph/rest",
}
LAZADA_AUTH_ENDPOINT = "https://auth.lazada.com/rest"
COMMON_CONFIG_SCHEMA = {
"$schema": "https://json-schema.org/draft-07/schema#",
"type": "object",
"title": "Lazada 渠道配置",
"properties": {
"appKey": {
"type": "string",
"title": "App Key",
"description": "Lazada 开放平台应用 Key (ISV Console 获取)",
},
"appSecret": {
"type": "string",
"title": "App Secret",
"x-ui-password": True,
"description": "Lazada 开放平台应用密钥",
},
"accessToken": {
"type": "string",
"title": "Access Token",
"x-ui-password": True,
"description": "OAuth 2.0 授权获得的 access_token",
},
"refreshToken": {
"type": "string",
"title": "Refresh Token",
"x-ui-password": True,
"description": "OAuth 2.0 授权获得的 refresh_token",
},
"sellerId": {
"type": "string",
"title": "Seller ID",
"description": "Lazada 卖家 ID",
},
"siteCode": {
"type": "string",
"title": "站点代码",
"enum": ["SG", "MY", "TH", "VN", "ID", "PH"],
"default": "SG",
"description": "六国站点代码",
},
"webhookPath": {
"type": "string",
"title": "Webhook 回调路径",
"default": "/webhook/lazada/callback",
},
"webhookSecret": {
"type": "string",
"title": "Webhook 签名密钥",
"x-ui-password": True,
"description": "用于验证 LPM 推送签名",
},
"dmPolicy": {
"type": "string",
"title": "DM 安全策略",
"enum": ["open", "pairing", "allowlist", "disabled"],
"default": "open",
},
"maxTextLength": {
"type": "integer",
"title": "单条消息最大字符数",
"default": 2000,
"maximum": 2000,
},
"httpTimeoutMs": {
"type": "integer",
"title": "HTTP 请求超时(毫秒)",
"default": 15000,
},
"pollingIntervalMs": {
"type": "integer",
"title": "长轮询间隔(毫秒)",
"default": 5000,
"minimum": 1000,
},
"enabled": {
"type": "boolean",
"title": "启用",
"default": False,
"description": "⚠️ 启用前请确保已完成 IM API 权限申请",
},
},
"required": ["appKey", "appSecret"],
}
class LazadaConfig:
ENV_MAP = {
"app_key": "LAZADA_APP_KEY",
"app_secret": "LAZADA_APP_SECRET",
"seller_id": "LAZADA_SELLER_ID",
"site_code": "LAZADA_SITE_CODE",
"dm_policy": "LAZADA_DM_POLICY",
}
def list_account_ids(self, config: dict | None = None) -> list[str]:
channels = (config or {}).get("channels", {})
lazada_config = channels.get("lazada", {})
accounts = lazada_config.get("accounts", {})
if accounts:
return list(accounts.keys())
if self._env_or_config("app_key"):
return ["default"]
return []
async def resolve_account(self, account_id: str = "default", config: dict | None = None) -> LazadaAccount:
channels = (config or {}).get("channels", {})
lazada_config = channels.get("lazada", {})
accounts = lazada_config.get("accounts", {})
account_data = accounts.get(account_id, {})
return LazadaAccount(
account_id=account_id,
app_key=account_data.get("app_key") or self._env_or_config("app_key") or "",
app_secret=account_data.get("app_secret") or self._env_or_config("app_secret") or "",
access_token=account_data.get("access_token", ""),
refresh_token=account_data.get("refresh_token", ""),
token_expires_at=account_data.get("token_expires_at", 0),
refresh_expires_at=account_data.get("refresh_expires_at", 0),
seller_id=account_data.get("seller_id") or self._env_or_config("seller_id") or "",
site_code=account_data.get("site_code") or self._env_or_config("site_code") or "SG",
webhook_path=account_data.get("webhook_path", "/webhook/lazada/callback"),
webhook_secret=account_data.get("webhook_secret", ""),
dm_policy=account_data.get("dm_policy") or self._env_or_config("dm_policy") or "open",
allow_from=account_data.get("allow_from", []),
max_text_length=account_data.get("max_text_length", 2000),
http_timeout_ms=account_data.get("http_timeout_ms", 15000),
polling_interval_ms=account_data.get("polling_interval_ms", 5000),
enabled=account_data.get("enabled", False),
)
def is_configured(self, account: dict | None = None) -> bool:
if account:
return bool(account.get("app_key") and account.get("app_secret"))
return bool(self._env_or_config("app_key") and self._env_or_config("app_secret"))
def _env_or_config(self, key: str, config: dict | None = None) -> str | None:
env_key = self.ENV_MAP.get(key, "")
if env_key:
val = os.getenv(env_key)
if val:
return val
return None
def config_schema(self) -> dict:
return COMMON_CONFIG_SCHEMA