Files
grok2api/app/services/grok/image_cache.py
T
2025-10-10 16:59:56 +08:00

152 lines
5.4 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.
"""图片缓存服务模块"""
import asyncio
from pathlib import Path
from typing import Optional
from curl_cffi.requests import AsyncSession
from app.core.config import setting
from app.core.logger import logger
from app.services.grok.statsig import get_dynamic_headers
class ImageCacheService:
"""图片缓存服务"""
def __init__(self):
self.cache_dir = Path("data/temp")
self.cache_dir.mkdir(parents=True, exist_ok=True)
@staticmethod
def _get_cache_filename(image_path: str) -> str:
"""将图片路径转换为缓存文件名"""
# 移除开头的斜杠并替换所有斜杠为短横线
filename = image_path.lstrip('/').replace('/', '-')
return filename
def _get_cache_path(self, image_path: str) -> Path:
"""获取缓存文件的完整路径"""
filename = self._get_cache_filename(image_path)
return self.cache_dir / filename
async def download_image(self, image_path: str, auth_token: str) -> Optional[Path]:
"""下载并缓存图片
Args:
image_path: 图片路径,如 /users/xxx/generated/xxx/image.jpg
auth_token: 认证令牌
Returns:
缓存文件路径,下载失败返回 None
"""
cache_path = self._get_cache_path(image_path)
if cache_path.exists():
logger.debug(f"[ImageCache] 图片已缓存: {cache_path}")
return cache_path
image_url = f"https://assets.grok.com{image_path}"
try:
# 构建 Cookie
cf_clearance = setting.grok_config.get("cf_clearance", "")
cookie = f"{auth_token};{cf_clearance}" if cf_clearance else auth_token
# 构建请求头
headers = {
**get_dynamic_headers(pathname=image_path),
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "same-site",
"Sec-Fetch-User": "?1",
"Upgrade-Insecure-Requests": "1",
"Referer": "https://grok.com/",
"Cookie": cookie
}
# 代理配置
proxy_url = setting.grok_config.get("proxy_url")
proxies = {"http": proxy_url, "https": proxy_url} if proxy_url else None
async with AsyncSession() as session:
logger.debug(f"[ImageCache] 开始下载图片: {image_url}")
response = await session.get(
image_url,
headers=headers,
proxies=proxies,
timeout=30.0,
allow_redirects=True,
impersonate="chrome133a"
)
response.raise_for_status()
cache_path.write_bytes(response.content)
logger.debug(f"[ImageCache] 图片已缓存: {cache_path} ({len(response.content)} bytes)")
asyncio.create_task(self.cleanup_cache())
return cache_path
except Exception as e:
logger.error(f"[ImageCache] 下载图片失败: {e}")
return None
def get_cached_image(self, image_path: str) -> Optional[Path]:
"""获取缓存的图片路径
Args:
image_path: 图片路径
Returns:
缓存文件路径,不存在返回 None
"""
cache_path = self._get_cache_path(image_path)
return cache_path if cache_path.exists() else None
async def cleanup_cache(self):
"""清理缓存目录,确保不超过配置的大小限制"""
try:
# 获取配置的最大缓存大小(MB)
max_size_mb = setting.global_config.get("temp_max_size_mb", 500)
max_size_bytes = max_size_mb * 1024 * 1024
# 获取所有缓存文件及其大小和修改时间
files = []
total_size = 0
for file_path in self.cache_dir.glob("*"):
if file_path.is_file():
size = file_path.stat().st_size
mtime = file_path.stat().st_mtime
files.append((file_path, size, mtime))
total_size += size
# 如果总大小未超限,无需清理
if total_size <= max_size_bytes:
logger.debug(f"[ImageCache] 缓存大小 {total_size / 1024 / 1024:.2f}MB,未超限")
return
logger.info(f"[ImageCache] 缓存大小 {total_size / 1024 / 1024:.2f}MB 超过限制 {max_size_mb}MB,开始清理")
# 按修改时间排序(最旧的在前)
files.sort(key=lambda x: x[2])
# 删除最旧的文件直到总大小低于限制
for file_path, size, _ in files:
if total_size <= max_size_bytes:
break
file_path.unlink()
total_size -= size
logger.debug(f"[ImageCache] 已删除缓存文件: {file_path}")
logger.info(f"[ImageCache] 缓存清理完成,当前大小 {total_size / 1024 / 1024:.2f}MB")
except Exception as e:
logger.error(f"[ImageCache] 清理缓存失败: {e}")
# 全局实例
image_cache_service = ImageCacheService()