mirror of
https://github.com/zhinianboke/xianyu-auto-reply.git
synced 2025-08-02 20:47:35 +08:00
Compare commits
3 Commits
ef8a48ea0f
...
f9a3881b74
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f9a3881b74 | ||
![]() |
1dd4d9b841 | ||
![]() |
81b1f924cc |
@ -32,10 +32,12 @@
|
||||
|
||||
### 🚚 自动发货功能
|
||||
- **智能匹配** - 基于商品信息自动匹配发货规则
|
||||
- **多规格支持** - 支持同一商品的不同规格自动匹配对应卡券
|
||||
- **精确匹配+兜底机制** - 优先精确匹配规格,失败时自动降级到普通卡券
|
||||
- **延时发货** - 支持设置发货延时时间(0-3600秒)
|
||||
- **多种触发** - 支持付款消息、小刀消息等多种触发条件
|
||||
- **防重复发货** - 智能防重复机制,避免重复发货
|
||||
- **多种发货方式** - 支持文本内容、卡密文件、API调用等发货方式
|
||||
- **多种发货方式** - 支持固定文字、批量数据、API调用等发货方式
|
||||
- **自动确认发货** - 检测到付款后自动调用闲鱼API确认发货
|
||||
- **防重复确认** - 智能防重复确认机制,避免重复API调用
|
||||
- **发货统计** - 完整的发货记录和统计功能
|
||||
@ -43,7 +45,8 @@
|
||||
### 🛍️ 商品管理
|
||||
- **自动收集** - 消息触发时自动收集商品信息
|
||||
- **API获取** - 通过闲鱼API获取完整商品详情
|
||||
- **批量管理** - 支持批量查看、编辑商品信息
|
||||
- **多规格支持** - 支持多规格商品的规格信息管理
|
||||
- **批量管理** - 支持批量查看、编辑、切换多规格状态
|
||||
- **智能去重** - 自动去重,避免重复存储
|
||||
|
||||
### 🔍 商品搜索功能
|
||||
@ -63,6 +66,8 @@
|
||||
- **模板生成** - 自动生成包含示例数据的导入模板
|
||||
- **批量操作** - 支持批量添加、更新关键词数据
|
||||
- **数据验证** - 导入时自动验证数据格式和重复性
|
||||
- **多规格卡券管理** - 支持创建和管理多规格卡券
|
||||
- **发货规则管理** - 支持多规格发货规则的创建和管理
|
||||
- **数据备份** - 自动数据备份和恢复
|
||||
|
||||
## 📁 项目结构
|
||||
|
@ -119,21 +119,30 @@ class XianyuLive:
|
||||
logger.error(f"【{self.cookie_id}】获取自动确认发货设置失败: {self._safe_str(e)}")
|
||||
return True # 出错时默认启用
|
||||
|
||||
def can_auto_delivery(self, item_id: str) -> bool:
|
||||
"""检查是否可以进行自动发货(防重复发货)"""
|
||||
|
||||
|
||||
def can_auto_delivery(self, order_id: str) -> bool:
|
||||
"""检查是否可以进行自动发货(防重复发货)- 基于订单ID"""
|
||||
if not order_id:
|
||||
# 如果没有订单ID,则不进行冷却检查,允许发货
|
||||
return True
|
||||
|
||||
current_time = time.time()
|
||||
last_delivery = self.last_delivery_time.get(item_id, 0)
|
||||
last_delivery = self.last_delivery_time.get(order_id, 0)
|
||||
|
||||
if current_time - last_delivery < self.delivery_cooldown:
|
||||
logger.info(f"【{self.cookie_id}】商品 {item_id} 在冷却期内,跳过自动发货")
|
||||
logger.info(f"【{self.cookie_id}】订单 {order_id} 在冷却期内,跳过自动发货")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def mark_delivery_sent(self, item_id: str):
|
||||
"""标记商品已发货"""
|
||||
self.last_delivery_time[item_id] = time.time()
|
||||
logger.debug(f"【{self.cookie_id}】标记商品 {item_id} 已发货")
|
||||
def mark_delivery_sent(self, order_id: str):
|
||||
"""标记订单已发货 - 基于订单ID"""
|
||||
if order_id:
|
||||
self.last_delivery_time[order_id] = time.time()
|
||||
logger.debug(f"【{self.cookie_id}】标记订单 {order_id} 已发货")
|
||||
else:
|
||||
logger.debug(f"【{self.cookie_id}】无订单ID,跳过发货标记")
|
||||
|
||||
|
||||
|
||||
@ -1062,8 +1071,10 @@ class XianyuLive:
|
||||
logger.error(f"发送自动发货通知异常: {self._safe_str(e)}")
|
||||
|
||||
async def auto_confirm(self, order_id, retry_count=0):
|
||||
"""自动确认发货 - 使用加密模块"""
|
||||
"""自动确认发货 - 使用加密模块,不包含延时处理(延时已在_auto_delivery中处理)"""
|
||||
try:
|
||||
logger.debug(f"【{self.cookie_id}】开始确认发货,订单ID: {order_id}")
|
||||
|
||||
# 导入超级混淆加密模块
|
||||
from secure_confirm_ultra import SecureConfirm
|
||||
|
||||
@ -1083,8 +1094,48 @@ 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 _auto_delivery(self, item_id: str, item_title: str = None):
|
||||
"""自动发货功能"""
|
||||
async def fetch_order_detail_info(self, order_id: str):
|
||||
"""获取订单详情信息"""
|
||||
try:
|
||||
logger.info(f"【{self.cookie_id}】开始获取订单详情: {order_id}")
|
||||
|
||||
# 导入订单详情获取器
|
||||
from utils.order_detail_fetcher import fetch_order_detail_simple
|
||||
|
||||
# 获取当前账号的cookie字符串
|
||||
cookie_string = self.cookies_str
|
||||
logger.debug(f"【{self.cookie_id}】使用Cookie长度: {len(cookie_string) if cookie_string else 0}")
|
||||
|
||||
# 异步获取订单详情(使用当前账号的cookie和无头模式)
|
||||
result = await fetch_order_detail_simple(order_id, cookie_string, headless=True)
|
||||
|
||||
if result:
|
||||
logger.info(f"【{self.cookie_id}】订单详情获取成功: {order_id}")
|
||||
logger.info(f"【{self.cookie_id}】页面标题: {result.get('title', '未知')}")
|
||||
|
||||
# 获取解析后的规格信息
|
||||
spec_name = result.get('spec_name', '')
|
||||
spec_value = result.get('spec_value', '')
|
||||
|
||||
if spec_name and spec_value:
|
||||
logger.info(f"【{self.cookie_id}】📋 规格名称: {spec_name}")
|
||||
logger.info(f"【{self.cookie_id}】📝 规格值: {spec_value}")
|
||||
print(f"🛍️ 【{self.cookie_id}】订单 {order_id} 规格信息: {spec_name} -> {spec_value}")
|
||||
else:
|
||||
logger.warning(f"【{self.cookie_id}】未获取到有效的规格信息")
|
||||
print(f"⚠️ 【{self.cookie_id}】订单 {order_id} 规格信息获取失败")
|
||||
|
||||
return result
|
||||
else:
|
||||
logger.warning(f"【{self.cookie_id}】订单详情获取失败: {order_id}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"【{self.cookie_id}】获取订单详情异常: {self._safe_str(e)}")
|
||||
return None
|
||||
|
||||
async def _auto_delivery(self, item_id: str, item_title: str = None, order_id: str = None):
|
||||
"""自动发货功能 - 获取卡券规则,执行延时,确认发货,发送内容"""
|
||||
try:
|
||||
from db_manager import db_manager
|
||||
|
||||
@ -1185,26 +1236,112 @@ class XianyuLive:
|
||||
|
||||
logger.info(f"使用搜索文本匹配发货规则: {search_text[:100]}...")
|
||||
|
||||
# 根据商品信息查找匹配的发货规则
|
||||
delivery_rules = db_manager.get_delivery_rules_by_keyword(search_text)
|
||||
# 检查商品是否为多规格商品
|
||||
is_multi_spec = db_manager.get_item_multi_spec_status(self.cookie_id, item_id)
|
||||
spec_name = None
|
||||
spec_value = None
|
||||
|
||||
# 如果是多规格商品且有订单ID,获取规格信息
|
||||
if is_multi_spec and order_id:
|
||||
logger.info(f"检测到多规格商品,获取订单规格信息: {order_id}")
|
||||
try:
|
||||
order_detail = await self.fetch_order_detail_info(order_id)
|
||||
if order_detail:
|
||||
spec_name = order_detail.get('spec_name', '')
|
||||
spec_value = order_detail.get('spec_value', '')
|
||||
if spec_name and spec_value:
|
||||
logger.info(f"获取到规格信息: {spec_name} = {spec_value}")
|
||||
else:
|
||||
logger.warning(f"未能获取到规格信息,将使用兜底匹配")
|
||||
else:
|
||||
logger.warning(f"获取订单详情失败,将使用兜底匹配")
|
||||
except Exception as e:
|
||||
logger.error(f"获取订单规格信息失败: {self._safe_str(e)},将使用兜底匹配")
|
||||
|
||||
# 智能匹配发货规则:优先精确匹配,然后兜底匹配
|
||||
delivery_rules = []
|
||||
|
||||
# 第一步:如果有规格信息,尝试精确匹配多规格发货规则
|
||||
if spec_name and spec_value:
|
||||
logger.info(f"尝试精确匹配多规格发货规则: {search_text[:50]}... [{spec_name}:{spec_value}]")
|
||||
delivery_rules = db_manager.get_delivery_rules_by_keyword_and_spec(search_text, spec_name, spec_value)
|
||||
|
||||
if delivery_rules:
|
||||
logger.info(f"✅ 找到精确匹配的多规格发货规则: {len(delivery_rules)}个")
|
||||
else:
|
||||
logger.info(f"❌ 未找到精确匹配的多规格发货规则")
|
||||
|
||||
# 第二步:如果精确匹配失败,尝试兜底匹配(普通发货规则)
|
||||
if not delivery_rules:
|
||||
logger.info(f"尝试兜底匹配普通发货规则: {search_text[:50]}...")
|
||||
delivery_rules = db_manager.get_delivery_rules_by_keyword(search_text)
|
||||
|
||||
if delivery_rules:
|
||||
logger.info(f"✅ 找到兜底匹配的普通发货规则: {len(delivery_rules)}个")
|
||||
else:
|
||||
logger.info(f"❌ 未找到任何匹配的发货规则")
|
||||
|
||||
if not delivery_rules:
|
||||
logger.info(f"未找到匹配的发货规则: {search_text[:50]}...")
|
||||
logger.warning(f"未找到匹配的发货规则: {search_text[:50]}...")
|
||||
return None
|
||||
|
||||
# 使用第一个匹配的规则(按关键字长度降序排列,优先匹配更精确的规则)
|
||||
|
||||
rule = delivery_rules[0]
|
||||
|
||||
# 保存商品信息到数据库
|
||||
await self.save_item_info_to_db(item_id, search_text)
|
||||
rule = delivery_rules[0]
|
||||
logger.info(f"找到匹配的发货规则: {rule['keyword']} -> {rule['card_name']} ({rule['card_type']})")
|
||||
|
||||
# 检查是否需要延时发货
|
||||
# 详细的匹配结果日志
|
||||
if rule.get('is_multi_spec'):
|
||||
if spec_name and spec_value:
|
||||
logger.info(f"🎯 精确匹配多规格发货规则: {rule['keyword']} -> {rule['card_name']} [{rule['spec_name']}:{rule['spec_value']}]")
|
||||
logger.info(f"📋 订单规格: {spec_name}:{spec_value} ✅ 匹配卡券规格: {rule['spec_name']}:{rule['spec_value']}")
|
||||
else:
|
||||
logger.info(f"⚠️ 使用多规格发货规则但无订单规格信息: {rule['keyword']} -> {rule['card_name']} [{rule['spec_name']}:{rule['spec_value']}]")
|
||||
else:
|
||||
if spec_name and spec_value:
|
||||
logger.info(f"🔄 兜底匹配普通发货规则: {rule['keyword']} -> {rule['card_name']} ({rule['card_type']})")
|
||||
logger.info(f"📋 订单规格: {spec_name}:{spec_value} ➡️ 使用普通卡券兜底")
|
||||
else:
|
||||
logger.info(f"✅ 匹配普通发货规则: {rule['keyword']} -> {rule['card_name']} ({rule['card_type']})")
|
||||
|
||||
# 获取延时设置
|
||||
delay_seconds = rule.get('card_delay_seconds', 0)
|
||||
|
||||
# 执行延时(不管是否确认发货,只要有延时设置就执行)
|
||||
if delay_seconds and delay_seconds > 0:
|
||||
logger.info(f"检测到延时发货设置: {delay_seconds}秒,开始延时...")
|
||||
logger.info(f"检测到发货延时设置: {delay_seconds}秒,开始延时...")
|
||||
await asyncio.sleep(delay_seconds)
|
||||
logger.info(f"延时发货完成,开始发送内容")
|
||||
logger.info(f"延时完成")
|
||||
|
||||
# 如果有订单ID,执行确认发货
|
||||
if order_id:
|
||||
# 检查是否启用自动确认发货
|
||||
if not self.is_auto_confirm_enabled():
|
||||
logger.info(f"自动确认发货已关闭,跳过订单 {order_id}")
|
||||
else:
|
||||
# 检查确认发货冷却时间
|
||||
current_time = time.time()
|
||||
should_confirm = True
|
||||
|
||||
if order_id in self.confirmed_orders:
|
||||
last_confirm_time = self.confirmed_orders[order_id]
|
||||
if current_time - last_confirm_time < self.order_confirm_cooldown:
|
||||
logger.info(f"订单 {order_id} 已在 {self.order_confirm_cooldown} 秒内确认过,跳过重复确认")
|
||||
should_confirm = False
|
||||
|
||||
if should_confirm:
|
||||
logger.info(f"开始自动确认发货: 订单ID={order_id}")
|
||||
confirm_result = await self.auto_confirm(order_id)
|
||||
if confirm_result.get('success'):
|
||||
self.confirmed_orders[order_id] = current_time
|
||||
logger.info(f"🎉 自动确认发货成功!订单ID: {order_id}")
|
||||
else:
|
||||
logger.warning(f"⚠️ 自动确认发货失败: {confirm_result.get('error', '未知错误')}")
|
||||
# 即使确认发货失败,也继续发送发货内容
|
||||
|
||||
# 开始处理发货内容
|
||||
logger.info(f"开始处理发货内容,规则: {rule['keyword']} -> {rule['card_name']} ({rule['card_type']})")
|
||||
|
||||
delivery_content = None
|
||||
|
||||
@ -1215,7 +1352,7 @@ class XianyuLive:
|
||||
|
||||
elif rule['card_type'] == 'text':
|
||||
# 固定文字类型:直接使用文字内容
|
||||
delivery_content = rule['card_text_content']
|
||||
delivery_content = rule['text_content']
|
||||
|
||||
elif rule['card_type'] == 'data':
|
||||
# 批量数据类型:获取并消费第一条数据
|
||||
@ -1269,10 +1406,12 @@ class XianyuLive:
|
||||
|
||||
try:
|
||||
import aiohttp
|
||||
import json
|
||||
|
||||
api_config = rule.get('card_api_config')
|
||||
api_config = rule.get('api_config')
|
||||
if not api_config:
|
||||
logger.error("API配置为空")
|
||||
logger.error(f"API配置为空,规则ID: {rule.get('id')}, 卡券名称: {rule.get('card_name')}")
|
||||
logger.debug(f"规则详情: {rule}")
|
||||
return None
|
||||
|
||||
# 解析API配置
|
||||
@ -1584,30 +1723,43 @@ class XianyuLive:
|
||||
|
||||
async def _create_websocket_connection(self, headers):
|
||||
"""创建WebSocket连接,兼容不同版本的websockets库"""
|
||||
import websockets
|
||||
|
||||
# 获取websockets版本用于调试
|
||||
websockets_version = getattr(websockets, '__version__', '未知')
|
||||
logger.debug(f"websockets库版本: {websockets_version}")
|
||||
|
||||
try:
|
||||
# 尝试使用extra_headers参数
|
||||
return websockets.connect(
|
||||
self.base_url,
|
||||
extra_headers=headers
|
||||
)
|
||||
except TypeError as e:
|
||||
# 安全地检查异常信息
|
||||
except Exception as e:
|
||||
# 捕获所有异常类型,不仅仅是TypeError
|
||||
error_msg = self._safe_str(e)
|
||||
logger.debug(f"extra_headers参数失败: {error_msg}")
|
||||
|
||||
if "extra_headers" in error_msg:
|
||||
logger.warning("websockets库不支持extra_headers参数,使用兼容模式")
|
||||
if "extra_headers" in error_msg or "unexpected keyword argument" in error_msg:
|
||||
logger.warning("websockets库不支持extra_headers参数,尝试additional_headers")
|
||||
# 使用additional_headers参数(较新版本)
|
||||
try:
|
||||
return websockets.connect(
|
||||
self.base_url,
|
||||
additional_headers=headers
|
||||
)
|
||||
except TypeError:
|
||||
# 如果都不支持,则不传递headers
|
||||
logger.warning("websockets库不支持headers参数,使用基础连接模式")
|
||||
return websockets.connect(self.base_url)
|
||||
except Exception as e2:
|
||||
error_msg2 = self._safe_str(e2)
|
||||
logger.debug(f"additional_headers参数失败: {error_msg2}")
|
||||
|
||||
if "additional_headers" in error_msg2 or "unexpected keyword argument" in error_msg2:
|
||||
# 如果都不支持,则不传递headers
|
||||
logger.warning("websockets库不支持headers参数,使用基础连接模式")
|
||||
return websockets.connect(self.base_url)
|
||||
else:
|
||||
raise e2
|
||||
else:
|
||||
raise
|
||||
raise e
|
||||
|
||||
async def _handle_websocket_connection(self, websocket, toid, item_id, text):
|
||||
"""处理WebSocket连接的具体逻辑"""
|
||||
@ -2024,50 +2176,17 @@ class XianyuLive:
|
||||
except Exception as parse_e:
|
||||
logger.debug(f"解析dynamicOperation JSON失败: {parse_e}")
|
||||
|
||||
# 如果成功获取到orderId,进行自动确认发货
|
||||
# 订单ID已提取,将在自动发货时进行确认发货处理
|
||||
if order_id:
|
||||
# 检查是否启用自动确认发货
|
||||
if not self.is_auto_confirm_enabled():
|
||||
logger.info(f'[{msg_time}] 【{self.cookie_id}】自动确认发货已关闭,跳过订单 {order_id}')
|
||||
else:
|
||||
# 检查是否已经确认过这个订单
|
||||
current_time = time.time()
|
||||
if order_id in self.confirmed_orders:
|
||||
last_confirm_time = self.confirmed_orders[order_id]
|
||||
if current_time - last_confirm_time < self.order_confirm_cooldown:
|
||||
logger.info(f'[{msg_time}] 【{self.cookie_id}】⏭️ 订单 {order_id} 已在 {self.order_confirm_cooldown} 秒内确认过,跳过重复确认')
|
||||
else:
|
||||
# 超过冷却时间,可以重新确认
|
||||
try:
|
||||
logger.info(f'[{msg_time}] 【{self.cookie_id}】开始自动确认发货,订单ID: {order_id}')
|
||||
confirm_result = await self.auto_confirm(order_id)
|
||||
if confirm_result.get('success'):
|
||||
self.confirmed_orders[order_id] = current_time
|
||||
logger.info(f'[{msg_time}] 【{self.cookie_id}】🎉 自动确认发货成功!订单ID: {order_id}')
|
||||
else:
|
||||
logger.warning(f'[{msg_time}] 【{self.cookie_id}】⚠️ 自动确认发货失败: {confirm_result.get("error", "未知错误")}')
|
||||
except Exception as confirm_e:
|
||||
logger.error(f'[{msg_time}] 【{self.cookie_id}】自动确认发货异常: {self._safe_str(confirm_e)}')
|
||||
else:
|
||||
# 首次确认这个订单
|
||||
try:
|
||||
logger.info(f'[{msg_time}] 【{self.cookie_id}】开始自动确认发货,订单ID: {order_id}')
|
||||
confirm_result = await self.auto_confirm(order_id)
|
||||
if confirm_result.get('success'):
|
||||
self.confirmed_orders[order_id] = current_time
|
||||
logger.info(f'[{msg_time}] 【{self.cookie_id}】🎉 自动确认发货成功!订单ID: {order_id}')
|
||||
else:
|
||||
logger.warning(f'[{msg_time}] 【{self.cookie_id}】⚠️ 自动确认发货失败: {confirm_result.get("error", "未知错误")}')
|
||||
except Exception as confirm_e:
|
||||
logger.error(f'[{msg_time}] 【{self.cookie_id}】自动确认发货异常: {self._safe_str(confirm_e)}')
|
||||
logger.info(f'[{msg_time}] 【{self.cookie_id}】提取到订单ID: {order_id},将在自动发货时处理确认发货')
|
||||
else:
|
||||
logger.warning(f'[{msg_time}] 【{self.cookie_id}】❌ 未能提取到订单ID')
|
||||
|
||||
except Exception as extract_e:
|
||||
logger.error(f"提取订单ID失败: {self._safe_str(extract_e)}")
|
||||
|
||||
# 检查是否可以进行自动发货(防重复)
|
||||
if not self.can_auto_delivery(item_id):
|
||||
# 检查是否可以进行自动发货(防重复)- 基于订单ID
|
||||
if not self.can_auto_delivery(order_id):
|
||||
return
|
||||
|
||||
# 构造用户URL
|
||||
@ -2080,12 +2199,12 @@ class XianyuLive:
|
||||
|
||||
logger.info(f"【{self.cookie_id}】准备自动发货: item_id={item_id}, item_title={item_title}")
|
||||
|
||||
# 调用自动发货方法
|
||||
delivery_content = await self._auto_delivery(item_id, item_title)
|
||||
# 调用自动发货方法(包含自动确认发货)
|
||||
delivery_content = await self._auto_delivery(item_id, item_title, order_id)
|
||||
|
||||
if delivery_content:
|
||||
# 标记已发货(防重复)
|
||||
self.mark_delivery_sent(item_id)
|
||||
# 标记已发货(防重复)- 基于订单ID
|
||||
self.mark_delivery_sent(order_id)
|
||||
|
||||
# 发送发货内容给买家
|
||||
await self.send_msg(websocket, chat_id, send_user_id, delivery_content)
|
||||
@ -2150,50 +2269,17 @@ class XianyuLive:
|
||||
except Exception as parse_e:
|
||||
logger.debug(f"解析dynamicOperation JSON失败: {parse_e}")
|
||||
|
||||
# 如果成功获取到orderId,进行自动确认发货
|
||||
# 订单ID已提取,将在自动发货时进行确认发货处理
|
||||
if order_id:
|
||||
# 检查是否启用自动确认发货
|
||||
if not self.is_auto_confirm_enabled():
|
||||
logger.info(f'[{msg_time}] 【{self.cookie_id}】自动确认发货已关闭,跳过订单 {order_id}')
|
||||
else:
|
||||
# 检查是否已经确认过这个订单
|
||||
current_time = time.time()
|
||||
if order_id in self.confirmed_orders:
|
||||
last_confirm_time = self.confirmed_orders[order_id]
|
||||
if current_time - last_confirm_time < self.order_confirm_cooldown:
|
||||
logger.info(f'[{msg_time}] 【{self.cookie_id}】⏭️ 订单 {order_id} 已在 {self.order_confirm_cooldown} 秒内确认过,跳过重复确认')
|
||||
else:
|
||||
# 超过冷却时间,可以重新确认
|
||||
try:
|
||||
logger.info(f'[{msg_time}] 【{self.cookie_id}】开始自动确认发货,订单ID: {order_id}')
|
||||
confirm_result = await self.auto_confirm(order_id)
|
||||
if confirm_result.get('success'):
|
||||
self.confirmed_orders[order_id] = current_time
|
||||
logger.info(f'[{msg_time}] 【{self.cookie_id}】🎉 自动确认发货成功!订单ID: {order_id}')
|
||||
else:
|
||||
logger.warning(f'[{msg_time}] 【{self.cookie_id}】⚠️ 自动确认发货失败: {confirm_result.get("error", "未知错误")}')
|
||||
except Exception as confirm_e:
|
||||
logger.error(f'[{msg_time}] 【{self.cookie_id}】自动确认发货异常: {self._safe_str(confirm_e)}')
|
||||
else:
|
||||
# 首次确认这个订单
|
||||
try:
|
||||
logger.info(f'[{msg_time}] 【{self.cookie_id}】开始自动确认发货,订单ID: {order_id}')
|
||||
confirm_result = await self.auto_confirm(order_id)
|
||||
if confirm_result.get('success'):
|
||||
self.confirmed_orders[order_id] = current_time
|
||||
logger.info(f'[{msg_time}] 【{self.cookie_id}】🎉 自动确认发货成功!订单ID: {order_id}')
|
||||
else:
|
||||
logger.warning(f'[{msg_time}] 【{self.cookie_id}】⚠️ 自动确认发货失败: {confirm_result.get("error", "未知错误")}')
|
||||
except Exception as confirm_e:
|
||||
logger.error(f'[{msg_time}] 【{self.cookie_id}】自动确认发货异常: {self._safe_str(confirm_e)}')
|
||||
logger.info(f'[{msg_time}] 【{self.cookie_id}】提取到订单ID: {order_id},将在自动发货时处理确认发货')
|
||||
else:
|
||||
logger.warning(f'[{msg_time}] 【{self.cookie_id}】❌ 未能提取到订单ID')
|
||||
|
||||
except Exception as extract_e:
|
||||
logger.error(f"提取订单ID失败: {self._safe_str(extract_e)}")
|
||||
|
||||
# 检查是否可以进行自动发货(防重复)
|
||||
if not self.can_auto_delivery(item_id):
|
||||
# 检查是否可以进行自动发货(防重复)- 基于订单ID
|
||||
if not self.can_auto_delivery(order_id):
|
||||
return
|
||||
|
||||
# 构造用户URL
|
||||
@ -2206,12 +2292,12 @@ class XianyuLive:
|
||||
|
||||
logger.info(f"【{self.cookie_id}】准备自动发货: item_id={item_id}, item_title={item_title}")
|
||||
|
||||
# 调用自动发货方法
|
||||
delivery_content = await self._auto_delivery(item_id, item_title)
|
||||
# 调用自动发货方法(包含自动确认发货)
|
||||
delivery_content = await self._auto_delivery(item_id, item_title, order_id)
|
||||
|
||||
if delivery_content:
|
||||
# 标记已发货(防重复)
|
||||
self.mark_delivery_sent(item_id)
|
||||
# 标记已发货(防重复)- 基于订单ID
|
||||
self.mark_delivery_sent(order_id)
|
||||
|
||||
# 发送发货内容给买家
|
||||
await self.send_msg(websocket, chat_id, send_user_id, delivery_content)
|
||||
@ -2300,50 +2386,17 @@ class XianyuLive:
|
||||
except Exception as parse_e:
|
||||
logger.debug(f"解析dynamicOperation JSON失败: {parse_e}")
|
||||
|
||||
# 如果成功获取到orderId,进行自动确认发货
|
||||
# 订单ID已提取,将在自动发货时进行确认发货处理
|
||||
if order_id:
|
||||
# 检查是否启用自动确认发货
|
||||
if not self.is_auto_confirm_enabled():
|
||||
logger.info(f'[{msg_time}] 【{self.cookie_id}】自动确认发货已关闭,跳过小刀成功订单 {order_id}')
|
||||
else:
|
||||
# 检查是否已经确认过这个订单
|
||||
current_time = time.time()
|
||||
if order_id in self.confirmed_orders:
|
||||
last_confirm_time = self.confirmed_orders[order_id]
|
||||
if current_time - last_confirm_time < self.order_confirm_cooldown:
|
||||
logger.info(f'[{msg_time}] 【{self.cookie_id}】⏭️ 订单 {order_id} 已在 {self.order_confirm_cooldown} 秒内确认过,跳过重复确认')
|
||||
else:
|
||||
# 超过冷却时间,可以重新确认
|
||||
try:
|
||||
logger.info(f'[{msg_time}] 【{self.cookie_id}】小刀成功,开始自动确认发货,订单ID: {order_id}')
|
||||
confirm_result = await self.auto_confirm(order_id)
|
||||
if confirm_result.get('success'):
|
||||
self.confirmed_orders[order_id] = current_time
|
||||
logger.info(f'[{msg_time}] 【{self.cookie_id}】🎉 小刀成功,自动确认发货成功!订单ID: {order_id}')
|
||||
else:
|
||||
logger.warning(f'[{msg_time}] 【{self.cookie_id}】⚠️ 小刀成功,自动确认发货失败: {confirm_result.get("error", "未知错误")}')
|
||||
except Exception as confirm_e:
|
||||
logger.error(f'[{msg_time}] 【{self.cookie_id}】小刀成功,自动确认发货异常: {self._safe_str(confirm_e)}')
|
||||
else:
|
||||
# 首次确认这个订单
|
||||
try:
|
||||
logger.info(f'[{msg_time}] 【{self.cookie_id}】小刀成功,开始自动确认发货,订单ID: {order_id}')
|
||||
confirm_result = await self.auto_confirm(order_id)
|
||||
if confirm_result.get('success'):
|
||||
self.confirmed_orders[order_id] = current_time
|
||||
logger.info(f'[{msg_time}] 【{self.cookie_id}】🎉 小刀成功,自动确认发货成功!订单ID: {order_id}')
|
||||
else:
|
||||
logger.warning(f'[{msg_time}] 【{self.cookie_id}】⚠️ 小刀成功,自动确认发货失败: {confirm_result.get("error", "未知错误")}')
|
||||
except Exception as confirm_e:
|
||||
logger.error(f'[{msg_time}] 【{self.cookie_id}】小刀成功,自动确认发货异常: {self._safe_str(confirm_e)}')
|
||||
logger.info(f'[{msg_time}] 【{self.cookie_id}】小刀成功,提取到订单ID: {order_id},将在自动发货时处理确认发货')
|
||||
else:
|
||||
logger.warning(f'[{msg_time}] 【{self.cookie_id}】❌ 小刀成功但未能提取到订单ID')
|
||||
|
||||
except Exception as extract_e:
|
||||
logger.error(f"提取订单ID失败: {self._safe_str(extract_e)}")
|
||||
|
||||
# 检查是否可以进行自动发货(防重复)
|
||||
if not self.can_auto_delivery(item_id):
|
||||
# 检查是否可以进行自动发货(防重复)- 基于订单ID
|
||||
if not self.can_auto_delivery(order_id):
|
||||
return
|
||||
|
||||
# 构造用户URL
|
||||
@ -2356,12 +2409,12 @@ class XianyuLive:
|
||||
|
||||
logger.info(f"【{self.cookie_id}】准备自动发货: item_id={item_id}, item_title={item_title}")
|
||||
|
||||
# 调用自动发货方法
|
||||
delivery_content = await self._auto_delivery(item_id, item_title)
|
||||
# 调用自动发货方法(包含自动确认发货)
|
||||
delivery_content = await self._auto_delivery(item_id, item_title, order_id)
|
||||
|
||||
if delivery_content:
|
||||
# 标记已发货(防重复)
|
||||
self.mark_delivery_sent(item_id)
|
||||
# 标记已发货(防重复)- 基于订单ID
|
||||
self.mark_delivery_sent(order_id)
|
||||
|
||||
# 发送发货内容给买家
|
||||
await self.send_msg(websocket, chat_id, send_user_id, delivery_content)
|
||||
|
296
db_manager.py
296
db_manager.py
@ -198,6 +198,9 @@ class DBManager:
|
||||
description TEXT,
|
||||
enabled BOOLEAN DEFAULT TRUE,
|
||||
delay_seconds INTEGER DEFAULT 0,
|
||||
is_multi_spec BOOLEAN DEFAULT FALSE,
|
||||
spec_name TEXT,
|
||||
spec_value TEXT,
|
||||
user_id INTEGER NOT NULL DEFAULT 1,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
@ -244,6 +247,7 @@ class DBManager:
|
||||
item_category TEXT,
|
||||
item_price TEXT,
|
||||
item_detail TEXT,
|
||||
is_multi_spec BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (cookie_id) REFERENCES cookies(id) ON DELETE CASCADE,
|
||||
@ -412,6 +416,24 @@ class DBManager:
|
||||
# type列存在,更新NULL值
|
||||
self._execute_sql(cursor, "UPDATE email_verifications SET type = 'register' WHERE type IS NULL")
|
||||
|
||||
# 为cards表添加多规格字段(如果不存在)
|
||||
try:
|
||||
self._execute_sql(cursor, "SELECT is_multi_spec FROM cards LIMIT 1")
|
||||
except sqlite3.OperationalError:
|
||||
# 多规格字段不存在,需要添加
|
||||
self._execute_sql(cursor, "ALTER TABLE cards ADD COLUMN is_multi_spec BOOLEAN DEFAULT FALSE")
|
||||
self._execute_sql(cursor, "ALTER TABLE cards ADD COLUMN spec_name TEXT")
|
||||
self._execute_sql(cursor, "ALTER TABLE cards ADD COLUMN spec_value TEXT")
|
||||
logger.info("为cards表添加多规格字段")
|
||||
|
||||
# 为item_info表添加多规格字段(如果不存在)
|
||||
try:
|
||||
self._execute_sql(cursor, "SELECT is_multi_spec FROM item_info LIMIT 1")
|
||||
except sqlite3.OperationalError:
|
||||
# 多规格字段不存在,需要添加
|
||||
self._execute_sql(cursor, "ALTER TABLE item_info ADD COLUMN is_multi_spec BOOLEAN DEFAULT FALSE")
|
||||
logger.info("为item_info表添加多规格字段")
|
||||
|
||||
self.conn.commit()
|
||||
logger.info(f"数据库初始化成功: {self.db_path}")
|
||||
except Exception as e:
|
||||
@ -1740,10 +1762,37 @@ class DBManager:
|
||||
|
||||
def create_card(self, name: str, card_type: str, api_config=None,
|
||||
text_content: str = None, data_content: str = None,
|
||||
description: str = None, enabled: bool = True, delay_seconds: int = 0, user_id: int = None):
|
||||
"""创建新卡券"""
|
||||
description: str = None, enabled: bool = True, delay_seconds: int = 0,
|
||||
is_multi_spec: bool = False, spec_name: str = None, spec_value: str = None,
|
||||
user_id: int = None):
|
||||
"""创建新卡券(支持多规格)"""
|
||||
with self.lock:
|
||||
try:
|
||||
# 验证多规格参数
|
||||
if is_multi_spec:
|
||||
if not spec_name or not spec_value:
|
||||
raise ValueError("多规格卡券必须提供规格名称和规格值")
|
||||
|
||||
# 检查唯一性:卡券名称+规格名称+规格值
|
||||
cursor = self.conn.cursor()
|
||||
cursor.execute('''
|
||||
SELECT COUNT(*) FROM cards
|
||||
WHERE name = ? AND spec_name = ? AND spec_value = ? AND user_id = ?
|
||||
''', (name, spec_name, spec_value, user_id))
|
||||
|
||||
if cursor.fetchone()[0] > 0:
|
||||
raise ValueError(f"卡券已存在:{name} - {spec_name}:{spec_value}")
|
||||
else:
|
||||
# 检查唯一性:仅卡券名称
|
||||
cursor = self.conn.cursor()
|
||||
cursor.execute('''
|
||||
SELECT COUNT(*) FROM cards
|
||||
WHERE name = ? AND (is_multi_spec = 0 OR is_multi_spec IS NULL) AND user_id = ?
|
||||
''', (name, user_id))
|
||||
|
||||
if cursor.fetchone()[0] > 0:
|
||||
raise ValueError(f"卡券名称已存在:{name}")
|
||||
|
||||
# 处理api_config参数 - 如果是字典则转换为JSON字符串
|
||||
api_config_str = None
|
||||
if api_config is not None:
|
||||
@ -1753,16 +1802,21 @@ class DBManager:
|
||||
else:
|
||||
api_config_str = str(api_config)
|
||||
|
||||
cursor = self.conn.cursor()
|
||||
cursor.execute('''
|
||||
INSERT INTO cards (name, type, api_config, text_content, data_content,
|
||||
description, enabled, delay_seconds, user_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
description, enabled, delay_seconds, is_multi_spec,
|
||||
spec_name, spec_value, user_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
''', (name, card_type, api_config_str, text_content, data_content,
|
||||
description, enabled, delay_seconds, user_id))
|
||||
description, enabled, delay_seconds, is_multi_spec,
|
||||
spec_name, spec_value, user_id))
|
||||
self.conn.commit()
|
||||
card_id = cursor.lastrowid
|
||||
logger.info(f"创建卡券成功: {name} (ID: {card_id})")
|
||||
|
||||
if is_multi_spec:
|
||||
logger.info(f"创建多规格卡券成功: {name} - {spec_name}:{spec_value} (ID: {card_id})")
|
||||
else:
|
||||
logger.info(f"创建卡券成功: {name} (ID: {card_id})")
|
||||
return card_id
|
||||
except Exception as e:
|
||||
logger.error(f"创建卡券失败: {e}")
|
||||
@ -1776,7 +1830,8 @@ class DBManager:
|
||||
if user_id is not None:
|
||||
cursor.execute('''
|
||||
SELECT id, name, type, api_config, text_content, data_content,
|
||||
description, enabled, delay_seconds, created_at, updated_at
|
||||
description, enabled, delay_seconds, is_multi_spec,
|
||||
spec_name, spec_value, created_at, updated_at
|
||||
FROM cards
|
||||
WHERE user_id = ?
|
||||
ORDER BY created_at DESC
|
||||
@ -1784,7 +1839,8 @@ class DBManager:
|
||||
else:
|
||||
cursor.execute('''
|
||||
SELECT id, name, type, api_config, text_content, data_content,
|
||||
description, enabled, delay_seconds, created_at, updated_at
|
||||
description, enabled, delay_seconds, is_multi_spec,
|
||||
spec_name, spec_value, created_at, updated_at
|
||||
FROM cards
|
||||
ORDER BY created_at DESC
|
||||
''')
|
||||
@ -1811,8 +1867,11 @@ class DBManager:
|
||||
'description': row[6],
|
||||
'enabled': bool(row[7]),
|
||||
'delay_seconds': row[8] or 0,
|
||||
'created_at': row[9],
|
||||
'updated_at': row[10]
|
||||
'is_multi_spec': bool(row[9]) if row[9] is not None else False,
|
||||
'spec_name': row[10],
|
||||
'spec_value': row[11],
|
||||
'created_at': row[12],
|
||||
'updated_at': row[13]
|
||||
})
|
||||
|
||||
return cards
|
||||
@ -1828,13 +1887,15 @@ class DBManager:
|
||||
if user_id is not None:
|
||||
cursor.execute('''
|
||||
SELECT id, name, type, api_config, text_content, data_content,
|
||||
description, enabled, delay_seconds, created_at, updated_at
|
||||
description, enabled, delay_seconds, is_multi_spec,
|
||||
spec_name, spec_value, created_at, updated_at
|
||||
FROM cards WHERE id = ? AND user_id = ?
|
||||
''', (card_id, user_id))
|
||||
else:
|
||||
cursor.execute('''
|
||||
SELECT id, name, type, api_config, text_content, data_content,
|
||||
description, enabled, delay_seconds, created_at, updated_at
|
||||
description, enabled, delay_seconds, is_multi_spec,
|
||||
spec_name, spec_value, created_at, updated_at
|
||||
FROM cards WHERE id = ?
|
||||
''', (card_id,))
|
||||
|
||||
@ -1860,8 +1921,11 @@ class DBManager:
|
||||
'description': row[6],
|
||||
'enabled': bool(row[7]),
|
||||
'delay_seconds': row[8] or 0,
|
||||
'created_at': row[9],
|
||||
'updated_at': row[10]
|
||||
'is_multi_spec': bool(row[9]) if row[9] is not None else False,
|
||||
'spec_name': row[10],
|
||||
'spec_value': row[11],
|
||||
'created_at': row[12],
|
||||
'updated_at': row[13]
|
||||
}
|
||||
return None
|
||||
except Exception as e:
|
||||
@ -1870,7 +1934,8 @@ class DBManager:
|
||||
|
||||
def update_card(self, card_id: int, name: str = None, card_type: str = None,
|
||||
api_config=None, text_content: str = None, data_content: str = None,
|
||||
description: str = None, enabled: bool = None, delay_seconds: int = None):
|
||||
description: str = None, enabled: bool = None, delay_seconds: int = None,
|
||||
is_multi_spec: bool = None, spec_name: str = None, spec_value: str = None):
|
||||
"""更新卡券"""
|
||||
with self.lock:
|
||||
try:
|
||||
@ -1913,6 +1978,15 @@ class DBManager:
|
||||
if delay_seconds is not None:
|
||||
update_fields.append("delay_seconds = ?")
|
||||
params.append(delay_seconds)
|
||||
if is_multi_spec is not None:
|
||||
update_fields.append("is_multi_spec = ?")
|
||||
params.append(is_multi_spec)
|
||||
if spec_name is not None:
|
||||
update_fields.append("spec_name = ?")
|
||||
params.append(spec_name)
|
||||
if spec_value is not None:
|
||||
update_fields.append("spec_value = ?")
|
||||
params.append(spec_value)
|
||||
|
||||
if not update_fields:
|
||||
return True # 没有需要更新的字段
|
||||
@ -1964,7 +2038,8 @@ class DBManager:
|
||||
cursor.execute('''
|
||||
SELECT dr.id, dr.keyword, dr.card_id, dr.delivery_count, dr.enabled,
|
||||
dr.description, dr.delivery_times, dr.created_at, dr.updated_at,
|
||||
c.name as card_name, c.type as card_type
|
||||
c.name as card_name, c.type as card_type,
|
||||
c.is_multi_spec, c.spec_name, c.spec_value
|
||||
FROM delivery_rules dr
|
||||
LEFT JOIN cards c ON dr.card_id = c.id
|
||||
WHERE dr.user_id = ?
|
||||
@ -1974,7 +2049,8 @@ class DBManager:
|
||||
cursor.execute('''
|
||||
SELECT dr.id, dr.keyword, dr.card_id, dr.delivery_count, dr.enabled,
|
||||
dr.description, dr.delivery_times, dr.created_at, dr.updated_at,
|
||||
c.name as card_name, c.type as card_type
|
||||
c.name as card_name, c.type as card_type,
|
||||
c.is_multi_spec, c.spec_name, c.spec_value
|
||||
FROM delivery_rules dr
|
||||
LEFT JOIN cards c ON dr.card_id = c.id
|
||||
ORDER BY dr.created_at DESC
|
||||
@ -1993,7 +2069,10 @@ class DBManager:
|
||||
'created_at': row[7],
|
||||
'updated_at': row[8],
|
||||
'card_name': row[9],
|
||||
'card_type': row[10]
|
||||
'card_type': row[10],
|
||||
'is_multi_spec': bool(row[11]) if row[11] is not None else False,
|
||||
'spec_name': row[12],
|
||||
'spec_value': row[13]
|
||||
})
|
||||
|
||||
return rules
|
||||
@ -2047,9 +2126,9 @@ class DBManager:
|
||||
'delivery_times': row[6],
|
||||
'card_name': row[7],
|
||||
'card_type': row[8],
|
||||
'card_api_config': api_config,
|
||||
'card_text_content': row[10],
|
||||
'card_data_content': row[11],
|
||||
'api_config': api_config, # 修复字段名
|
||||
'text_content': row[10],
|
||||
'data_content': row[11],
|
||||
'card_enabled': bool(row[12]),
|
||||
'card_description': row[13], # 卡券备注信息
|
||||
'card_delay_seconds': row[14] or 0 # 延时秒数
|
||||
@ -2173,6 +2252,136 @@ class DBManager:
|
||||
except Exception as e:
|
||||
logger.error(f"更新发货次数失败: {e}")
|
||||
|
||||
def get_delivery_rules_by_keyword_and_spec(self, keyword: str, spec_name: str = None, spec_value: str = None):
|
||||
"""根据关键字和规格信息获取匹配的发货规则(支持多规格)"""
|
||||
with self.lock:
|
||||
try:
|
||||
cursor = self.conn.cursor()
|
||||
|
||||
# 优先匹配:卡券名称+规格名称+规格值
|
||||
if spec_name and spec_value:
|
||||
cursor.execute('''
|
||||
SELECT dr.id, dr.keyword, dr.card_id, dr.delivery_count, dr.enabled,
|
||||
dr.description, dr.delivery_times,
|
||||
c.name as card_name, c.type as card_type, c.api_config,
|
||||
c.text_content, c.data_content, c.enabled as card_enabled,
|
||||
c.description as card_description, c.delay_seconds as card_delay_seconds,
|
||||
c.is_multi_spec, c.spec_name, c.spec_value
|
||||
FROM delivery_rules dr
|
||||
LEFT JOIN cards c ON dr.card_id = c.id
|
||||
WHERE dr.enabled = 1 AND c.enabled = 1
|
||||
AND (? LIKE '%' || dr.keyword || '%' OR dr.keyword LIKE '%' || ? || '%')
|
||||
AND c.is_multi_spec = 1 AND c.spec_name = ? AND c.spec_value = ?
|
||||
ORDER BY
|
||||
CASE
|
||||
WHEN ? LIKE '%' || dr.keyword || '%' THEN LENGTH(dr.keyword)
|
||||
ELSE LENGTH(dr.keyword) / 2
|
||||
END DESC,
|
||||
dr.delivery_times ASC
|
||||
''', (keyword, keyword, spec_name, spec_value, keyword))
|
||||
|
||||
rules = []
|
||||
for row in cursor.fetchall():
|
||||
# 解析api_config JSON字符串
|
||||
api_config = row[9]
|
||||
if api_config:
|
||||
try:
|
||||
import json
|
||||
api_config = json.loads(api_config)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
# 如果解析失败,保持原始字符串
|
||||
pass
|
||||
|
||||
rules.append({
|
||||
'id': row[0],
|
||||
'keyword': row[1],
|
||||
'card_id': row[2],
|
||||
'delivery_count': row[3],
|
||||
'enabled': bool(row[4]),
|
||||
'description': row[5],
|
||||
'delivery_times': row[6] or 0,
|
||||
'card_name': row[7],
|
||||
'card_type': row[8],
|
||||
'api_config': api_config,
|
||||
'text_content': row[10],
|
||||
'data_content': row[11],
|
||||
'card_enabled': bool(row[12]),
|
||||
'card_description': row[13],
|
||||
'card_delay_seconds': row[14] or 0,
|
||||
'is_multi_spec': bool(row[15]),
|
||||
'spec_name': row[16],
|
||||
'spec_value': row[17]
|
||||
})
|
||||
|
||||
if rules:
|
||||
logger.info(f"找到多规格匹配规则: {keyword} - {spec_name}:{spec_value}")
|
||||
return rules
|
||||
|
||||
# 兜底匹配:仅卡券名称
|
||||
cursor.execute('''
|
||||
SELECT dr.id, dr.keyword, dr.card_id, dr.delivery_count, dr.enabled,
|
||||
dr.description, dr.delivery_times,
|
||||
c.name as card_name, c.type as card_type, c.api_config,
|
||||
c.text_content, c.data_content, c.enabled as card_enabled,
|
||||
c.description as card_description, c.delay_seconds as card_delay_seconds,
|
||||
c.is_multi_spec, c.spec_name, c.spec_value
|
||||
FROM delivery_rules dr
|
||||
LEFT JOIN cards c ON dr.card_id = c.id
|
||||
WHERE dr.enabled = 1 AND c.enabled = 1
|
||||
AND (? LIKE '%' || dr.keyword || '%' OR dr.keyword LIKE '%' || ? || '%')
|
||||
AND (c.is_multi_spec = 0 OR c.is_multi_spec IS NULL)
|
||||
ORDER BY
|
||||
CASE
|
||||
WHEN ? LIKE '%' || dr.keyword || '%' THEN LENGTH(dr.keyword)
|
||||
ELSE LENGTH(dr.keyword) / 2
|
||||
END DESC,
|
||||
dr.delivery_times ASC
|
||||
''', (keyword, keyword, keyword))
|
||||
|
||||
rules = []
|
||||
for row in cursor.fetchall():
|
||||
# 解析api_config JSON字符串
|
||||
api_config = row[9]
|
||||
if api_config:
|
||||
try:
|
||||
import json
|
||||
api_config = json.loads(api_config)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
# 如果解析失败,保持原始字符串
|
||||
pass
|
||||
|
||||
rules.append({
|
||||
'id': row[0],
|
||||
'keyword': row[1],
|
||||
'card_id': row[2],
|
||||
'delivery_count': row[3],
|
||||
'enabled': bool(row[4]),
|
||||
'description': row[5],
|
||||
'delivery_times': row[6] or 0,
|
||||
'card_name': row[7],
|
||||
'card_type': row[8],
|
||||
'api_config': api_config,
|
||||
'text_content': row[10],
|
||||
'data_content': row[11],
|
||||
'card_enabled': bool(row[12]),
|
||||
'card_description': row[13],
|
||||
'card_delay_seconds': row[14] or 0,
|
||||
'is_multi_spec': bool(row[15]) if row[15] is not None else False,
|
||||
'spec_name': row[16],
|
||||
'spec_value': row[17]
|
||||
})
|
||||
|
||||
if rules:
|
||||
logger.info(f"找到兜底匹配规则: {keyword}")
|
||||
else:
|
||||
logger.info(f"未找到匹配规则: {keyword}")
|
||||
|
||||
return rules
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取发货规则失败: {e}")
|
||||
return []
|
||||
|
||||
def delete_card(self, card_id: int):
|
||||
"""删除卡券"""
|
||||
with self.lock:
|
||||
@ -2463,6 +2672,49 @@ class DBManager:
|
||||
logger.error(f"获取商品信息失败: {e}")
|
||||
return None
|
||||
|
||||
def update_item_multi_spec_status(self, cookie_id: str, item_id: str, is_multi_spec: bool) -> bool:
|
||||
"""更新商品的多规格状态"""
|
||||
try:
|
||||
with self.lock:
|
||||
cursor = self.conn.cursor()
|
||||
cursor.execute('''
|
||||
UPDATE item_info
|
||||
SET is_multi_spec = ?, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE cookie_id = ? AND item_id = ?
|
||||
''', (is_multi_spec, cookie_id, item_id))
|
||||
|
||||
if cursor.rowcount > 0:
|
||||
self.conn.commit()
|
||||
logger.info(f"更新商品多规格状态成功: {item_id} -> {is_multi_spec}")
|
||||
return True
|
||||
else:
|
||||
logger.warning(f"商品不存在,无法更新多规格状态: {item_id}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"更新商品多规格状态失败: {e}")
|
||||
self.conn.rollback()
|
||||
return False
|
||||
|
||||
def get_item_multi_spec_status(self, cookie_id: str, item_id: str) -> bool:
|
||||
"""获取商品的多规格状态"""
|
||||
try:
|
||||
with self.lock:
|
||||
cursor = self.conn.cursor()
|
||||
cursor.execute('''
|
||||
SELECT is_multi_spec FROM item_info
|
||||
WHERE cookie_id = ? AND item_id = ?
|
||||
''', (cookie_id, item_id))
|
||||
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
return bool(row[0]) if row[0] is not None else False
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取商品多规格状态失败: {e}")
|
||||
return False
|
||||
|
||||
def get_items_by_cookie(self, cookie_id: str) -> List[Dict]:
|
||||
"""获取指定Cookie的所有商品信息
|
||||
|
||||
|
93
docker-rebuild.sh
Normal file
93
docker-rebuild.sh
Normal file
@ -0,0 +1,93 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ================================
|
||||
# 闲鱼自动回复系统 - Docker重新构建脚本
|
||||
# ================================
|
||||
|
||||
set -e
|
||||
|
||||
echo "🐳 闲鱼自动回复系统 - Docker重新构建"
|
||||
echo "=================================="
|
||||
|
||||
# 检查Docker是否运行
|
||||
if ! docker info > /dev/null 2>&1; then
|
||||
echo "❌ Docker未运行,请先启动Docker"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "📋 步骤1: 停止并删除现有容器"
|
||||
echo "--------------------------------"
|
||||
|
||||
# 停止现有容器
|
||||
if docker ps -q --filter "name=xianyu-auto-reply" | grep -q .; then
|
||||
echo "🛑 停止现有容器..."
|
||||
docker stop xianyu-auto-reply
|
||||
fi
|
||||
|
||||
# 删除现有容器
|
||||
if docker ps -aq --filter "name=xianyu-auto-reply" | grep -q .; then
|
||||
echo "🗑️ 删除现有容器..."
|
||||
docker rm xianyu-auto-reply
|
||||
fi
|
||||
|
||||
echo "📋 步骤2: 删除现有镜像"
|
||||
echo "--------------------------------"
|
||||
|
||||
# 删除现有镜像
|
||||
if docker images -q xianyu-auto-reply | grep -q .; then
|
||||
echo "🗑️ 删除现有镜像..."
|
||||
docker rmi xianyu-auto-reply
|
||||
fi
|
||||
|
||||
echo "📋 步骤3: 重新构建镜像"
|
||||
echo "--------------------------------"
|
||||
|
||||
echo "🔨 开始构建新镜像..."
|
||||
docker build -t xianyu-auto-reply .
|
||||
|
||||
echo "📋 步骤4: 启动新容器"
|
||||
echo "--------------------------------"
|
||||
|
||||
echo "🚀 启动新容器..."
|
||||
docker run -d \
|
||||
--name xianyu-auto-reply \
|
||||
--restart unless-stopped \
|
||||
-p 8080:8080 \
|
||||
-v "$(pwd)/data:/app/data" \
|
||||
-v "$(pwd)/logs:/app/logs" \
|
||||
-v "$(pwd)/backups:/app/backups" \
|
||||
-e DOCKER_ENV=true \
|
||||
xianyu-auto-reply
|
||||
|
||||
echo "📋 步骤5: 检查容器状态"
|
||||
echo "--------------------------------"
|
||||
|
||||
# 等待容器启动
|
||||
echo "⏳ 等待容器启动..."
|
||||
sleep 5
|
||||
|
||||
# 检查容器状态
|
||||
if docker ps --filter "name=xianyu-auto-reply" --filter "status=running" | grep -q xianyu-auto-reply; then
|
||||
echo "✅ 容器启动成功"
|
||||
|
||||
echo "📋 容器信息:"
|
||||
docker ps --filter "name=xianyu-auto-reply" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
|
||||
|
||||
echo "📋 最近日志:"
|
||||
docker logs --tail 20 xianyu-auto-reply
|
||||
|
||||
echo ""
|
||||
echo "🎉 Docker重新构建完成!"
|
||||
echo "=================================="
|
||||
echo "📱 Web界面: http://localhost:8080"
|
||||
echo "📊 健康检查: http://localhost:8080/health"
|
||||
echo "📋 查看日志: docker logs -f xianyu-auto-reply"
|
||||
echo "🛑 停止容器: docker stop xianyu-auto-reply"
|
||||
echo "🗑️ 删除容器: docker rm xianyu-auto-reply"
|
||||
|
||||
else
|
||||
echo "❌ 容器启动失败"
|
||||
echo "📋 错误日志:"
|
||||
docker logs xianyu-auto-reply
|
||||
exit 1
|
||||
fi
|
@ -1737,6 +1737,12 @@ def create_card(card_data: dict, current_user: Dict[str, Any] = Depends(get_curr
|
||||
|
||||
log_with_user('info', f"创建卡券: {card_name}", current_user)
|
||||
|
||||
# 验证多规格字段
|
||||
is_multi_spec = card_data.get('is_multi_spec', False)
|
||||
if is_multi_spec:
|
||||
if not card_data.get('spec_name') or not card_data.get('spec_value'):
|
||||
raise HTTPException(status_code=400, detail="多规格卡券必须提供规格名称和规格值")
|
||||
|
||||
card_id = db_manager.create_card(
|
||||
name=card_data.get('name'),
|
||||
card_type=card_data.get('type'),
|
||||
@ -1746,6 +1752,9 @@ def create_card(card_data: dict, current_user: Dict[str, Any] = Depends(get_curr
|
||||
description=card_data.get('description'),
|
||||
enabled=card_data.get('enabled', True),
|
||||
delay_seconds=card_data.get('delay_seconds', 0),
|
||||
is_multi_spec=is_multi_spec,
|
||||
spec_name=card_data.get('spec_name') if is_multi_spec else None,
|
||||
spec_value=card_data.get('spec_value') if is_multi_spec else None,
|
||||
user_id=user_id
|
||||
)
|
||||
|
||||
@ -1776,6 +1785,12 @@ def update_card(card_id: int, card_data: dict, _: None = Depends(require_auth)):
|
||||
"""更新卡券"""
|
||||
try:
|
||||
from db_manager import db_manager
|
||||
# 验证多规格字段
|
||||
is_multi_spec = card_data.get('is_multi_spec')
|
||||
if is_multi_spec:
|
||||
if not card_data.get('spec_name') or not card_data.get('spec_value'):
|
||||
raise HTTPException(status_code=400, detail="多规格卡券必须提供规格名称和规格值")
|
||||
|
||||
success = db_manager.update_card(
|
||||
card_id=card_id,
|
||||
name=card_data.get('name'),
|
||||
@ -1785,7 +1800,10 @@ def update_card(card_id: int, card_data: dict, _: None = Depends(require_auth)):
|
||||
data_content=card_data.get('data_content'),
|
||||
description=card_data.get('description'),
|
||||
enabled=card_data.get('enabled', True),
|
||||
delay_seconds=card_data.get('delay_seconds')
|
||||
delay_seconds=card_data.get('delay_seconds'),
|
||||
is_multi_spec=is_multi_spec,
|
||||
spec_name=card_data.get('spec_name'),
|
||||
spec_value=card_data.get('spec_value')
|
||||
)
|
||||
if success:
|
||||
return {"message": "卡券更新成功"}
|
||||
@ -3039,5 +3057,25 @@ def clear_table_data(table_name: str, admin_user: Dict[str, Any] = Depends(requi
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
# 商品多规格管理API
|
||||
@app.put("/items/{cookie_id}/{item_id}/multi-spec")
|
||||
def update_item_multi_spec(cookie_id: str, item_id: str, spec_data: dict, _: None = Depends(require_auth)):
|
||||
"""更新商品的多规格状态"""
|
||||
try:
|
||||
from db_manager import db_manager
|
||||
|
||||
is_multi_spec = spec_data.get('is_multi_spec', False)
|
||||
|
||||
success = db_manager.update_item_multi_spec_status(cookie_id, item_id, is_multi_spec)
|
||||
|
||||
if success:
|
||||
return {"message": f"商品多规格状态已{'开启' if is_multi_spec else '关闭'}"}
|
||||
else:
|
||||
raise HTTPException(status_code=404, detail="商品不存在")
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run(app, host="0.0.0.0", port=8080)
|
@ -2,7 +2,7 @@
|
||||
# 闲鱼自动回复系统 - Python依赖包
|
||||
# ================================
|
||||
|
||||
# Web框架和API相关
|
||||
# 核心Web框架
|
||||
fastapi>=0.111.0
|
||||
uvicorn[standard]>=0.29.0
|
||||
pydantic>=2.7.0
|
||||
@ -13,9 +13,11 @@ loguru>=0.7.0
|
||||
# 网络通信
|
||||
websockets>=10.0,<13.0
|
||||
aiohttp>=3.9.0
|
||||
requests>=2.31.0
|
||||
|
||||
# 配置文件处理
|
||||
PyYAML>=6.0.0
|
||||
python-dotenv>=1.0.1
|
||||
|
||||
# JavaScript执行引擎
|
||||
PyExecJS>=1.5.1
|
||||
@ -26,26 +28,21 @@ blackboxprotobuf>=1.0.1
|
||||
# 系统监控
|
||||
psutil>=5.9.0
|
||||
|
||||
# HTTP客户端
|
||||
requests>=2.31.0
|
||||
|
||||
# 文件上传支持
|
||||
python-multipart>=0.0.6
|
||||
|
||||
# AI回复相关
|
||||
# AI回复引擎
|
||||
openai>=1.65.5
|
||||
python-dotenv>=1.0.1
|
||||
|
||||
# 图像处理(图形验证码)
|
||||
# 图像处理(验证码生成)
|
||||
Pillow>=10.0.0
|
||||
|
||||
# 浏览器自动化(商品搜索功能)
|
||||
# 浏览器自动化(商品搜索、订单详情获取)
|
||||
playwright>=1.40.0
|
||||
|
||||
# 加密和安全
|
||||
PyJWT>=2.8.0
|
||||
passlib>=1.7.4
|
||||
bcrypt>=4.0.1
|
||||
passlib[bcrypt]>=1.7.4
|
||||
cryptography>=41.0.0
|
||||
|
||||
# 时间处理
|
||||
@ -54,7 +51,7 @@ python-dateutil>=2.8.2
|
||||
# 正则表达式增强
|
||||
regex>=2023.10.3
|
||||
|
||||
# Excel文件处理(导入导出功能)
|
||||
# Excel文件处理(数据导入导出)
|
||||
pandas>=2.0.0
|
||||
openpyxl>=3.1.0
|
||||
|
||||
|
@ -1419,12 +1419,13 @@
|
||||
<th style="width: 5%">
|
||||
<input type="checkbox" id="selectAllItems" onchange="toggleSelectAll(this)">
|
||||
</th>
|
||||
<th style="width: 15%">账号ID</th>
|
||||
<th style="width: 15%">商品ID</th>
|
||||
<th style="width: 20%">商品标题</th>
|
||||
<th style="width: 25%">商品详情</th>
|
||||
<th style="width: 12%">更新时间</th>
|
||||
<th style="width: 8%">操作</th>
|
||||
<th style="width: 12%">账号ID</th>
|
||||
<th style="width: 12%">商品ID</th>
|
||||
<th style="width: 18%">商品标题</th>
|
||||
<th style="width: 20%">商品详情</th>
|
||||
<th style="width: 8%">多规格</th>
|
||||
<th style="width: 10%">更新时间</th>
|
||||
<th style="width: 15%">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="itemsTableBody">
|
||||
@ -1567,6 +1568,7 @@
|
||||
<tr>
|
||||
<th>卡券名称</th>
|
||||
<th>类型</th>
|
||||
<th>规格信息</th>
|
||||
<th>数据量</th>
|
||||
<th>延时时间</th>
|
||||
<th>状态</th>
|
||||
@ -2157,7 +2159,7 @@
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="addCardForm">
|
||||
<form id="addCardForm" onsubmit="event.preventDefault(); saveCard(); return false;">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">卡券名称 <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="cardName" placeholder="例如:游戏点卡、会员卡等" required>
|
||||
@ -2253,6 +2255,46 @@
|
||||
备注内容会与发货内容一起发送。使用 <code>{DELIVERY_CONTENT}</code> 变量可以在备注中插入实际的发货内容。
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<!-- 多规格设置 -->
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="isMultiSpec" onchange="toggleMultiSpecFields()">
|
||||
<label class="form-check-label" for="isMultiSpec">
|
||||
<strong>多规格卡券</strong>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-text">开启后可以为同一商品的不同规格创建不同的卡券</div>
|
||||
</div>
|
||||
|
||||
<!-- 多规格字段 -->
|
||||
<div id="multiSpecFields" style="display: none;">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">规格名称 <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="specName" placeholder="例如:套餐类型、颜色、尺寸">
|
||||
<div class="form-text">规格的名称,如套餐类型、颜色等</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">规格值 <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="specValue" placeholder="例如:30天、红色、XL">
|
||||
<div class="form-text">具体的规格值,如30天、红色等</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle"></i>
|
||||
<strong>多规格说明:</strong>
|
||||
<ul class="mb-0 mt-2">
|
||||
<li>同一卡券名称可以创建多个不同规格的卡券</li>
|
||||
<li>卡券名称+规格名称+规格值必须唯一</li>
|
||||
<li>自动发货时会优先匹配精确规格,找不到时使用普通卡券兜底</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@ -2380,6 +2422,46 @@
|
||||
备注内容会与发货内容一起发送。使用 <code>{DELIVERY_CONTENT}</code> 变量可以在备注中插入实际的发货内容。
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<!-- 多规格设置 -->
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="editIsMultiSpec" onchange="toggleEditMultiSpecFields()">
|
||||
<label class="form-check-label" for="editIsMultiSpec">
|
||||
<strong>多规格卡券</strong>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-text">开启后可以为同一商品的不同规格创建不同的卡券</div>
|
||||
</div>
|
||||
|
||||
<!-- 多规格字段 -->
|
||||
<div id="editMultiSpecFields" style="display: none;">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">规格名称 <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="editSpecName" placeholder="例如:套餐类型、颜色、尺寸">
|
||||
<div class="form-text">规格的名称,如套餐类型、颜色等</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">规格值 <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="editSpecValue" placeholder="例如:30天、红色、XL">
|
||||
<div class="form-text">具体的规格值,如30天、红色等</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle"></i>
|
||||
<strong>多规格说明:</strong>
|
||||
<ul class="mb-0 mt-2">
|
||||
<li>同一卡券名称可以创建多个不同规格的卡券</li>
|
||||
<li>卡券名称+规格名称+规格值必须唯一</li>
|
||||
<li>自动发货时会优先匹配精确规格,找不到时使用普通卡券兜底</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@ -5021,7 +5103,7 @@
|
||||
if (cards.length === 0) {
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="7" class="text-center py-4 text-muted">
|
||||
<td colspan="8" class="text-center py-4 text-muted">
|
||||
<i class="bi bi-credit-card fs-1 d-block mb-3"></i>
|
||||
<h5>暂无卡券数据</h5>
|
||||
<p class="mb-0">点击"添加卡券"开始创建您的第一个卡券</p>
|
||||
@ -5071,12 +5153,19 @@
|
||||
`${card.delay_seconds}秒` :
|
||||
'<span class="text-muted">立即</span>';
|
||||
|
||||
// 规格信息显示
|
||||
let specDisplay = '<span class="text-muted">普通卡券</span>';
|
||||
if (card.is_multi_spec && card.spec_name && card.spec_value) {
|
||||
specDisplay = `<span class="badge bg-primary">${card.spec_name}: ${card.spec_value}</span>`;
|
||||
}
|
||||
|
||||
tr.innerHTML = `
|
||||
<td>
|
||||
<div class="fw-bold">${card.name}</div>
|
||||
${card.description ? `<small class="text-muted">${card.description}</small>` : ''}
|
||||
</td>
|
||||
<td>${typeBadge}</td>
|
||||
<td>${specDisplay}</td>
|
||||
<td>${dataCount}</td>
|
||||
<td>${delayDisplay}</td>
|
||||
<td>${statusBadge}</td>
|
||||
@ -5132,6 +5221,93 @@
|
||||
document.getElementById('dataFields').style.display = cardType === 'data' ? 'block' : 'none';
|
||||
}
|
||||
|
||||
// 切换多规格字段显示
|
||||
function toggleMultiSpecFields() {
|
||||
const isMultiSpec = document.getElementById('isMultiSpec').checked;
|
||||
document.getElementById('multiSpecFields').style.display = isMultiSpec ? 'block' : 'none';
|
||||
}
|
||||
|
||||
// 切换编辑多规格字段显示
|
||||
function toggleEditMultiSpecFields() {
|
||||
const checkbox = document.getElementById('editIsMultiSpec');
|
||||
const fieldsDiv = document.getElementById('editMultiSpecFields');
|
||||
|
||||
if (!checkbox) {
|
||||
console.error('编辑多规格开关元素未找到');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fieldsDiv) {
|
||||
console.error('编辑多规格字段容器未找到');
|
||||
return;
|
||||
}
|
||||
|
||||
const isMultiSpec = checkbox.checked;
|
||||
const displayStyle = isMultiSpec ? 'block' : 'none';
|
||||
|
||||
console.log('toggleEditMultiSpecFields - 多规格状态:', isMultiSpec);
|
||||
console.log('toggleEditMultiSpecFields - 设置显示样式:', displayStyle);
|
||||
|
||||
fieldsDiv.style.display = displayStyle;
|
||||
|
||||
// 验证设置是否生效
|
||||
console.log('toggleEditMultiSpecFields - 实际显示样式:', fieldsDiv.style.display);
|
||||
}
|
||||
|
||||
// 清空添加卡券表单
|
||||
function clearAddCardForm() {
|
||||
try {
|
||||
// 安全地清空表单字段
|
||||
const setElementValue = (id, value) => {
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
if (element.type === 'checkbox') {
|
||||
element.checked = value;
|
||||
} else {
|
||||
element.value = value;
|
||||
}
|
||||
} else {
|
||||
console.warn(`Element with id '${id}' not found`);
|
||||
}
|
||||
};
|
||||
|
||||
const setElementDisplay = (id, display) => {
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
element.style.display = display;
|
||||
} else {
|
||||
console.warn(`Element with id '${id}' not found`);
|
||||
}
|
||||
};
|
||||
|
||||
// 清空基本字段
|
||||
setElementValue('cardName', '');
|
||||
setElementValue('cardType', 'text');
|
||||
setElementValue('cardDescription', '');
|
||||
setElementValue('cardDelaySeconds', '0');
|
||||
setElementValue('isMultiSpec', false);
|
||||
setElementValue('specName', '');
|
||||
setElementValue('specValue', '');
|
||||
|
||||
// 隐藏多规格字段
|
||||
setElementDisplay('multiSpecFields', 'none');
|
||||
|
||||
// 清空类型相关字段
|
||||
setElementValue('textContent', '');
|
||||
setElementValue('dataContent', '');
|
||||
setElementValue('apiUrl', '');
|
||||
setElementValue('apiMethod', 'GET');
|
||||
setElementValue('apiHeaders', '');
|
||||
setElementValue('apiParams', '');
|
||||
setElementValue('apiTimeout', '10');
|
||||
|
||||
// 重置字段显示
|
||||
toggleCardTypeFields();
|
||||
} catch (error) {
|
||||
console.error('清空表单时出错:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 保存卡券
|
||||
async function saveCard() {
|
||||
try {
|
||||
@ -5143,12 +5319,26 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查多规格设置
|
||||
const isMultiSpec = document.getElementById('isMultiSpec').checked;
|
||||
const specName = document.getElementById('specName').value;
|
||||
const specValue = document.getElementById('specValue').value;
|
||||
|
||||
// 验证多规格字段
|
||||
if (isMultiSpec && (!specName || !specValue)) {
|
||||
showToast('多规格卡券必须填写规格名称和规格值', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const cardData = {
|
||||
name: cardName,
|
||||
type: cardType,
|
||||
description: document.getElementById('cardDescription').value,
|
||||
delay_seconds: parseInt(document.getElementById('cardDelaySeconds').value) || 0,
|
||||
enabled: true
|
||||
enabled: true,
|
||||
is_multi_spec: isMultiSpec,
|
||||
spec_name: isMultiSpec ? specName : null,
|
||||
spec_value: isMultiSpec ? specValue : null
|
||||
};
|
||||
|
||||
// 根据类型添加特定配置
|
||||
@ -5208,14 +5398,28 @@
|
||||
if (response.ok) {
|
||||
showToast('卡券保存成功', 'success');
|
||||
bootstrap.Modal.getInstance(document.getElementById('addCardModal')).hide();
|
||||
// 清空表单
|
||||
clearAddCardForm();
|
||||
loadCards();
|
||||
} else {
|
||||
const error = await response.text();
|
||||
showToast(`保存失败: ${error}`, 'danger');
|
||||
let errorMessage = '保存失败';
|
||||
try {
|
||||
const errorData = await response.json();
|
||||
errorMessage = errorData.error || errorData.detail || errorMessage;
|
||||
} catch (e) {
|
||||
// 如果不是JSON格式,尝试获取文本
|
||||
try {
|
||||
const errorText = await response.text();
|
||||
errorMessage = errorText || errorMessage;
|
||||
} catch (e2) {
|
||||
errorMessage = `HTTP ${response.status}: ${response.statusText}`;
|
||||
}
|
||||
}
|
||||
showToast(`保存失败: ${errorMessage}`, 'danger');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存卡券失败:', error);
|
||||
showToast('保存卡券失败', 'danger');
|
||||
showToast(`网络错误: ${error.message}`, 'danger');
|
||||
}
|
||||
}
|
||||
// ==================== 自动发货功能 ====================
|
||||
@ -5294,7 +5498,12 @@
|
||||
${rule.description ? `<small class="text-muted">${rule.description}</small>` : ''}
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-primary">${rule.card_name || '未知卡券'}</span>
|
||||
<div>
|
||||
<span class="badge bg-primary">${rule.card_name || '未知卡券'}</span>
|
||||
${rule.is_multi_spec && rule.spec_name && rule.spec_value ?
|
||||
`<br><small class="text-muted mt-1 d-block"><i class="bi bi-tags"></i> ${rule.spec_name}: ${rule.spec_value}</small>` :
|
||||
''}
|
||||
</div>
|
||||
</td>
|
||||
<td>${cardTypeBadge}</td>
|
||||
<td>
|
||||
@ -5364,7 +5573,20 @@
|
||||
if (card.enabled) { // 只显示启用的卡券
|
||||
const option = document.createElement('option');
|
||||
option.value = card.id;
|
||||
option.textContent = `${card.name} (${card.type === 'api' ? 'API' : card.type === 'text' ? '固定文字' : '批量数据'})`;
|
||||
|
||||
// 构建显示文本
|
||||
let displayText = card.name;
|
||||
|
||||
// 添加类型信息
|
||||
const typeText = card.type === 'api' ? 'API' : card.type === 'text' ? '固定文字' : '批量数据';
|
||||
displayText += ` (${typeText})`;
|
||||
|
||||
// 添加规格信息
|
||||
if (card.is_multi_spec && card.spec_name && card.spec_value) {
|
||||
displayText += ` [${card.spec_name}:${card.spec_value}]`;
|
||||
}
|
||||
|
||||
option.textContent = displayText;
|
||||
select.appendChild(option);
|
||||
}
|
||||
});
|
||||
@ -5440,6 +5662,17 @@
|
||||
document.getElementById('editCardDelaySeconds').value = card.delay_seconds || 0;
|
||||
document.getElementById('editCardEnabled').checked = card.enabled;
|
||||
|
||||
// 填充多规格字段
|
||||
const isMultiSpec = card.is_multi_spec || false;
|
||||
document.getElementById('editIsMultiSpec').checked = isMultiSpec;
|
||||
document.getElementById('editSpecName').value = card.spec_name || '';
|
||||
document.getElementById('editSpecValue').value = card.spec_value || '';
|
||||
|
||||
// 添加调试日志
|
||||
console.log('编辑卡券 - 多规格状态:', isMultiSpec);
|
||||
console.log('编辑卡券 - 规格名称:', card.spec_name);
|
||||
console.log('编辑卡券 - 规格值:', card.spec_value);
|
||||
|
||||
// 根据类型填充特定字段
|
||||
if (card.type === 'api' && card.api_config) {
|
||||
document.getElementById('editApiUrl').value = card.api_config.url || '';
|
||||
@ -5456,6 +5689,19 @@
|
||||
// 显示对应的字段
|
||||
toggleEditCardTypeFields();
|
||||
|
||||
// 使用延迟调用确保DOM更新完成后再显示多规格字段
|
||||
setTimeout(() => {
|
||||
console.log('延迟调用 toggleEditMultiSpecFields');
|
||||
toggleEditMultiSpecFields();
|
||||
|
||||
// 验证多规格字段是否正确显示
|
||||
const multiSpecElement = document.getElementById('editMultiSpecFields');
|
||||
const isChecked = document.getElementById('editIsMultiSpec').checked;
|
||||
console.log('多规格元素存在:', !!multiSpecElement);
|
||||
console.log('多规格开关状态:', isChecked);
|
||||
console.log('多规格字段显示状态:', multiSpecElement ? multiSpecElement.style.display : 'element not found');
|
||||
}, 100);
|
||||
|
||||
// 显示模态框
|
||||
const modal = new bootstrap.Modal(document.getElementById('editCardModal'));
|
||||
modal.show();
|
||||
@ -5489,12 +5735,26 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查多规格设置
|
||||
const isMultiSpec = document.getElementById('editIsMultiSpec').checked;
|
||||
const specName = document.getElementById('editSpecName').value;
|
||||
const specValue = document.getElementById('editSpecValue').value;
|
||||
|
||||
// 验证多规格字段
|
||||
if (isMultiSpec && (!specName || !specValue)) {
|
||||
showToast('多规格卡券必须填写规格名称和规格值', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const cardData = {
|
||||
name: cardName,
|
||||
type: cardType,
|
||||
description: document.getElementById('editCardDescription').value,
|
||||
delay_seconds: parseInt(document.getElementById('editCardDelaySeconds').value) || 0,
|
||||
enabled: document.getElementById('editCardEnabled').checked
|
||||
enabled: document.getElementById('editCardEnabled').checked,
|
||||
is_multi_spec: isMultiSpec,
|
||||
spec_name: isMultiSpec ? specName : null,
|
||||
spec_value: isMultiSpec ? specValue : null
|
||||
};
|
||||
|
||||
// 根据类型添加特定配置
|
||||
@ -5651,7 +5911,20 @@
|
||||
if (card.enabled) { // 只显示启用的卡券
|
||||
const option = document.createElement('option');
|
||||
option.value = card.id;
|
||||
option.textContent = `${card.name} (${card.type === 'api' ? 'API' : card.type === 'text' ? '固定文字' : '批量数据'})`;
|
||||
|
||||
// 构建显示文本
|
||||
let displayText = card.name;
|
||||
|
||||
// 添加类型信息
|
||||
const typeText = card.type === 'api' ? 'API' : card.type === 'text' ? '固定文字' : '批量数据';
|
||||
displayText += ` (${typeText})`;
|
||||
|
||||
// 添加规格信息
|
||||
if (card.is_multi_spec && card.spec_name && card.spec_value) {
|
||||
displayText += ` [${card.spec_name}:${card.spec_value}]`;
|
||||
}
|
||||
|
||||
option.textContent = displayText;
|
||||
select.appendChild(option);
|
||||
}
|
||||
});
|
||||
@ -6316,6 +6589,34 @@
|
||||
|
||||
// ==================== 商品管理功能 ====================
|
||||
|
||||
// 切换商品多规格状态
|
||||
async function toggleItemMultiSpec(cookieId, itemId, isMultiSpec) {
|
||||
try {
|
||||
const response = await fetch(`${apiBase}/items/${encodeURIComponent(cookieId)}/${encodeURIComponent(itemId)}/multi-spec`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${authToken}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
is_multi_spec: isMultiSpec
|
||||
})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
showToast(`${isMultiSpec ? '开启' : '关闭'}多规格成功`, 'success');
|
||||
// 刷新商品列表
|
||||
await refreshItemsData();
|
||||
} else {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.error || '操作失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('切换多规格状态失败:', error);
|
||||
showToast(`切换多规格状态失败: ${error.message}`, 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
// 加载商品列表
|
||||
async function loadItems() {
|
||||
try {
|
||||
@ -6475,7 +6776,7 @@
|
||||
const tbody = document.getElementById('itemsTableBody');
|
||||
|
||||
if (!items || items.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="7" class="text-center text-muted">暂无商品数据</td></tr>';
|
||||
tbody.innerHTML = '<tr><td colspan="8" class="text-center text-muted">暂无商品数据</td></tr>';
|
||||
// 重置选择状态
|
||||
const selectAllCheckbox = document.getElementById('selectAllItems');
|
||||
if (selectAllCheckbox) {
|
||||
@ -6511,6 +6812,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 多规格状态显示
|
||||
const isMultiSpec = item.is_multi_spec;
|
||||
const multiSpecDisplay = isMultiSpec ?
|
||||
'<span class="badge bg-success">多规格</span>' :
|
||||
'<span class="badge bg-secondary">普通</span>';
|
||||
|
||||
return `
|
||||
<tr>
|
||||
<td>
|
||||
@ -6523,6 +6830,7 @@
|
||||
<td>${escapeHtml(item.item_id)}</td>
|
||||
<td title="${escapeHtml(item.item_title || '未设置')}">${escapeHtml(itemTitleDisplay)}</td>
|
||||
<td title="${escapeHtml(item.item_detail || '未设置')}">${escapeHtml(itemDetailDisplay)}</td>
|
||||
<td>${multiSpecDisplay}</td>
|
||||
<td>${formatDateTime(item.updated_at)}</td>
|
||||
<td>
|
||||
<div class="btn-group" role="group">
|
||||
@ -6532,6 +6840,9 @@
|
||||
<button class="btn btn-sm btn-outline-danger" onclick="deleteItem('${escapeHtml(item.cookie_id)}', '${escapeHtml(item.item_id)}', '${escapeHtml(item.item_title || item.item_id)}')" title="删除">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm ${isMultiSpec ? 'btn-warning' : 'btn-success'}" onclick="toggleItemMultiSpec('${escapeHtml(item.cookie_id)}', '${escapeHtml(item.item_id)}', ${!isMultiSpec})" title="${isMultiSpec ? '关闭多规格' : '开启多规格'}">
|
||||
<i class="bi ${isMultiSpec ? 'bi-toggle-on' : 'bi-toggle-off'}"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
316
utils/order_detail_fetcher.py
Normal file
316
utils/order_detail_fetcher.py
Normal file
@ -0,0 +1,316 @@
|
||||
"""
|
||||
闲鱼订单详情获取工具
|
||||
基于Playwright实现订单详情页面访问和数据提取
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
from typing import Optional, Dict, Any
|
||||
from playwright.async_api import async_playwright, Browser, BrowserContext, Page
|
||||
from loguru import logger
|
||||
|
||||
|
||||
class OrderDetailFetcher:
|
||||
"""闲鱼订单详情获取器"""
|
||||
|
||||
def __init__(self, cookie_string: str = None):
|
||||
self.browser: Optional[Browser] = None
|
||||
self.context: Optional[BrowserContext] = None
|
||||
self.page: Optional[Page] = None
|
||||
|
||||
# 请求头配置
|
||||
self.headers = {
|
||||
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
||||
"accept-language": "en,zh-CN;q=0.9,zh;q=0.8,ru;q=0.7",
|
||||
"cache-control": "no-cache",
|
||||
"pragma": "no-cache",
|
||||
"priority": "u=0, i",
|
||||
"sec-ch-ua": "\"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"138\", \"Google Chrome\";v=\"138\"",
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-ch-ua-platform": "\"Windows\"",
|
||||
"sec-fetch-dest": "document",
|
||||
"sec-fetch-mode": "navigate",
|
||||
"sec-fetch-site": "same-origin",
|
||||
"sec-fetch-user": "?1",
|
||||
"upgrade-insecure-requests": "1"
|
||||
}
|
||||
|
||||
# Cookie配置 - 支持动态传入
|
||||
self.cookie = cookie_string
|
||||
|
||||
async def init_browser(self, headless: bool = True):
|
||||
"""初始化浏览器"""
|
||||
try:
|
||||
playwright = await async_playwright().start()
|
||||
|
||||
# 启动浏览器
|
||||
self.browser = await playwright.chromium.launch(
|
||||
headless=headless,
|
||||
args=[
|
||||
'--no-sandbox',
|
||||
'--disable-setuid-sandbox',
|
||||
'--disable-dev-shm-usage',
|
||||
'--disable-accelerated-2d-canvas',
|
||||
'--no-first-run',
|
||||
'--no-zygote',
|
||||
'--disable-gpu'
|
||||
]
|
||||
)
|
||||
|
||||
# 创建浏览器上下文
|
||||
self.context = await self.browser.new_context(
|
||||
viewport={'width': 1920, 'height': 1080},
|
||||
user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36'
|
||||
)
|
||||
|
||||
# 设置额外的HTTP头
|
||||
await self.context.set_extra_http_headers(self.headers)
|
||||
|
||||
# 创建页面
|
||||
self.page = await self.context.new_page()
|
||||
|
||||
# 设置Cookie
|
||||
await self._set_cookies()
|
||||
|
||||
logger.info("浏览器初始化成功")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"浏览器初始化失败: {e}")
|
||||
return False
|
||||
|
||||
async def _set_cookies(self):
|
||||
"""设置Cookie"""
|
||||
try:
|
||||
# 解析Cookie字符串
|
||||
cookies = []
|
||||
for cookie_pair in self.cookie.split('; '):
|
||||
if '=' in cookie_pair:
|
||||
name, value = cookie_pair.split('=', 1)
|
||||
cookies.append({
|
||||
'name': name.strip(),
|
||||
'value': value.strip(),
|
||||
'domain': '.goofish.com',
|
||||
'path': '/'
|
||||
})
|
||||
|
||||
# 添加Cookie到上下文
|
||||
await self.context.add_cookies(cookies)
|
||||
logger.info(f"已设置 {len(cookies)} 个Cookie")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"设置Cookie失败: {e}")
|
||||
|
||||
async def fetch_order_detail(self, order_id: str, timeout: int = 30) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
获取订单详情
|
||||
|
||||
Args:
|
||||
order_id: 订单ID
|
||||
timeout: 超时时间(秒)
|
||||
|
||||
Returns:
|
||||
包含订单详情的字典,失败时返回None
|
||||
"""
|
||||
try:
|
||||
if not self.page:
|
||||
logger.error("浏览器未初始化")
|
||||
return None
|
||||
|
||||
# 构建订单详情URL
|
||||
url = f"https://www.goofish.com/order-detail?orderId={order_id}&role=seller"
|
||||
logger.info(f"开始访问订单详情页面: {url}")
|
||||
|
||||
# 访问页面
|
||||
response = await self.page.goto(url, wait_until='networkidle', timeout=timeout * 1000)
|
||||
|
||||
if not response or response.status != 200:
|
||||
logger.error(f"页面访问失败,状态码: {response.status if response else 'None'}")
|
||||
return None
|
||||
|
||||
logger.info("页面加载成功,等待内容渲染...")
|
||||
|
||||
# 等待页面完全加载
|
||||
await self.page.wait_for_load_state('networkidle')
|
||||
|
||||
# 额外等待确保动态内容加载完成
|
||||
await asyncio.sleep(3)
|
||||
|
||||
# 获取并解析SKU信息
|
||||
sku_info = await self._get_sku_content()
|
||||
|
||||
# 获取页面标题
|
||||
title = await self.page.title()
|
||||
|
||||
result = {
|
||||
'order_id': order_id,
|
||||
'url': url,
|
||||
'title': title,
|
||||
'sku_info': sku_info, # 包含解析后的规格信息
|
||||
'spec_name': sku_info.get('spec_name', '') if sku_info else '',
|
||||
'spec_value': sku_info.get('spec_value', '') if sku_info else '',
|
||||
'timestamp': time.time()
|
||||
}
|
||||
|
||||
logger.info(f"订单详情获取成功: {order_id}")
|
||||
if sku_info:
|
||||
logger.info(f"规格信息 - 名称: {result['spec_name']}, 值: {result['spec_value']}")
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取订单详情失败: {e}")
|
||||
return None
|
||||
|
||||
def _parse_sku_content(self, sku_content: str) -> Dict[str, str]:
|
||||
"""
|
||||
解析SKU内容,根据冒号分割规格名称和规格值
|
||||
|
||||
Args:
|
||||
sku_content: 原始SKU内容字符串
|
||||
|
||||
Returns:
|
||||
包含规格名称和规格值的字典,如果解析失败则返回空字典
|
||||
"""
|
||||
try:
|
||||
if not sku_content or ':' not in sku_content:
|
||||
logger.warning(f"SKU内容格式无效或不包含冒号: {sku_content}")
|
||||
return {}
|
||||
|
||||
# 根据冒号分割
|
||||
parts = sku_content.split(':', 1) # 只分割第一个冒号
|
||||
|
||||
if len(parts) == 2:
|
||||
spec_name = parts[0].strip()
|
||||
spec_value = parts[1].strip()
|
||||
|
||||
if spec_name and spec_value:
|
||||
result = {
|
||||
'spec_name': spec_name,
|
||||
'spec_value': spec_value
|
||||
}
|
||||
logger.info(f"SKU解析成功 - 规格名称: {spec_name}, 规格值: {spec_value}")
|
||||
return result
|
||||
else:
|
||||
logger.warning(f"SKU解析失败,规格名称或值为空: 名称='{spec_name}', 值='{spec_value}'")
|
||||
return {}
|
||||
else:
|
||||
logger.warning(f"SKU内容分割失败: {sku_content}")
|
||||
return {}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"解析SKU内容异常: {e}")
|
||||
return {}
|
||||
|
||||
async def _get_sku_content(self) -> Optional[Dict[str, str]]:
|
||||
"""获取并解析SKU内容"""
|
||||
try:
|
||||
# 等待SKU元素出现
|
||||
sku_selector = '.sku--u_ddZval'
|
||||
|
||||
# 检查元素是否存在
|
||||
sku_element = await self.page.query_selector(sku_selector)
|
||||
|
||||
if sku_element:
|
||||
# 获取元素文本内容
|
||||
sku_content = await sku_element.text_content()
|
||||
if sku_content:
|
||||
sku_content = sku_content.strip()
|
||||
logger.info(f"找到SKU原始内容: {sku_content}")
|
||||
print(f"🛍️ SKU原始内容: {sku_content}")
|
||||
|
||||
# 解析SKU内容
|
||||
parsed_sku = self._parse_sku_content(sku_content)
|
||||
if parsed_sku:
|
||||
print(f"📋 规格名称: {parsed_sku['spec_name']}")
|
||||
print(f"📝 规格值: {parsed_sku['spec_value']}")
|
||||
return parsed_sku
|
||||
else:
|
||||
logger.warning("SKU内容解析失败")
|
||||
return {}
|
||||
else:
|
||||
logger.warning("SKU元素内容为空")
|
||||
return {}
|
||||
else:
|
||||
logger.warning("未找到SKU元素")
|
||||
|
||||
# 尝试获取页面的所有class包含sku的元素
|
||||
all_sku_elements = await self.page.query_selector_all('[class*="sku"]')
|
||||
if all_sku_elements:
|
||||
logger.info(f"找到 {len(all_sku_elements)} 个包含'sku'的元素")
|
||||
for i, element in enumerate(all_sku_elements):
|
||||
class_name = await element.get_attribute('class')
|
||||
text_content = await element.text_content()
|
||||
logger.info(f"SKU元素 {i+1}: class='{class_name}', text='{text_content}'")
|
||||
|
||||
return {}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取SKU内容失败: {e}")
|
||||
return {}
|
||||
|
||||
async def close(self):
|
||||
"""关闭浏览器"""
|
||||
try:
|
||||
if self.page:
|
||||
await self.page.close()
|
||||
if self.context:
|
||||
await self.context.close()
|
||||
if self.browser:
|
||||
await self.browser.close()
|
||||
logger.info("浏览器已关闭")
|
||||
except Exception as e:
|
||||
logger.error(f"关闭浏览器失败: {e}")
|
||||
|
||||
async def __aenter__(self):
|
||||
"""异步上下文管理器入口"""
|
||||
await self.init_browser()
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||
"""异步上下文管理器出口"""
|
||||
await self.close()
|
||||
|
||||
|
||||
# 便捷函数
|
||||
async def fetch_order_detail_simple(order_id: str, cookie_string: str = None, headless: bool = True) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
简单的订单详情获取函数
|
||||
|
||||
Args:
|
||||
order_id: 订单ID
|
||||
cookie_string: Cookie字符串,如果不提供则使用默认值
|
||||
headless: 是否无头模式
|
||||
|
||||
Returns:
|
||||
订单详情字典或None
|
||||
"""
|
||||
fetcher = OrderDetailFetcher(cookie_string)
|
||||
try:
|
||||
if await fetcher.init_browser(headless=headless):
|
||||
return await fetcher.fetch_order_detail(order_id)
|
||||
finally:
|
||||
await fetcher.close()
|
||||
return None
|
||||
|
||||
|
||||
# 测试代码
|
||||
if __name__ == "__main__":
|
||||
async def test():
|
||||
# 测试订单ID
|
||||
test_order_id = "2856024697612814489"
|
||||
|
||||
print(f"🔍 开始获取订单详情: {test_order_id}")
|
||||
|
||||
result = await fetch_order_detail_simple(test_order_id, headless=False)
|
||||
|
||||
if result:
|
||||
print("✅ 订单详情获取成功:")
|
||||
print(f"📋 订单ID: {result['order_id']}")
|
||||
print(f"🌐 URL: {result['url']}")
|
||||
print(f"📄 页面标题: {result['title']}")
|
||||
print(f"🛍️ SKU内容: {result['sku_content']}")
|
||||
else:
|
||||
print("❌ 订单详情获取失败")
|
||||
|
||||
# 运行测试
|
||||
asyncio.run(test())
|
Loading…
x
Reference in New Issue
Block a user