修复:企微群聊不发送告警截图
根因:_get_presigned_url 对完整 COS 永久 URL 直接原样返回, 未重新生成预签名 URL,导致私有桶的图片下载失败(403)。 修复: 1. _get_presigned_url 增加 COS URL 识别,提取 object key 重新签名 2. 新增 _extract_cos_object_key 解析两种 COS URL 格式 3. send-card 增加截图诊断日志(追踪 IoT/DB 来源和最终 URL) 4. upload_media_from_url 增加下载/上传诊断日志
This commit is contained in:
@@ -128,6 +128,11 @@ async def send_card(request: Request):
|
||||
if alarm_snapshot_key:
|
||||
presigned_url = _get_presigned_url(alarm_snapshot_key)
|
||||
|
||||
logger.info(f"群聊截图诊断: alarm={req.alarmId}, "
|
||||
f"iot_snapshot={req.snapshotUrl!r}, "
|
||||
f"db_snapshot={alarm_snapshot_key!r}, "
|
||||
f"presigned_url={presigned_url[:80] if presigned_url else '(空)'}...")
|
||||
|
||||
# alarm_type: 用 alarm_type_code 避免"告警告警"
|
||||
actual_alarm_type = alarm_type_code or req.title
|
||||
|
||||
|
||||
@@ -144,10 +144,19 @@ async def process_alarm_notification(alarm_data: Dict):
|
||||
|
||||
|
||||
def _get_presigned_url(snapshot_url: str) -> str:
|
||||
"""将 COS object key 转为预签名 URL"""
|
||||
"""将 COS object key 或永久 URL 转为预签名 URL"""
|
||||
if not snapshot_url:
|
||||
return ""
|
||||
# 如果是完整 COS 永久 URL,提取 object key 后重新生成预签名 URL
|
||||
if snapshot_url.startswith("http"):
|
||||
object_key = _extract_cos_object_key(snapshot_url)
|
||||
if object_key:
|
||||
try:
|
||||
from app.services.oss_storage import get_oss_storage
|
||||
return get_oss_storage().get_presigned_url(object_key)
|
||||
except Exception as e:
|
||||
logger.warning(f"从COS URL生成预签名失败: {e}")
|
||||
# 无法提取 key,返回原 URL(可能是外部 URL)
|
||||
return snapshot_url
|
||||
try:
|
||||
from app.services.oss_storage import get_oss_storage
|
||||
@@ -157,6 +166,37 @@ def _get_presigned_url(snapshot_url: str) -> str:
|
||||
return snapshot_url
|
||||
|
||||
|
||||
def _extract_cos_object_key(url: str) -> str:
|
||||
"""从 COS 永久 URL 中提取 object key
|
||||
|
||||
支持格式:
|
||||
- https://{bucket}.cos.{region}.myqcloud.com/{key}
|
||||
- https://cos.{region}.myqcloud.com/{bucket}/{key}
|
||||
"""
|
||||
try:
|
||||
from urllib.parse import urlparse
|
||||
parsed = urlparse(url)
|
||||
host = parsed.hostname or ""
|
||||
path = parsed.path.lstrip("/")
|
||||
|
||||
if not path or "myqcloud.com" not in host:
|
||||
return ""
|
||||
|
||||
# 格式1: {bucket}.cos.{region}.myqcloud.com/{key}
|
||||
if ".cos." in host and host.endswith(".myqcloud.com"):
|
||||
return path
|
||||
|
||||
# 格式2: cos.{region}.myqcloud.com/{bucket}/{key}
|
||||
if host.startswith("cos.") and host.endswith(".myqcloud.com"):
|
||||
parts = path.split("/", 1)
|
||||
if len(parts) == 2:
|
||||
return parts[1]
|
||||
|
||||
return ""
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
|
||||
def _save_vlm_result(alarm_id: str, vlm_result: Dict):
|
||||
"""将 VLM 复核结果写入 alarm_llm_analysis 表"""
|
||||
db = get_session()
|
||||
|
||||
@@ -218,9 +218,12 @@ class WeChatService:
|
||||
|
||||
async def upload_media_from_url(self, image_url: str) -> Optional[str]:
|
||||
"""从 URL 下载图片后上传到企微,返回 media_id"""
|
||||
logger.info(f"企微截图上传: 开始下载 url={image_url[:100]}...")
|
||||
image_data = await self._download_image(image_url)
|
||||
if not image_data:
|
||||
logger.warning(f"企微截图上传: 下载失败,无法上传 url={image_url[:100]}")
|
||||
return None
|
||||
logger.info(f"企微截图上传: 下载成功,size={len(image_data)} bytes,开始上传到企微")
|
||||
return await self.upload_media(image_data)
|
||||
|
||||
# ==================== 个人消息:按钮交互型模板卡片 ====================
|
||||
|
||||
Reference in New Issue
Block a user