Spaces:
Running
Running
from fastapi import APIRouter, HTTPException, Depends | |
from datetime import datetime, timedelta | |
import time | |
import asyncio | |
import random | |
import threading | |
from app.utils import ( | |
log_manager, | |
ResponseCacheManager, | |
ActiveRequestsManager, | |
clean_expired_stats | |
) | |
import app.config.settings as settings | |
import app.vertex.config as app_config | |
from app.services import GeminiClient | |
from app.utils.auth import verify_web_password | |
from app.utils.maintenance import api_call_stats_clean | |
from app.utils.logging import log, vertex_log_manager | |
from app.config.persistence import save_settings | |
from app.utils.stats import api_stats_manager | |
from typing import List | |
import json | |
# Import necessary components for Google Credentials JSON update | |
from app.vertex.credentials_manager import CredentialManager, parse_multiple_json_credentials | |
# 引入重新初始化vertex的函数 | |
from app.vertex.vertex_ai_init import init_vertex_ai as re_init_vertex_ai_function, reset_global_fallback_client | |
# 创建路由器 | |
dashboard_router = APIRouter(prefix="/api", tags=["dashboard"]) | |
# 全局变量引用,将在init_dashboard_router中设置 | |
key_manager = None | |
response_cache_manager = None | |
active_requests_manager = None | |
credential_manager = None # 添加全局credential_manager变量 | |
# 用于存储API密钥检测的进度信息 | |
api_key_test_progress = { | |
"is_running": False, | |
"completed": 0, | |
"total": 0, | |
"valid": 0, | |
"invalid": 0, | |
"is_completed": False | |
} | |
def init_dashboard_router( | |
key_mgr, | |
cache_mgr, | |
active_req_mgr, | |
cred_mgr=None # 添加credential_manager参数 | |
): | |
"""初始化仪表盘路由器""" | |
global key_manager, response_cache_manager, active_requests_manager, credential_manager | |
key_manager = key_mgr | |
response_cache_manager = cache_mgr | |
active_requests_manager = active_req_mgr | |
credential_manager = cred_mgr # 保存credential_manager | |
return dashboard_router | |
async def run_blocking_init_vertex(): | |
"""Helper to run the init_vertex_ai function with the current credential_manager.""" | |
try: | |
if credential_manager is None: | |
# 如果credential_manager为None,记录警告并创建一个新的实例 | |
log('warning', "Credential Manager不存在,将创建一个新的实例用于初始化") | |
temp_credential_manager = CredentialManager() | |
credentials_count = temp_credential_manager.get_total_credentials() | |
log('info', f"临时Credential Manager已创建,包含{credentials_count}个凭证") | |
# 传递临时创建的credential_manager实例 | |
success = await re_init_vertex_ai_function(credential_manager=temp_credential_manager) | |
else: | |
# 记录当前有多少凭证可用 | |
credentials_count = credential_manager.get_total_credentials() | |
log('info', f"使用现有Credential Manager进行初始化,当前有{credentials_count}个凭证") | |
# 传递当前的credential_manager实例 | |
success = await re_init_vertex_ai_function(credential_manager=credential_manager) | |
if success: | |
log('info', "异步重新执行 init_vertex_ai 成功,以响应 Google Credentials JSON 的更新。") | |
else: | |
log('warning', "异步重新执行 init_vertex_ai 失败或未完成,在 Google Credentials JSON 更新后。") | |
except Exception as e: | |
log('error', f"执行 run_blocking_init_vertex 时出错: {e}") | |
async def get_dashboard_data(): | |
"""获取仪表盘数据的API端点,用于动态刷新""" | |
# 先清理过期数据,确保统计数据是最新的 | |
await api_stats_manager.maybe_cleanup() | |
await response_cache_manager.clean_expired() # 使用管理器清理缓存 | |
active_requests_manager.clean_completed() # 使用管理器清理活跃请求 | |
# 获取当前统计数据 | |
now = datetime.now() | |
# 使用新的统计系统获取调用数据 | |
last_24h_calls = api_stats_manager.get_calls_last_24h() | |
hourly_calls = api_stats_manager.get_calls_last_hour(now) | |
minute_calls = api_stats_manager.get_calls_last_minute(now) | |
# 获取时间序列数据 | |
time_series_data, tokens_time_series = api_stats_manager.get_time_series_data(30, now) | |
# 获取API密钥使用统计 | |
api_key_stats = api_stats_manager.get_api_key_stats(key_manager.api_keys) | |
# 根据ENABLE_VERTEX设置决定返回哪种日志 | |
if settings.ENABLE_VERTEX: | |
recent_logs = vertex_log_manager.get_recent_logs(500) # 获取最近500条Vertex日志 | |
else: | |
recent_logs = log_manager.get_recent_logs(500) # 获取最近500条普通日志 | |
# 获取缓存统计 | |
total_cache = response_cache_manager.cur_cache_num | |
# 获取活跃请求统计 | |
active_count = len(active_requests_manager.active_requests) | |
active_done = sum(1 for task in active_requests_manager.active_requests.values() if task.done()) | |
active_pending = active_count - active_done | |
# 获取凭证数量 | |
credentials_count = 0 | |
if credential_manager is not None: | |
credentials_count = credential_manager.get_total_credentials() | |
# 返回JSON格式的数据 | |
return { | |
"key_count": len(key_manager.api_keys), | |
"model_count": len(GeminiClient.AVAILABLE_MODELS), | |
"retry_count": settings.MAX_RETRY_NUM, | |
"credentials_count": credentials_count, # 添加凭证数量 | |
"last_24h_calls": last_24h_calls, | |
"hourly_calls": hourly_calls, | |
"minute_calls": minute_calls, | |
"calls_time_series": time_series_data, # 添加API调用时间序列 | |
"tokens_time_series": tokens_time_series, # 添加Token使用时间序列 | |
"current_time": datetime.now().strftime('%H:%M:%S'), | |
"logs": recent_logs, | |
"api_key_stats": api_key_stats, | |
# 添加配置信息 | |
"max_requests_per_minute": settings.MAX_REQUESTS_PER_MINUTE, | |
"max_requests_per_day_per_ip": settings.MAX_REQUESTS_PER_DAY_PER_IP, | |
# 添加版本信息 | |
"local_version": settings.version["local_version"], | |
"remote_version": settings.version["remote_version"], | |
"has_update": settings.version["has_update"], | |
# 添加流式响应配置 | |
"fake_streaming": settings.FAKE_STREAMING, | |
"fake_streaming_interval": settings.FAKE_STREAMING_INTERVAL, | |
# 添加随机字符串配置 | |
"random_string": settings.RANDOM_STRING, | |
"random_string_length": settings.RANDOM_STRING_LENGTH, | |
# 添加联网搜索配置 | |
"search_mode": settings.search["search_mode"], | |
"search_prompt": settings.search["search_prompt"], | |
# 添加缓存信息 | |
"cache_entries": total_cache, | |
"cache_expiry_time": settings.CACHE_EXPIRY_TIME, | |
"max_cache_entries": settings.MAX_CACHE_ENTRIES, | |
# 添加活跃请求池信息 | |
"active_count": active_count, | |
"active_done": active_done, | |
"active_pending": active_pending, | |
# 添加并发请求配置 | |
"concurrent_requests": settings.CONCURRENT_REQUESTS, | |
"increase_concurrent_on_failure": settings.INCREASE_CONCURRENT_ON_FAILURE, | |
"max_concurrent_requests": settings.MAX_CONCURRENT_REQUESTS, | |
# 启用vertex | |
"enable_vertex": settings.ENABLE_VERTEX, | |
# 添加Vertex Express配置 | |
"enable_vertex_express": settings.ENABLE_VERTEX_EXPRESS, | |
"vertex_express_api_key": bool(settings.VERTEX_EXPRESS_API_KEY), # 只返回是否设置的状态 | |
"google_credentials_json": bool(settings.GOOGLE_CREDENTIALS_JSON), # 只返回是否设置的状态 | |
# 添加最大重试次数 | |
"max_retry_num": settings.MAX_RETRY_NUM, | |
# 添加空响应重试次数限制 | |
"max_empty_responses": settings.MAX_EMPTY_RESPONSES, | |
} | |
async def reset_stats(password_data: dict): | |
""" | |
重置API调用统计数据 | |
Args: | |
password_data (dict): 包含密码的字典 | |
Returns: | |
dict: 操作结果 | |
""" | |
try: | |
if not isinstance(password_data, dict): | |
raise HTTPException(status_code=422, detail="请求体格式错误:应为JSON对象") | |
password = password_data.get("password") | |
if not password: | |
raise HTTPException(status_code=400, detail="缺少密码参数") | |
if not isinstance(password, str): | |
raise HTTPException(status_code=422, detail="密码参数类型错误:应为字符串") | |
if not verify_web_password(password): | |
raise HTTPException(status_code=401, detail="密码错误") | |
# 调用重置函数 | |
await api_stats_manager.reset() | |
return {"status": "success", "message": "API调用统计数据已重置"} | |
except HTTPException: | |
raise | |
except Exception as e: | |
raise HTTPException(status_code=500, detail=f"重置失败:{str(e)}") | |
async def update_config(config_data: dict): | |
""" | |
更新配置项 | |
Args: | |
config_data (dict): 包含配置项和密码的字典 | |
Returns: | |
dict: 操作结果 | |
""" | |
try: | |
if not isinstance(config_data, dict): | |
raise HTTPException(status_code=422, detail="请求体格式错误:应为JSON对象") | |
password = config_data.get("password") | |
if not password: | |
raise HTTPException(status_code=400, detail="缺少密码参数") | |
if not isinstance(password, str): | |
raise HTTPException(status_code=422, detail="密码参数类型错误:应为字符串") | |
if not verify_web_password(password): | |
raise HTTPException(status_code=401, detail="密码错误") | |
# 获取要更新的配置项 | |
config_key = config_data.get("key") | |
config_value = config_data.get("value") | |
if not config_key: | |
raise HTTPException(status_code=400, detail="缺少配置项键名") | |
# 根据配置项类型进行类型转换和验证 | |
if config_key == "max_requests_per_minute": | |
try: | |
value = int(config_value) | |
if value <= 0: | |
raise ValueError("每分钟请求限制必须大于0") | |
settings.MAX_REQUESTS_PER_MINUTE = value | |
log('info', f"每分钟请求限制已更新为:{value}") | |
except ValueError as e: | |
raise HTTPException(status_code=422, detail=f"参数类型错误:{str(e)}") | |
elif config_key == "max_requests_per_day_per_ip": | |
try: | |
value = int(config_value) | |
if value <= 0: | |
raise ValueError("每IP每日请求限制必须大于0") | |
settings.MAX_REQUESTS_PER_DAY_PER_IP = value | |
log('info', f"每IP每日请求限制已更新为:{value}") | |
except ValueError as e: | |
raise HTTPException(status_code=422, detail=f"参数类型错误:{str(e)}") | |
elif config_key == "fake_streaming": | |
if not isinstance(config_value, bool): | |
raise HTTPException(status_code=422, detail="参数类型错误:应为布尔值") | |
settings.FAKE_STREAMING = config_value | |
log('info', f"假流式请求已更新为:{config_value}") | |
# 同步更新vertex配置中的假流式设置 | |
try: | |
import app.vertex.config as vertex_config | |
vertex_config.FAKE_STREAMING_ENABLED = config_value # 直接更新全局变量 | |
vertex_config.update_config('FAKE_STREAMING', config_value) # 同时调用更新函数 | |
log('info', f"已同步更新Vertex中的假流式设置为:{config_value}") | |
except Exception as e: | |
log('warning', f"更新Vertex假流式设置时出错: {str(e)}") | |
elif config_key == "enable_vertex_express": | |
if not isinstance(config_value, bool): | |
raise HTTPException(status_code=422, detail="参数类型错误:应为布尔值") | |
settings.ENABLE_VERTEX_EXPRESS = config_value | |
log('info', f"Vertex Express已更新为:{config_value}") | |
elif config_key == "vertex_express_api_key": | |
if not isinstance(config_value, str): | |
raise HTTPException(status_code=422, detail="参数类型错误:应为字符串") | |
# 检查是否为空字符串或"true",如果是,则不更新 | |
if not config_value or config_value.lower() == "true": | |
log('info', f"Vertex Express API Key未更新,因为值为空或为'true'") | |
else: | |
settings.VERTEX_EXPRESS_API_KEY = config_value | |
# 更新app_config中的API密钥列表 | |
app_config.VERTEX_EXPRESS_API_KEY_VAL = [key.strip() for key in config_value.split(',') if key.strip()] | |
log('info', f"Vertex Express API Key已更新,共{len(app_config.VERTEX_EXPRESS_API_KEY_VAL)}个有效密钥") | |
# 尝试刷新模型配置 | |
try: | |
from app.vertex.model_loader import refresh_models_config_cache | |
refresh_success = await refresh_models_config_cache() | |
if refresh_success: | |
log('info', "更新Express API Key后成功刷新模型配置") | |
else: | |
log('warning', "更新Express API Key后刷新模型配置失败,将使用默认模型或现有缓存") | |
except Exception as e: | |
log('warning', f"尝试刷新模型配置时出错: {str(e)}") | |
elif config_key == "fake_streaming_interval": | |
try: | |
value = float(config_value) | |
if value <= 0: | |
raise ValueError("假流式间隔必须大于0") | |
settings.FAKE_STREAMING_INTERVAL = value | |
log('info', f"假流式间隔已更新为:{value}") | |
# 同步更新vertex配置中的假流式间隔设置 | |
try: | |
import app.vertex.config as vertex_config | |
vertex_config.update_config('FAKE_STREAMING_INTERVAL', value) | |
log('info', f"已同步更新Vertex中的假流式间隔设置为:{value}") | |
except Exception as e: | |
log('warning', f"更新Vertex假流式间隔设置时出错: {str(e)}") | |
except ValueError as e: | |
raise HTTPException(status_code=422, detail=f"参数类型错误:{str(e)}") | |
elif config_key == "random_string": | |
if not isinstance(config_value, bool): | |
raise HTTPException(status_code=422, detail="参数类型错误:应为布尔值") | |
settings.RANDOM_STRING = config_value | |
log('info', f"随机字符串已更新为:{config_value}") | |
elif config_key == "random_string_length": | |
try: | |
value = int(config_value) | |
if value <= 0: | |
raise ValueError("随机字符串长度必须大于0") | |
settings.RANDOM_STRING_LENGTH = value | |
log('info', f"随机字符串长度已更新为:{value}") | |
except ValueError as e: | |
raise HTTPException(status_code=422, detail=f"参数类型错误:{str(e)}") | |
elif config_key == "search_mode": | |
if not isinstance(config_value, bool): | |
raise HTTPException(status_code=422, detail="参数类型错误:应为布尔值") | |
settings.search["search_mode"] = config_value | |
log('info', f"联网搜索模式已更新为:{config_value}") | |
# 在切换search_mode时,重新获取一次可用模型列表 | |
try: | |
# 重置密钥栈以确保随机性 | |
key_manager._reset_key_stack() | |
# 获取一个随机API密钥 | |
for key in key_manager.api_keys: | |
log('info', f"使用API密钥 {key[:8]}... 刷新可用模型列表") | |
# 使用随机密钥获取可用模型 | |
all_models = await GeminiClient.list_available_models(key) | |
GeminiClient.AVAILABLE_MODELS = [model.replace("models/", "") for model in all_models] | |
if len(GeminiClient.AVAILABLE_MODELS) > 0: | |
log('info', f"可用模型列表已更新,当前模型数量:{len(GeminiClient.AVAILABLE_MODELS)}") | |
break | |
else: | |
log('warning', f"没有可用的API密钥,无法刷新可用模型列表") | |
except Exception as e: | |
log('warning', f"刷新可用模型列表时发生错误: {str(e)}") | |
elif config_key == "concurrent_requests": | |
try: | |
value = int(config_value) | |
if value <= 0: | |
raise ValueError("并发请求数必须大于0") | |
settings.CONCURRENT_REQUESTS = value | |
log('info', f"并发请求数已更新为:{value}") | |
except ValueError as e: | |
raise HTTPException(status_code=422, detail=f"参数类型错误:{str(e)}") | |
elif config_key == "increase_concurrent_on_failure": | |
try: | |
value = int(config_value) | |
if value < 0: | |
raise ValueError("失败时增加的并发数不能为负数") | |
settings.INCREASE_CONCURRENT_ON_FAILURE = value | |
log('info', f"失败时增加的并发数已更新为:{value}") | |
except ValueError as e: | |
raise HTTPException(status_code=422, detail=f"参数类型错误:{str(e)}") | |
elif config_key == "max_concurrent_requests": | |
try: | |
value = int(config_value) | |
if value <= 0: | |
raise ValueError("最大并发请求数必须大于0") | |
settings.MAX_CONCURRENT_REQUESTS = value | |
log('info', f"最大并发请求数已更新为:{value}") | |
except ValueError as e: | |
raise HTTPException(status_code=422, detail=f"参数类型错误:{str(e)}") | |
elif config_key == "enable_vertex": | |
if not isinstance(config_value, bool): | |
raise HTTPException(status_code=422, detail="参数类型错误:应为布尔值") | |
settings.ENABLE_VERTEX = config_value | |
log('info', f"Vertex AI 已更新为:{config_value}") | |
elif config_key == "google_credentials_json": | |
if not isinstance(config_value, str): # Allow empty string to clear | |
raise HTTPException(status_code=422, detail="参数类型错误:Google Credentials JSON 应为字符串") | |
# 检查是否为空字符串或"true",如果是,则不更新 | |
if not config_value or config_value.lower() == "true": | |
log('info', f"Google Credentials JSON未更新,因为值为空或为'true'") | |
save_settings() # 仍然保存其他可能的设置更改 | |
return {"status": "success", "message": f"配置项 {config_key} 未更新,值为空或为'true'"} | |
# Validate JSON structure if not empty | |
if config_value: | |
try: | |
# Attempt to parse as single or multiple JSONs | |
# parse_multiple_json_credentials logs errors if parsing fails but returns list. | |
temp_parsed = parse_multiple_json_credentials(config_value) | |
# If parse_multiple_json_credentials returns an empty list for a non-empty string, | |
# it means it didn't find any valid top-level JSON objects as per its logic. | |
# We can do an additional check for a single valid JSON object. | |
if not temp_parsed: # and config_value.strip(): # ensure non-empty string before json.loads | |
try: | |
# This is a stricter check. If parse_multiple_json_credentials, which is more lenient, | |
# failed to find anything, and this also fails, then it's likely malformed. | |
json.loads(config_value) # Try parsing as a single JSON object | |
# If this succeeds, it implies the string IS a valid single JSON, | |
# but not in the multi-JSON format parse_multiple_json_credentials might be looking for initially. | |
# parse_multiple_json_credentials will be called again later and should handle it. | |
except json.JSONDecodeError: | |
# This specific error means it's not even a valid single JSON. | |
raise HTTPException(status_code=422, detail="Google Credentials JSON 格式无效。它既不是有效的单个JSON对象,也不是逗号分隔的多个JSON对象。") | |
except HTTPException: # Re-raise if it's already an HTTPException from inner check | |
raise | |
except Exception as e: # Catch any other error during this pre-check | |
# This might catch errors if parse_multiple_json_credentials itself had an unexpected issue | |
# not related to JSONDecodeError but still an error. | |
raise HTTPException(status_code=422, detail=f"Google Credentials JSON 预检查失败: {str(e)}") | |
settings.GOOGLE_CREDENTIALS_JSON = config_value | |
log('info', "Google Credentials JSON 设置已更新 (内容未记录)。") | |
# Reset global fallback client first | |
reset_global_fallback_client() | |
# Clear previously loaded JSON string credentials from manager | |
if credential_manager is not None: | |
cleared_count = credential_manager.clear_json_string_credentials() | |
log('info', f"从 CredentialManager 中清除了 {cleared_count} 个先前由 JSON 字符串加载的凭据。") | |
if config_value: # If new JSON string is provided | |
parsed_json_objects = parse_multiple_json_credentials(config_value) | |
if parsed_json_objects: | |
loaded_count = credential_manager.load_credentials_from_json_list(parsed_json_objects) | |
if loaded_count > 0: | |
log('info', f"从更新的 Google Credentials JSON 中加载了 {loaded_count} 个凭据到 CredentialManager。") | |
else: | |
log('warning', "尝试加载Google Credentials JSON凭据失败,没有凭据被成功加载。") | |
else: | |
# 尝试作为单个JSON对象加载 | |
try: | |
single_cred = json.loads(config_value) | |
if credential_manager.add_credential_from_json(single_cred): | |
log('info', "作为单个JSON对象成功加载了一个凭据。") | |
else: | |
log('warning', "作为单个JSON对象加载凭据失败。") | |
except json.JSONDecodeError: | |
log('warning', "Google Credentials JSON无法作为JSON对象解析。") | |
except Exception as e: | |
log('warning', f"尝试加载单个JSON凭据时出错: {str(e)}") | |
else: | |
log('info', "Google Credentials JSON 已被清空。CredentialManager 中来自 JSON 字符串的凭据已被移除。") | |
# 检查凭证是否存在 | |
if credential_manager.get_total_credentials() == 0: | |
log('warning', "警告:当前没有可用的凭证。Vertex AI功能可能无法正常工作。") | |
else: | |
log('warning', "CredentialManager未初始化,无法加载Google Credentials JSON。") | |
# Save all settings changes | |
save_settings() # Moved save_settings here to ensure it's called for this key | |
# Trigger re-initialization of Vertex AI (which can re-init the global client) | |
try: | |
# 检查credential_manager是否可用 | |
if credential_manager is None: | |
log('warning', "重新初始化Vertex AI时发现credential_manager为None") | |
else: | |
log('info', f"开始重新初始化Vertex AI,当前凭证数: {credential_manager.get_total_credentials()}") | |
# 调用run_blocking_init_vertex | |
await run_blocking_init_vertex() | |
log('info', "Vertex AI服务重新初始化完成") | |
# 显式刷新模型配置缓存 | |
from app.vertex.model_loader import refresh_models_config_cache | |
refresh_success = await refresh_models_config_cache() | |
if refresh_success: | |
log('info', "成功刷新模型配置缓存") | |
else: | |
log('warning', "刷新模型配置缓存失败,将使用默认模型或现有缓存") | |
except Exception as e: | |
log('error', f"重新初始化Vertex AI服务时出错: {str(e)}") | |
elif config_key == "max_retry_num": | |
try: | |
value = int(config_value) | |
if value <= 0: | |
raise ValueError("最大重试次数必须大于0") | |
settings.MAX_RETRY_NUM = value | |
log('info', f"最大重试次数已更新为:{value}") | |
except ValueError as e: | |
raise HTTPException(status_code=422, detail=f"参数类型错误:{str(e)}") | |
elif config_key == "search_prompt": | |
if not isinstance(config_value, str): | |
raise HTTPException(status_code=422, detail="参数类型错误:应为字符串") | |
settings.search["search_prompt"] = config_value | |
log('info', f"联网搜索提示已更新为:{config_value}") | |
elif config_key == "gemini_api_keys": | |
if not isinstance(config_value, str): | |
raise HTTPException(status_code=422, detail="参数类型错误:API密钥应为逗号分隔的字符串") | |
# 分割并清理API密钥 | |
new_keys = [key.strip() for key in config_value.split(',') if key.strip()] | |
if not new_keys: | |
raise HTTPException(status_code=400, detail="未提供有效的API密钥") | |
# 添加到现有的API密钥字符串中 | |
current_keys = settings.GEMINI_API_KEYS.split(',') if settings.GEMINI_API_KEYS else [] | |
current_keys = [key.strip() for key in current_keys if key.strip()] | |
# 合并新旧密钥并去重 | |
all_keys = list(set(current_keys + new_keys)) | |
settings.GEMINI_API_KEYS = ','.join(all_keys) | |
# 计算新添加的密钥数量 | |
added_key_count = 0 | |
for key in new_keys: | |
if key not in key_manager.api_keys: | |
key_manager.api_keys.append(key) | |
added_key_count += 1 | |
# 重置密钥栈 | |
key_manager._reset_key_stack() | |
# 如果可用模型为空,尝试获取模型列表 | |
if not GeminiClient.AVAILABLE_MODELS: | |
try: | |
# 使用新添加的密钥之一尝试获取可用模型 | |
for key in new_keys: | |
log('info', f"使用新添加的API密钥 {key[:8]}... 获取可用模型列表") | |
all_models = await GeminiClient.list_available_models(key) | |
GeminiClient.AVAILABLE_MODELS = [model.replace("models/", "") for model in all_models] | |
if GeminiClient.AVAILABLE_MODELS: | |
log('info', f"成功获取可用模型列表,共 {len(GeminiClient.AVAILABLE_MODELS)} 个模型") | |
break | |
except Exception as e: | |
log('warning', f"获取可用模型列表时发生错误: {str(e)}") | |
log('info', f"已添加 {added_key_count} 个新API密钥,当前共有 {len(key_manager.api_keys)} 个") | |
elif config_key == "max_empty_responses": | |
try: | |
value = int(config_value) | |
if value < 0: # 通常至少为0或1,根据实际需求调整 | |
raise ValueError("空响应重试次数不能为负数") | |
settings.MAX_EMPTY_RESPONSES = value | |
log('info', f"空响应重试次数已更新为:{value}") | |
except ValueError as e: | |
raise HTTPException(status_code=422, detail=f"参数类型错误:{str(e)}") | |
else: | |
raise HTTPException(status_code=400, detail=f"不支持的配置项:{config_key}") | |
save_settings() | |
return {"status": "success", "message": f"配置项 {config_key} 已更新"} | |
except HTTPException: | |
raise | |
except Exception as e: | |
raise HTTPException(status_code=500, detail=f"更新失败:{str(e)}") | |
async def test_api_keys(password_data: dict): | |
""" | |
测试所有API密钥的有效性 | |
Args: | |
password_data (dict): 包含密码的字典 | |
Returns: | |
dict: 操作结果 | |
""" | |
try: | |
if not isinstance(password_data, dict): | |
raise HTTPException(status_code=422, detail="请求体格式错误:应为JSON对象") | |
password = password_data.get("password") | |
if not password: | |
raise HTTPException(status_code=400, detail="缺少密码参数") | |
if not isinstance(password, str): | |
raise HTTPException(status_code=422, detail="密码参数类型错误:应为字符串") | |
if not verify_web_password(password): | |
raise HTTPException(status_code=401, detail="密码错误") | |
# 检查是否已经有测试在运行 | |
if api_key_test_progress["is_running"]: | |
raise HTTPException(status_code=409, detail="已有API密钥检测正在进行中") | |
# 获取有效密钥列表 | |
valid_keys = key_manager.api_keys.copy() | |
# 启动异步测试 | |
threading.Thread( | |
target=start_api_key_test_in_thread, | |
args=(valid_keys,), | |
daemon=True | |
).start() | |
return {"status": "success", "message": "API密钥检测已启动,将同时检测有效密钥和无效密钥"} | |
except HTTPException: | |
raise | |
except Exception as e: | |
raise HTTPException(status_code=500, detail=f"启动API密钥检测失败:{str(e)}") | |
async def get_test_api_keys_progress(): | |
""" | |
获取API密钥检测进度 | |
Returns: | |
dict: 进度信息 | |
""" | |
return api_key_test_progress | |
def check_api_key_in_thread(key): | |
"""在线程中检查单个API密钥的有效性""" | |
loop = asyncio.new_event_loop() | |
asyncio.set_event_loop(loop) | |
try: | |
is_valid = loop.run_until_complete(test_api_key(key)) | |
if is_valid: | |
log('info', f"API密钥 {key[:8]}... 有效") | |
return key, True | |
else: | |
log('warning', f"API密钥 {key[:8]}... 无效") | |
return key, False | |
finally: | |
loop.close() | |
async def test_api_key(key): | |
"""测试单个API密钥是否有效""" | |
try: | |
# 尝试列出可用模型来检查API密钥是否有效 | |
all_models = await GeminiClient.list_available_models(key) | |
return len(all_models) > 0 | |
except Exception as e: | |
log('error', f"测试API密钥 {key[:8]}... 时出错: {str(e)}") | |
return False | |
def start_api_key_test_in_thread(keys): | |
"""在线程中启动API密钥检测过程""" | |
# 重置进度信息 | |
api_key_test_progress.update({ | |
"is_running": True, | |
"completed": 0, | |
"total": 0, # 稍后会更新 | |
"valid": 0, | |
"invalid": 0, | |
"is_completed": False | |
}) | |
try: | |
# 获取所有需要检测的密钥(包括当前GEMINI_API_KEYS和INVALID_API_KEYS) | |
current_keys = keys | |
# 获取当前无效密钥 | |
invalid_api_keys = settings.INVALID_API_KEYS.split(',') if settings.INVALID_API_KEYS else [] | |
invalid_api_keys = [key.strip() for key in invalid_api_keys if key.strip()] | |
# 合并所有需要测试的密钥,去重 | |
all_keys_to_test = list(set(current_keys + invalid_api_keys)) | |
# 更新总数 | |
api_key_test_progress["total"] = len(all_keys_to_test) | |
# 创建有效和无效密钥列表 | |
valid_keys = [] | |
invalid_keys = [] | |
# 检查每个密钥 | |
for key in all_keys_to_test: | |
# 检查密钥 | |
_, is_valid = check_api_key_in_thread(key) | |
# 更新进度 | |
api_key_test_progress["completed"] += 1 | |
# 将密钥添加到相应列表 | |
if is_valid: | |
valid_keys.append(key) | |
api_key_test_progress["valid"] += 1 | |
else: | |
invalid_keys.append(key) | |
api_key_test_progress["invalid"] += 1 | |
# 更新全局密钥列表 | |
key_manager.api_keys = valid_keys | |
# 更新设置中的有效和无效密钥 | |
settings.GEMINI_API_KEYS = ','.join(valid_keys) | |
settings.INVALID_API_KEYS = ','.join(invalid_keys) | |
# 保存设置 | |
save_settings() | |
# 重置密钥栈 | |
key_manager._reset_key_stack() | |
log('info', f"API密钥检测完成。有效密钥: {len(valid_keys)},无效密钥: {len(invalid_keys)}") | |
except Exception as e: | |
log('error', f"API密钥检测过程中发生错误: {str(e)}") | |
finally: | |
# 标记检测完成 | |
api_key_test_progress.update({ | |
"is_running": False, | |
"is_completed": True | |
}) | |
async def clear_invalid_api_keys(password_data: dict): | |
""" | |
清除所有失效的API密钥 | |
Args: | |
password_data (dict): 包含密码的字典 | |
Returns: | |
dict: 操作结果 | |
""" | |
try: | |
if not isinstance(password_data, dict): | |
raise HTTPException(status_code=422, detail="请求体格式错误:应为JSON对象") | |
password = password_data.get("password") | |
if not password: | |
raise HTTPException(status_code=400, detail="缺少密码参数") | |
if not isinstance(password, str): | |
raise HTTPException(status_code=422, detail="密码参数类型错误:应为字符串") | |
if not verify_web_password(password): | |
raise HTTPException(status_code=401, detail="密码错误") | |
# 获取当前无效密钥数量 | |
current_invalid_keys = settings.INVALID_API_KEYS.split(',') if settings.INVALID_API_KEYS else [] | |
current_invalid_keys = [key.strip() for key in current_invalid_keys if key.strip()] | |
invalid_count = len(current_invalid_keys) | |
if invalid_count == 0: | |
return {"status": "success", "message": "没有失效的API密钥需要清除"} | |
# 清除无效密钥 | |
settings.INVALID_API_KEYS = "" | |
save_settings() | |
log('info', f"已清除 {invalid_count} 个失效的API密钥") | |
return { | |
"status": "success", | |
"message": f"已成功清除 {invalid_count} 个失效的API密钥" | |
} | |
except HTTPException: | |
raise | |
except Exception as e: | |
raise HTTPException(status_code=500, detail=f"清除失效API密钥失败:{str(e)}") | |
async def export_valid_api_keys(password_data: dict): | |
""" | |
输出所有有效的API密钥 | |
Args: | |
password_data (dict): 包含密码的字典 | |
Returns: | |
dict: 操作结果,包含有效密钥列表 | |
""" | |
try: | |
if not isinstance(password_data, dict): | |
raise HTTPException(status_code=422, detail="请求体格式错误:应为JSON对象") | |
password = password_data.get("password") | |
if not password: | |
raise HTTPException(status_code=400, detail="缺少密码参数") | |
if not isinstance(password, str): | |
raise HTTPException(status_code=422, detail="密码参数类型错误:应为字符串") | |
if not verify_web_password(password): | |
raise HTTPException(status_code=401, detail="密码错误") | |
# 获取当前有效密钥 | |
valid_keys = key_manager.api_keys.copy() | |
if not valid_keys: | |
return {"status": "success", "message": "当前没有有效的API密钥", "keys": []} | |
# 直接返回完整的密钥列表 | |
log('info', f"用户导出了 {len(valid_keys)} 个有效API密钥") | |
return { | |
"status": "success", | |
"message": f"成功获取 {len(valid_keys)} 个有效API密钥", | |
"keys": valid_keys, | |
"count": len(valid_keys) | |
} | |
except HTTPException: | |
raise | |
except Exception as e: | |
raise HTTPException(status_code=500, detail=f"获取有效API密钥失败:{str(e)}") | |