支持api卡券传递订单信息

支持api卡券传递订单信息
This commit is contained in:
zhinianboke 2025-08-13 19:36:27 +08:00 committed by GitHub
commit fb0a31645a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 465 additions and 7 deletions

View File

@ -2558,8 +2558,8 @@ class XianyuLive:
# 根据卡券类型处理发货内容 # 根据卡券类型处理发货内容
if rule['card_type'] == 'api': if rule['card_type'] == 'api':
# API类型调用API获取内容 # API类型调用API获取内容,传入订单和商品信息用于动态参数替换
delivery_content = await self._get_api_card_content(rule) delivery_content = await self._get_api_card_content(rule, order_id, item_id, send_user_id, spec_name, spec_value)
elif rule['card_type'] == 'text': elif rule['card_type'] == 'text':
# 固定文字类型:直接使用文字内容 # 固定文字类型:直接使用文字内容
@ -2623,8 +2623,8 @@ class XianyuLive:
# 出错时返回原始发货内容 # 出错时返回原始发货内容
return delivery_content return delivery_content
async def _get_api_card_content(self, rule, retry_count=0): async def _get_api_card_content(self, rule, order_id=None, item_id=None, buyer_id=None, spec_name=None, spec_value=None, retry_count=0):
"""调用API获取卡券内容支持重试机制""" """调用API获取卡券内容支持动态参数替换和重试机制"""
max_retries = 4 max_retries = 4
if retry_count >= max_retries: if retry_count >= max_retries:
@ -2657,8 +2657,14 @@ class XianyuLive:
if isinstance(params, str): if isinstance(params, str):
params = json.loads(params) params = json.loads(params)
# 如果是POST请求且有动态参数进行参数替换
if method == 'POST' and params:
params = await self._replace_api_dynamic_params(params, order_id, item_id, buyer_id, spec_name, spec_value)
retry_info = f" (重试 {retry_count + 1}/{max_retries})" if retry_count > 0 else "" retry_info = f" (重试 {retry_count + 1}/{max_retries})" if retry_count > 0 else ""
logger.info(f"调用API获取卡券: {method} {url}{retry_info}") logger.info(f"调用API获取卡券: {method} {url}{retry_info}")
if method == 'POST' and params:
logger.debug(f"POST请求参数: {json.dumps(params, ensure_ascii=False)}")
# 确保session存在 # 确保session存在
if not self.session: if not self.session:
@ -2702,7 +2708,7 @@ class XianyuLive:
wait_time = (retry_count + 1) * 2 # 递增等待时间: 2s, 4s, 6s wait_time = (retry_count + 1) * 2 # 递增等待时间: 2s, 4s, 6s
logger.info(f"等待 {wait_time} 秒后重试...") logger.info(f"等待 {wait_time} 秒后重试...")
await asyncio.sleep(wait_time) await asyncio.sleep(wait_time)
return await self._get_api_card_content(rule, retry_count + 1) return await self._get_api_card_content(rule, order_id, item_id, buyer_id, spec_name, spec_value, retry_count + 1)
return None return None
@ -2714,7 +2720,7 @@ class XianyuLive:
wait_time = (retry_count + 1) * 2 # 递增等待时间 wait_time = (retry_count + 1) * 2 # 递增等待时间
logger.info(f"等待 {wait_time} 秒后重试...") logger.info(f"等待 {wait_time} 秒后重试...")
await asyncio.sleep(wait_time) await asyncio.sleep(wait_time)
return await self._get_api_card_content(rule, retry_count + 1) return await self._get_api_card_content(rule, order_id, item_id, buyer_id, spec_name, spec_value, retry_count + 1)
else: else:
logger.error(f"API调用网络异常已达到最大重试次数: {self._safe_str(e)}") logger.error(f"API调用网络异常已达到最大重试次数: {self._safe_str(e)}")
return None return None
@ -2723,6 +2729,122 @@ class XianyuLive:
logger.error(f"API调用异常: {self._safe_str(e)}") logger.error(f"API调用异常: {self._safe_str(e)}")
return None return None
async def _replace_api_dynamic_params(self, params, order_id=None, item_id=None, buyer_id=None, spec_name=None, spec_value=None):
"""替换API请求参数中的动态参数"""
try:
if not params or not isinstance(params, dict):
return params
# 获取订单和商品信息
order_info = None
item_info = None
# 如果有订单ID获取订单信息
if order_id:
try:
from db_manager import db_manager
# 尝试从数据库获取订单信息
order_info = db_manager.get_order_by_id(order_id)
if not order_info:
# 如果数据库中没有尝试通过API获取
order_detail = await self.fetch_order_detail_info(order_id, item_id, buyer_id)
if order_detail:
order_info = order_detail
logger.debug(f"通过API获取到订单信息: {order_id}")
else:
logger.warning(f"无法获取订单信息: {order_id}")
else:
logger.debug(f"从数据库获取到订单信息: {order_id}")
except Exception as e:
logger.warning(f"获取订单信息失败: {self._safe_str(e)}")
# 如果有商品ID获取商品信息
if item_id:
try:
from db_manager import db_manager
item_info = db_manager.get_item_info(self.cookie_id, item_id)
if item_info:
logger.debug(f"从数据库获取到商品信息: {item_id}")
else:
logger.warning(f"无法获取商品信息: {item_id}")
except Exception as e:
logger.warning(f"获取商品信息失败: {self._safe_str(e)}")
# 构建参数映射
param_mapping = {
'order_id': order_id or '',
'item_id': item_id or '',
'buyer_id': buyer_id or '',
'cookie_id': self.cookie_id or '',
'spec_name': spec_name or '',
'spec_value': spec_value or '',
}
# 从订单信息中提取参数
if order_info:
param_mapping.update({
'order_amount': str(order_info.get('amount', '')),
'order_quantity': str(order_info.get('quantity', '')),
})
# 从商品信息中提取参数
if item_info:
# 处理商品详情如果是JSON字符串则提取detail字段
item_detail = item_info.get('item_detail', '')
if item_detail:
try:
# 尝试解析JSON
import json
detail_data = json.loads(item_detail)
if isinstance(detail_data, dict) and 'detail' in detail_data:
item_detail = detail_data['detail']
except (json.JSONDecodeError, TypeError):
# 如果不是JSON或解析失败使用原始字符串
pass
param_mapping.update({
'item_detail': item_detail,
})
# 递归替换参数
replaced_params = self._recursive_replace_params(params, param_mapping)
# 记录替换的参数
replaced_keys = []
for key, value in replaced_params.items():
if isinstance(value, str) and '{' in str(params.get(key, '')):
replaced_keys.append(key)
if replaced_keys:
logger.info(f"API动态参数替换完成替换的参数: {replaced_keys}")
logger.debug(f"参数映射: {param_mapping}")
return replaced_params
except Exception as e:
logger.error(f"替换API动态参数失败: {self._safe_str(e)}")
return params
def _recursive_replace_params(self, obj, param_mapping):
"""递归替换参数中的占位符"""
if isinstance(obj, dict):
result = {}
for key, value in obj.items():
result[key] = self._recursive_replace_params(value, param_mapping)
return result
elif isinstance(obj, list):
return [self._recursive_replace_params(item, param_mapping) for item in obj]
elif isinstance(obj, str):
# 替换字符串中的占位符
result = obj
for param_key, param_value in param_mapping.items():
placeholder = f"{{{param_key}}}"
if placeholder in result:
result = result.replace(placeholder, str(param_value))
return result
else:
return obj
async def token_refresh_loop(self): async def token_refresh_loop(self):
"""Token刷新循环""" """Token刷新循环"""
while True: while True:

View File

@ -301,3 +301,103 @@
font-size: 0.75rem; font-size: 0.75rem;
} }
} }
/* ================================
API参数提示样式
================================ */
.param-item {
display: flex;
align-items: center;
padding: 0.25rem 0;
}
.param-name {
background-color: #f8f9fa;
color: #495057;
padding: 0.2rem 0.4rem;
border-radius: 4px;
font-family: 'Courier New', monospace;
font-size: 0.85rem;
font-weight: 500;
border: 1px solid #dee2e6;
cursor: pointer;
transition: all 0.2s ease;
min-width: 120px;
display: inline-block;
}
.param-name:hover {
background-color: #e9ecef;
border-color: #adb5bd;
transform: translateY(-1px);
}
#postParamsHelp,
#editPostParamsHelp {
background-color: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 1rem;
}
#postParamsHelp .alert,
#editPostParamsHelp .alert {
margin-bottom: 0;
border-radius: 6px;
}
/* 参数项点击效果 */
.param-name:active {
transform: translateY(0);
background-color: #dee2e6;
}
/* ================================
API参数提示样式
================================ */
.param-item {
display: flex;
align-items: center;
padding: 0.25rem 0;
}
.param-name {
background-color: #f8f9fa;
color: #495057;
padding: 0.2rem 0.4rem;
border-radius: 4px;
font-family: 'Courier New', monospace;
font-size: 0.85rem;
font-weight: 500;
border: 1px solid #dee2e6;
cursor: pointer;
transition: all 0.2s ease;
min-width: 120px;
display: inline-block;
}
.param-name:hover {
background-color: #e9ecef;
border-color: #adb5bd;
transform: translateY(-1px);
}
#postParamsHelp,
#editPostParamsHelp {
background-color: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 1rem;
}
#postParamsHelp .alert,
#editPostParamsHelp .alert {
margin-bottom: 0;
border-radius: 6px;
}
/* 参数项点击效果 */
.param-name:active {
transform: translateY(0);
background-color: #dee2e6;
}

View File

@ -509,7 +509,7 @@
<!-- 商品列表 --> <!-- 商品列表 -->
<div class="card"> <div class="card">
<div class="card-header d-flex justify-content-between align-items-center"> <div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">商品列表(自动发货根据商品标题和商品详情匹配关键字)</h5> <h5 class="mb-0">商品列表(用于设置商品的默认回复信息)</h5>
<button class="btn btn-sm btn-outline-danger" onclick="batchDeleteItemReplies()"> <button class="btn btn-sm btn-outline-danger" onclick="batchDeleteItemReplies()">
<i class="bi bi-trash"></i> 批量删除 <i class="bi bi-trash"></i> 批量删除
</button> </button>
@ -2152,6 +2152,66 @@
<div class="mb-3"> <div class="mb-3">
<label class="form-label">请求参数 (JSON格式)</label> <label class="form-label">请求参数 (JSON格式)</label>
<textarea class="form-control" id="apiParams" rows="3" placeholder='{"type": "card", "count": 1}'></textarea> <textarea class="form-control" id="apiParams" rows="3" placeholder='{"type": "card", "count": 1}'></textarea>
<!-- POST类型参数提示 -->
<div id="postParamsHelp" class="mt-2" style="display: none;">
<small class="form-text text-muted">
<i class="bi bi-info-circle me-1"></i>
<strong>POST请求可用参数可选</strong>
</small>
<div class="mt-2">
<div class="row">
<div class="col-md-6">
<div class="param-item mb-2">
<code class="param-name">order_id</code>
<span class="text-muted ms-2">订单编号</span>
</div>
<div class="param-item mb-2">
<code class="param-name">item_id</code>
<span class="text-muted ms-2">商品编号</span>
</div>
<div class="param-item mb-2">
<code class="param-name">item_detail</code>
<span class="text-muted ms-2">商品详情</span>
</div>
<div class="param-item mb-2">
<code class="param-name">order_amount</code>
<span class="text-muted ms-2">订单金额</span>
</div>
</div>
<div class="col-md-6">
<div class="param-item mb-2">
<code class="param-name">order_quantity</code>
<span class="text-muted ms-2">订单数量</span>
</div>
<div class="param-item mb-2">
<code class="param-name">spec_name</code>
<span class="text-muted ms-2">规格名称</span>
</div>
<div class="param-item mb-2">
<code class="param-name">spec_value</code>
<span class="text-muted ms-2">规格值</span>
</div>
<div class="param-item mb-2">
<code class="param-name">cookie_id</code>
<span class="text-muted ms-2">cookies账号id</span>
</div>
<div class="param-item mb-2">
<code class="param-name">buyer_id</code>
<span class="text-muted ms-2">买家id</span>
</div>
</div>
</div>
<div class="alert alert-info mt-2 mb-0">
<small>
<i class="bi bi-lightbulb me-1"></i>
<strong>使用说明:</strong>这些参数都是可选的您可以根据API需要选择使用。例如
<br>
<code>{"order_id": "{order_id}", "item_id": "{item_id}", "quantity": "{order_quantity}"}</code>
</small>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -2336,6 +2396,66 @@
<div class="mb-3"> <div class="mb-3">
<label class="form-label">请求参数 (JSON格式)</label> <label class="form-label">请求参数 (JSON格式)</label>
<textarea class="form-control" id="editApiParams" rows="3"></textarea> <textarea class="form-control" id="editApiParams" rows="3"></textarea>
<!-- POST类型参数提示 -->
<div id="editPostParamsHelp" class="mt-2" style="display: none;">
<small class="form-text text-muted">
<i class="bi bi-info-circle me-1"></i>
<strong>POST请求可用参数可选</strong>
</small>
<div class="mt-2">
<div class="row">
<div class="col-md-6">
<div class="param-item mb-2">
<code class="param-name">order_id</code>
<span class="text-muted ms-2">订单编号</span>
</div>
<div class="param-item mb-2">
<code class="param-name">item_id</code>
<span class="text-muted ms-2">商品编号</span>
</div>
<div class="param-item mb-2">
<code class="param-name">item_detail</code>
<span class="text-muted ms-2">商品详情</span>
</div>
<div class="param-item mb-2">
<code class="param-name">order_amount</code>
<span class="text-muted ms-2">订单金额</span>
</div>
</div>
<div class="col-md-6">
<div class="param-item mb-2">
<code class="param-name">order_quantity</code>
<span class="text-muted ms-2">订单数量</span>
</div>
<div class="param-item mb-2">
<code class="param-name">spec_name</code>
<span class="text-muted ms-2">规格名称</span>
</div>
<div class="param-item mb-2">
<code class="param-name">spec_value</code>
<span class="text-muted ms-2">规格值</span>
</div>
<div class="param-item mb-2">
<code class="param-name">cookie_id</code>
<span class="text-muted ms-2">cookies账号id</span>
</div>
<div class="param-item mb-2">
<code class="param-name">buyer_id</code>
<span class="text-muted ms-2">买家id</span>
</div>
</div>
</div>
<div class="alert alert-info mt-2 mb-0">
<small>
<i class="bi bi-lightbulb me-1"></i>
<strong>使用说明:</strong>这些参数都是可选的您可以根据API需要选择使用。例如
<br>
<code>{"order_id": "{order_id}", "item_id": "{item_id}", "quantity": "{order_quantity}"}</code>
</small>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -3374,6 +3374,96 @@ function toggleCardTypeFields() {
document.getElementById('textFields').style.display = cardType === 'text' ? 'block' : 'none'; document.getElementById('textFields').style.display = cardType === 'text' ? 'block' : 'none';
document.getElementById('dataFields').style.display = cardType === 'data' ? 'block' : 'none'; document.getElementById('dataFields').style.display = cardType === 'data' ? 'block' : 'none';
document.getElementById('imageFields').style.display = cardType === 'image' ? 'block' : 'none'; document.getElementById('imageFields').style.display = cardType === 'image' ? 'block' : 'none';
// 如果是API类型初始化API方法监听
if (cardType === 'api') {
toggleApiParamsHelp();
// 添加API方法变化监听
const apiMethodSelect = document.getElementById('apiMethod');
if (apiMethodSelect) {
apiMethodSelect.removeEventListener('change', toggleApiParamsHelp);
apiMethodSelect.addEventListener('change', toggleApiParamsHelp);
}
}
}
// 切换API参数提示显示
function toggleApiParamsHelp() {
const apiMethod = document.getElementById('apiMethod').value;
const postParamsHelp = document.getElementById('postParamsHelp');
if (postParamsHelp) {
postParamsHelp.style.display = apiMethod === 'POST' ? 'block' : 'none';
// 如果显示参数提示,添加点击事件
if (apiMethod === 'POST') {
initParamClickHandlers('apiParams', 'postParamsHelp');
}
}
}
// 初始化参数点击处理器
function initParamClickHandlers(textareaId, containerId) {
const container = document.getElementById(containerId);
const textarea = document.getElementById(textareaId);
if (!container || !textarea) return;
// 移除现有的点击事件监听器
const paramNames = container.querySelectorAll('.param-name');
paramNames.forEach(paramName => {
paramName.removeEventListener('click', handleParamClick);
});
// 添加新的点击事件监听器
paramNames.forEach(paramName => {
paramName.addEventListener('click', function() {
handleParamClick(this, textarea);
});
});
}
// 处理参数点击事件
function handleParamClick(paramElement, textarea) {
const paramName = paramElement.textContent.trim();
const paramValue = `{${paramName}}`;
try {
// 获取当前textarea的值
let currentValue = textarea.value.trim();
// 如果当前值为空或不是有效的JSON创建新的JSON对象
if (!currentValue || currentValue === '{}') {
const newJson = {};
newJson[paramName] = paramValue;
textarea.value = JSON.stringify(newJson, null, 2);
} else {
// 尝试解析现有的JSON
let jsonObj;
try {
jsonObj = JSON.parse(currentValue);
} catch (e) {
// 如果解析失败创建新的JSON对象
jsonObj = {};
}
// 添加新参数
jsonObj[paramName] = paramValue;
// 更新textarea
textarea.value = JSON.stringify(jsonObj, null, 2);
}
// 触发change事件
textarea.dispatchEvent(new Event('change'));
// 显示成功提示
showToast(`已添加参数: ${paramName}`, 'success');
} catch (error) {
console.error('添加参数时出错:', error);
showToast('添加参数失败', 'danger');
}
} }
// 切换多规格字段显示 // 切换多规格字段显示
@ -4147,6 +4237,32 @@ function toggleEditCardTypeFields() {
document.getElementById('editTextFields').style.display = cardType === 'text' ? 'block' : 'none'; document.getElementById('editTextFields').style.display = cardType === 'text' ? 'block' : 'none';
document.getElementById('editDataFields').style.display = cardType === 'data' ? 'block' : 'none'; document.getElementById('editDataFields').style.display = cardType === 'data' ? 'block' : 'none';
document.getElementById('editImageFields').style.display = cardType === 'image' ? 'block' : 'none'; document.getElementById('editImageFields').style.display = cardType === 'image' ? 'block' : 'none';
// 如果是API类型初始化API方法监听
if (cardType === 'api') {
toggleEditApiParamsHelp();
// 添加API方法变化监听
const editApiMethodSelect = document.getElementById('editApiMethod');
if (editApiMethodSelect) {
editApiMethodSelect.removeEventListener('change', toggleEditApiParamsHelp);
editApiMethodSelect.addEventListener('change', toggleEditApiParamsHelp);
}
}
}
// 切换编辑API参数提示显示
function toggleEditApiParamsHelp() {
const apiMethod = document.getElementById('editApiMethod').value;
const editPostParamsHelp = document.getElementById('editPostParamsHelp');
if (editPostParamsHelp) {
editPostParamsHelp.style.display = apiMethod === 'POST' ? 'block' : 'none';
// 如果显示参数提示,添加点击事件
if (apiMethod === 'POST') {
initParamClickHandlers('editApiParams', 'editPostParamsHelp');
}
}
} }
// 更新卡券 // 更新卡券