支持多数量发货

This commit is contained in:
zhinianboke 2025-08-12 17:14:20 +08:00
parent 042c09329c
commit d5ae0488b5
5 changed files with 214 additions and 36 deletions

View File

@ -522,10 +522,56 @@ 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, order_id, send_user_id)
# 检查是否需要多数量发货
from db_manager import db_manager
quantity_to_send = 1 # 默认发送1个
if delivery_content:
# 检查商品是否开启了多数量发货
multi_quantity_delivery = db_manager.get_item_multi_quantity_delivery_status(self.cookie_id, item_id)
if multi_quantity_delivery and order_id:
logger.info(f"商品 {item_id} 开启了多数量发货,获取订单详情...")
try:
# 使用现有方法获取订单详情
order_detail = await self.fetch_order_detail_info(order_id, item_id, send_user_id)
if order_detail and order_detail.get('quantity'):
try:
order_quantity = int(order_detail['quantity'])
if order_quantity > 1:
quantity_to_send = order_quantity
logger.info(f"从订单详情获取数量: {order_quantity},将发送 {quantity_to_send} 个卡券")
else:
logger.info(f"订单数量为 {order_quantity},发送单个卡券")
except (ValueError, TypeError):
logger.warning(f"订单数量格式无效: {order_detail.get('quantity')},发送单个卡券")
else:
logger.info(f"未获取到订单数量信息,发送单个卡券")
except Exception as e:
logger.error(f"获取订单详情失败: {self._safe_str(e)},发送单个卡券")
elif not multi_quantity_delivery:
logger.info(f"商品 {item_id} 未开启多数量发货,发送单个卡券")
else:
logger.info(f"无订单ID发送单个卡券")
# 多次调用自动发货方法,每次获取不同的内容
delivery_contents = []
success_count = 0
for i in range(quantity_to_send):
try:
# 每次调用都可能获取不同的内容API卡券、批量数据等
delivery_content = await self._auto_delivery(item_id, item_title, order_id, send_user_id)
if delivery_content:
delivery_contents.append(delivery_content)
success_count += 1
if quantity_to_send > 1:
logger.info(f"{i+1}/{quantity_to_send} 个卡券内容获取成功")
else:
logger.warning(f"{i+1}/{quantity_to_send} 个卡券内容获取失败")
except Exception as e:
logger.error(f"{i+1}/{quantity_to_send} 个卡券获取异常: {self._safe_str(e)}")
if delivery_contents:
# 标记已发货(防重复)- 基于订单ID
self.mark_delivery_sent(order_id)
@ -541,35 +587,55 @@ class XianyuLive:
delay_task = asyncio.create_task(self._delayed_lock_release(lock_key, delay_minutes=10))
self._lock_hold_info[lock_key]['task'] = delay_task
# 检查是否是图片发送标记
if delivery_content.startswith("__IMAGE_SEND__"):
# 提取卡券ID和图片URL
image_data = delivery_content.replace("__IMAGE_SEND__", "")
if "|" in image_data:
card_id_str, image_url = image_data.split("|", 1)
try:
card_id = int(card_id_str)
except ValueError:
logger.error(f"无效的卡券ID: {card_id_str}")
card_id = None
else:
# 兼容旧格式没有卡券ID
card_id = None
image_url = image_data
# 发送图片消息
# 发送所有获取到的发货内容
for i, delivery_content in enumerate(delivery_contents):
try:
await self.send_image_msg(websocket, chat_id, send_user_id, image_url, card_id=card_id)
logger.info(f'[{msg_time}] 【自动发货图片】已向 {user_url} 发送图片: {image_url}')
await self.send_delivery_failure_notification(send_user_name, send_user_id, item_id, "发货成功")
# 检查是否是图片发送标记
if delivery_content.startswith("__IMAGE_SEND__"):
# 提取卡券ID和图片URL
image_data = delivery_content.replace("__IMAGE_SEND__", "")
if "|" in image_data:
card_id_str, image_url = image_data.split("|", 1)
try:
card_id = int(card_id_str)
except ValueError:
logger.error(f"无效的卡券ID: {card_id_str}")
card_id = None
else:
# 兼容旧格式没有卡券ID
card_id = None
image_url = image_data
# 发送图片消息
await self.send_image_msg(websocket, chat_id, send_user_id, image_url, card_id=card_id)
if len(delivery_contents) > 1:
logger.info(f'[{msg_time}] 【多数量自动发货图片】第 {i+1}/{len(delivery_contents)} 张已向 {user_url} 发送图片: {image_url}')
else:
logger.info(f'[{msg_time}] 【自动发货图片】已向 {user_url} 发送图片: {image_url}')
# 多数量发货时消息间隔1秒
if len(delivery_contents) > 1 and i < len(delivery_contents) - 1:
await asyncio.sleep(1)
else:
# 普通文本发货内容
await self.send_msg(websocket, chat_id, send_user_id, delivery_content)
if len(delivery_contents) > 1:
logger.info(f'[{msg_time}] 【多数量自动发货】第 {i+1}/{len(delivery_contents)} 条已向 {user_url} 发送发货内容')
else:
logger.info(f'[{msg_time}] 【自动发货】已向 {user_url} 发送发货内容')
# 多数量发货时消息间隔1秒
if len(delivery_contents) > 1 and i < len(delivery_contents) - 1:
await asyncio.sleep(1)
except Exception as e:
logger.error(f"自动发货图片失败: {self._safe_str(e)}")
await self.send_msg(websocket, chat_id, send_user_id, "抱歉,图片发送失败,请联系客服。")
await self.send_delivery_failure_notification(send_user_name, send_user_id, item_id, "图片发送失败")
logger.error(f"发送第 {i+1} 条消息失败: {self._safe_str(e)}")
# 发送成功通知
if len(delivery_contents) > 1:
await self.send_delivery_failure_notification(send_user_name, send_user_id, item_id, f"多数量发货成功,共发送 {len(delivery_contents)} 个卡券")
else:
# 普通文本发货内容
await self.send_msg(websocket, chat_id, send_user_id, delivery_content)
logger.info(f'[{msg_time}] 【自动发货】已向 {user_url} 发送发货内容')
await self.send_delivery_failure_notification(send_user_name, send_user_id, item_id, "发货成功")
else:
logger.warning(f'[{msg_time}] 【自动发货】未找到匹配的发货规则或获取发货内容失败')
@ -2530,6 +2596,8 @@ class XianyuLive:
logger.error(f"自动发货失败: {self._safe_str(e)}")
return None
def _process_delivery_content_with_description(self, delivery_content: str, card_description: str) -> str:
"""处理发货内容和备注信息,实现变量替换"""
try:

View File

@ -660,6 +660,14 @@ class DBManager:
self._execute_sql(cursor, "ALTER TABLE item_info ADD COLUMN is_multi_spec BOOLEAN DEFAULT FALSE")
logger.info("为item_info表添加多规格字段")
# 为item_info表添加多数量发货字段如果不存在
try:
self._execute_sql(cursor, "SELECT multi_quantity_delivery FROM item_info LIMIT 1")
except sqlite3.OperationalError:
# 多数量发货字段不存在,需要添加
self._execute_sql(cursor, "ALTER TABLE item_info ADD COLUMN multi_quantity_delivery BOOLEAN DEFAULT FALSE")
logger.info("为item_info表添加多数量发货字段")
# 处理keywords表的唯一约束问题
# 由于SQLite不支持直接修改约束我们需要重建表
self._migrate_keywords_table_constraints(cursor)
@ -3613,6 +3621,49 @@ class DBManager:
logger.error(f"获取商品多规格状态失败: {e}")
return False
def update_item_multi_quantity_delivery_status(self, cookie_id: str, item_id: str, multi_quantity_delivery: bool) -> bool:
"""更新商品的多数量发货状态"""
try:
with self.lock:
cursor = self.conn.cursor()
cursor.execute('''
UPDATE item_info
SET multi_quantity_delivery = ?, updated_at = CURRENT_TIMESTAMP
WHERE cookie_id = ? AND item_id = ?
''', (multi_quantity_delivery, cookie_id, item_id))
if cursor.rowcount > 0:
self.conn.commit()
logger.info(f"更新商品多数量发货状态成功: {item_id} -> {multi_quantity_delivery}")
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_quantity_delivery_status(self, cookie_id: str, item_id: str) -> bool:
"""获取商品的多数量发货状态"""
try:
with self.lock:
cursor = self.conn.cursor()
cursor.execute('''
SELECT multi_quantity_delivery 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的所有商品信息

View File

@ -4041,6 +4041,26 @@ def update_item_multi_spec(cookie_id: str, item_id: str, spec_data: dict, _: Non
raise HTTPException(status_code=500, detail=str(e))
# 商品多数量发货管理API
@app.put("/items/{cookie_id}/{item_id}/multi-quantity-delivery")
def update_item_multi_quantity_delivery(cookie_id: str, item_id: str, delivery_data: dict, _: None = Depends(require_auth)):
"""更新商品的多数量发货状态"""
try:
from db_manager import db_manager
multi_quantity_delivery = delivery_data.get('multi_quantity_delivery', False)
success = db_manager.update_item_multi_quantity_delivery_status(cookie_id, item_id, multi_quantity_delivery)
if success:
return {"message": f"商品多数量发货状态已{'开启' if multi_quantity_delivery else '关闭'}"}
else:
raise HTTPException(status_code=404, detail="商品不存在")
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# 移除自动启动由Start.py或手动启动
# if __name__ == "__main__":
# uvicorn.run(app, host="0.0.0.0", port=8080)

View File

@ -399,19 +399,20 @@
<th style="width: 5%">
<input type="checkbox" id="selectAllItems" onchange="toggleSelectAll(this)">
</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: 20%">商品价格</th>
<th style="width: 10%">账号ID</th>
<th style="width: 10%">商品ID</th>
<th style="width: 16%">商品标题</th>
<th style="width: 18%">商品详情</th>
<th style="width: 18%">商品价格</th>
<th style="width: 8%">多规格</th>
<th style="width: 8%">多数量发货</th>
<th style="width: 10%">更新时间</th>
<th style="width: 15%">操作</th>
</tr>
</thead>
<tbody id="itemsTableBody">
<tr>
<td colspan="9" class="text-center text-muted">加载中...</td>
<td colspan="10" class="text-center text-muted">加载中...</td>
</tr>
</tbody>
</table>

View File

@ -4966,6 +4966,34 @@ async function toggleItemMultiSpec(cookieId, itemId, isMultiSpec) {
}
}
// 切换商品多数量发货状态
async function toggleItemMultiQuantityDelivery(cookieId, itemId, multiQuantityDelivery) {
try {
const response = await fetch(`${apiBase}/items/${encodeURIComponent(cookieId)}/${encodeURIComponent(itemId)}/multi-quantity-delivery`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`
},
body: JSON.stringify({
multi_quantity_delivery: multiQuantityDelivery
})
});
if (response.ok) {
showToast(`${multiQuantityDelivery ? '开启' : '关闭'}多数量发货成功`, '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 {
@ -5181,7 +5209,7 @@ function displayCurrentPageItems() {
const tbody = document.getElementById('itemsTableBody');
if (!filteredItemsData || filteredItemsData.length === 0) {
tbody.innerHTML = '<tr><td colspan="9" class="text-center text-muted">暂无商品数据</td></tr>';
tbody.innerHTML = '<tr><td colspan="10" class="text-center text-muted">暂无商品数据</td></tr>';
resetItemsSelection();
return;
}
@ -5211,6 +5239,12 @@ function displayCurrentPageItems() {
'<span class="badge bg-success">多规格</span>' :
'<span class="badge bg-secondary">普通</span>';
// 多数量发货状态显示
const isMultiQuantityDelivery = item.multi_quantity_delivery;
const multiQuantityDeliveryDisplay = isMultiQuantityDelivery ?
'<span class="badge bg-success">已开启</span>' :
'<span class="badge bg-secondary">已关闭</span>';
return `
<tr>
<td>
@ -5225,6 +5259,7 @@ function displayCurrentPageItems() {
<td title="${escapeHtml(getItemDetailText(item.item_detail || ''))}">${escapeHtml(itemDetailDisplay)}</td>
<td>${escapeHtml(item.item_price || '未设置')}</td>
<td>${multiSpecDisplay}</td>
<td>${multiQuantityDeliveryDisplay}</td>
<td>${formatDateTime(item.updated_at)}</td>
<td>
<div class="btn-group" role="group">
@ -5237,6 +5272,9 @@ function displayCurrentPageItems() {
<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>
<button class="btn btn-sm ${isMultiQuantityDelivery ? 'btn-warning' : 'btn-success'}" onclick="toggleItemMultiQuantityDelivery('${escapeHtml(item.cookie_id)}', '${escapeHtml(item.item_id)}', ${!isMultiQuantityDelivery})" title="${isMultiQuantityDelivery ? '关闭多数量发货' : '开启多数量发货'}">
<i class="bi ${isMultiQuantityDelivery ? 'bi-box-arrow-down' : 'bi-box-arrow-up'}"></i>
</button>
</div>
</td>
</tr>