mirror of
https://github.com/GuDong2003/xianyu-auto-reply-fix.git
synced 2026-08-28 17:40:45 +08:00
feat: 添加手动发货和刷新订单状态功能
- 订单管理新增"手动发货"和"刷新状态"按钮 - 手动发货使用现有WebSocket连接发送消息(与自动发货逻辑一致) - 支持从闲鱼平台获取真实订单状态 - CookieManager新增live_instances存储XianyuLive实例 - 订单详情获取支持force_refresh强制刷新参数 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
+69
-22
@@ -5127,15 +5127,16 @@ class XianyuLive:
|
||||
logger.error(f"【{self.cookie_id}】免拼发货模块调用失败: {self._safe_str(e)}")
|
||||
return {"error": f"免拼发货模块调用失败: {self._safe_str(e)}", "order_id": order_id}
|
||||
|
||||
async def fetch_order_detail_info(self, order_id: str, item_id: str = None, buyer_id: str = None, debug_headless: bool = None, sid: str = None):
|
||||
async def fetch_order_detail_info(self, order_id: str, item_id: str = None, buyer_id: str = None, debug_headless: bool = None, sid: str = None, force_refresh: bool = False):
|
||||
"""获取订单详情信息(使用独立的锁机制,不受延迟锁影响)
|
||||
|
||||
|
||||
Args:
|
||||
order_id: 订单ID
|
||||
item_id: 商品ID
|
||||
buyer_id: 买家ID
|
||||
debug_headless: 是否使用有头模式调试
|
||||
sid: 会话ID(如 56226853668@goofish),用于简化消息匹配订单
|
||||
force_refresh: 是否强制刷新(跳过缓存直接从闲鱼获取)
|
||||
"""
|
||||
# 使用独立的订单详情锁,不与自动发货锁冲突
|
||||
order_detail_lock = self._order_detail_locks[order_id]
|
||||
@@ -5163,7 +5164,7 @@ class XianyuLive:
|
||||
logger.info(f"【{self.cookie_id}】🖥️ 启用有头模式进行调试")
|
||||
|
||||
# 异步获取订单详情(使用当前账号的cookie)
|
||||
result = await fetch_order_detail_simple(order_id, cookie_string, headless=headless_mode)
|
||||
result = await fetch_order_detail_simple(order_id, cookie_string, headless=headless_mode, force_refresh=force_refresh)
|
||||
|
||||
if result:
|
||||
logger.info(f"【{self.cookie_id}】订单详情获取成功: {order_id}")
|
||||
@@ -5176,6 +5177,10 @@ class XianyuLive:
|
||||
spec_value_2 = result.get('spec_value_2', '')
|
||||
quantity = result.get('quantity', '')
|
||||
amount = result.get('amount', '')
|
||||
# 获取订单状态(从闲鱼页面解析)
|
||||
order_status = result.get('order_status', '')
|
||||
if order_status:
|
||||
logger.info(f"【{self.cookie_id}】📊 订单状态: {order_status}")
|
||||
|
||||
if spec_name and spec_value:
|
||||
logger.info(f"【{self.cookie_id}】📋 规格名称: {spec_name}")
|
||||
@@ -5209,7 +5214,8 @@ class XianyuLive:
|
||||
spec_value_2=spec_value_2,
|
||||
quantity=quantity,
|
||||
amount=amount,
|
||||
cookie_id=self.cookie_id
|
||||
cookie_id=self.cookie_id,
|
||||
order_status=order_status if order_status else None # 传递从闲鱼获取的订单状态
|
||||
)
|
||||
|
||||
# 使用订单状态处理器设置状态
|
||||
@@ -7620,6 +7626,7 @@ class XianyuLive:
|
||||
logger.warning(f"【{self.cookie_id}】强制关闭时出现异常(已忽略): {e}")
|
||||
|
||||
async def send_msg_once(self, toid, item_id, text):
|
||||
"""单次发送消息(创建新的WebSocket连接)"""
|
||||
headers = {
|
||||
"Cookie": self.cookies_str,
|
||||
"Host": "wss-goofish.dingtalk.com",
|
||||
@@ -7631,27 +7638,46 @@ class XianyuLive:
|
||||
"Accept-Encoding": "gzip, deflate, br, zstd",
|
||||
"Accept-Language": "zh-CN,zh;q=0.9",
|
||||
}
|
||||
|
||||
logger.info(f"【{self.cookie_id}】开始单次发送消息: toid={toid}, item_id={item_id}")
|
||||
|
||||
# 兼容不同版本的websockets库
|
||||
try:
|
||||
async with websockets.connect(
|
||||
self.base_url,
|
||||
extra_headers=headers
|
||||
extra_headers=headers,
|
||||
close_timeout=5 # 添加关闭超时
|
||||
) as websocket:
|
||||
await self._handle_websocket_connection(websocket, toid, item_id, text)
|
||||
result = await self._handle_websocket_connection(websocket, toid, item_id, text)
|
||||
if result:
|
||||
logger.info(f"【{self.cookie_id}】单次发送消息成功")
|
||||
else:
|
||||
raise Exception("消息发送失败")
|
||||
except TypeError as e:
|
||||
# 安全地检查异常信息
|
||||
error_msg = self._safe_str(e)
|
||||
|
||||
if "extra_headers" in error_msg:
|
||||
logger.warning("websockets库不支持extra_headers参数,使用兼容模式")
|
||||
# 使用兼容模式,通过subprotocols传递部分头信息
|
||||
# 使用兼容模式
|
||||
async with websockets.connect(
|
||||
self.base_url,
|
||||
additional_headers=headers
|
||||
additional_headers=headers,
|
||||
close_timeout=5
|
||||
) as websocket:
|
||||
await self._handle_websocket_connection(websocket, toid, item_id, text)
|
||||
result = await self._handle_websocket_connection(websocket, toid, item_id, text)
|
||||
if result:
|
||||
logger.info(f"【{self.cookie_id}】单次发送消息成功(兼容模式)")
|
||||
else:
|
||||
raise Exception("消息发送失败")
|
||||
else:
|
||||
raise
|
||||
except websockets.exceptions.ConnectionClosedError as e:
|
||||
logger.warning(f"【{self.cookie_id}】WebSocket连接关闭: {self._safe_str(e)}")
|
||||
# 连接关闭但消息可能已发送,不抛出异常
|
||||
except Exception as e:
|
||||
logger.error(f"【{self.cookie_id}】单次发送消息异常: {self._safe_str(e)}")
|
||||
raise
|
||||
|
||||
async def _create_websocket_connection(self, headers):
|
||||
"""创建WebSocket连接,兼容不同版本的websockets库,支持代理配置"""
|
||||
@@ -7773,19 +7799,40 @@ class XianyuLive:
|
||||
|
||||
async def _handle_websocket_connection(self, websocket, toid, item_id, text):
|
||||
"""处理WebSocket连接的具体逻辑"""
|
||||
await self.init(websocket)
|
||||
await self.create_chat(websocket, toid, item_id)
|
||||
async for message in websocket:
|
||||
try:
|
||||
logger.info(f"【{self.cookie_id}】message: {message}")
|
||||
message = json.loads(message)
|
||||
cid = message["body"]["singleChatConversation"]["cid"]
|
||||
cid = cid.split('@')[0]
|
||||
await self.send_msg(websocket, cid, toid, text)
|
||||
logger.info(f'【{self.cookie_id}】send message')
|
||||
return
|
||||
except Exception as e:
|
||||
pass
|
||||
try:
|
||||
await self.init(websocket)
|
||||
await self.create_chat(websocket, toid, item_id)
|
||||
|
||||
# 添加超时处理,最多等待30秒
|
||||
timeout = 30
|
||||
start_time = time.time()
|
||||
|
||||
async for message in websocket:
|
||||
try:
|
||||
# 检查是否超时
|
||||
if time.time() - start_time > timeout:
|
||||
logger.warning(f"【{self.cookie_id}】WebSocket消息等待超时")
|
||||
break
|
||||
|
||||
logger.info(f"【{self.cookie_id}】message: {message}")
|
||||
message = json.loads(message)
|
||||
cid = message["body"]["singleChatConversation"]["cid"]
|
||||
cid = cid.split('@')[0]
|
||||
await self.send_msg(websocket, cid, toid, text)
|
||||
logger.info(f'【{self.cookie_id}】send message success')
|
||||
return True
|
||||
except KeyError:
|
||||
# 消息格式不符合预期,继续等待
|
||||
continue
|
||||
except Exception as e:
|
||||
logger.warning(f"【{self.cookie_id}】处理消息异常: {self._safe_str(e)}")
|
||||
continue
|
||||
|
||||
logger.warning(f"【{self.cookie_id}】WebSocket连接关闭,未能发送消息")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"【{self.cookie_id}】WebSocket连接处理异常: {self._safe_str(e)}")
|
||||
return False
|
||||
|
||||
def is_chat_message(self, message):
|
||||
"""判断是否为用户聊天消息"""
|
||||
|
||||
+12
-3
@@ -18,6 +18,7 @@ class CookieManager:
|
||||
self.cookie_status: Dict[str, bool] = {} # 账号启用状态
|
||||
self.auto_confirm_settings: Dict[str, bool] = {} # 自动确认发货设置
|
||||
self._task_locks: Dict[str, asyncio.Lock] = {} # 每个cookie_id的任务锁,防止重复创建
|
||||
self.live_instances: Dict[str, Any] = {} # 存储 XianyuLive 实例,供外部调用
|
||||
self._load_from_db()
|
||||
|
||||
def _load_from_db(self):
|
||||
@@ -69,17 +70,19 @@ class CookieManager:
|
||||
logger.info(f"【{cookie_id}】开始创建XianyuLive实例...")
|
||||
logger.info(f"【{cookie_id}】Cookie值长度: {len(cookie_value)}")
|
||||
live = XianyuLive(cookie_value, cookie_id=cookie_id, user_id=user_id)
|
||||
# 保存实例供外部调用
|
||||
self.live_instances[cookie_id] = live
|
||||
logger.info(f"【{cookie_id}】XianyuLive实例创建成功,开始调用main()...")
|
||||
|
||||
|
||||
# 强制刷新日志,确保日志被写入
|
||||
try:
|
||||
import sys
|
||||
sys.stdout.flush()
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
await live.main()
|
||||
|
||||
|
||||
# main() 正常退出(不应该发生,因为main()内部有无限循环)
|
||||
logger.warning(f"【{cookie_id}】XianyuLive.main() 正常退出(这通常不应该发生)")
|
||||
except asyncio.CancelledError:
|
||||
@@ -101,6 +104,8 @@ class CookieManager:
|
||||
except:
|
||||
pass
|
||||
finally:
|
||||
# 清理实例引用
|
||||
self.live_instances.pop(cookie_id, None)
|
||||
logger.info(f"【{cookie_id}】_run_xianyu方法执行结束")
|
||||
# 确保日志被刷新
|
||||
try:
|
||||
@@ -330,6 +335,10 @@ class CookieManager:
|
||||
return {cid: value for cid, value in self.cookies.items()
|
||||
if self.cookie_status.get(cid, True)}
|
||||
|
||||
def get_xianyu_instance(self, cookie_id: str):
|
||||
"""获取指定Cookie的XianyuLive实例(如果正在运行)"""
|
||||
return self.live_instances.get(cookie_id)
|
||||
|
||||
def _start_cookie_task(self, cookie_id: str):
|
||||
"""启动指定Cookie的任务"""
|
||||
if cookie_id in self.tasks:
|
||||
|
||||
+164
@@ -6710,6 +6710,170 @@ def get_user_orders(current_user: Dict[str, Any] = Depends(get_current_user)):
|
||||
raise HTTPException(status_code=500, detail=f"查询订单失败: {str(e)}")
|
||||
|
||||
|
||||
@app.post('/api/orders/{order_id}/deliver')
|
||||
async def manual_deliver_order(order_id: str, current_user: Dict[str, Any] = Depends(get_current_user)):
|
||||
"""手动发货 - 根据订单信息匹配发货规则并发送卡券"""
|
||||
try:
|
||||
from db_manager import db_manager
|
||||
import cookie_manager
|
||||
|
||||
user_id = current_user['user_id']
|
||||
log_with_user('info', f"手动发货请求: 订单 {order_id}", current_user)
|
||||
|
||||
# 获取订单信息
|
||||
order = db_manager.get_order_by_id(order_id)
|
||||
if not order:
|
||||
return {"success": False, "delivered": False, "message": "订单不存在"}
|
||||
|
||||
# 验证订单属于当前用户
|
||||
cookie_id = order.get('cookie_id')
|
||||
if not cookie_id:
|
||||
return {"success": False, "delivered": False, "message": "订单缺少账号信息"}
|
||||
|
||||
cookie_info = db_manager.get_cookie_details(cookie_id)
|
||||
if not cookie_info or cookie_info.get('user_id') != user_id:
|
||||
return {"success": False, "delivered": False, "message": "无权操作此订单"}
|
||||
|
||||
# 获取 XianyuLive 实例
|
||||
xianyu_instance = cookie_manager.manager.get_xianyu_instance(cookie_id) if cookie_manager.manager else None
|
||||
if not xianyu_instance:
|
||||
return {"success": False, "delivered": False, "message": f"账号 {cookie_id} 未运行,请先启动账号"}
|
||||
|
||||
# 获取订单详情
|
||||
item_id = order.get('item_id')
|
||||
buyer_id = order.get('buyer_id')
|
||||
|
||||
if not item_id:
|
||||
return {"success": False, "delivered": False, "message": "订单缺少商品信息"}
|
||||
|
||||
if not buyer_id:
|
||||
return {"success": False, "delivered": False, "message": "订单缺少买家信息,无法发送消息"}
|
||||
|
||||
# 获取商品标题
|
||||
item_info = db_manager.get_item_info(cookie_id, item_id)
|
||||
item_title = item_info.get('item_title', '') if item_info else ''
|
||||
|
||||
# 调用自动发货逻辑获取发货内容
|
||||
delivery_content = await xianyu_instance._auto_delivery(
|
||||
item_id=item_id,
|
||||
item_title=item_title,
|
||||
order_id=order_id,
|
||||
send_user_id=buyer_id
|
||||
)
|
||||
|
||||
if delivery_content:
|
||||
# 发送发货内容给买家
|
||||
try:
|
||||
if delivery_content.startswith("__IMAGE_SEND__"):
|
||||
# 图片类型暂不支持手动发货
|
||||
log_with_user('warning', f"手动发货: 订单 {order_id} 为图片类型,暂不支持手动发送", current_user)
|
||||
return {"success": False, "delivered": False, "message": "图片类型卡券暂不支持手动发货"}
|
||||
else:
|
||||
# 使用现有的WebSocket连接发送消息(与自动发货逻辑一致)
|
||||
ws = getattr(xianyu_instance, 'ws', None)
|
||||
if ws:
|
||||
# 获取订单的sid(会话ID)
|
||||
sid = order.get('sid', '')
|
||||
if sid:
|
||||
# 提取cid部分(去掉@goofish后缀)
|
||||
cid = sid.replace('@goofish', '')
|
||||
log_with_user('info', f"手动发货: 使用现有WebSocket连接发送, cid={cid}, buyer_id={buyer_id}", current_user)
|
||||
await xianyu_instance.send_msg(ws, cid, buyer_id, delivery_content)
|
||||
else:
|
||||
# 如果没有sid,尝试用buyer_id作为cid
|
||||
log_with_user('warning', f"手动发货: 订单无sid,尝试使用buyer_id作为cid", current_user)
|
||||
await xianyu_instance.send_msg(ws, buyer_id, buyer_id, delivery_content)
|
||||
else:
|
||||
# 没有现有连接,回退到send_msg_once
|
||||
log_with_user('warning', f"手动发货: 无现有WebSocket连接,使用send_msg_once", current_user)
|
||||
await xianyu_instance.send_msg_once(buyer_id, item_id, delivery_content)
|
||||
log_with_user('info', f"手动发货消息已发送: 订单 {order_id}, 买家 {buyer_id}", current_user)
|
||||
|
||||
# 更新订单状态为已发货
|
||||
db_manager.insert_or_update_order(order_id=order_id, order_status='shipped')
|
||||
log_with_user('info', f"手动发货成功: 订单 {order_id}", current_user)
|
||||
return {"success": True, "delivered": True, "message": "发货成功,消息已发送给买家"}
|
||||
except Exception as send_error:
|
||||
log_with_user('error', f"手动发货发送消息失败: 订单 {order_id} - {str(send_error)}", current_user)
|
||||
return {"success": False, "delivered": False, "message": f"获取发货内容成功但发送消息失败: {str(send_error)}"}
|
||||
else:
|
||||
log_with_user('warning', f"手动发货失败: 订单 {order_id} - 未匹配到发货规则", current_user)
|
||||
return {"success": False, "delivered": False, "message": "未匹配到发货规则,请检查卡券和发货规则配置"}
|
||||
|
||||
except Exception as e:
|
||||
log_with_user('error', f"手动发货异常: 订单 {order_id} - {str(e)}", current_user)
|
||||
import traceback
|
||||
logger.error(f"手动发货异常堆栈: {traceback.format_exc()}")
|
||||
return {"success": False, "delivered": False, "message": f"发货失败: {str(e)}"}
|
||||
|
||||
|
||||
@app.post('/api/orders/{order_id}/refresh')
|
||||
async def refresh_order_status(order_id: str, current_user: Dict[str, Any] = Depends(get_current_user)):
|
||||
"""刷新订单状态 - 从闲鱼平台获取最新订单状态"""
|
||||
try:
|
||||
from db_manager import db_manager
|
||||
import cookie_manager
|
||||
|
||||
user_id = current_user['user_id']
|
||||
log_with_user('info', f"刷新订单状态请求: 订单 {order_id}", current_user)
|
||||
|
||||
# 获取订单信息
|
||||
order = db_manager.get_order_by_id(order_id)
|
||||
if not order:
|
||||
return {"success": False, "updated": False, "message": "订单不存在"}
|
||||
|
||||
old_status = order.get('order_status', '')
|
||||
|
||||
# 验证订单属于当前用户
|
||||
cookie_id = order.get('cookie_id')
|
||||
if not cookie_id:
|
||||
return {"success": False, "updated": False, "message": "订单缺少账号信息"}
|
||||
|
||||
cookie_info = db_manager.get_cookie_details(cookie_id)
|
||||
if not cookie_info or cookie_info.get('user_id') != user_id:
|
||||
return {"success": False, "updated": False, "message": "无权操作此订单"}
|
||||
|
||||
# 获取 XianyuLive 实例
|
||||
xianyu_instance = cookie_manager.manager.get_xianyu_instance(cookie_id) if cookie_manager.manager else None
|
||||
if not xianyu_instance:
|
||||
return {"success": False, "updated": False, "message": f"账号 {cookie_id} 未运行,请先启动账号"}
|
||||
|
||||
# 获取订单详情(强制从闲鱼平台获取最新信息,跳过缓存)
|
||||
item_id = order.get('item_id')
|
||||
buyer_id = order.get('buyer_id')
|
||||
sid = order.get('sid')
|
||||
|
||||
result = await xianyu_instance.fetch_order_detail_info(
|
||||
order_id=order_id,
|
||||
item_id=item_id,
|
||||
buyer_id=buyer_id,
|
||||
sid=sid,
|
||||
force_refresh=True # 强制刷新,跳过缓存
|
||||
)
|
||||
|
||||
if result:
|
||||
# 获取更新后的订单信息
|
||||
updated_order = db_manager.get_order_by_id(order_id)
|
||||
new_status = updated_order.get('order_status', '') if updated_order else ''
|
||||
status_changed = old_status != new_status
|
||||
log_with_user('info', f"刷新订单状态成功: 订单 {order_id}, 状态: {old_status} -> {new_status}", current_user)
|
||||
return {
|
||||
"success": True,
|
||||
"updated": status_changed,
|
||||
"new_status": new_status,
|
||||
"message": f"状态已更新: {new_status}" if status_changed else "订单状态无变化"
|
||||
}
|
||||
else:
|
||||
log_with_user('warning', f"刷新订单状态失败: 订单 {order_id}", current_user)
|
||||
return {"success": False, "updated": False, "message": "获取订单详情失败,请稍后重试"}
|
||||
|
||||
except Exception as e:
|
||||
log_with_user('error', f"刷新订单状态异常: 订单 {order_id} - {str(e)}", current_user)
|
||||
import traceback
|
||||
logger.error(f"刷新订单状态异常堆栈: {traceback.format_exc()}")
|
||||
return {"success": False, "updated": False, "message": f"刷新失败: {str(e)}"}
|
||||
|
||||
|
||||
# ==================== 自动更新接口 ====================
|
||||
|
||||
from auto_updater import get_updater, UpdateStatus, init_updater
|
||||
|
||||
@@ -9884,6 +9884,9 @@ function createOrderRow(order) {
|
||||
const statusClass = getOrderStatusClass(order.order_status);
|
||||
const statusText = getOrderStatusText(order.order_status);
|
||||
|
||||
// 判断是否可以手动发货(允许多次发货,除了交易关闭的订单)
|
||||
const canDeliver = !['closed', 'refunded'].includes(order.order_status);
|
||||
|
||||
return `
|
||||
<tr>
|
||||
<td>
|
||||
@@ -9924,6 +9927,12 @@ function createOrderRow(order) {
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<button class="btn btn-outline-success btn-sm" onclick="manualDeliverOrder('${order.order_id}')" title="手动发货" ${canDeliver ? '' : 'disabled'}>
|
||||
<i class="bi bi-truck"></i>
|
||||
</button>
|
||||
<button class="btn btn-outline-info btn-sm" onclick="refreshOrderStatus('${order.order_id}')" title="刷新状态">
|
||||
<i class="bi bi-arrow-repeat"></i>
|
||||
</button>
|
||||
<button class="btn btn-outline-primary btn-sm" onclick="showOrderDetail('${order.order_id}')" title="查看详情">
|
||||
<i class="bi bi-eye"></i>
|
||||
</button>
|
||||
@@ -10335,6 +10344,75 @@ async function batchDeleteOrders() {
|
||||
}
|
||||
}
|
||||
|
||||
// 手动发货订单
|
||||
async function manualDeliverOrder(orderId) {
|
||||
try {
|
||||
const confirmed = confirm(`确定要手动发货此订单吗?\n\n订单ID: ${orderId}\n\n系统将根据发货规则自动匹配发货内容并发送给买家。`);
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
showToast('正在执行发货...', 'info');
|
||||
|
||||
const response = await fetch(`${apiBase}/api/orders/${orderId}/deliver`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authToken}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
if (result.delivered) {
|
||||
showToast(`发货成功!\n${result.message}`, 'success');
|
||||
} else {
|
||||
showToast(`发货失败: ${result.message}`, 'warning');
|
||||
}
|
||||
// 刷新订单列表
|
||||
await refreshOrdersData();
|
||||
} else {
|
||||
showToast(`发货失败: ${result.detail || '未知错误'}`, 'danger');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('手动发货失败:', error);
|
||||
showToast('手动发货失败: ' + error.message, 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新订单状态
|
||||
async function refreshOrderStatus(orderId) {
|
||||
try {
|
||||
showToast('正在刷新订单状态...', 'info');
|
||||
|
||||
const response = await fetch(`${apiBase}/api/orders/${orderId}/refresh`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authToken}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
if (result.updated) {
|
||||
showToast(`订单状态已更新: ${result.new_status}`, 'success');
|
||||
} else {
|
||||
showToast(result.message || '订单状态无变化', 'info');
|
||||
}
|
||||
// 刷新订单列表
|
||||
await refreshOrdersData();
|
||||
} else {
|
||||
showToast(`刷新失败: ${result.detail || '未知错误'}`, 'danger');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('刷新订单状态失败:', error);
|
||||
showToast('刷新订单状态失败: ' + error.message, 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
// 切换全选订单
|
||||
function toggleSelectAllOrders(checkbox) {
|
||||
const orderCheckboxes = document.querySelectorAll('.order-checkbox');
|
||||
|
||||
+148
-43
@@ -328,6 +328,10 @@ class OrderDetailFetcher:
|
||||
# 获取并解析SKU信息
|
||||
sku_info = await self._get_sku_content()
|
||||
|
||||
# 获取订单状态
|
||||
order_status = await self._get_order_status()
|
||||
logger.info(f"订单 {order_id} 状态: {order_status}")
|
||||
|
||||
# 获取页面标题
|
||||
try:
|
||||
title = await self.page.title()
|
||||
@@ -346,6 +350,7 @@ class OrderDetailFetcher:
|
||||
'spec_value_2': sku_info.get('spec_value_2', '') if sku_info else '', # 规格2值
|
||||
'quantity': sku_info.get('quantity', '') if sku_info else '', # 数量
|
||||
'amount': sku_info.get('amount', '') if sku_info else '', # 金额
|
||||
'order_status': order_status, # 订单状态
|
||||
'timestamp': time.time(),
|
||||
'from_cache': False # 标记数据来源
|
||||
}
|
||||
@@ -442,6 +447,100 @@ class OrderDetailFetcher:
|
||||
logger.error(f"解析SKU内容异常: {e}")
|
||||
return {}
|
||||
|
||||
async def _get_order_status(self) -> str:
|
||||
"""
|
||||
从订单详情页面获取订单状态
|
||||
|
||||
Returns:
|
||||
订单状态字符串,可能的值:
|
||||
- 'success': 交易成功
|
||||
- 'closed': 交易关闭
|
||||
- 'pending_payment': 待付款
|
||||
- 'pending_delivery': 待发货
|
||||
- 'shipped': 已发货/待收货
|
||||
- 'refunding': 退款中
|
||||
- 'unknown': 未知状态
|
||||
"""
|
||||
try:
|
||||
if not await self._check_browser_status():
|
||||
logger.error("浏览器状态异常,无法获取订单状态")
|
||||
return 'unknown'
|
||||
|
||||
# 尝试多种选择器获取订单状态
|
||||
status_selectors = [
|
||||
'.orderStatusText--F6eoVcHD', # 常见的订单状态选择器
|
||||
'.order-status',
|
||||
'.status-text',
|
||||
'[class*="orderStatus"]',
|
||||
'[class*="StatusText"]',
|
||||
]
|
||||
|
||||
status_text = ''
|
||||
for selector in status_selectors:
|
||||
try:
|
||||
element = await self.page.query_selector(selector)
|
||||
if element:
|
||||
text = await element.text_content()
|
||||
if text:
|
||||
status_text = text.strip()
|
||||
logger.info(f"通过选择器 {selector} 获取到订单状态: {status_text}")
|
||||
break
|
||||
except Exception as e:
|
||||
logger.debug(f"选择器 {selector} 获取失败: {e}")
|
||||
continue
|
||||
|
||||
# 如果选择器都失败,尝试从页面文本中提取
|
||||
if not status_text:
|
||||
try:
|
||||
page_content = await self.page.content()
|
||||
# 检查常见的状态文本
|
||||
status_patterns = [
|
||||
('交易成功', 'success'),
|
||||
('交易关闭', 'closed'),
|
||||
('已关闭', 'closed'),
|
||||
('待付款', 'pending_payment'),
|
||||
('待发货', 'pending_delivery'),
|
||||
('已发货', 'shipped'),
|
||||
('待收货', 'shipped'),
|
||||
('退款中', 'refunding'),
|
||||
('退款成功', 'refunded'),
|
||||
]
|
||||
for pattern, status in status_patterns:
|
||||
if pattern in page_content:
|
||||
logger.info(f"从页面内容中检测到订单状态: {pattern} -> {status}")
|
||||
return status
|
||||
except Exception as e:
|
||||
logger.warning(f"从页面内容获取状态失败: {e}")
|
||||
|
||||
# 解析状态文本
|
||||
if status_text:
|
||||
status_mapping = {
|
||||
'交易成功': 'success',
|
||||
'交易关闭': 'closed',
|
||||
'已关闭': 'closed',
|
||||
'待付款': 'pending_payment',
|
||||
'待发货': 'pending_delivery',
|
||||
'已发货': 'shipped',
|
||||
'待收货': 'shipped',
|
||||
'退款中': 'refunding',
|
||||
'退款成功': 'refunded',
|
||||
}
|
||||
|
||||
for text, status in status_mapping.items():
|
||||
if text in status_text:
|
||||
logger.info(f"订单状态解析: {status_text} -> {status}")
|
||||
return status
|
||||
|
||||
logger.warning(f"未知的订单状态文本: {status_text}")
|
||||
return 'unknown'
|
||||
|
||||
logger.warning("无法获取订单状态")
|
||||
return 'unknown'
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取订单状态异常: {e}")
|
||||
return 'unknown'
|
||||
|
||||
async def _get_sku_content(self) -> Optional[Dict[str, str]]:
|
||||
"""获取并解析SKU内容,包括规格、数量和金额,支持双规格"""
|
||||
try:
|
||||
@@ -690,7 +789,7 @@ class OrderDetailFetcher:
|
||||
|
||||
|
||||
# 便捷函数
|
||||
async def fetch_order_detail_simple(order_id: str, cookie_string: str = None, headless: bool = True) -> Optional[Dict[str, Any]]:
|
||||
async def fetch_order_detail_simple(order_id: str, cookie_string: str = None, headless: bool = True, force_refresh: bool = False) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
简单的订单详情获取函数(优化版:先检查数据库,再初始化浏览器)
|
||||
|
||||
@@ -698,6 +797,7 @@ async def fetch_order_detail_simple(order_id: str, cookie_string: str = None, he
|
||||
order_id: 订单ID
|
||||
cookie_string: Cookie字符串,如果不提供则使用默认值
|
||||
headless: 是否无头模式
|
||||
force_refresh: 是否强制刷新(跳过缓存直接从闲鱼获取)
|
||||
|
||||
Returns:
|
||||
订单详情字典,包含以下字段:
|
||||
@@ -709,60 +809,65 @@ async def fetch_order_detail_simple(order_id: str, cookie_string: str = None, he
|
||||
- spec_value: 规格值
|
||||
- quantity: 数量
|
||||
- amount: 金额
|
||||
- order_status: 订单状态
|
||||
- timestamp: 获取时间戳
|
||||
失败时返回None
|
||||
"""
|
||||
# 先检查数据库中是否有有效数据
|
||||
try:
|
||||
from db_manager import db_manager
|
||||
existing_order = db_manager.get_order_by_id(order_id)
|
||||
# 如果不是强制刷新,先检查数据库中是否有有效数据
|
||||
if not force_refresh:
|
||||
try:
|
||||
from db_manager import db_manager
|
||||
existing_order = db_manager.get_order_by_id(order_id)
|
||||
|
||||
if existing_order:
|
||||
# 检查金额字段是否有效
|
||||
amount = existing_order.get('amount', '')
|
||||
amount_valid = False
|
||||
if existing_order:
|
||||
# 检查金额字段是否有效
|
||||
amount = existing_order.get('amount', '')
|
||||
amount_valid = False
|
||||
|
||||
if amount:
|
||||
amount_clean = str(amount).replace('¥', '').replace('¥', '').replace('$', '').strip()
|
||||
try:
|
||||
amount_value = float(amount_clean)
|
||||
amount_valid = amount_value > 0
|
||||
except (ValueError, TypeError):
|
||||
amount_valid = False
|
||||
if amount:
|
||||
amount_clean = str(amount).replace('¥', '').replace('¥', '').replace('$', '').strip()
|
||||
try:
|
||||
amount_value = float(amount_clean)
|
||||
amount_valid = amount_value > 0
|
||||
except (ValueError, TypeError):
|
||||
amount_valid = False
|
||||
|
||||
if amount_valid:
|
||||
logger.info(f"📋 订单 {order_id} 已存在于数据库中且金额有效({amount}),直接返回缓存数据")
|
||||
print(f"✅ 订单 {order_id} 使用缓存数据,跳过浏览器获取")
|
||||
if amount_valid:
|
||||
logger.info(f"📋 订单 {order_id} 已存在于数据库中且金额有效({amount}),直接返回缓存数据")
|
||||
print(f"✅ 订单 {order_id} 使用缓存数据,跳过浏览器获取")
|
||||
|
||||
# 构建返回格式
|
||||
result = {
|
||||
'order_id': existing_order['order_id'],
|
||||
'url': f"https://www.goofish.com/order-detail?orderId={order_id}&role=seller",
|
||||
'title': f"订单详情 - {order_id}",
|
||||
'sku_info': {
|
||||
# 构建返回格式
|
||||
result = {
|
||||
'order_id': existing_order['order_id'],
|
||||
'url': f"https://www.goofish.com/order-detail?orderId={order_id}&role=seller",
|
||||
'title': f"订单详情 - {order_id}",
|
||||
'sku_info': {
|
||||
'spec_name': existing_order.get('spec_name', ''),
|
||||
'spec_value': existing_order.get('spec_value', ''),
|
||||
'spec_name_2': existing_order.get('spec_name_2', ''),
|
||||
'spec_value_2': existing_order.get('spec_value_2', ''),
|
||||
'quantity': existing_order.get('quantity', ''),
|
||||
'amount': existing_order.get('amount', '')
|
||||
},
|
||||
'spec_name': existing_order.get('spec_name', ''),
|
||||
'spec_value': existing_order.get('spec_value', ''),
|
||||
'spec_name_2': existing_order.get('spec_name_2', ''),
|
||||
'spec_value_2': existing_order.get('spec_value_2', ''),
|
||||
'quantity': existing_order.get('quantity', ''),
|
||||
'amount': existing_order.get('amount', '')
|
||||
},
|
||||
'spec_name': existing_order.get('spec_name', ''),
|
||||
'spec_value': existing_order.get('spec_value', ''),
|
||||
'spec_name_2': existing_order.get('spec_name_2', ''),
|
||||
'spec_value_2': existing_order.get('spec_value_2', ''),
|
||||
'quantity': existing_order.get('quantity', ''),
|
||||
'amount': existing_order.get('amount', ''),
|
||||
'order_status': existing_order.get('order_status', 'unknown'), # 添加订单状态
|
||||
'timestamp': time.time(),
|
||||
'from_cache': True
|
||||
}
|
||||
return result
|
||||
else:
|
||||
logger.info(f"📋 订单 {order_id} 存在于数据库中但金额无效({amount}),需要重新获取")
|
||||
print(f"⚠️ 订单 {order_id} 金额无效,重新获取详情...")
|
||||
except Exception as e:
|
||||
logger.warning(f"检查数据库缓存失败: {e}")
|
||||
'amount': existing_order.get('amount', ''),
|
||||
'order_status': existing_order.get('order_status', 'unknown'), # 添加订单状态
|
||||
'timestamp': time.time(),
|
||||
'from_cache': True
|
||||
}
|
||||
return result
|
||||
else:
|
||||
logger.info(f"📋 订单 {order_id} 存在于数据库中但金额无效({amount}),需要重新获取")
|
||||
print(f"⚠️ 订单 {order_id} 金额无效,重新获取详情...")
|
||||
except Exception as e:
|
||||
logger.warning(f"检查数据库缓存失败: {e}")
|
||||
else:
|
||||
logger.info(f"🔄 订单 {order_id} 强制刷新,跳过缓存检查")
|
||||
print(f"🔄 订单 {order_id} 强制刷新模式...")
|
||||
|
||||
# 数据库中没有有效数据,使用浏览器获取
|
||||
logger.info(f"🌐 订单 {order_id} 需要浏览器获取,开始初始化浏览器...")
|
||||
|
||||
Reference in New Issue
Block a user