mirror of
https://github.com/snailyp/gemini-balance.git
synced 2026-05-13 09:00:49 +08:00
feat: 添加密钥检查调度器并重构前端UI
主要变更:
- **调度器功能:**
- 集成 APScheduler 实现定时任务,用于定期检查API密钥的有效性。
- 在 `.env.example` 和 `app/config/config.py` 中添加了 `CHECK_INTERVAL_HOURS` 和 `TIMEZONE` 配置项。
- 在应用生命周期 (`app/core/application.py`) 中添加了调度器的启动和停止逻辑。
- 新增 `app/scheduler/` 目录及相关实现 (`key_checker.py`)。
- 新增 `app/router/scheduler_routes.py` 用于调度器相关API (如果未来需要)。
- 在 `requirements.txt` 中添加 `apscheduler` 依赖。
- **前端重构与改进:**
- 引入 `app/templates/base.html` 作为基础模板,统一页面结构和样式引入。
- 使用新的样式(推测为Tailwind CSS)重构了 `auth.html`, `config_editor.html`, `error_logs.html`, `keys_status.html` 页面,提升了UI一致性和响应式布局。
- 删除了旧的CSS文件 (`auth.css`, `config_editor.css`, `error_logs.css`, `keys_status.css`)。
- 更新了对应的 JavaScript 文件 (`config_editor.js`, `error_logs.js`, `keys_status.js`) 以适应新的HTML结构和交互。
- 在 `keys_status.html` 页面增加了按失败次数过滤密钥、批量重置失败次数、确认模态框等功能。
- 添加了新的 Logo 图片 (`logo.png`, `logo1.png`)。
- **其他:**
- 更新了 `app/router/routes.py` 以包含新的路由。
- 对 `app/service/key/key_manager.py` 和 `app/database/services.py` 进行了相关调整以支持新功能。
```
134 lines
4.6 KiB
Python
134 lines
4.6 KiB
Python
import asyncio
|
||
from itertools import cycle
|
||
from typing import Dict
|
||
|
||
|
||
from app.config.config import settings
|
||
from app.log.logger import get_key_manager_logger
|
||
|
||
logger = get_key_manager_logger()
|
||
|
||
|
||
class KeyManager:
|
||
def __init__(self, api_keys: list):
|
||
self.api_keys = api_keys
|
||
self.key_cycle = cycle(api_keys)
|
||
self.key_cycle_lock = asyncio.Lock()
|
||
self.failure_count_lock = asyncio.Lock()
|
||
self.key_failure_counts: Dict[str, int] = {key: 0 for key in api_keys}
|
||
self.MAX_FAILURES = settings.MAX_FAILURES
|
||
self.paid_key = settings.PAID_KEY
|
||
|
||
async def get_paid_key(self) -> str:
|
||
return self.paid_key
|
||
|
||
async def get_next_key(self) -> str:
|
||
"""获取下一个API key"""
|
||
async with self.key_cycle_lock:
|
||
return next(self.key_cycle)
|
||
|
||
async def is_key_valid(self, key: str) -> bool:
|
||
"""检查key是否有效"""
|
||
async with self.failure_count_lock:
|
||
return self.key_failure_counts[key] < self.MAX_FAILURES
|
||
|
||
async def reset_failure_counts(self):
|
||
"""重置所有key的失败计数"""
|
||
async with self.failure_count_lock:
|
||
for key in self.key_failure_counts:
|
||
self.key_failure_counts[key] = 0
|
||
|
||
async def reset_key_failure_count(self, key: str) -> bool:
|
||
"""重置指定key的失败计数"""
|
||
async with self.failure_count_lock:
|
||
if key in self.key_failure_counts:
|
||
self.key_failure_counts[key] = 0
|
||
logger.info(f"Reset failure count for key: {key}")
|
||
return True
|
||
logger.warning(f"Attempt to reset failure count for non-existent key: {key}")
|
||
return False
|
||
|
||
async def get_next_working_key(self) -> str:
|
||
"""获取下一可用的API key"""
|
||
initial_key = await self.get_next_key()
|
||
current_key = initial_key
|
||
|
||
while True:
|
||
if await self.is_key_valid(current_key):
|
||
return current_key
|
||
|
||
current_key = await self.get_next_key()
|
||
if current_key == initial_key:
|
||
# await self.reset_failure_counts() 取消重置
|
||
return current_key
|
||
|
||
async def handle_api_failure(self, api_key: str,retries: int) -> str:
|
||
"""处理API调用失败"""
|
||
async with self.failure_count_lock:
|
||
self.key_failure_counts[api_key] += 1
|
||
if self.key_failure_counts[api_key] >= self.MAX_FAILURES:
|
||
logger.warning(
|
||
f"API key {api_key} has failed {self.MAX_FAILURES} times"
|
||
)
|
||
if retries < settings.MAX_RETRIES:
|
||
return await self.get_next_working_key()
|
||
else:
|
||
return ""
|
||
|
||
def get_fail_count(self, key: str) -> int:
|
||
"""获取指定密钥的失败次数"""
|
||
return self.key_failure_counts.get(key, 0)
|
||
|
||
async def get_keys_by_status(self) -> dict:
|
||
"""获取分类后的API key列表,包括失败次数"""
|
||
valid_keys = {}
|
||
invalid_keys = {}
|
||
|
||
async with self.failure_count_lock:
|
||
for key in self.api_keys:
|
||
fail_count = self.key_failure_counts[key]
|
||
if fail_count < self.MAX_FAILURES:
|
||
valid_keys[key] = fail_count
|
||
else:
|
||
invalid_keys[key] = fail_count
|
||
|
||
return {"valid_keys": valid_keys, "invalid_keys": invalid_keys}
|
||
|
||
async def get_first_valid_key(self) -> str:
|
||
"""获取第一个有效的API key"""
|
||
async with self.failure_count_lock:
|
||
for key in self.key_failure_counts:
|
||
if self.key_failure_counts[key] < self.MAX_FAILURES:
|
||
return key
|
||
return self.api_keys[0]
|
||
|
||
_singleton_instance = None
|
||
_singleton_lock = asyncio.Lock()
|
||
|
||
|
||
async def get_key_manager_instance(api_keys: list = None) -> KeyManager:
|
||
"""
|
||
获取 KeyManager 单例实例。
|
||
|
||
如果尚未创建实例,将使用提供的 api_keys 初始化 KeyManager。
|
||
如果已创建实例,则忽略 api_keys 参数,返回现有单例。
|
||
"""
|
||
global _singleton_instance
|
||
|
||
async with _singleton_lock:
|
||
if _singleton_instance is None:
|
||
if api_keys is None:
|
||
raise ValueError("API keys are required to initialize the KeyManager")
|
||
_singleton_instance = KeyManager(api_keys)
|
||
logger.info("KeyManager instance created.")
|
||
return _singleton_instance
|
||
|
||
|
||
async def reset_key_manager_instance():
|
||
"""重置 KeyManager 单例实例"""
|
||
global _singleton_instance
|
||
async with _singleton_lock:
|
||
if _singleton_instance:
|
||
_singleton_instance = None
|
||
logger.info("KeyManager instance reset.")
|