Files
life-echo/api/app/core/cos_url_keys.py

122 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""从 URL 解析当前环境腾讯云 COS object key仅当 host 与配置一致时)。"""
from __future__ import annotations
from typing import Any
from urllib.parse import urlparse
from app.core.config import settings
from app.core.logging import get_logger
from app.ports.storage import ObjectStorage
logger = get_logger(__name__)
# 客户端再读 TTS / 拉取音频:预签名有效期(秒),与移动端会话长度匹配
TTS_PRESIGNED_EXPIRES_SEC = 86_400
def extract_cos_object_key_if_owned(url: str | None) -> str | None:
"""
若 url 指向 settings 中配置的 COS 域名,返回去掉前导 / 的 object key。
非 http(s)、或 host 不匹配时返回 None。
"""
if not url:
return None
s = str(url).strip()
if not s.startswith(("http://", "https://")):
return None
parsed = urlparse(s)
host = (parsed.netloc or "").lower()
if not host:
return None
candidates: list[str] = []
bucket = (settings.tencent_cos_bucket or "").strip().lower()
region = (settings.tencent_cos_region or "").strip().lower()
if bucket and region:
candidates.append(f"{bucket}.cos.{region}.myqcloud.com")
base = (settings.tencent_cos_base_url or "").strip()
if base:
base_parsed = urlparse(base if "://" in base else f"https://{base}")
bh = (base_parsed.netloc or "").lower()
if bh:
candidates.append(bh)
if not candidates:
return None
matched = any(host == c for c in candidates if c)
if not matched:
return None
key = (parsed.path or "").lstrip("/")
return key or None
def collect_cos_keys_from_conversation_history(
history: list[dict[str, Any]],
) -> set[str]:
"""从 Redis 会话历史中收集 AI 消息附带的 TTS 音频 COS object key。"""
keys: set[str] = set()
for msg in history:
if msg.get("role") != "ai":
continue
raw = msg.get("ttsAudioUrls")
if not isinstance(raw, list):
continue
for u in raw:
if isinstance(u, str):
k = extract_cos_object_key_if_owned(u)
if k:
keys.add(k)
return keys
def collect_cos_keys_from_tts_url_list(urls: list[str] | None) -> set[str]:
if not urls:
return set()
keys: set[str] = set()
for u in urls:
if isinstance(u, str):
k = extract_cos_object_key_if_owned(u)
if k:
keys.add(k)
return keys
def presign_tts_urls_for_playback(
urls: list[str],
storage: ObjectStorage | None,
*,
expires: int = TTS_PRESIGNED_EXPIRES_SEC,
) -> list[str]:
"""
将本环境 COS 直链替换为预签名下载 URL私有桶下匿名 GET 会 AccessDenied
目的与回忆录 `normalize_image_assets_for_api` 中对 `get_download_url` 的用法一致。
非本环境 URL 或无法解析 key 时原样返回。
"""
if not storage or not urls:
return list(urls)
out: list[str] = []
for u in urls:
if not isinstance(u, str):
continue
s = u.strip()
if not s:
continue
key = extract_cos_object_key_if_owned(s)
if key:
try:
out.append(storage.get_url(key, expires=expires))
except Exception as exc:
logger.warning(
"presign tts url failed, keeping original url: key={} err={}",
key,
exc,
)
out.append(s)
else:
out.append(s)
return out