From 705d602deebc77d8f253449b0c90928a560b68a5 Mon Sep 17 00:00:00 2001 From: snaily Date: Sat, 26 Apr 2025 03:04:40 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E9=9B=86=E4=B8=AD=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E9=80=BB=E8=BE=91=E5=B9=B6=E6=B7=BB=E5=8A=A0=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E6=A3=80=E6=9F=A5API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 `get_current_version` 函数从 `application.py` 移动到 `helpers.py` 以实现更好的代码组织和可重用性。 - 在 `version_routes.py` 中引入新的 API 端点 `/api/version/check`,以提供当前版本、最新可用版本和更新状态。 - 更新了 `base.html`,通过调用新的 API 端点,使用 JavaScript 异步获取和显示版本信息。这取代了以前服务器端渲染版本信息的方式,并增加了定期检查。 - 移除了应用程序启动时(`lifespan` 函数)的自动更新检查,因为版本检查现在由前端通过 API 处理。 - 在 `routes.py` 中注册了新的版本路由。 --- app/core/application.py | 28 ++++------------- app/router/routes.py | 3 +- app/router/version_routes.py | 38 +++++++++++++++++++++++ app/templates/base.html | 59 +++++++++++++++++++++++++++--------- app/utils/helpers.py | 28 +++++++++++++++++ 5 files changed, 119 insertions(+), 37 deletions(-) create mode 100644 app/router/version_routes.py diff --git a/app/core/application.py b/app/core/application.py index da1bf6d..a36cc4c 100644 --- a/app/core/application.py +++ b/app/core/application.py @@ -11,6 +11,7 @@ from app.exception.exceptions import setup_exception_handlers from app.router.routes import setup_routers from app.service.key.key_manager import get_key_manager_instance from app.database.connection import connect_to_db, disconnect_from_db +from app.utils.helpers import get_current_version # Import from helpers from app.database.initialization import initialize_database from app.scheduler.key_checker import start_scheduler, stop_scheduler from app.service.update.update_service import check_for_updates @@ -20,28 +21,11 @@ logger = get_application_logger() # Define project paths using pathlib # Assuming this file is at app/core/application.py PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent -VERSION_FILE_PATH = PROJECT_ROOT / "VERSION" +# VERSION_FILE_PATH = PROJECT_ROOT / "VERSION" # Removed: Defined in helpers.py STATIC_DIR = PROJECT_ROOT / "app" / "static" TEMPLATES_DIR = PROJECT_ROOT / "app" / "templates" - -def _get_current_version(default_version: str = "0.0.0") -> str: - """Reads the current version from the VERSION file.""" - version_file = VERSION_FILE_PATH # Use Path object - try: - # Use Path object's open method - with version_file.open('r', encoding='utf-8') as f: - version = f.read().strip() - if not version: - logger.warning(f"VERSION file ('{version_file}') is empty. Using default version '{default_version}'.") - return default_version - return version - except FileNotFoundError: - logger.warning(f"VERSION file not found at '{version_file}'. Using default version '{default_version}'.") - return default_version - except IOError as e: - logger.error(f"Error reading VERSION file ('{version_file}'): {e}. Using default version '{default_version}'.") - return default_version +# Removed _get_current_version function definition, moved to helpers.py # 初始化模板引擎,并添加全局变量 templates = Jinja2Templates(directory="app/templates") @@ -88,7 +72,7 @@ def _stop_scheduler(): async def _perform_update_check(app: FastAPI): """Checks for updates and stores the info in app.state.""" update_available, latest_version, error_message = await check_for_updates() - current_version = _get_current_version() # Read from VERSION file + current_version = get_current_version() # Use imported function update_info = { "update_available": update_available, "latest_version": latest_version, @@ -119,7 +103,7 @@ async def lifespan(app: FastAPI): await _setup_database_and_config(settings) # Pass settings object # Perform update check after core components are ready - await _perform_update_check(app) + # await _perform_update_check(app) # Removed: Version check moved to frontend API call # Start the scheduler _start_scheduler() @@ -148,7 +132,7 @@ def create_app() -> FastAPI: # 创建FastAPI应用 # Read version from file for consistency - current_version = _get_current_version() + current_version = get_current_version() # Use imported function app = FastAPI( title="Gemini Balance API", description="Gemini API代理服务,支持负载均衡和密钥管理", diff --git a/app/router/routes.py b/app/router/routes.py index 7693ad0..5cf23ff 100644 --- a/app/router/routes.py +++ b/app/router/routes.py @@ -8,7 +8,7 @@ from fastapi.templating import Jinja2Templates from app.core.security import verify_auth_token from app.log.logger import get_routes_logger -from app.router import error_log_routes, gemini_routes, openai_routes, config_routes, scheduler_routes, stats_routes # 新增导入 stats_routes +from app.router import error_log_routes, gemini_routes, openai_routes, config_routes, scheduler_routes, stats_routes, version_routes # 新增导入 version_routes from app.service.key.key_manager import get_key_manager_instance from app.service.stats_service import StatsService @@ -33,6 +33,7 @@ def setup_routers(app: FastAPI) -> None: app.include_router(error_log_routes.router) app.include_router(scheduler_routes.router) # 新增包含 scheduler 路由 app.include_router(stats_routes.router) # 包含 stats API 路由 + app.include_router(version_routes.router) # 包含 version API 路由 # 添加页面路由 setup_page_routes(app) diff --git a/app/router/version_routes.py b/app/router/version_routes.py new file mode 100644 index 0000000..8f7b27a --- /dev/null +++ b/app/router/version_routes.py @@ -0,0 +1,38 @@ +from fastapi import APIRouter, HTTPException +from pydantic import BaseModel, Field +from typing import Optional + +from app.service.update.update_service import check_for_updates +from app.utils.helpers import get_current_version +from app.log.logger import get_update_logger + +router = APIRouter(prefix="/api/version", tags=["Version"]) +logger = get_update_logger() + +class VersionInfo(BaseModel): + current_version: str = Field(..., description="当前应用程序版本") + latest_version: Optional[str] = Field(None, description="可用的最新版本") + update_available: bool = Field(False, description="是否有可用更新") + error_message: Optional[str] = Field(None, description="检查更新时发生的错误信息") + +@router.get("/check", response_model=VersionInfo, summary="检查应用程序更新") +async def get_version_info(): + """ + 检查当前应用程序版本与最新的 GitHub release 版本。 + """ + try: + current_version = get_current_version() # Use imported function + update_available, latest_version, error_message = await check_for_updates() + + # Log the result for debugging + logger.info(f"Version check API result: current={current_version}, latest={latest_version}, available={update_available}, error='{error_message}'") + + return VersionInfo( + current_version=current_version, + latest_version=latest_version, + update_available=update_available, + error_message=error_message + ) + except Exception as e: + logger.error(f"Error in /api/version/check endpoint: {e}", exc_info=True) + raise HTTPException(status_code=500, detail="检查版本信息时发生内部错误") \ No newline at end of file diff --git a/app/templates/base.html b/app/templates/base.html index 11d95bd..55e4d57 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -202,20 +202,9 @@ 免费项目,谨防诈骗 - {% if request and request.app.state.update_info %} - {% set update_info = request.app.state.update_info %} - | - v{{ update_info.current_version }} - {% if update_info.update_available %} - | - - 新版本: v{{ update_info.latest_version }} - - {% elif update_info.error_message and update_info.error_message != 'Checking...' %} - | - 更新检查失败 - {% endif %} - {% endif %} + + + @@ -279,6 +268,48 @@ }, 300); // Short delay to show spinner } + // --- Version Check --- + const versionInfoContainer = document.getElementById('version-info-container'); + + async function fetchVersionInfo() { + if (!versionInfoContainer) return; + versionInfoContainer.innerHTML = '|检查更新中...'; // Initial loading state + + try { + const response = await fetch('/api/version/check'); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + + let versionHtml = `|v${data.current_version}`; + if (data.update_available) { + versionHtml += ` + | + + 新版本: v${data.latest_version} + `; + } else if (data.error_message) { + versionHtml += ` + | + 更新检查失败`; + } else { + versionHtml += `|已是最新`; // Indicate up-to-date + } + versionInfoContainer.innerHTML = versionHtml; + + } catch (error) { + console.error('Error fetching version info:', error); + versionInfoContainer.innerHTML = `|更新检查失败`; + } + } + + // Fetch immediately on load + fetchVersionInfo(); + + // Fetch periodically (e.g., every hour) + setInterval(fetchVersionInfo, 3600000); // 3600000 ms = 1 hour + {% block body_scripts %}{% endblock %} diff --git a/app/utils/helpers.py b/app/utils/helpers.py index fef7ed6..74a129e 100644 --- a/app/utils/helpers.py +++ b/app/utils/helpers.py @@ -6,9 +6,19 @@ import re import base64 import requests from typing import Dict, Any, List, Optional, Tuple +from pathlib import Path +import logging # Import logging from app.core.constants import DATA_URL_PATTERN, IMAGE_URL_PATTERN, VALID_IMAGE_RATIOS +# Define logger for helper functions if needed, or use specific loggers +helper_logger = logging.getLogger("app.utils") # Or use a more specific logger if available + +# Define project root and version file path here for get_current_version +# Assuming this file is at app/utils/helpers.py +PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent +VERSION_FILE_PATH = PROJECT_ROOT / "VERSION" + def extract_mime_type_and_data(base64_string: str) -> Tuple[Optional[str], str]: """ @@ -146,3 +156,21 @@ def is_valid_api_key(key: str) -> bool: return False + +def get_current_version(default_version: str = "0.0.0") -> str: + """Reads the current version from the VERSION file.""" + version_file = VERSION_FILE_PATH # Use Path object defined above + try: + # Use Path object's open method + with version_file.open('r', encoding='utf-8') as f: + version = f.read().strip() + if not version: + helper_logger.warning(f"VERSION file ('{version_file}') is empty. Using default version '{default_version}'.") + return default_version + return version + except FileNotFoundError: + helper_logger.warning(f"VERSION file not found at '{version_file}'. Using default version '{default_version}'.") + return default_version + except IOError as e: + helper_logger.error(f"Error reading VERSION file ('{version_file}'): {e}. Using default version '{default_version}'.") + return default_version