diff --git a/docker-compose.yml b/docker-compose.yml index fbf0c9b..e2dc6cb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,11 +1,14 @@ services: - favicon-api: + favicon: image: favicon-api:latest container_name: favicon-api ports: - 8001:8000 + env_file: + - .env environment: TZ: Asia/Shanghai + REDIS_URL: redis://redis:6379/0 volumes: - /usr/share/zoneinfo/Asia/Shanghai:/usr/share/zoneinfo/Asia/Shanghai:ro - /etc/localtime:/etc/localtime:ro @@ -13,4 +16,18 @@ services: - ./data:/app/data:rw - ./conf:/app/conf:rw - ./logs:/app/logs:rw - restart: unless-stopped \ No newline at end of file + restart: unless-stopped + networks: + - favicon_network + depends_on: + - redis + + redis: + image: redis:7-alpine + container_name: favicon-redis + networks: + - favicon_network + +networks: + favicon_network: + driver: bridge diff --git a/favicon_app/asyncs/favicon_async.py b/favicon_app/asyncs/favicon_async.py index 0fb1595..041d97a 100644 --- a/favicon_app/asyncs/favicon_async.py +++ b/favicon_app/asyncs/favicon_async.py @@ -7,6 +7,7 @@ from typing import Tuple, Optional import aiohttp import setting +from favicon_app.asyncs import redis_pool from favicon_app.models import favicon from favicon_app.utils import header from favicon_app.utils.filetype import helpers, filetype @@ -135,6 +136,7 @@ class FaviconAsync(favicon.Favicon): content = await resp.read() return content, ct_type else: + await redis_pool.set_cache(domain, setting.time_of_7_days, setting.time_of_7_days) favicon.failed_url_cache(domain, setting.time_of_7_days) logger.error('异步请求失败: %d, URL: %s', resp.status, url) break @@ -146,6 +148,7 @@ class FaviconAsync(favicon.Favicon): logger.warning('异步请求超时,正在重试(%d/%d): %s', retry_count, retries, url) continue except Exception as e: + await redis_pool.set_cache(domain, setting.time_of_7_days, setting.time_of_7_days) favicon.failed_url_cache(domain, setting.time_of_7_days) logger.error('异步请求异常: %s, URL: %s', str(e), url) break diff --git a/favicon_app/asyncs/favicon_service_async.py b/favicon_app/asyncs/favicon_service_async.py index bedf87c..a5749b1 100644 --- a/favicon_app/asyncs/favicon_service_async.py +++ b/favicon_app/asyncs/favicon_service_async.py @@ -60,7 +60,7 @@ class FaviconServiceAsync(favicon_service.FaviconService): strategy_url, strategy_name = strategy() if strategy_url is not None: - logger.info(f"-> 异步尝试从 {strategy_name} 获取图标") + logger.debug(f"-> 异步尝试从 {strategy_name} 获取图标") icon_content, icon_type = await entity.async_get_icon_file(strategy_url, strategy_url == '') # 图标获取失败,或图标不是支持的图片格式,写入默认图标 diff --git a/favicon_app/asyncs/redis_pool.py b/favicon_app/asyncs/redis_pool.py new file mode 100644 index 0000000..ad20a02 --- /dev/null +++ b/favicon_app/asyncs/redis_pool.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- + +from typing import AsyncGenerator + +from fastapi import Depends +from redis.asyncio import ConnectionPool, Redis + +import setting + +REDIS_URL = setting.REDIS_URL + +pool = ConnectionPool.from_url( + REDIS_URL, + max_connections=200, + decode_responses=True, +) + + +async def get_redis() -> AsyncGenerator[Redis, None]: + async with Redis(connection_pool=pool) as conn: + yield conn + + +async def set_cache( + key: str, + value: str, + ttl: int | None = None, + r: Redis = Depends(get_redis), +): + await r.set(key, value, ex=ttl) + + +async def get_cache(key: str, r: Redis = Depends(get_redis)): + return await r.get(key) + + +async def del_cache(key: str, r: Redis = Depends(get_redis)): + await r.delete(key) diff --git a/main.py b/main.py index 8f72a9e..5842e1f 100644 --- a/main.py +++ b/main.py @@ -2,11 +2,13 @@ import logging import os +from contextlib import asynccontextmanager from fastapi import FastAPI, Request from fastapi.responses import Response import setting +from favicon_app.asyncs.redis_pool import pool from favicon_app.routes import favicon_router from favicon_app.utils.file_util import FileUtil @@ -21,8 +23,18 @@ default_icon_file = setting.default_icon_file # referer日志文件路径 referer_log_file = setting.referer_log_file + +@asynccontextmanager +async def lifespan(app: FastAPI): + """应用级生命周期:启动/清理。""" + print("Redis pool ready.") + yield + await pool.aclose() + print("Redis pool closed.") + + # fastapi -app = FastAPI(title="Favicon API", description="获取网站favicon图标", version="3.0.0") +app = FastAPI(lifespan=lifespan, title="Favicon API", description="获取网站favicon图标", version="3.0") app.include_router(favicon_router) diff --git a/requirements.txt b/requirements.txt index 69dfaf7..8afbbbd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,16 +1,17 @@ --index https://mirrors.xinac.net/pypi/simple --extra-index-url https://pypi.tuna.tsinghua.edu.cn/simple -fastapi~=0.116.1 -pydantic~=2.11.7 -pydantic_core~=2.33.2 -starlette~=0.47.3 -requests~=2.32.5 -aiohttp~=3.12.15 -bs4~=0.0.2 -beautifulsoup4~=4.13.5 -lxml~=6.0.1 -PyYAML~=6.0.2 -uvicorn~=0.35.0 -uvicorn-worker~=0.3.0 -gunicorn~=23.0.0 +fastapi~=0.116 +pydantic~=2.11 +pydantic_core~=2.33 +starlette~=0.47 +requests~=2.32 +aiohttp~=3.12 +bs4~=0.0 +beautifulsoup4~=4.13 +lxml~=6.0 +PyYAML~=6.0 +uvicorn~=0.35 +uvicorn-worker~=0.3 +gunicorn~=23.0 +redis[hiredis]~=6.4 diff --git a/setting.py b/setting.py index 9b397bc..e80e2a2 100644 --- a/setting.py +++ b/setting.py @@ -39,3 +39,4 @@ time_of_30_days = 30 * time_of_1_days # 是否使用同步方式 sync = 'false' +REDIS_URL = os.getenv("REDIS_URL", "redis://localhost:6379/0")