feat: 重构订单和物流信息展示格式
主要改动: - 订单列表:使用 order_list 格式,展示 5 个订单(全部状态) - 订单详情:使用 order_detail 格式,优化价格和时间显示 - 物流信息:使用 logistics 格式,根据 track id 动态生成步骤 - 商品图片:从 orderProduct.imageUrl 字段获取 - 时间格式:统一为 YYYY-MM-DD HH:MM:SS - 多语言支持:amountLabel、orderTime 支持中英文 - 配置管理:新增 FRONTEND_URL 环境变量 - API 集成:改进 Mall API tracks 数据解析 - 认证优化:account_id 从 webhook 动态获取 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -420,7 +420,7 @@ async def get_logistics(
|
||||
)
|
||||
|
||||
print(f"[get_logistics] SUCCESS: result_keys={list(result.keys()) if isinstance(result, dict) else type(result).__name__}")
|
||||
print(f"[get_logistics] Sample data: {str(result)[:500]}")
|
||||
print(f"[get_logistics] Sample data: {str(result)[:1000]}")
|
||||
|
||||
# Mall API 返回结构:{ "total": 1, "data": [{ "trackingCode": "...", "carrier": "...", ... }] }
|
||||
logistics_list = result.get("data", [])
|
||||
@@ -430,7 +430,21 @@ async def get_logistics(
|
||||
tracking_number = first_logistics.get("trackingCode", "")
|
||||
carrier = first_logistics.get("carrier", "未知")
|
||||
|
||||
print(f"[get_logistics] Extracted: tracking_number={tracking_number}, carrier={carrier}")
|
||||
# 提取 tracks 数组(物流轨迹)
|
||||
tracks = first_logistics.get("tracks", [])
|
||||
timeline = []
|
||||
|
||||
if tracks and isinstance(tracks, list):
|
||||
for track in tracks:
|
||||
if isinstance(track, dict):
|
||||
timeline.append({
|
||||
"id": track.get("id", ""),
|
||||
"remark": track.get("remark", ""),
|
||||
"time": track.get("time", track.get("date", "")),
|
||||
"location": track.get("location", "")
|
||||
})
|
||||
|
||||
print(f"[get_logistics] Extracted: tracking_number={tracking_number}, carrier={carrier}, tracks_count={len(timeline)}")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
@@ -439,7 +453,7 @@ async def get_logistics(
|
||||
"courier": carrier,
|
||||
"tracking_url": first_logistics.get("trackingUrl", ""),
|
||||
"status": first_logistics.get("status", ""),
|
||||
"timeline": [] # 如果 API 返回轨迹信息,可以在这里添加
|
||||
"timeline": timeline
|
||||
}
|
||||
else:
|
||||
print(f"[get_logistics] WARNING: No logistics data found in response")
|
||||
@@ -466,6 +480,159 @@ async def get_logistics(
|
||||
await client.close()
|
||||
|
||||
|
||||
@register_tool("get_mall_order_list")
|
||||
@mcp.tool()
|
||||
async def get_mall_order_list(
|
||||
user_token: str = None,
|
||||
user_id: str = None,
|
||||
account_id: str = None,
|
||||
page: int = 1,
|
||||
limit: int = 5,
|
||||
customer_id: int = 0,
|
||||
order_types: Optional[List[int]] = None,
|
||||
shipping_status: int = 10000,
|
||||
date_added: Optional[str] = None,
|
||||
date_end: Optional[str] = None,
|
||||
no: Optional[str] = None,
|
||||
status: Optional[int] = None,
|
||||
is_drop_shopping: int = 0
|
||||
) -> dict:
|
||||
"""Query order list from Mall API with filters
|
||||
|
||||
从 Mall API 查询订单列表,支持多种筛选条件
|
||||
|
||||
规则:
|
||||
- 默认返回最近 5 个订单
|
||||
- 包含全部状态的订单(不限制状态)
|
||||
|
||||
Args:
|
||||
user_token: 用户 JWT token(必需,用于身份验证)
|
||||
user_id: 用户 ID(自动注入,此工具不使用)
|
||||
account_id: 账户 ID(自动注入,此工具不使用)
|
||||
page: 页码 (default: 1)
|
||||
limit: 每页数量 (default: 5, max 50)
|
||||
customer_id: 客户ID (default: 0, 0表示所有客户)
|
||||
order_types: 订单类型数组,如 [1, 2] (default: None)
|
||||
shipping_status: 物流状态 (default: 10000, 10000表示全部状态)
|
||||
date_added: 开始日期,格式 YYYY-MM-DD (default: None)
|
||||
date_end: 结束日期,格式 YYYY-MM-DD (default: None)
|
||||
no: 订单号筛选 (default: None)
|
||||
status: 订单状态筛选 (default: None, None表示全部状态)
|
||||
is_drop_shopping: 是否代发货 (default: 0)
|
||||
|
||||
Returns:
|
||||
订单列表和分页信息
|
||||
{
|
||||
"success": true,
|
||||
"orders": [...], # 订单列表
|
||||
"total": 100, # 总订单数
|
||||
"page": 1, # 当前页
|
||||
"limit": 5, # 每页数量
|
||||
"total_pages": 20 # 总页数
|
||||
}
|
||||
|
||||
Example:
|
||||
用户问: "我的订单有哪些?"
|
||||
Agent 调用: get_mall_order_list(page=1, limit=5)
|
||||
"""
|
||||
import logging
|
||||
import json
|
||||
import base64
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
logger.info(
|
||||
f"get_mall_order_list called: page={page}, limit={limit}, "
|
||||
f"has_user_token={bool(user_token)}, customer_id={customer_id}"
|
||||
)
|
||||
|
||||
# 必须提供 user_token
|
||||
if not user_token:
|
||||
logger.error("No user_token provided, user must be logged in")
|
||||
return {
|
||||
"success": False,
|
||||
"error": "用户未登录,请先登录账户以查询订单列表",
|
||||
"require_login": True,
|
||||
"orders": []
|
||||
}
|
||||
|
||||
# 从 JWT token 中提取 userId 作为 customer_id
|
||||
if customer_id == 0:
|
||||
try:
|
||||
# JWT token 格式: header.payload.signature
|
||||
parts = user_token.split('.')
|
||||
if len(parts) >= 2:
|
||||
# 解码 payload
|
||||
payload = parts[1]
|
||||
# 添加 padding 如果需要
|
||||
payload += '=' * (4 - len(payload) % 4)
|
||||
decoded = base64.b64decode(payload)
|
||||
payload_data = json.loads(decoded)
|
||||
|
||||
# 尝试从不同字段获取 userId
|
||||
customer_id = payload_data.get('userId') or payload_data.get('uid') or payload_data.get('sub') or 0
|
||||
|
||||
logger.info(
|
||||
f"Extracted customer_id from token: customer_id={customer_id}, "
|
||||
f"token_payload_keys={list(payload_data.keys())}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to extract customer_id from token: {e}")
|
||||
customer_id = 0
|
||||
|
||||
try:
|
||||
client = MallClient(
|
||||
api_url=settings.mall_api_url,
|
||||
api_token=user_token,
|
||||
tenant_id=settings.mall_tenant_id,
|
||||
currency_code=settings.mall_currency_code,
|
||||
language_id=settings.mall_language_id,
|
||||
source=settings.mall_source
|
||||
)
|
||||
|
||||
result = await client.get_order_list(
|
||||
page=page,
|
||||
limit=limit,
|
||||
customer_id=customer_id,
|
||||
order_types=order_types,
|
||||
shipping_status=shipping_status,
|
||||
date_added=date_added,
|
||||
date_end=date_end,
|
||||
no=no,
|
||||
status=status,
|
||||
is_drop_shopping=is_drop_shopping
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Mall API request successful: page={page}, "
|
||||
f"result_keys={list(result.keys()) if isinstance(result, dict) else None}"
|
||||
)
|
||||
|
||||
# Mall API 返回结构: {"data": [...], "total": 100}
|
||||
orders = result.get("data", [])
|
||||
total = result.get("total", 0)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"orders": orders,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"limit": limit,
|
||||
"total_pages": (total + limit - 1) // limit if limit > 0 else 0
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Mall API request failed: page={page}, error={str(e)}"
|
||||
)
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"orders": []
|
||||
}
|
||||
finally:
|
||||
if 'client' in dir() and client:
|
||||
await client.close()
|
||||
|
||||
|
||||
# Health check endpoint
|
||||
@register_tool("health_check")
|
||||
@mcp.tool()
|
||||
|
||||
@@ -47,25 +47,36 @@ class MallClient:
|
||||
self.source = source or settings.mall_source
|
||||
self._client: Optional[httpx.AsyncClient] = None
|
||||
|
||||
async def _get_client(self) -> httpx.AsyncClient:
|
||||
"""Get or create HTTP client with default headers"""
|
||||
async def _get_client(self, extra_headers: Optional[dict[str, str]] = None) -> httpx.AsyncClient:
|
||||
"""Get or create HTTP client with default headers
|
||||
|
||||
Args:
|
||||
extra_headers: 额外的请求头,某些接口需要特殊的 header(如 Authorization2)
|
||||
"""
|
||||
if self._client is None:
|
||||
default_headers = {
|
||||
"Authorization": f"Bearer {self.api_token}",
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json, text/plain, */*",
|
||||
"Device-Type": "pc",
|
||||
"tenant-Id": self.tenant_id,
|
||||
"currency-code": self.currency_code,
|
||||
"language-id": self.language_id,
|
||||
"language_id": self.language_id, # 某些接口使用下划线
|
||||
"source": self.source,
|
||||
"Origin": "https://www.qa1.gaia888.com",
|
||||
"Referer": "https://www.qa1.gaia888.com/",
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36",
|
||||
"DNT": "1",
|
||||
}
|
||||
|
||||
# 合并额外的 headers(用于 Authorization2 等)
|
||||
if extra_headers:
|
||||
default_headers.update(extra_headers)
|
||||
|
||||
self._client = httpx.AsyncClient(
|
||||
base_url=self.api_url,
|
||||
headers={
|
||||
"Authorization": f"Bearer {self.api_token}",
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json, text/plain, */*",
|
||||
"Device-Type": "pc",
|
||||
"tenant-Id": self.tenant_id,
|
||||
"currency-code": self.currency_code,
|
||||
"language-id": self.language_id,
|
||||
"source": self.source,
|
||||
"Origin": "https://www.qa1.gaia888.com",
|
||||
"Referer": "https://www.qa1.gaia888.com/",
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36",
|
||||
"DNT": "1",
|
||||
},
|
||||
headers=default_headers,
|
||||
timeout=30.0
|
||||
)
|
||||
return self._client
|
||||
@@ -82,7 +93,8 @@ class MallClient:
|
||||
endpoint: str,
|
||||
params: Optional[dict[str, Any]] = None,
|
||||
json: Optional[dict[str, Any]] = None,
|
||||
headers: Optional[dict[str, str]] = None
|
||||
headers: Optional[dict[str, str]] = None,
|
||||
use_authorization2: bool = False
|
||||
) -> dict[str, Any]:
|
||||
"""Make API request and handle response
|
||||
|
||||
@@ -92,11 +104,20 @@ class MallClient:
|
||||
params: Query parameters
|
||||
json: JSON body
|
||||
headers: Additional headers
|
||||
use_authorization2: 是否使用 Authorization2 header 而不是 Authorization
|
||||
|
||||
Returns:
|
||||
Response data
|
||||
"""
|
||||
client = await self._get_client()
|
||||
# 如果需要 Authorization2,关闭现有 client 并用新的 headers 创建
|
||||
if use_authorization2:
|
||||
if self._client:
|
||||
await self._client.aclose()
|
||||
self._client = None
|
||||
extra_headers = {"Authorization2": f"Bearer {self.api_token}"}
|
||||
client = await self._get_client(extra_headers)
|
||||
else:
|
||||
client = await self._get_client()
|
||||
|
||||
# Merge additional headers
|
||||
request_headers = {}
|
||||
@@ -126,19 +147,21 @@ class MallClient:
|
||||
self,
|
||||
endpoint: str,
|
||||
params: Optional[dict[str, Any]] = None,
|
||||
use_authorization2: bool = False,
|
||||
**kwargs: Any
|
||||
) -> dict[str, Any]:
|
||||
"""GET request"""
|
||||
return await self.request("GET", endpoint, params=params, **kwargs)
|
||||
return await self.request("GET", endpoint, params=params, use_authorization2=use_authorization2, **kwargs)
|
||||
|
||||
async def post(
|
||||
self,
|
||||
endpoint: str,
|
||||
json: Optional[dict[str, Any]] = None,
|
||||
use_authorization2: bool = False,
|
||||
**kwargs: Any
|
||||
) -> dict[str, Any]:
|
||||
"""POST request"""
|
||||
return await self.request("POST", endpoint, json=json, **kwargs)
|
||||
return await self.request("POST", endpoint, json=json, use_authorization2=use_authorization2, **kwargs)
|
||||
|
||||
# ============ Order APIs ============
|
||||
|
||||
@@ -171,6 +194,77 @@ class MallClient:
|
||||
except Exception as e:
|
||||
raise Exception(f"查询订单失败 (Query order failed): {str(e)}")
|
||||
|
||||
async def get_order_list(
|
||||
self,
|
||||
page: int = 1,
|
||||
limit: int = 10,
|
||||
customer_id: int = 0,
|
||||
order_types: Optional[list[int]] = None,
|
||||
shipping_status: int = 10000,
|
||||
date_added: Optional[str] = None,
|
||||
date_end: Optional[str] = None,
|
||||
no: Optional[str] = None,
|
||||
status: Optional[int] = None,
|
||||
is_drop_shopping: int = 0
|
||||
) -> dict[str, Any]:
|
||||
"""Query order list with filters
|
||||
|
||||
查询订单列表,支持多种筛选条件
|
||||
|
||||
Args:
|
||||
page: 页码 (default: 1)
|
||||
limit: 每页数量 (default: 10)
|
||||
customer_id: 客户ID (default: 0)
|
||||
order_types: 订单类型数组,如 [1, 2] (default: None)
|
||||
shipping_status: 物流状态 (default: 10000)
|
||||
date_added: 开始日期,格式 YYYY-MM-DD (default: None)
|
||||
date_end: 结束日期,格式 YYYY-MM-DD (default: None)
|
||||
no: 订单号 (default: None)
|
||||
status: 订单状态 (default: None)
|
||||
is_drop_shopping: 是否代发货 (default: 0)
|
||||
|
||||
Returns:
|
||||
订单列表和分页信息
|
||||
Order list and pagination info
|
||||
|
||||
Example:
|
||||
>>> client = MallClient()
|
||||
>>> result = await client.get_order_list(page=1, limit=10)
|
||||
>>> print(result["data"]) # 订单列表
|
||||
>>> print(result["total"]) # 总数
|
||||
"""
|
||||
try:
|
||||
params = {
|
||||
"page": page,
|
||||
"limit": limit,
|
||||
"customerId": customer_id,
|
||||
"shippingStatus": shipping_status,
|
||||
"isDropShopping": is_drop_shopping
|
||||
}
|
||||
|
||||
# 可选参数
|
||||
if order_types:
|
||||
# orderTypes 是数组,需要特殊处理
|
||||
# API 格式: orderTypes[]=1&orderTypes[]=2
|
||||
params["orderTypes"] = order_types
|
||||
if date_added:
|
||||
params["dateAdded"] = date_added
|
||||
if date_end:
|
||||
params["dateEnd"] = date_end
|
||||
if no:
|
||||
params["no"] = no
|
||||
if status is not None:
|
||||
params["status"] = status
|
||||
|
||||
result = await self.get(
|
||||
"/mall/api/order/list",
|
||||
params=params,
|
||||
use_authorization2=True # 订单列表接口需要 Authorization2
|
||||
)
|
||||
return result
|
||||
except Exception as e:
|
||||
raise Exception(f"查询订单列表失败 (Query order list failed): {str(e)}")
|
||||
|
||||
|
||||
# Global Mall client instance
|
||||
mall_client: Optional[MallClient] = None
|
||||
|
||||
Reference in New Issue
Block a user