优化发送图片

This commit is contained in:
zhinianboke 2025-08-04 23:09:25 +08:00
parent 3a09c74963
commit a8296a82e5
5 changed files with 353 additions and 10 deletions

View File

@ -261,11 +261,23 @@ class XianyuLive:
# 检查是否是图片发送标记
if delivery_content.startswith("__IMAGE_SEND__"):
# 提取图片URL
image_url = delivery_content.replace("__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
# 发送图片消息
try:
await self.send_image_msg(websocket, chat_id, send_user_id, image_url)
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, "发货成功")
except Exception as e:
@ -1081,6 +1093,18 @@ class XianyuLive:
except Exception as e:
logger.error(f"更新关键词图片URL失败: {e}")
async def _update_card_image_url(self, card_id: int, new_image_url: str):
"""更新卡券的图片URL"""
try:
from db_manager import db_manager
success = db_manager.update_card_image_url(card_id, new_image_url)
if success:
logger.info(f"卡券图片URL已更新: 卡券ID={card_id} -> {new_image_url}")
else:
logger.warning(f"卡券图片URL更新失败: 卡券ID={card_id}")
except Exception as e:
logger.error(f"更新卡券图片URL失败: {e}")
async def get_ai_reply(self, send_user_name: str, send_user_id: str, send_message: str, item_id: str, chat_id: str):
"""获取AI回复"""
try:
@ -1935,11 +1959,11 @@ class XianyuLive:
delivery_content = db_manager.consume_batch_data(rule['card_id'])
elif rule['card_type'] == 'image':
# 图片类型:返回图片发送标记
# 图片类型:返回图片发送标记包含卡券ID
image_url = rule.get('image_url')
if image_url:
delivery_content = f"__IMAGE_SEND__{image_url}"
logger.info(f"准备发送图片: {image_url}")
delivery_content = f"__IMAGE_SEND__{rule['card_id']}|{image_url}"
logger.info(f"准备发送图片: {image_url} (卡券ID: {rule['card_id']})")
else:
logger.error(f"图片卡券缺少图片URL: 卡券ID={rule['card_id']}")
delivery_content = None
@ -2788,7 +2812,7 @@ class XianyuLive:
if reply:
# 检查是否是图片发送标记
if reply.startswith("__IMAGE_SEND__"):
# 提取图片URL
# 提取图片URL关键词回复不包含卡券ID
image_url = reply.replace("__IMAGE_SEND__", "")
# 发送图片消息
try:
@ -3124,7 +3148,7 @@ class XianyuLive:
"items": all_items
}
async def send_image_msg(self, ws, cid, toid, image_url, width=800, height=600):
async def send_image_msg(self, ws, cid, toid, image_url, width=800, height=600, card_id=None):
"""发送图片消息"""
try:
# 检查图片URL是否需要上传到CDN
@ -3149,6 +3173,10 @@ class XianyuLive:
logger.info(f"{self.cookie_id}】图片上传成功CDN URL: {cdn_url}")
image_url = cdn_url
# 如果是卡券图片更新数据库中的图片URL
if card_id is not None:
await self._update_card_image_url(card_id, cdn_url)
# 获取实际图片尺寸
from utils.image_utils import image_manager
try:

View File

@ -2545,8 +2545,9 @@ 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,
is_multi_spec: bool = None, spec_name: str = None, spec_value: str = None):
image_url: str = 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:
@ -2580,6 +2581,9 @@ class DBManager:
if data_content is not None:
update_fields.append("data_content = ?")
params.append(data_content)
if image_url is not None:
update_fields.append("image_url = ?")
params.append(image_url)
if description is not None:
update_fields.append("description = ?")
params.append(description)
@ -2620,6 +2624,32 @@ class DBManager:
self.conn.rollback()
raise
def update_card_image_url(self, card_id: int, new_image_url: str) -> bool:
"""更新卡券的图片URL"""
with self.lock:
try:
cursor = self.conn.cursor()
# 更新图片URL
self._execute_sql(cursor,
"UPDATE cards SET image_url = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND type = 'image'",
(new_image_url, card_id))
self.conn.commit()
# 检查是否有行被更新
if cursor.rowcount > 0:
logger.info(f"卡券图片URL更新成功: 卡券ID: {card_id}, 新URL: {new_image_url}")
return True
else:
logger.warning(f"未找到匹配的图片卡券: 卡券ID: {card_id}")
return False
except Exception as e:
logger.error(f"更新卡券图片URL失败: {e}")
self.conn.rollback()
return False
# ==================== 自动发货规则方法 ====================
def create_delivery_rule(self, keyword: str, card_id: int, delivery_count: int = 1,

View File

@ -2148,6 +2148,7 @@ def update_card(card_id: int, card_data: dict, _: None = Depends(require_auth)):
api_config=card_data.get('api_config'),
text_content=card_data.get('text_content'),
data_content=card_data.get('data_content'),
image_url=card_data.get('image_url'),
description=card_data.get('description'),
enabled=card_data.get('enabled', True),
delay_seconds=card_data.get('delay_seconds'),
@ -2163,6 +2164,76 @@ def update_card(card_id: int, card_data: dict, _: None = Depends(require_auth)):
raise HTTPException(status_code=500, detail=str(e))
@app.put("/cards/{card_id}/image")
async def update_card_with_image(
card_id: int,
image: UploadFile = File(...),
name: str = Form(...),
type: str = Form(...),
description: str = Form(default=""),
delay_seconds: int = Form(default=0),
enabled: bool = Form(default=True),
is_multi_spec: bool = Form(default=False),
spec_name: str = Form(default=""),
spec_value: str = Form(default=""),
current_user: Dict[str, Any] = Depends(get_current_user)
):
"""更新带图片的卡券"""
try:
logger.info(f"接收到带图片的卡券更新请求: card_id={card_id}, name={name}, type={type}")
# 验证图片文件
if not image.content_type or not image.content_type.startswith('image/'):
logger.warning(f"无效的图片文件类型: {image.content_type}")
raise HTTPException(status_code=400, detail="请上传图片文件")
# 验证多规格字段
if is_multi_spec:
if not spec_name or not spec_value:
raise HTTPException(status_code=400, detail="多规格卡券必须提供规格名称和规格值")
# 读取图片数据
image_data = await image.read()
logger.info(f"读取图片数据成功,大小: {len(image_data)} bytes")
# 保存图片
image_url = image_manager.save_image(image_data, image.filename)
if not image_url:
logger.error("图片保存失败")
raise HTTPException(status_code=400, detail="图片保存失败")
logger.info(f"图片保存成功: {image_url}")
# 更新卡券
from db_manager import db_manager
success = db_manager.update_card(
card_id=card_id,
name=name,
card_type=type,
image_url=image_url,
description=description,
enabled=enabled,
delay_seconds=delay_seconds,
is_multi_spec=is_multi_spec,
spec_name=spec_name if is_multi_spec else None,
spec_value=spec_value if is_multi_spec else None
)
if success:
logger.info(f"卡券更新成功: {name} (ID: {card_id})")
return {"message": "卡券更新成功", "image_url": image_url}
else:
# 如果数据库更新失败,删除已保存的图片
image_manager.delete_image(image_url)
raise HTTPException(status_code=404, detail="卡券不存在")
except HTTPException:
raise
except Exception as e:
logger.error(f"更新带图片的卡券失败: {e}")
raise HTTPException(status_code=500, detail=str(e))
# 自动发货规则API
@app.get("/delivery-rules")
def get_delivery_rules(current_user: Dict[str, Any] = Depends(get_current_user)):

View File

@ -1528,6 +1528,7 @@
<option value="api">API接口</option>
<option value="text">固定文字</option>
<option value="data">批量数据</option>
<option value="image">图片</option>
</select>
</div>
@ -1592,6 +1593,45 @@
</div>
</div>
<!-- 图片配置 -->
<div id="editImageFields" class="card mb-3" style="display: none;">
<div class="card-header">
<h6 class="mb-0">图片配置</h6>
</div>
<div class="card-body">
<div class="mb-3">
<label class="form-label">当前图片</label>
<div id="editCurrentImagePreview" style="display: none;">
<img id="editCurrentImg" src="" alt="当前图片"
style="max-width: 100%; max-height: 200px; border-radius: 8px; border: 1px solid #ddd;">
<div class="mt-2">
<small class="text-muted">当前使用的图片</small>
</div>
</div>
<div id="editNoImageText" class="text-muted">
<i class="bi bi-image me-1"></i>暂无图片
</div>
</div>
<div class="mb-3">
<label class="form-label">更换图片</label>
<input type="file" class="form-control" id="editCardImageFile" accept="image/*">
<small class="form-text text-muted">
<i class="bi bi-info-circle me-1"></i>
支持JPG、PNG、GIF格式最大5MB建议尺寸不超过4096x4096像素
</small>
</div>
<div id="editCardImagePreview" class="mb-3" style="display: none;">
<label class="form-label">新图片预览</label>
<div class="preview-container">
<img id="editCardPreviewImg" src="" alt="预览图片"
style="max-width: 100%; max-height: 300px; border-radius: 8px; border: 1px solid #ddd;">
</div>
</div>
</div>
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="editCardEnabled">

View File

@ -1682,6 +1682,9 @@ document.addEventListener('DOMContentLoaded', async () => {
// 初始化卡券图片文件选择器
initCardImageFileSelector();
// 初始化编辑卡券图片文件选择器
initEditCardImageFileSelector();
// 点击侧边栏外部关闭移动端菜单
document.addEventListener('click', function(e) {
const sidebar = document.getElementById('sidebar');
@ -3183,6 +3186,101 @@ function hideCardImagePreview() {
}
}
// 初始化编辑卡券图片文件选择器
function initEditCardImageFileSelector() {
const fileInput = document.getElementById('editCardImageFile');
if (fileInput) {
fileInput.addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
// 验证文件类型
if (!file.type.startsWith('image/')) {
showToast('❌ 请选择图片文件,当前文件类型:' + file.type, 'warning');
e.target.value = '';
hideEditCardImagePreview();
return;
}
// 验证文件大小5MB
if (file.size > 5 * 1024 * 1024) {
showToast('❌ 图片文件大小不能超过 5MB当前文件大小' + (file.size / 1024 / 1024).toFixed(1) + 'MB', 'warning');
e.target.value = '';
hideEditCardImagePreview();
return;
}
// 验证图片尺寸
validateEditCardImageDimensions(file, e.target);
} else {
hideEditCardImagePreview();
}
});
}
}
// 验证编辑卡券图片尺寸
function validateEditCardImageDimensions(file, inputElement) {
const img = new Image();
const url = URL.createObjectURL(file);
img.onload = function() {
const width = this.naturalWidth;
const height = this.naturalHeight;
URL.revokeObjectURL(url);
// 检查尺寸限制
if (width > 4096 || height > 4096) {
showToast(`❌ 图片尺寸过大(${width}x${height}),最大支持 4096x4096 像素`, 'warning');
inputElement.value = '';
hideEditCardImagePreview();
return;
}
// 显示图片预览
showEditCardImagePreview(file);
// 如果图片较大,提示会被压缩
if (width > 2048 || height > 2048) {
showToast(` 图片尺寸较大(${width}x${height}),上传时将自动压缩以优化性能`, 'info');
} else {
showToast(`✅ 图片尺寸合适(${width}x${height}),可以上传`, 'success');
}
};
img.onerror = function() {
URL.revokeObjectURL(url);
showToast('❌ 无法读取图片文件,请选择有效的图片', 'warning');
inputElement.value = '';
hideEditCardImagePreview();
};
img.src = url;
}
// 显示编辑卡券图片预览
function showEditCardImagePreview(file) {
const reader = new FileReader();
reader.onload = function(e) {
const previewImg = document.getElementById('editCardPreviewImg');
const previewContainer = document.getElementById('editCardImagePreview');
if (previewImg && previewContainer) {
previewImg.src = e.target.result;
previewContainer.style.display = 'block';
}
};
reader.readAsDataURL(file);
}
// 隐藏编辑卡券图片预览
function hideEditCardImagePreview() {
const previewContainer = document.getElementById('editCardImagePreview');
if (previewContainer) {
previewContainer.style.display = 'none';
}
}
// 切换编辑多规格字段显示
function toggleEditMultiSpecFields() {
const checkbox = document.getElementById('editIsMultiSpec');
@ -3688,6 +3786,26 @@ async function editCard(cardId) {
document.getElementById('editTextContent').value = card.text_content || '';
} else if (card.type === 'data') {
document.getElementById('editDataContent').value = card.data_content || '';
} else if (card.type === 'image') {
// 处理图片类型
const currentImagePreview = document.getElementById('editCurrentImagePreview');
const currentImg = document.getElementById('editCurrentImg');
const noImageText = document.getElementById('editNoImageText');
if (card.image_url) {
// 显示当前图片
currentImg.src = card.image_url;
currentImagePreview.style.display = 'block';
noImageText.style.display = 'none';
} else {
// 没有图片
currentImagePreview.style.display = 'none';
noImageText.style.display = 'block';
}
// 清空文件选择器和预览
document.getElementById('editCardImageFile').value = '';
document.getElementById('editCardImagePreview').style.display = 'none';
}
// 显示对应的字段
@ -3725,6 +3843,7 @@ function toggleEditCardTypeFields() {
document.getElementById('editApiFields').style.display = cardType === 'api' ? 'block' : 'none';
document.getElementById('editTextFields').style.display = cardType === 'text' ? 'block' : 'none';
document.getElementById('editDataFields').style.display = cardType === 'data' ? 'block' : 'none';
document.getElementById('editImageFields').style.display = cardType === 'image' ? 'block' : 'none';
}
// 更新卡券
@ -3804,6 +3923,16 @@ async function updateCard() {
case 'data':
cardData.data_content = document.getElementById('editDataContent').value;
break;
case 'image':
// 处理图片类型 - 如果有新图片则上传,否则保持原有图片
const imageFile = document.getElementById('editCardImageFile').files[0];
if (imageFile) {
// 有新图片,需要上传
await updateCardWithImage(cardId, cardData, imageFile);
return; // 提前返回,因为上传图片是异步的
}
// 没有新图片,保持原有配置,继续正常更新流程
break;
}
const response = await fetch(`${apiBase}/cards/${cardId}`, {
@ -3829,6 +3958,51 @@ async function updateCard() {
}
}
// 更新带图片的卡券
async function updateCardWithImage(cardId, cardData, imageFile) {
try {
// 创建FormData对象
const formData = new FormData();
// 添加图片文件
formData.append('image', imageFile);
// 添加卡券数据
Object.keys(cardData).forEach(key => {
if (cardData[key] !== null && cardData[key] !== undefined) {
if (typeof cardData[key] === 'object') {
formData.append(key, JSON.stringify(cardData[key]));
} else {
formData.append(key, cardData[key]);
}
}
});
const response = await fetch(`${apiBase}/cards/${cardId}/image`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${authToken}`
// 不设置Content-Type让浏览器自动设置multipart/form-data
},
body: formData
});
if (response.ok) {
showToast('卡券更新成功', 'success');
bootstrap.Modal.getInstance(document.getElementById('editCardModal')).hide();
loadCards();
} else {
const error = await response.text();
showToast(`更新失败: ${error}`, 'danger');
}
} catch (error) {
console.error('更新带图片的卡券失败:', error);
showToast('更新卡券失败', 'danger');
}
}
// 测试卡券(占位函数)
function testCard(cardId) {
showToast('测试功能开发中...', 'info');