| |
|
|
| import logging |
| import base64 |
| import httpx |
| import asyncio |
| from fastapi import Request, HTTPException |
| from fastapi.responses import StreamingResponse, JSONResponse, Response |
| from typing import Optional, Tuple |
|
|
| |
| from app.core.gemini_client_manager import get_next_gemini_cookie_tuple |
|
|
| logger = logging.getLogger(__name__) |
|
|
| |
| SKIP_DOWNLOAD_DOMAINS = ["googleusercontent.com", "blogger.googleusercontent.com", "lh3.googleusercontent.com"] |
|
|
| async def proxy_image_request(image_url: str, request: Request): |
| """ |
| 代理图片下载,解决前端跨域或防盗链问题。 |
| """ |
| logger.info(f"接收到图片代理请求: {image_url}") |
| |
| |
| cookie_tuple: Optional[Tuple[str, str]] = get_next_gemini_cookie_tuple() |
| cookies = {} |
| if cookie_tuple: |
| sid, sidts = cookie_tuple |
| cookies = { |
| "__Secure-1PSID": sid, |
| "__Secure-1PSIDTS": sidts |
| } |
| logger.info(f"使用Cookie代理图片下载: sid={sid[:5]}..., sidts={sidts[:5]}...") |
| |
| |
| headers = { |
| "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36", |
| "Accept": "image/webp,image/apng,image/*,*/*;q=0.8", |
| "Accept-Language": "en-US,en;q=0.9", |
| "Referer": "https://gemini.google.com/", |
| "Sec-Fetch-Dest": "image", |
| "Sec-Fetch-Mode": "no-cors", |
| "Sec-Fetch-Site": "same-origin", |
| } |
| |
| max_retries = 3 |
| retry_delay = 1.0 |
| |
| for attempt in range(max_retries): |
| try: |
| async with httpx.AsyncClient( |
| follow_redirects=True, |
| trust_env=False, |
| max_redirects=5, |
| cookies=cookies, |
| http2=True |
| ) as client: |
| |
| logger.debug(f"开始代理下载图片 (尝试 {attempt+1}/{max_retries}): {image_url}") |
| response = await client.get(image_url, timeout=30, headers=headers) |
| response.raise_for_status() |
| |
| |
| logger.info(f"图片代理成功: URL={image_url}, 状态码={response.status_code}, 类型={response.headers.get('Content-Type')}") |
| |
| |
| return StreamingResponse( |
| content=response.iter_bytes(), |
| media_type=response.headers.get("Content-Type", "image/jpeg"), |
| headers={ |
| "Cache-Control": "public, max-age=86400", |
| "Access-Control-Allow-Origin": "*" |
| } |
| ) |
| except httpx.HTTPStatusError as e: |
| if attempt == max_retries - 1: |
| logger.error(f"代理下载图片失败: URL: {image_url}, 状态码: {e.response.status_code}") |
| |
| transparent_gif_base64 = "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" |
| transparent_gif_bytes = base64.b64decode(transparent_gif_base64) |
| return Response( |
| content=transparent_gif_bytes, |
| media_type="image/gif", |
| headers={ |
| "Cache-Control": "public, max-age=60", |
| "Access-Control-Allow-Origin": "*" |
| } |
| ) |
| logger.warning(f"尝试 {attempt+1}/{max_retries} 失败,状态码: {e.response.status_code}") |
| except httpx.TooManyRedirects: |
| if attempt == max_retries - 1: |
| logger.error(f"代理下载图片重定向过多: URL: {image_url}") |
| |
| transparent_gif_base64 = "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" |
| transparent_gif_bytes = base64.b64decode(transparent_gif_base64) |
| return Response( |
| content=transparent_gif_bytes, |
| media_type="image/gif", |
| headers={ |
| "Cache-Control": "public, max-age=60", |
| "Access-Control-Allow-Origin": "*" |
| } |
| ) |
| logger.warning(f"尝试 {attempt+1}/{max_retries} 失败: 重定向过多") |
| except (httpx.RequestError, Exception) as e: |
| if attempt == max_retries - 1: |
| logger.error(f"代理下载图片失败: URL: {image_url}, 错误: {type(e).__name__} - {e}") |
| |
| transparent_gif_base64 = "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" |
| transparent_gif_bytes = base64.b64decode(transparent_gif_base64) |
| return Response( |
| content=transparent_gif_bytes, |
| media_type="image/gif", |
| headers={ |
| "Cache-Control": "public, max-age=60", |
| "Access-Control-Allow-Origin": "*" |
| } |
| ) |
| logger.warning(f"尝试 {attempt+1}/{max_retries} 失败: {type(e).__name__} - {e}") |
| |
| |
| await asyncio.sleep(retry_delay * (attempt + 1)) |
|
|