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:
wangliang
2026-01-23 18:49:40 +08:00
parent e8e89601a5
commit 0b5d0a8086
11 changed files with 1493 additions and 394 deletions

View File

@@ -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()